Kami menembus perlindungan dari bot



Baru-baru ini, incapsula telah mulai muncul di banyak situs asing - sebuah sistem yang meningkatkan keamanan situs, kecepatan kerja dan pada saat yang sama sangat menyulitkan kehidupan pengembang perangkat lunak. Inti dari sistem ini adalah perlindungan komprehensif menggunakan JavaScript, yang, omong-omong, banyak bot DDOS telah belajar untuk mengeksekusi dan bahkan mem-bypass CloudFlare. Hari ini kita akan mempelajari incapsula, menulis deobfuscator skrip JS dan mengajar bot DDOS kita untuk mengatasinya!

Situs dalam tangkapan layar di bawah ini diambil sebagai contoh yang baik untuk sebuah artikel, tidak berbeda dari yang lain, di forum subyek yang meragukan, banyak yang mencari kekerasan untuk itu, tapi saya punya tugas lain - perangkat lunak untuk mengotomatisasi berbagai tindakan dengan situs.

Mari kita mulai dengan memeriksa pertanyaan, karena hanya ada 2, dan di sini adalah yang pertama:



Permintaan ini memuat skrip yang dikaburkan secara alami:



Mengubah eval ke document.write () memberikan kode yang sedikit lebih mudah dibaca:



Saya tidak tahu mengapa, tetapi alat otomatis untuk memformat kode keluaran mematahkan kode ini, jadi saya harus memformatnya dengan tangan saya dan memberikan variabel nama normal. Untuk semua manipulasi ini saya menggunakan notepad sederhana ++ dan fungsi penggantian teks, sebagai hasilnya kita dapat melanjutkan dan memeriksa baris pertama:

var _0x3a59=['wpgXPQ==','cVjDjw==','Tk/DrFl/','GMOMd8K2w4jCpw==','wpkwwpE=','w6zDmmrClMKVHA==', ... ,'w4w+w5MGBQI=','w6TDr8Obw6TDlTJQaQ==']; 

Ini adalah array yang berisi nama fungsi terenkripsi dan string lainnya, jadi kita perlu mencari fungsi yang mendekripsi array ini, tidak perlu melangkah jauh:

Kode Deobfuscated
 var Decrypt=function(base64_Encoded_Param,Key_Param){ var CounterArray=[], CRC=0x0, TempVar, result='', EncodedSTR=''; base64_Encoded_Param=atob(base64_Encoded_Param); for(var n=0,input_length=base64_Encoded_Param.length;n<input_length;n++){ EncodedSTR+='%'+('00'+base64_Encoded_Param.charCodeAt(n).toString(0x10)).slice(-0x2); } base64_Encoded_Param=decodeURIComponent(EncodedSTR); for(var n=0x0;n<0x100;n++){ CounterArray[n]=n; } for(n=0x0;n<0x100;n++){ CRC=(CRC+CounterArray[n]+Key_Param.charCodeAt(n%Key_Param.length))%0x100; TempVar=CounterArray[n]; CounterArray[n]=CounterArray[CRC]; CounterArray[CRC]=TempVar; } n=0x0; CRC=0x0; for(var i=0x0;i<base64_Encoded_Param.length;i++){ n=(n+0x1)%0x100; CRC=(CRC+CounterArray[n])%0x100; TempVar=CounterArray[n]; CounterArray[n]=CounterArray[CRC]; CounterArray[CRC]=TempVar; result+=String.fromCharCode(base64_Encoded_Param.charCodeAt(i)^CounterArray[(CounterArray[n]+CounterArray[CRC])%0x100]); } return result; }; 


Jika kami menyebutnya secara terpisah:

 ParamDecryptor('0x0', '0Et]') 

Hasil itu akan jauh dari yang kami harapkan. Dan semuanya harus disalahkan untuk fungsi lain:

 var shift_array=function(number_of_shifts){ while(--number_of_shifts){ EncodedParams['push'](EncodedParams['shift']()); } }; 

Yang ada di tempat yang sangat tidak terduga - dalam fungsi yang disebut di bagian paling awal dan memeriksa cookie. Rupanya, para pengembang dengan demikian “melindungi” cek cookie dari pemotongan. Seperti yang Anda lihat - tidak ada yang rumit, loop hanya menggeser array dengan jumlah elemen yang ditentukan, dalam kasus kami 223 elemen. Dari mana datangnya angka ajaib 223 ini? Saya mengambil nomor ini dari panggilan ke fungsi verifikasi cookie, sepertinya 0xdf di sana dan mengikuti rute ini:

 //  function(EncodedParams,EncodedParamsShifts); //  (AllParams,0xdf); //0xdf => 223 //    var _0x5e622e=function(_0x486a40,_0x1de600){_0x486a40(++_0x1de600);}; _0x5e622e(shift_array,EncodedParamsShifts); //     : shift_array(++EncodedParamsShifts); 

Secara alami, itu berubah setiap saat, siapa yang akan ragu ...

Sekarang yang tersisa adalah mengganti semua panggilan

 var _0x85e545=this[ParamDecryptor('0x0', '0Et]')]; 

pada

 var _0x85e545=this['window']; 

Atau, lebih baik lagi, terus

 var ThisWindow=this.window; 

Saya menjadikan konversi terakhir sebagai reguler biasa. Oh ya, mereka benar-benar lupa untuk memberikan baris berikut:

\x77\x6f\x4a\x33\x58\x68\x6b\x59\x77\x34\x44\x44\x6e\x78\x64\x70

Untuk tampilan normal. Tidak ada yang rumit, ini adalah UrlEncode biasa, ubah \ x menjadi% dan dekode untuk mendapatkan baris berikut:

woJ3XhkYw4DDnxdp

Kemudian saya mulai mengganti semua panggilan ala

 ParamDecryptor('0x0', '0Et]') 

ke baris yang sudah didekripsi menggunakan fungsi yang ditulis sendiri dari modul saya. Ya, kodenya tidak bersinar dengan indah, tenggat waktu terbakar (seperti yang biasanya diperlukan kemarin) dan saya terlalu malas untuk berpikir karena saya dulu memprogram dengan mouse , namun, ia bekerja dengan baik:



Saya menulis ulang kode dari sumbernya hampir 1v1.

Selanjutnya, metode lain untuk mengaburkan kode menarik perhatian saya. Saya harus menulis fungsi yang cukup besar yang mencari panggilan seperti itu:

 case'7':while(_0x30fe16["XNg"](_0x13d8ee,_0x5a370d)) 

Dan menggantinya dengan analog sederhana:

Daftar fitur hebat
 var _0x30fe16={ 'XNg':function _0x19aabd(_0x425e3c,_0x481cd6){return _0x425e3c<_0x481cd6;}, 'sUd':function _0x320363(_0xa24206,_0x49d66b){return _0xa24206&_0x49d66b;}, 'wMk':function _0x32974a(_0x2cdcf4,_0x250e85){return _0x2cdcf4>>_0x250e85;}, 'FnU':function _0x22ce98(_0x2f5577,_0x4feea7){return _0x2f5577<<_0x4feea7;}, 'mTe':function _0x35a8bc(_0x11fecf,_0x29718e){return _0x11fecf&_0x29718e;}, 'doo':function _0x5ce08b(_0x4e5976,_0x4757ea){return _0x4e5976>>_0x4757ea;}, 'vmP':function _0x5d415c(_0x39dc96,_0x59022e){return _0x39dc96<<_0x59022e;}, 'bGL':function _0xd49b(_0x7e8c9f,_0x301346){return _0x7e8c9f|_0x301346;}, 'rXw':function _0x4dfb4d(_0x39d33a,_0x36fd1e){return _0x39d33a<<_0x36fd1e;}, 'svD':function _0x387610(_0x3cd4f7,_0x58fd9e){return _0x3cd4f7&_0x58fd9e;}, 'cuj':function _0x472c54(_0x4e473a,_0x26f3fd){return _0x4e473a==_0x26f3fd;}, 'OrY':function _0x3c6e85(_0x445d0b,_0x1caacf){return _0x445d0b|_0x1caacf;}, 'AKn':function _0x4dac5b(_0x521c05,_0x27b6bd){return _0x521c05>>_0x27b6bd;}, 'gtj':function _0x5416f0(_0x3e0965,_0x560062){return _0x3e0965&_0x560062;} }; 


Untuk mendapatkan:

 case'7':while(_0x13d8ee < _0x5a370d){ 

Algoritma kerja sangat sederhana, pada kenyataannya, kami hanya mengganti variabel:

  1. Kami menemukan nama array, dalam kasus kami: _0x30fe16
  2. Parameter input parsim: _0x425e3c, _0x481cd6
  3. Badan fungsi parsim: _0x425e3c <_0x481cd6
  4. Ganti _0x425e3c dengan _0x13d8ee
  5. Ganti _0x481cd6 dengan _0x5a370d
  6. Kami mendapatkan : _0x13d8ee <_0x5a370d
  7. Ganti _0x30fe16.XNg (_0x13d8ee, _0x5a370d) dengan kode di atas
  8. Ulangi sampai fungsi berakhir

Parsing nama, parameter dan isi fungsi dilakukan oleh satu biasa. Tentu saja, dalam versi final modul metode ini tidak terlalu diperlukan, hanya ada 1 panggilan yang sedikit lebih dikaburkan, tetapi pelanggan mengatakan untuk melakukan segalanya dan karena itu melakukannya, selain itu, fungsi-fungsi lain juga menjadi lebih jelas. Di situs lain ada juga desain seperti:

Tampilkan kode besar dan buruk
 var _0x4dc9f4 = { 'NTSjj': _0x3d6e1f.dEVDh, 'tZeHx': function(_0x2a40cd, _0x2faf22) { return _0x3d6e1f.JIehC(_0x2a40cd, _0x2faf22); }, 'ocgoO': "https://site/login", 'WmiOO': _0x3d6e1f.vsCuf }; //    ,      : var _0x3d6e1f = { 'dEVDh': "4|0|2|3|5|1", 'JIehC': function(_0x34757f, _0xd344e8) { return _0x34757f != _0xd344e8; }, 'vsCuf': ".countdownGroup", 'awUzV': function(_0x4b3914, _0x1f9e41) { return _0x4b3914 === _0x1f9e41; }, 'smOkd': "NSpHE", 'bvCub': function(_0x208c1d, _0x160d32) { return _0x208c1d(_0x160d32); }, 'PmBNl': function(_0x33524f, _0x29b35a) { return _0x33524f(_0x29b35a); }, 'Fhbrr': "#stopBtn", 'Vkpkf': function(_0x2de6ac, _0x31bb8b) { return _0x2de6ac + _0x31bb8b; }, 'HbSaV': function(_0x429822, _0x1a46e9) { return _0x429822 + _0x1a46e9; }, 'UsdKM': "https://site/register", 'JCXqh': "Timer started. ", 'GBXqx': function(_0x18f912, _0x5829b5) { return _0x18f912 / _0x5829b5; }, 'sSdZf': function(_0x45f64c, _0x152cb4) { return _0x45f64c(_0x152cb4); }, 'AAmKj': ".countdownTimer" }; 

Seperti yang Anda lihat di sini, parameter dari satu array merujuk ke yang lain. Saya memecahkan masalah ini hanya:

  1. Parsim semua array
  2. Kami membersihkannya dari kode
  3. Salin semua elemen ke dalam array asosiatif (nama, nilai)
  4. Dalam satu lingkaran oleh pencarian rekursif, kami mencari semua fungsi bersarang
  5. Ganti fungsi bersarang dengan tindakan yang mereka lakukan
  6. Rentangkan semua tautan ke string dengan cara yang sama

Setelah menerapkan metode deobfuscation ini, kode menjadi sedikit kurang membingungkan. Anda dapat segera melihat fungsi base64 dengan dua cara. Yang pertama:

 CharArray="ABCDE...XYZabcde...xyz0123456789+/"; 

Dan yang kedua:

 if(!window["btoa"])window["btoa"]=_0x386a89; 

Anda tidak lagi dapat membalikkan dan beralih ke fungsi lain yang lebih penting, atau lebih tepatnya, ke fungsi yang bekerja dengan cookie. Saya menemukannya di baris incap_ses_ dan melihat fitur kebingungan lainnya - kode kebingungan menggunakan loop:

Tampilkan kode
 var _0x290283="4|2|5|0|3|1"["split"]('|'), _0x290611=0x0; while(!![]){ switch(_0x290283[_0x290611++]){ case'0':for(var n=0x0;n<CookieArray["length"];n++){ var _0x27e53a=CookieArray[n]["substr"](0x0,CookieArray[n]["indexOf"]('=')); var _0x4b4644=CookieArray[n]["substr"](CookieArray[n]["indexOf"]('=')+0x1,CookieArray[n]["length"]); if(_0x5ebd6a["test"](_0x27e53a)){ResultCookieArray[ResultCookieArray["length"]]=_0x4b4644;} } continue; case'1':return ResultCookieArray;continue; case'2':var _0x5ebd6a=new this.window.RegExp("^\s?incap_ses_");continue; case'3':_0x4d5690();continue; case'4':var ResultCookieArray=new this.window.Array();continue; case'5':var CookieArray=this.window.document.cookie["split"](';');continue; } break; } 


Semuanya sangat sederhana di sini: kami mengatur ulang baris sesuai dengan urutan eksekusi: 4 | 2 | 5 | 0 | 3 | 1 dan mendapatkan fungsi aslinya. Metode deobfusasi ini juga tidak diperlukan dalam versi final, tetapi tidak menyebabkan masalah besar, semuanya diurai dengan cara dasar, hal utama yang perlu dipertimbangkan adalah bahwa bisa ada loop bersarang dan oleh karena itu saya hanya melakukan pencarian rekursif.

Fungsi cookie
 var _0x30fe16={ function _0x2829d5(){ var ResultCookieArray=new this.window.Array(); var _0x5ebd6a=new this.window.RegExp("^\s?incap_ses_"); var CookieArray=this.window.document.cookie["split"](';'); for(var n=0x0;n<CookieArray["length"];n++){ var _0x27e53a=CookieArray[n]["substr"](0x0,CookieArray[n]["indexOf"]('=')); var _0x4b4644=CookieArray[n]["substr"](CookieArray[n]["indexOf"]('=')+0x1,CookieArray[n]["length"]); if(_0x5ebd6a["test"](_0x27e53a)){ResultCookieArray[ResultCookieArray["length"]]=_0x4b4644;} } _0x4d5690(); return ResultCookieArray; } 

Itu hanya menyimpan nilai - nilai semua cookie yang dimulai dengan incap_ses_ dalam array dan kemudian metode lain menghitung checksum mereka hanya dengan menjumlahkan kode ASCII:

Tampilkan kode
 function TIncapsula.CharCRC(text: string): string; var i, crc:integer; begin crc:=0; for i:=1 to Length(text) do crc:=crc+ord(text[i]); result:=IntToStr(crc); end; function TIncapsula.GetCookieDigest: string; var i:integer; res:string; begin res:=''; for i:=0 to FCookies.Count-1 do begin if res='' then res:=CharCRC(browserinfo+FCookies[i]) else res:=res+','+CharCRC(browserinfo+FCookies[i]); end; result:=res; end; 


Kita akan membutuhkan checksum sedikit lebih jauh, dan sekarang mari kita mencari tahu seperti apa fungsi _0x4d5690 ini yang dipanggil dari tempat yang berbeda. Untuk melakukan ini, lihat saja metode yang dipanggil dan berikan mereka nama yang sesuai:

 function CheckDebugger(){ if(new this.window.Date()["getTime"]() - RunTime) > 0x1f4){ FuckDebugger(); } } 

Penulis skrip ini sangat naif :)

Poin penting lainnya:

 ParamDecryptor('0x65', '\x55\xa9\xf9\x1c\x1a\xd5\xfc\x60') //result = "ca3XP6zjTSB3w3gEwMl6lqgsdEVDTV9aF4rEDQ=="; 

Dari sini kita membutuhkan 5 huruf pertama: ca3XP , saya akan memberitahu Anda mengapa di bawah ini. Dan ingat, kami menghitung checksum dari nilai cookie? Sekarang kita membutuhkan mereka untuk mendapatkan apa yang disebut hash.

Fungsi hash
 function TIncapsula.GetDigestHash(Digest: string): string; var i:integer; CookieDigest, res:string; begin CookieDigest:=GetCookieDigest; //85530,85722 res:=''; for i:=0 to Length(Digest)-1 do begin res:=res+IntToHex(ord(Digest[i+1]) + ord(CookieDigest[i mod Length(CookieDigest)+1]),1); end; result:=res; end; 


Bandingkan:



Hebat! Langkah terakhir tetap - menerima cookie untuk respons:

 ResCooka=((((ParamDecryptor(btoa(PluginsInfo),"ca3XP")+",digest=")+DigestArray)+",s=")+AllDigestHash); Set_Cookies("___utmvc",btoa(ResCooka),0x14); 

Kode asli di awal menambahkan parameter browser yang disandikan di base64 ke akhir array AllParams yang digeser, "mengenkripsi" mereka dengan fungsi ParamDecryptor dengan kunci ca3XP dan kemudian menghapus elemen yang ditambahkan sebelumnya. Saya dapat berasumsi bahwa kruk ini dibuat karena fitur kecil: fungsi ParamDecryptor menerima indeks elemen dalam array dan kunci, yang berarti bahwa Anda dapat mentransfer string di sana hanya melalui array. Kenapa tidak melakukannya dengan benar? Programmer, tuan.

Well, sebenarnya semuanya, cookie sudah siap, tetap untuk menginstalnya dan mengirim permintaan. Benar, Anda tidak akan menerimanya karena satu detail kecil yang saya lebih suka diam.

Optimasi


Potongan-potongan kode dalam Delphi hanyalah sebuah prototipe. Semua kode deobfuscator ditulis ulang dalam assembler karena persyaratan pelanggan dan kecepatan eksekusi meningkat beberapa kali. Berikut ini juga memiliki efek positif pada kecepatan:

  1. Memotong kelebihan potongan kode dan array dalam satu iterasi dari loop. Ini diperlukan untuk secara signifikan mengurangi jumlah kode dan mempercepat pencarian di masa depan.
  2. Karena fungsi dalam kode tidak tercampur, kami tahu perkiraan lokasinya, oleh karena itu, jika fungsi pemasangan cookie di bagian paling akhir, maka Anda harus mencarinya setidaknya dari tengah.
  3. Cari fitur-fitur utama sebelumnya. Algoritma assembler mencari mereka bahkan pada saat membersihkan kode dari sampah yang tidak perlu.
  4. Dalam versi final, saya menyingkirkan sekitar setengah fungsi deobfuscator yang diperlukan untuk memahami kode dan tidak diperlukan untuk bot karena parameter yang diperlukan didapat tanpa masalah

Kesimpulan


Ketika saya mengunjungi situs, saya ingin itu bekerja dengan cepat, dan mengacaukan skrip JS menggunakan metode ini tidak sopan kepada pengguna. Apakah ini membantu melindungi dari bot? Tidak, tentu saja, seperti yang Anda lihat, harganya benar-benar melebihi malam, dan hanya beberapa sandwich dan beberapa cangkir teh adalah biayanya.

Tujuan artikel ini adalah untuk berbicara tentang prinsip pengoperasian solusi ini dan untuk menunjukkan betapa tidak bergunanya solusi ini. Untuk alasan yang jelas, modul yang sudah jadi untuk menghindari perlindungan ini tidak akan dipublikasikan (setelah bocor, game-stop yang buruk beralih ke akamai), yang perlu melakukannya sendiri berdasarkan studi ini (yang dilakukan sekitar setahun yang lalu dan masih relevan), dan anak sekolah yang ingin untuk menggerakkan akun orang lain pergi ke hutan. Jika Anda memiliki pertanyaan, saya selalu siap untuk menjawabnya di komentar.

Source: https://habr.com/ru/post/id429662/


All Articles