Rompemos a proteção contra bots



Recentemente, a incapsula começou a aparecer em muitos sites estrangeiros - um sistema que aumenta a segurança, a velocidade e, ao mesmo tempo, complica muito a vida dos desenvolvedores de software. A essência deste sistema é uma proteção abrangente usando JavaScript, que, a propósito, muitos bots DDOS já aprenderam a executar e até mesmo ignorar o CloudFlare. Hoje vamos estudar a cápsula, escrever um desobfuscador de script JS e ensinar nosso bot do DDOS a contornar isso!

O site na captura de tela abaixo é considerado um bom exemplo de um artigo, não difere de outros, em fóruns de assuntos duvidosos, muitos estão procurando por brutos, mas eu tenho outra tarefa - software para automatizar várias ações com sites.

Vamos começar examinando as consultas, já que existem apenas duas delas, e aqui está a primeira:



Essa solicitação carrega um script que é ofuscado naturalmente:



Alterar eval para document.write () fornece um código um pouco mais legível:



Não sei por que, mas as ferramentas automáticas para formatar o código de saída quebram esse código, então tive que formatá-lo com as mãos e fornecer nomes normais às variáveis. Para todas essas manipulações, usei um bloco de notas simples ++ e uma função de substituição de texto, como resultado, podemos seguir em frente e examinar a primeira linha:

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

Esta é uma matriz que contém nomes de funções criptografadas e outras strings, portanto, precisamos procurar por uma função que descriptografe essa matriz, sem precisar ir muito longe:

Código desobfuscado
 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; }; 


Se o chamarmos separadamente:

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

Esse resultado estará longe do que esperávamos. E tudo é culpado por outra função:

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

Que está em um lugar muito inesperado - em uma função que é chamada no início e verifica os cookies. Aparentemente, os desenvolvedores "protegeram" a verificação de cookies de serem cortados. Como você pode ver - nada complicado, o loop simplesmente muda a matriz pelo número especificado de elementos, no nosso caso 223 elementos. De onde vem esse número mágico 223? Peguei esse número de uma chamada para a função de verificação de cookies, parece 0xdf e segue esta rota:

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

Naturalmente, muda sempre, quem duvidaria ...

Agora, tudo o que resta é substituir todas as chamadas

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

em

 var _0x85e545=this['window']; 

Ou, melhor ainda, em

 var ThisWindow=this.window; 

Fiz a última conversão regularmente. Ah, sim, eles esqueceram completamente de dar as seguintes linhas:

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

Para visualização normal. Não há nada complicado, este é um UrlEncode comum, altere \ x para% e decodifique para obter a seguinte linha:

woJ3XhkYw4DDnxdp

Então eu comecei a substituir todas as chamadas ala

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

para descriptografar linhas usando uma função auto-escrita do meu módulo. Sim, o código não brilha com a beleza, os prazos estavam queimando (como era geralmente necessário ontem) e eu estava com preguiça de pensar porque costumava programar com o mouse , mas funciona bem:



Eu reescrevi o código da fonte quase 1v1.

Em seguida, outro método para ofuscar o código chamou minha atenção. Eu tive que escrever uma função bastante grande que procura por essas chamadas:

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

E substituindo-os por análogos mais simples:

Ótima lista de recursos
 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;} }; 


Para obter:

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

O algoritmo de trabalho é terrivelmente simples; na verdade, apenas substituímos as variáveis:

  1. Encontramos o nome da matriz, no nosso caso: _0x30fe16
  2. Parâmetros de entrada de parcimônia: _0x425e3c, _0x481cd6
  3. Corpo da função de parcimônia: _0x425e3c <_0x481cd6
  4. Substitua _0x425e3c por _0x13d8ee
  5. Substitua _0x481cd6 por _0x5a370d
  6. Temos: _0x13d8ee <_0x5a370d
  7. Substitua _0x30fe16.XNg (_0x13d8ee, _0x5a370d) pelo código acima
  8. Repita até que as funções terminem

A análise do nome, parâmetros e corpo da função é feita por um regular. Obviamente, na versão final do módulo esse método não é particularmente necessário, há apenas uma chamada um pouco mais ofuscada, mas o cliente disse para fazer tudo e, portanto, fez, além disso, outras funções também ficaram mais claras. Em outros sites, também existem modelos:

Mostrar código grande e incorreto
 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" }; 

Como você pode ver aqui, um parâmetro de uma matriz se refere a outra. Eu resolvi esse problema simplesmente:

  1. Parsim todas as matrizes
  2. Nós os limpamos do código
  3. Copie todos os elementos em uma matriz associativa (nome, valor)
  4. Em um loop por uma pesquisa recursiva, procuramos todas as funções aninhadas
  5. Substitua funções aninhadas pelas ações que executam
  6. Expanda todos os links para strings da mesma maneira

Depois de aplicar esse método de desofuscação, o código ficou um pouco menos confuso. Você pode notar imediatamente a função base64 de duas maneiras. Primeiro:

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

E o segundo:

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

Você não pode mais reverter e passar para outras funções mais importantes ou, para ser mais preciso, para uma função que funcione com cookies. Eu o encontrei na linha incap_ses_ e notei outro chip de ofuscação - ofuscação de código usando loops:

Mostrar código
 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; } 


Tudo é muito simples aqui: reorganizamos as linhas de acordo com a ordem de execução: 4 | 2 | 5 | 0 | 3 | 1 e obtemos a função original. Esse método de desofuscação também não é necessário na versão final, mas não causou grandes problemas, tudo é analisado de forma elementar, o principal a considerar é que pode haver loops aninhados e, portanto, fiz uma pesquisa recursiva.

Função de 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; } 

Ele simplesmente armazena os valores de todos os cookies que começam com incap_ses_ em uma matriz e, em seguida, outro método calcula sua soma de verificação simplesmente somando códigos ASCII:

Mostrar código
 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; 


Vamos precisar de uma soma de verificação um pouco mais e agora vamos descobrir que tipo de função é essa _0x4d5690 que é chamada de lugares diferentes. Para fazer isso, basta olhar para os métodos chamados e atribuir a eles os nomes apropriados:

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

O autor deste script é muito ingênuo :)

Outro ponto importante:

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

A partir daqui, precisamos das 5 primeiras letras: ca3XP , vou explicar por que abaixo. E lembre-se, estávamos calculando somas de verificação a partir dos valores dos cookies? Agora precisamos deles para obter o chamado hash.

Função 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; 


Compare:



Ótimo! O último passo permanece - o recebimento de cookies para a resposta:

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

O código original no início adicionou os parâmetros do navegador codificados em base64 ao final da matriz AllParams deslocada, os "criptografou" com a função ParamDecryptor com a chave ca3XP e excluiu o elemento adicionado anteriormente. Eu posso assumir que essa muleta foi feita devido a um pequeno recurso: a função ParamDecryptor aceita o índice do elemento na matriz e a chave, o que significa que você pode transferir a cadeia apenas através da matriz. Por que não fazer isso certo? Programadores, senhor.

Bem, na verdade tudo, o cookie está pronto, resta instalá-lo e enviar uma solicitação. É verdade que você não o aceitará por causa de um pequeno detalhe sobre o qual prefiro permanecer calado.

Otimização


Pedaços de código no Delphi são apenas um protótipo. Todo o código do desobfuscador foi reescrito no assembler devido aos requisitos do cliente e sua velocidade de execução aumentou várias vezes. O seguinte também teve um efeito positivo na velocidade:

  1. Cortar pedaços de código em excesso e matrizes em uma iteração do loop. Isso é para reduzir significativamente a quantidade de código e acelerar a pesquisa no futuro.
  2. Como as funções no código não são misturadas, sabemos sua localização aproximada; portanto, se a função de instalação de cookies estiver no final, será necessário procurá-la pelo menos do meio.
  3. Pesquise os principais recursos com antecedência. O algoritmo do assembler procura por eles mesmo no momento da limpeza do código de lixo desnecessário.
  4. Na versão final, eu me livrei de cerca da metade das funções do desobfuscador que eram necessárias para entender o código e que não eram necessárias para o bot, pois os parâmetros necessários ficaram sem problemas

Conclusão


Quando visito o site, quero que ele funcione rapidamente, e ofuscar scripts JS usando esse método é desrespeitoso para o usuário. Isso ajuda a proteger contra bots? Não, é claro, como você pode ver, custa literalmente durante a noite, e apenas dois sanduíches e algumas xícaras de chá são o custo.

O objetivo deste artigo é falar sobre o princípio de operação desta solução e mostrar como ela é inútil. Por razões óbvias, um módulo pronto para contornar essa proteção não será publicado (após o vazamento, a má parada do jogo mudou para akamai), que precisa fazer isso com base nesse estudo (realizado há cerca de um ano e que ainda é relevante) e estudantes que desejam para contrair as contas de outras pessoas, vá para a floresta. Se você tiver perguntas, estou sempre pronto para respondê-las nos comentários.

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


All Articles