CURSILLO OTOÑAL DE CRACKEO - LECCIÓN 9 Procedamos a introducirnos en un nueva y densa lección que nos aporte alguna información complementaria

acerca del comportamiento habitual de los programas con los que solemos enfrentarnos. Se espera que el conjunto de técnicas y puntos de ataque comentados aquí sean de igual utilidad para los crackers principiantes (entre los que se cuenta mr.nobody), así como para los programadores que se decidan a leer estas líneas para mejorar y perfeccionar la protección de sus productos. A lo largo de la lección nos toparemos con temáticas distintas. Nuestro análisis empezará con una revisión del tema de los límites temporales y los errores más habitualmente cometidos en su codificación; continuaremos con una pequeña reflexión sobre la forma en que un programa "se entera" de las cosas: ¿cuando debo saltar o no? ¿estoy registrado o no? ¿Este código es correcto o no?..y terminaremos con un breve vistazo a un simple método de encriptación basado en la operación XOR. Vamos allá.... LÍMITACIONESVARIADAS ========================================= LIMITES TEMPORALES ================== Hay diversas formas de abordar la cuestión de la evaluación limitada en el tiempo de un producto. Algunos crackers experimentados recurren a la interrupción del programa (breakpoint) en cuanto éste hace uso de APIs como Getlocaltime, Getsystemtime,.. Su nombre es suficientemente transparente como para deducir que pueden tener mucho que ver con el cálculo de los días transcurridos desde la instalación de un producto,etc.. A pesar de ello, hay que decir que no todas las llamadas que se producen a estas APIs son debidas al deseo de comprobar el límite de tiempo, así que podemos encontrarnos con múltiples llamadas a Getlocaltime o Getsystemtime al iniciarse un programa. ¿Cuál de ellas será la que nos interesa desviar? Bfffff.... Uno puede llegar a perder la paciencia cuando hay tantas líneas que no entendemos,...pero todo tiene remedio. Probablemente el error más grande cometido por los programadores consiste en informarnos de que "la evaluación ha expirado". Nuestra pregunta entonces debe ser: ¿De dónde ha salido este mensaje? En este sentido puede sernos de gran ayuda el listado muerto de Wdasm.

Localizar el mensaje de error y ver qué ha provocado el salto hacia aquella zona son los primeros pasos a seguir. Esta especie de back-tracing o búsqueda hacia atrás nos puede llevar hasta la zona a parchear (o hasta la dirección concreta de la API que nos interesaba). Vayamos a por los ejemplos: S E A R C H & R E P L A C E 2.88 (www.funduc.com - ya van por la 3.0!) ----------------------------------(ftp://ftp.funduc.com/ftp/) Cuando arrancamos el programa después de 30 días nos encontramos con un mensaje diciendo "The evaluation period expired". Craso error!!!y aún más cuando el código se puede desensamblar libremente con Wdasm. Sólo nos queda ir a las StringDataReferences, doble-clickear y aparecemos en.... :0041E78E E8DD270000 call 00420F70 :0041E793 85C0 test eax, eax :0041E795 742A je 0041E7C1 >>>>>>>>********sí********* :0041E797 6AFF push FFFFFFFF :0041E799 6A04 push 00000004 * Possible Reference to String Resource ID=02170: "The evaluation period expired.you like to see Orderin" | :0041E79B 687A080000 push 0000087A >>>>>>aquí! Fijémonos en las líneas anteriores al mensaje de "expiración". Hay un salto que, de haberse producido, nos habría ahorrado la molestia de la ventanita con el mensaje puesto que habríamos ido a parar muy lejos (41e7c1). ¿Cómo podríamos asegurarnos de que el salto se produjera? Sencillo: cambiar 742A por EB2A (jmp forzoso :). Efectivamente este cambio sería suficiente para segurarnos una victoria. Otro cambio posible radicaría en la sutileza de alterar aquello que causa o no el salto. Veamos, ¿porque no se produce el salto je (salta si es igual, salta si es cero)? Está claro: previamente hay el testeo de eax. Si eax = 0 se salta, si eax es diferente de 0 no se salta y se llega a "expired". ¿Quién se encarga de gestionar este valor de EAX? El call que aparece más arriba. Podemos intentar entrar el call y forzar su salida con un valor EAX=0. Nos colocamos sobre la línea del CALL, le damos a ExecuteCALL (menú Execute text de Wdasm) y llegamos a...420f70. Una vez allí podemos

forzar la siguiente instrucción: 420f70 33 C0 : xor eax,eax (convertir eax en 0) 420f72 C3 : ret (volver al sitio desde donde se llamó el call) (el resto de instrucciones en el interior del call no se llegarán a ejecutar nunca, debido al ret que hemos impuesto, que fuerza el retorno inmediato) Con estas dos simples instrucciones nos aseguramos que el call devuelva un eax = 0 . Con ello, no hará falta modificar el salto condicional (que sí tendrá lugar) Reflexión: ¿Siempre se puede forzar de esta forma el valor de determinado registro al regresar de un CALL? No siempre. En este caso ha sido posible porque previamente habríamos comprobado con Softice que no había diferencias en el registro ESP, la pila. Si al regresar del call hubiera algún cambio, deberíamos asegurarnos de ajustar debidamente estos valores porque ello podría generar un error ya que al programa no le saldrían las cuentas. En este sentido, hemos tenido la suerte de toparnos con un CALL muy agradable puesto que no ha hecho falta hacerle más ajustes que el de forzar el valor de EAX. Es de vital importancia entender el sentido de una secuencia como: CALL XXXXX TEST EAX,EAX JE XXXXX (SI EAX ES 0, SALTA) CALL XXXXX TEST EAX,EAX JNE XXXX (SI EAX NO ES 0,SALTA) En el interior del CALL (que puede corresponder por ejemplo a una rutina que compara un código chungo y uno auténtico) se acaba emitiendo un veredicto: EAX 1 o EAX 0, que será decisivo para la ejecución (o no) del salto siguiente. Conocer este hecho puede ahorrarnos mucha faena. Algunos ejemplos: *Getright: Hubo quien intentó parchear alguna versión de este programa. Pues bien, para llevarla a cabo, era imprescindible al menos invertir unos 10 o 12 saltos condicionales. Todos ellos estaban precedidos por un mismo CALL y estaban localizados en sitios diferentes. Lógicamente, resulta mucho más práctico enterarse de qué valor nos interesa al salir del CALL (eax =1 o eax =0) y forzar alguno

de esos valores. De esta forma, con un único cambio en el interior del CALL nos aseguramos de que los saltos que se van a producir en diferentes partes del código, serán buenos para nosotros. A eso se le llama patchear elegantemente. Cada vez que desde algún lugar se llame esta rutina, el resultado será el deseable!! La diferencia está en parchear una vez la causa (call,..) o doce veces la consecuencia (je/jne) *Winzip: Los interesados en parchearlo se darán cuenta de que la clave para que acepte cualquier código chungo radica en el valor que tiene eax al salir de determinado CALL. Eax=1 querrá decir "vale, registrado" / Eax=0 significará "vayamos a incorrect-code". De alguna forma u otra, deberemos asegurarnos de que EAX sea 1 al salir del call (por cierto, a veces el programa puede esperar un 0 y no un 1. Sólo hace falta estar atento hacia donde nos hará saltar un valor u otro. Dentro de Softice podremos cambiar el valor de EAX anotando cuando nos interese R EAX=1 (+intro) o R EAX=0 (+intro). Sólo hace falta experimentar un poco. *Acdsee 2.40 / 2.41: Después de entrar los datos y tracear con f8/F10, uno acaba viendo que al salir de un CALL con EAX=0 nos enviarán al mensaje del perdedor. Si entramos en el CALL en cuestión, vemos que hay mucha prisa por enviarnos hacia una instrucción XOR EAX,EAX que convierte EAX a 0 y seguidamente un RET que nos hace salir de la rutina. Únicamente nopeando esta instrucción, salimos del CALL con EAX diferente de 0 y el programa admite in aeternum el código y los datos que hayamos introducido (además este resulta ser el mismo call que verifica los datos del registro de Windows al iniciar la aplicación, o sea, que el resultado es óptimo para nosotros). ps: Este truquillo no es ya válido para la versión Acdsee 2.42 U L T R A E D I T 3 2 V. 7.00a -------------------------------Otro ejemplo de programa de evaluación limitada temporalmente. En este caso disponemos de 45 días para decidirnos. Cuando se supera este període de tiempo, una ventanilla nos avisa del hecho ("UltraEdit 45 Day Evaluation time expired!") y el programa se cierra. Puesto que el programador se toma la molestia de avisarnos, pongamos un bpx messageboxa bajo Softice para que se nos informe de la zona en que nos movemos. Al re-iniciar Ultraedit, justo después de ignorar (con la tecla Escape) el cuadro de diálogo que nos sugiere que entremos nombre y código, Softice rompe. Un F12 nos deja en pleno código y nos enteramos de la dirección por la que nos movemos:

* Reference To: USER32.MessageBoxA, Ord:01BEh | :00469242 FF1514B74B00 Call dword ptr [004BB714] :00469248 399E44010000 cmp dword ptr [esi+00000144], ebx >>>>Aquí (Mess.box) Es el momento de desensamblar el ejecutable con Wdasm y buscar qué es lo que nos ha hecho aterrizar hasta aquí. Unas líneas más arriba podremos apreciar una instrucción altamente sospechosa: * Possible StringData Ref from Data Obj ->"Days to expire" | :004691D4 68C01A4E00 push 004E1AC0 * Possible StringData Ref from Data Obj ->"Settings" | :004691D9 68141A4E00 push 004E1A14 :004691DE E8B50F0300 call 0049A198 :004691E3 A16C844E00 mov eax, dword ptr [004E846C] :004691E8 2B0574844E00 sub eax, dword ptr [004E8474] :004691EE 50 push eax :004691EF E896440100 call 0047D68A :004691F4 83F82D cmp eax, 0000002D >>>compara EAX con 2Dhexa(45 decim!!!) :004691F7 59 pop ecx :004691F8 7F0C jg 00469206 >>>>(jump if great: salta a "expired" si es mayor) :004691FA 399E44010000 cmp dword ptr [esi+00000144], ebx :00469200 0F8576FFFFFF jne 0046917C * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:004691F8(C) bla,bla * Possible Reference to String Resource ID=00068: "UltraEdit 45 Day Evaluation time expired!!!!" bla,bla * Possible Reference to String Resource ID=00069: "To continue to use UltraEdit you must send the registration " bla,bla

* Reference To: USER32.MessageBoxA, Ord:01BEh (Mensaje de Expiración!!) | :00469242 FF1514B74B00 Call dword ptr [004BB714] :00469248 399E44010000 cmp dword ptr [esi+00000144], ebx >>>>Aquí (Mess.box) Bueno,bueno,bueno...veamos más de cerca el meollo: :004691F4 83F82D cmp eax, 0000002D ................. ............ :004691F8 7F0C jg 00469206 ¿No es realmente muy sospechoso? Comparar EAX con 45 (¿días?). Si es mayor, saltar y mostrar mensaje de Expiración. El método para hacer la comprobación temporal ha sido demasiado visible. Aquí lo que nos interesa es no-saltar hacia Expired, con lo que sólo tendremos que nopear el salto : 7F0C cambiado por 9090(nop,nop). A partir de este momento dispondremos del tiempo necesario para evaluar sin presiones cronológicas este fenomenal programa y decidirnos por su compra. Hubo un tiempo en que los programas limitados a 30 días recurrían mayoritariamente a unas secuencias muy fácilmente identificables: cmp eax, 0000001E (compara eax con 1Ehexa(30 decimal: 30 días) jng Continuar (Si no es mayor, salta a Continuar) (Esta secuencia se podía "arreglar" forzando un JMP (saltar siempre) en lugar del JNG (saltar si no es mayor de 30 días) cmp eax,0000001E (compara eax con 30 "días") jg Expired (Si es mayor, salta a Expired) (En este caso, el "arreglo" consiste en evitar el salto a Expired, con un nopeo (tantos 90 como hagan falta) de la instrucción JG xxx

Sign up to vote on this title
UsefulNot useful