Wir durchbrechen den Schutz vor Bots



In letzter Zeit taucht Incapsula auf vielen ausländischen Websites auf - ein System, das die Sicherheit der Website und die Arbeitsgeschwindigkeit erhöht und gleichzeitig das Leben von Softwareentwicklern erheblich verkompliziert. Das Wesentliche dieses Systems ist der umfassende Schutz mit JavaScript, das übrigens viele DDOS-Bots bereits gelernt haben, CloudFlare auszuführen und sogar zu umgehen. Heute werden wir Incapsula studieren, einen JS-Skript-Deobfuscator schreiben und unserem DDOS- Bot beibringen, ihn zu umgehen !

Die Site im folgenden Screenshot ist ein gutes Beispiel für einen Artikel, unterscheidet sich nicht von anderen, in Foren zweifelhafter Themen suchen viele nach Rohlingen dafür, aber ich habe eine andere Aufgabe - Software zur Automatisierung verschiedener Aktionen mit Sites.

Beginnen wir mit der Untersuchung der Abfragen, es gibt nur zwei davon, und hier ist die erste:



Diese Anforderung lädt ein Skript, das natürlich verschleiert ist:



Wenn Sie eval in document.write () ändern, erhalten Sie einen etwas besser lesbaren Code:



Ich weiß nicht warum, aber automatische Tools zum Formatieren des Ausgabecodes brechen diesen Code, sodass ich ihn mit meinen Händen formatieren und den Variablen normale Namen geben musste. Für all diese Manipulationen habe ich einen einfachen Editor ++ und eine Textersetzungsfunktion verwendet. Als Ergebnis können wir fortfahren und die erste Zeile untersuchen:

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

Dies ist ein Array, das verschlüsselte Funktionsnamen und andere Zeichenfolgen enthält. Daher müssen wir nach einer Funktion suchen, die dieses Array entschlüsselt, ohne weit gehen zu müssen:

Deobfuscated Code
 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; }; 


Wenn wir es separat nennen:

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

Dieses Ergebnis wird weit von unseren Erwartungen entfernt sein. Und alles ist schuld an einer anderen Funktion:

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

Das ist an einem sehr unerwarteten Ort - in einer Funktion, die ganz am Anfang aufgerufen wird und die Cookies überprüft. Anscheinend haben die Entwickler damit den Cookie-Check vor dem Ausschneiden "geschützt". Wie Sie sehen können - nichts Kompliziertes - verschiebt die Schleife das Array einfach um die angegebene Anzahl von Elementen, in unserem Fall 223 Elemente. Woher kommt diese magische Zahl 223? Ich habe diese Nummer aus einem Aufruf der Cookie-Überprüfungsfunktion übernommen. Sie sieht dort wie 0xdf aus und geht diesen Weg:

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

Natürlich ändert es sich jedes Mal, wer zweifeln würde ...

Jetzt müssen nur noch alle Anrufe ersetzt werden

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

auf

 var _0x85e545=this['window']; 

Oder noch besser weiter

 var ThisWindow=this.window; 

Ich habe die letzte Konvertierung regelmäßig durchgeführt. Oh ja, sie haben völlig vergessen, die folgenden Zeilen anzugeben:

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

Zur normalen Ansicht. Es ist nichts kompliziertes, dies ist ein regulärer UrlEncode. Ändern Sie \ x in% und dekodieren Sie, um die folgende Zeile zu erhalten:

woJ3XhkYw4DDnxdp

Dann fing ich an, alle Anrufe ala zu ersetzen

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

zu bereits entschlüsselten Zeilen mit einer selbstgeschriebenen Funktion aus meinem Modul. Ja, der Code funkelt nicht vor Schönheit, die Fristen brannten (wie es normalerweise gestern nötig war) und es war zu faul zu denken, weil ich früher mit der Maus programmiert habe , aber es funktioniert gut:



Ich habe den Code aus der Quelle fast 1v1 umgeschrieben.

Als nächstes fiel mir eine andere Methode zur Verschleierung des Codes auf. Ich musste eine ziemlich große Funktion schreiben, die nach solchen Aufrufen sucht:

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

Und sie durch einfachere Analoga ersetzen:

Tolle Feature-Liste
 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;} }; 


Um zu bekommen:

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

Der Algorithmus der Arbeit ist furchtbar einfach, tatsächlich ersetzen wir nur die Variablen:

  1. Wir finden den Namen des Arrays, in unserem Fall: _0x30fe16
  2. Parsim-Eingabeparameter: _0x425e3c, _0x481cd6
  3. Parsim-Funktionskörper: _0x425e3c <_0x481cd6
  4. Ersetzen Sie _0x425e3c durch _0x13d8ee
  5. Ersetzen Sie _0x481cd6 durch _0x5a370d
  6. Wir erhalten : _0x13d8ee <_0x5a370d
  7. Ersetzen Sie _0x30fe16.XNg (_0x13d8ee, _0x5a370d) durch den obigen Code
  8. Wiederholen, bis die Funktionen beendet sind

Das Parsen des Namens, der Parameter und des Hauptteils der Funktion erfolgt durch einen Regulären. Natürlich wird diese Methode in der endgültigen Version des Moduls nicht besonders benötigt, es gibt nur einen Aufruf, der etwas verschleierter ist, aber der Kunde sagte, er solle alles tun, und deshalb wurden auch andere Funktionen klarer. Auf anderen Websites gibt es auch solche Designs:

Zeige großen und schlechten Code
 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" }; 

Wie Sie hier sehen können, verweist ein Parameter aus einem Array auf einen anderen. Ich habe dieses Problem einfach gelöst:

  1. Parsim alle Arrays
  2. Wir entfernen sie aus dem Code
  3. Kopieren Sie alle Elemente in ein assoziatives Array (Name, Wert).
  4. In einer Schleife durch eine rekursive Suche suchen wir nach allen verschachtelten Funktionen
  5. Ersetzen Sie verschachtelte Funktionen durch die Aktionen, die sie ausführen
  6. Erweitern Sie alle Links zu Zeichenfolgen auf dieselbe Weise

Nach Anwendung dieser Deobfuscationsmethode wurde der Code etwas weniger verwirrend. Sie können die base64-Funktion auf zwei Arten sofort bemerken. Erster:

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

Und der zweite:

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

Sie können nicht mehr rückgängig machen und zu anderen, wichtigeren Funktionen übergehen, genauer gesagt zu einer Funktion, die mit Cookies funktioniert. Ich fand es in der Zeile incap_ses_ und bemerkte einen weiteren Verschleierungschip - Code-Verschleierung mit Schleifen:

Code anzeigen
 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; } 


Hier ist alles sehr einfach: Wir ordnen die Zeilen in der Reihenfolge der Ausführung neu an: 4 | 2 | 5 | 0 | 3 | 1 und erhalten die ursprüngliche Funktion. Diese Methode der Deobfuscation wird auch in der endgültigen Version nicht benötigt, hat aber keine großen Probleme verursacht. Alles wird auf elementare Weise analysiert. Die Hauptsache ist, dass es verschachtelte Schleifen geben kann und ich deshalb nur eine rekursive Suche durchgeführt habe.

Cookies funktionieren
 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; } 

Es speichert einfach die Werte aller Cookies, die mit incap_ses_ beginnen, in einem Array, und dann berechnet eine andere Methode ihre Prüfsumme einfach durch Summieren von ASCII-Codes:

Code anzeigen
 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; 


Wir werden eine Prüfsumme etwas weiter benötigen, und jetzt wollen wir herausfinden, welche Art von Funktion diese _0x4d5690 ist, die von verschiedenen Stellen aufgerufen wird. Schauen Sie sich dazu einfach die aufgerufenen Methoden an und weisen Sie ihnen die entsprechenden Namen zu:

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

Der Autor dieses Skripts ist sehr naiv :)

Ein weiterer wichtiger Punkt:

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

Von hier aus brauchen wir die ersten 5 Buchstaben: ca3XP , ich werde Ihnen unten erklären , warum. Und denken Sie daran, wir haben Prüfsummen aus Cookie-Werten berechnet? Jetzt brauchen wir sie, um den sogenannten Hash zu bekommen.

Hash-Funktion
 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; 


Vergleichen Sie:



Großartig! Der letzte Schritt bleibt - Cookies für die Antwort erhalten:

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

Der ursprüngliche Code am Anfang fügte die in base64 codierten Browserparameter am Ende des verschobenen AllParams-Arrays hinzu, verschlüsselte sie mit der ParamDecryptor-Funktion mit dem ca3XP- Schlüssel und löschte dann das zuvor hinzugefügte Element. Ich kann davon ausgehen, dass diese Krücke aufgrund einer kleinen Funktion hergestellt wurde: Die ParamDecryptor-Funktion akzeptiert den Index des Elements im Array und den Schlüssel, was bedeutet, dass Sie die Zeichenfolge dort nur über das Array übertragen können. Warum nicht richtig machen? Programmierer, Sir.

Nun, eigentlich alles, das Cookie ist fertig, es bleibt zu installieren und eine Anfrage zu senden. Sie werden es zwar nicht akzeptieren, weil ich lieber über ein kleines Detail schweige.

Optimierung


Code-Teile in Delphi sind nur ein Prototyp. Der gesamte Code des Deobfuscators wurde aufgrund von Kundenanforderungen im Assembler neu geschrieben und seine Ausführungsgeschwindigkeit um ein Vielfaches erhöht. Folgendes wirkte sich ebenfalls positiv auf die Geschwindigkeit aus:

  1. Schneiden Sie überschüssige Codeteile und Arrays in einer Iteration der Schleife aus. Dies ist erforderlich, um die Codemenge erheblich zu reduzieren und die Suche in Zukunft zu beschleunigen.
  2. Da die Funktionen im Code nicht gemischt sind, kennen wir ihre ungefähre Position. Wenn sich die Cookie-Installationsfunktion ganz am Ende befindet, müssen Sie sie zumindest von der Mitte aus suchen.
  3. Suchen Sie im Voraus nach wichtigen Funktionen. Der Assembler-Algorithmus sucht nach ihnen, selbst wenn der Code von unnötigem Müll befreit wird.
  4. In der endgültigen Version habe ich ungefähr die Hälfte der Funktionen des Deobfuscators entfernt, die zum Verständnis des Codes benötigt wurden und für den Bot nicht benötigt wurden, da die erforderlichen Parameter ohne Probleme verfügbar waren

Fazit


Wenn ich die Site besuche, möchte ich, dass sie schnell funktioniert, und das Verschleiern von JS-Skripten mit dieser Methode ist für den Benutzer respektlos. Hilft dies vor Bots zu schützen? Nein, natürlich, wie Sie sehen können, kostet es buchstäblich über den Abend, und nur ein paar Sandwiches und ein paar Tassen Tee sind die Kosten.

Der Zweck dieses Artikels ist es, über das Funktionsprinzip dieser Lösung zu sprechen und zu zeigen, wie nutzlos sie ist. Aus offensichtlichen Gründen wird ein vorgefertigtes Modul zur Umgehung dieses Schutzes nicht veröffentlicht (nach seinem Durchsickern wurde der schlechte Spielstopp auf akamai umgestellt), der es aufgrund dieser Studie (die vor etwa einem Jahr durchgeführt wurde und immer noch relevant ist) selbst durchführen muss, und Studenten, die dies möchten um die Konten anderer Leute zu zucken, gehe in den Wald. Wenn Sie Fragen haben, bin ich immer bereit, diese in den Kommentaren zu beantworten.

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


All Articles