Nous brisons la protection contre les bots



Récemment, des incapsules ont commencé à apparaître sur de nombreux sites étrangers - un système qui augmente la sécurité du site, la vitesse et en même temps complique considérablement la vie des développeurs de logiciels. L'essence de ce système est une protection complète à l'aide de JavaScript, qui, soit dit en passant, de nombreux robots DDOS ont déjà appris à exécuter et même à contourner CloudFlare. Aujourd'hui, nous étudierons l'incapsule, rédigerons un désobfuscateur de script JS et enseignerons à notre bot DDOS comment le contourner!

Le site dans la capture d'écran ci-dessous est pris comme un bon exemple pour un article, ne diffère pas des autres, sur les forums de sujets douteux, beaucoup recherchent des brutes pour cela, mais j'ai une autre tâche - un logiciel pour automatiser diverses actions avec les sites.

Commençons par examiner les requêtes, car il n'y en a que 2, et voici la première:



Cette requête charge un script naturellement obscurci:



Changer eval en document.write () donne un code légèrement plus lisible:



Je ne sais pas pourquoi, mais les outils automatiques de formatage du code de sortie cassent ce code, j'ai donc dû le formater avec mes mains et donner aux variables des noms normaux. Pour toutes ces manipulations, j'ai utilisé un simple bloc-notes ++ et une fonction de remplacement de texte.Par conséquent, nous pouvons continuer et examiner la première ligne:

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

Il s'agit d'un tableau qui contient des noms de fonction chiffrés et d'autres chaînes, nous devons donc rechercher une fonction qui déchiffre ce tableau, pas besoin d'aller loin:

Code désobfusqué
 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; }; 


Si nous l'appelons séparément:

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

Ce résultat sera loin de ce que nous attendions. Et tout est à blâmer pour une autre fonction:

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

Ce qui est dans un endroit très inattendu - dans une fonction qui est appelée au tout début et vérifie les cookies. Apparemment, les développeurs ont ainsi «protégé» la suppression du contrôle des cookies. Comme vous pouvez le voir - rien de compliqué, la boucle décale simplement le tableau du nombre spécifié d'éléments, dans notre cas 223 éléments. D'où vient ce nombre magique 223? J'ai pris ce numéro d'un appel à la fonction de vérification des cookies, il ressemble à 0xdf et suit cette route:

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

Naturellement, cela change à chaque fois, qui en douterait ...

Il ne reste plus qu'à remplacer tous les appels

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

sur

 var _0x85e545=this['window']; 

Ou, mieux encore, sur

 var ThisWindow=this.window; 

J'ai fait la dernière conversion en tant que régulier régulier. Oh oui, ils ont complètement oublié de donner les lignes suivantes:

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

En vue normale. Il n'y a rien de compliqué, c'est un UrlEncode normal, changez \ x en% et décodez pour obtenir la ligne suivante:

woJ3XhkYw4DDnxdp

Puis j'ai commencé à remplacer tous les appels ala

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

aux lignes déjà déchiffrées en utilisant une fonction auto-écrite de mon module. Oui, le code ne brille pas de beauté, les délais brûlaient (comme c'était généralement le cas hier) et j'étais trop paresseux pour penser parce que j'avais l'habitude de programmer avec la souris , néanmoins, ça marche bien:



J'ai réécrit le code de la source presque 1v1.

Ensuite, une autre méthode pour brouiller le code a attiré mon attention. J'ai dû écrire une fonction assez grande qui recherche de tels appels:

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

Et en les remplaçant par des analogues plus simples:

Grande liste de fonctionnalités
 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;} }; 


Pour obtenir:

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

L'algorithme de travail est terriblement simple, en fait, nous substituons juste les variables:

  1. On retrouve le nom du tableau, dans notre cas: _0x30fe16
  2. Paramètres d'entrée Parsim: _0x425e3c, _0x481cd6
  3. Corps de la fonction Parsim: _0x425e3c <_0x481cd6
  4. Remplacez _0x425e3c par _0x13d8ee
  5. Remplacez _0x481cd6 par _0x5a370d
  6. Nous obtenons : _0x13d8ee <_0x5a370d
  7. Remplacez _0x30fe16.XNg (_0x13d8ee, _0x5a370d) par le code ci-dessus
  8. Répéter jusqu'à la fin des fonctions

L'analyse du nom, des paramètres et du corps de la fonction est effectuée par une personne régulière. Bien sûr, dans la version finale du module, cette méthode n'est pas particulièrement nécessaire, il n'y a qu'un seul appel qui est légèrement plus obscurci, mais le client a dit de tout faire et donc l'a fait, en plus, d'autres fonctions sont également devenues plus claires. Sur d'autres sites, il existe également de tels modèles:

Afficher le gros et le mauvais 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" }; 

Comme vous pouvez le voir ici, un paramètre d'un tableau se réfère à un autre. J'ai résolu ce problème simplement:

  1. Analyser tous les tableaux
  2. Nous les nettoyons du code
  3. Copiez tous les éléments dans un tableau associatif (nom, valeur)
  4. Dans une boucle par une recherche récursive, nous recherchons toutes les fonctions imbriquées
  5. Remplacer les fonctions imbriquées par les actions qu'elles font
  6. Développez tous les liens vers les chaînes de la même manière

Après avoir appliqué cette méthode de désobfuscation, le code est devenu un peu moins déroutant. Vous pouvez immédiatement remarquer la fonction base64 de deux manières. Premier:

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

Et le second:

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

Vous ne pouvez plus inverser et passer à d'autres fonctions plus importantes ou, pour être plus précis, à une fonction qui fonctionne avec les cookies. Je l'ai trouvé sur la ligne incap_ses_ et j'ai remarqué une autre puce d'obfuscation - obfuscation de code à l'aide de boucles:

Afficher le code
 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; } 


Ici, tout est très simple: nous réorganisons les lignes selon l'ordre d'exécution: 4 | 2 | 5 | 0 | 3 | 1 et obtenons la fonction d'origine. Cette méthode de désobfuscation n'est également pas nécessaire dans la version finale, mais elle n'a pas causé de gros problèmes, tout est analysé de manière élémentaire, la principale chose à considérer est qu'il peut y avoir des boucles imbriquées et donc je viens de faire une recherche récursive.

Fonction cookies
 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; } 

Il stocke simplement les valeurs de tous les cookies qui commencent par incap_ses_ dans un tableau , puis une autre méthode calcule leur somme de contrôle simplement en additionnant les codes ASCII:

Afficher le code
 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; 


Nous aurons besoin d'une somme de contrôle un peu plus loin, et voyons maintenant quel type de fonction ce _0x4d5690 est appelé à partir de différents endroits. Pour ce faire, il suffit de regarder les méthodes appelées et de leur attribuer les noms appropriés:

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

L'auteur de ce script est très naïf :)

Un autre point important:

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

De là, nous avons besoin des 5 premières lettres: ca3XP , je vais vous expliquer pourquoi ci-dessous. Et rappelez-vous, nous calculions les sommes de contrôle à partir des valeurs des cookies? Maintenant, nous en avons besoin pour obtenir le soi-disant hachage.

Fonction de hachage
 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; 


Comparez:



Super! La dernière étape reste - recevoir des cookies pour la réponse:

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

Le code d'origine au début a ajouté les paramètres du navigateur encodés en base64 à la fin du tableau AllParams décalé, les a «chiffrés» avec la fonction ParamDecryptor avec la clé ca3XP , puis supprimé l'élément ajouté précédemment. Je peux supposer que cette béquille a été faite à cause d'une petite fonctionnalité: la fonction ParamDecryptor accepte l'index de l'élément dans le tableau et la clé, ce qui signifie que vous ne pouvez y transférer la chaîne que via le tableau. Pourquoi ne pas le faire correctement? Programmeurs, monsieur.

Eh bien, en fait tout, le cookie est prêt, il reste à l'installer et à envoyer une demande. Certes, vous ne l'accepterez pas à cause d'un petit détail sur lequel je préfère rester silencieux.

Optimisation


Des morceaux de code dans Delphi ne sont qu'un prototype. Tout le code du désobfuscateur a été réécrit dans l'assembleur en raison des exigences du client et sa vitesse d'exécution a été augmentée plusieurs fois. Les éléments suivants ont également eu un effet positif sur la vitesse:

  1. Couper les morceaux de code et les tableaux en excès en une seule itération de la boucle. Cela est nécessaire pour réduire considérablement la quantité de code et accélérer la recherche à l'avenir.
  2. Étant donné que les fonctions du code ne sont pas mixtes, nous connaissons leur emplacement approximatif.Par conséquent, si la fonction d'installation des cookies est à la toute fin, vous devez la rechercher au moins à partir du milieu.
  3. Recherchez à l'avance les fonctionnalités clés. L'algorithme assembleur les recherche même au moment de nettoyer le code des ordures inutiles.
  4. Dans la version finale, je me suis débarrassé d'environ la moitié des fonctions du déobfuscateur qui étaient nécessaires pour comprendre le code et qui n'étaient pas nécessaires pour le bot car les paramètres nécessaires se sont déroulés sans problème

Conclusion


Lorsque je visite le site, je souhaite qu'il fonctionne rapidement et que le brouillage des scripts JS à l'aide de cette méthode est irrespectueux pour l'utilisateur. Est-ce que cela aide à protéger contre les robots? Non, bien sûr, comme vous pouvez le voir, cela coûte littéralement au cours de la soirée, et seulement quelques sandwichs et quelques tasses de thé sont le coût.

Le but de cet article est de parler du principe de fonctionnement de cette solution et de montrer à quel point elle est inutile. Pour des raisons évidentes, un module prêt à l'emploi pour contourner cette protection ne sera pas publié (après sa fuite, le pauvre game-stop est passé à akamai), qui doit le faire lui-même sur la base de cette étude (qui a été réalisée il y a environ un an et qui est toujours d'actualité), et les étudiants qui le souhaitent pour tordre les comptes des autres, allez dans la forêt. Si vous avez des questions, je suis toujours prêt à y répondre dans les commentaires.

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


All Articles