Rompemos la protección de los bots



Recientemente, la incapsula ha comenzado a aparecer en muchos sitios extranjeros, un sistema que aumenta la seguridad del sitio, la velocidad del trabajo y al mismo tiempo complica enormemente la vida de los desarrolladores de software. La esencia de este sistema es la protección integral mediante JavaScript, que, por cierto, muchos bots DDOS ya han aprendido a ejecutar e incluso evitar CloudFlare. Hoy estudiaremos incapsula, escribiremos un desobfuscador de script JS y le enseñaremos a nuestro robot DDOS a evitarlo.

El sitio en la captura de pantalla a continuación se toma como un buen ejemplo para un artículo, no difiere de otros, en foros de temas dudosos, muchos lo buscan en bruto, pero tengo otra tarea: software para automatizar varias acciones con sitios.

Comencemos examinando las consultas, ya que solo hay 2 de ellas, y aquí está la primera:



Esta solicitud carga un script que se ofusca naturalmente:



Cambiar eval a document.write () da un código un poco más legible:



No sé por qué, pero las herramientas automáticas para formatear el código de salida rompen este código, así que tuve que formatearlo con mis manos y darles a las variables nombres normales. Para todas estas manipulaciones, utilicé un simple bloc de notas ++ y una función de reemplazo de texto, como resultado podemos seguir y examinar la primera línea:

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

Esta es una matriz que contiene nombres de funciones cifradas y otras cadenas, por lo que debemos buscar una función que descifre esta matriz, no es necesario ir muy lejos:

Código desobuscado
 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 lo llamamos por separado:

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

Ese resultado estará lejos de lo que esperábamos. Y todo tiene la culpa de otra función:

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

Que está en un lugar muy inesperado, en una función que se llama desde el principio y comprueba las cookies. Aparentemente, los desarrolladores por lo tanto "protegieron" la verificación de cookies para que no se cortara. Como puede ver, nada complicado, el ciclo simplemente cambia la matriz por el número especificado de elementos, en nuestro caso 223 elementos. ¿De dónde viene este número mágico 223? Tomé este número de una llamada a la función de verificación de cookies, parece 0xdf allí y sigue esta ruta:

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

Naturalmente, cambia cada vez, quién dudaría ...

Ahora todo lo que queda es reemplazar todas las llamadas

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

en

 var _0x85e545=this['window']; 

O, mejor aún, en

 var ThisWindow=this.window; 

Hice la última conversión como un regular regular. Oh sí, se olvidaron por completo de dar las siguientes líneas:

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

A la vista normal. No hay nada complicado, este es un UrlEncode normal, cambia \ x a% y decodifica para obtener la siguiente línea:

woJ3XhkYw4DDnxdp

Entonces comencé a reemplazar todas las llamadas ala

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

a líneas ya descifradas usando una función autoescrita desde mi módulo. Sí, el código no brilla con belleza, los plazos se estaban agotando (como solía ser necesario ayer) y era demasiado vago para pensar porque solía programar con el mouse , sin embargo, funciona bien:



Reescribí el código de la fuente casi 1v1.

Luego, otro método para ofuscar el código me llamó la atención. Tuve que escribir una función bastante grande que busca tales llamadas:

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

Y reemplazándolos por análogos más simples:

Gran lista de funciones
 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 obtener:

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

El algoritmo de trabajo es terriblemente simple, de hecho, solo sustituimos las variables:

  1. Encontramos el nombre de la matriz, en nuestro caso: _0x30fe16
  2. Parámetros de entrada de Parsim: _0x425e3c, _0x481cd6
  3. Cuerpo de la función Parsim: _0x425e3c <_0x481cd6
  4. Reemplace _0x425e3c con _0x13d8ee
  5. Reemplace _0x481cd6 con _0x5a370d
  6. Obtenemos: _0x13d8ee <_0x5a370d
  7. Reemplace _0x30fe16.XNg (_0x13d8ee, _0x5a370d) con el código anterior
  8. Repita hasta que finalicen las funciones

El análisis del nombre, los parámetros y el cuerpo de la función lo realiza una persona regular. Por supuesto, en la versión final del módulo, este método no es particularmente necesario, solo hay una llamada que está un poco más ofuscada, pero el cliente dijo que hiciera todo y, por lo tanto, lo hizo, además, otras funciones también se hicieron más claras. En otros sitios también hay tales diseños:

Mostrar código grande y malo
 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 puede ver aquí, un parámetro de una matriz se refiere a otro. Resolví este problema simplemente:

  1. Parsim todas las matrices
  2. Los limpiamos del código.
  3. Copie todos los elementos en una matriz asociativa (nombre, valor)
  4. En un bucle por una búsqueda recursiva, buscamos todas las funciones anidadas
  5. Reemplazar funciones anidadas con las acciones que realizan
  6. Expanda todos los enlaces a cadenas de la misma manera

Después de aplicar este método de desofuscación, el código se volvió un poco menos confuso. Puede notar de inmediato la función base64 de dos maneras. Primero:

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

Y el segundo:

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

Ya no puede revertir y pasar a otras funciones más importantes, o para ser más precisos, a una función que funciona con cookies. Lo encontré en la línea incap_ses_ y noté otro chip de ofuscación: ofuscación de código usando bucles:

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


Aquí todo es muy simple: reorganizamos las líneas de acuerdo con el orden de ejecución: 4 | 2 | 5 | 0 | 3 | 1 y obtenemos la función original. Este método de desofuscación tampoco es necesario en la versión final, pero no causó grandes problemas, todo se analiza de manera elemental, lo principal a considerar es que puede haber bucles anidados y, por lo tanto, solo hice una búsqueda recursiva.

Función 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; } 

Simplemente almacena los valores de todas las cookies que comienzan con incap_ses_ en una matriz y luego otro método calcula su suma de verificación simplemente sumando 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; 


Necesitaremos una suma de comprobación un poco más y ahora veamos qué tipo de función se llama a este _0x4d5690 desde diferentes lugares. Para hacer esto, solo mire los métodos llamados y asígneles los nombres apropiados:

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

El autor de este script es muy ingenuo :)

Otro punto importante:

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

A partir de aquí, necesitamos las primeras 5 letras: ca3XP , te diré por qué a continuación. Y recuerde, ¿estábamos calculando sumas de verificación a partir de valores de cookies? Ahora los necesitamos para obtener el llamado hash.

Función 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; 


Compara:



Genial El último paso permanece: recibir cookies para la respuesta:

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

El código original al principio agregó los parámetros del navegador codificados en base64 al final de la matriz AllParams desplazada, los "cifró" con la función ParamDecryptor con la clave ca3XP y luego eliminó el elemento agregado anteriormente. Puedo suponer que esta muleta se hizo debido a una pequeña característica: la función ParamDecryptor acepta el índice del elemento en la matriz y la clave, lo que significa que solo puede pasar la cadena a través de la matriz. ¿Por qué no hacerlo bien? Programadores, señor.

Bueno, en realidad todo, la cookie está lista, queda por instalarla y enviar una solicitud. Es cierto que no lo aceptará debido a un pequeño detalle sobre el que prefiero guardar silencio.

Optimización


Las piezas de código en Delphi son solo un prototipo. Todo el código del desobuscador se reescribió en ensamblador debido a los requisitos del cliente y su velocidad de ejecución aumentó varias veces. Lo siguiente también tuvo un efecto positivo en la velocidad:

  1. Cortar el exceso de piezas de código y matrices en una iteración del bucle. Esto es necesario para reducir significativamente la cantidad de código y acelerar la búsqueda en el futuro.
  2. Dado que las funciones en el código no están mezcladas, conocemos su ubicación aproximada, por lo tanto, si la función de instalación de cookies está al final, entonces debe buscarla al menos desde el medio.
  3. Busque características clave por adelantado. El algoritmo ensamblador los busca incluso al momento de limpiar el código de basura innecesaria.
  4. En la versión final, eliminé aproximadamente la mitad de las funciones del desobfuscador que se necesitaban para comprender el código y que no se necesitaban para el bot, ya que los parámetros necesarios se obtuvieron sin problemas

Conclusión


Cuando visito el sitio, quiero que funcione rápidamente, y ofuscar las secuencias de comandos JS con este método es irrespetuoso para el usuario. ¿Esto ayuda a proteger contra los robots? No, por supuesto, como puede ver, cuesta literalmente durante la noche, y solo cuestan un par de sándwiches y unas pocas tazas de té.

El propósito de este artículo es hablar sobre el principio de funcionamiento de esta solución y mostrar cuán inútil es. Por razones obvias, no se publicará un módulo listo para omitir esta protección (después de su fuga, la pobre parada del juego cambió a akamai), que necesita hacerlo él mismo en base a este estudio (que se realizó hace aproximadamente un año y aún es relevante), y los estudiantes que quieran para sacudir las cuentas de otras personas ir al bosque. Si tiene preguntas, siempre estoy listo para responderlas en los comentarios.

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


All Articles