
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:
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:
- Encontramos o nome da matriz, no nosso caso: _0x30fe16
- Parâmetros de entrada de parcimônia: _0x425e3c, _0x481cd6
- Corpo da função de parcimônia: _0x425e3c <_0x481cd6
- Substitua _0x425e3c por _0x13d8ee
- Substitua _0x481cd6 por _0x5a370d
- Temos: _0x13d8ee <_0x5a370d
- Substitua _0x30fe16.XNg (_0x13d8ee, _0x5a370d) pelo código acima
- 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 };
Como você pode ver aqui, um parâmetro de uma matriz se refere a outra. Eu resolvi esse problema simplesmente:
- Parsim todas as matrizes
- Nós os limpamos do código
- Copie todos os elementos em uma matriz associativa (nome, valor)
- Em um loop por uma pesquisa recursiva, procuramos todas as funções aninhadas
- Substitua funções aninhadas pelas ações que executam
- 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')
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;
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:
- 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.
- 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.
- 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.
- 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.