Karpoff Spanish Tutor

Programa:

FlexiSIGN PRO and it's family of products 


 

PROTECCION: SuperPro de Sentinel
Objetivo: Destrabar las API de funciones de la DLL SentinelSuperPro WIN32
Descripcion: En la web esta la descripción
Dificultad: Media
DOWNLOAD: http://www.amiable.com
Herramientas: Softice W32dasm IDA Editor Hexadecimal
CRACKER: Goatass, CrackZ   FECHA: ../../....

 

 INTRODUCCION

"Después de leer el muy buen tutorial de goatass, me senté y me puse a tomar una cerveza bien fría, mientras pensaba: ¿porque los desarrolladores de software delegan su estrategia de protección a los vendedores de dongles? Eso es realmente frustrante. Echale una mirada a mi sitio, y llegarás a la conclusión de que los dongles son un gasto al pedo de dinero. Yo me pregunto: ¿algún usuario de dongles no ha pensado alguna vez en iniciarles acciones legales a estos comerciantes metirosos? Hay un buen caso con los dongles que no cumplen con el propósito para el que fueron creados".  "Cuando reescribas la función sproRead() acordáte que el código que uso en el ejemplo de emulación inicialmente depende del stack, (para obtener la dirección de lectura), goatass  apuntó algunos problemas haciendo esto, probablemente debido a las características del compilador, sproRead() no siempre preserva el valor del registro, EBX como te darás cuenta en este artículo. Por supuesto que si tenés acceso a mi página privada, aprenderás que hay una manera de emular todas las rutinas de Sentinel en un solo lugar, goatass incluso muestra algunos de los códigos claves para hacer esto :-) él dice que la palabra (word) 0A copiada a la estructura de grabación + 30h luce sospechosa". "Tutorial ligeramente editado por CrackZ".

Segunda Introduccion:

Primero que nada, quiero decir que compré este soft, y que tengo el dongle y la licencia original de FlexiSIGN PRO. Amiable es una empresa que hace soft de alta calidad para la industria de Letreros o carteles. Un tutorial escrito por CrackZ hace tiempo, sobre el CASMate, fue el primer paper sobre protección usado por esta compañía. Yo también crackeé, de la misma empresa, el  Inspire 1.6 pero no tuve el tiempo suficiente para escribir un tutorial. De cualquier manera, esta aplicación usa la mochila SuperPro de Sentinel, en conjunto con un nombre de usuario y una password que deciden cuál programa estás autorizado a instalar. En este tutorial te mostraré cómo encontrar y emular la mochila de Sentinel, y cómo bypass todos los checks que estén relacionados.

Herramientas

http://zencrack2.cjb.net/ - Todos los tuturiales sobre Sentinel tutorials (y mucho más).
ftp://ftp.rainbow.com/pub/online_documents/ - No olvidarse de leer pro223.pdf, spro_dev.pdf.
SoftICE, Hex Editor, IDA or W32Das

 

 AL ATAKE

En marcha!

OK, después de instalar el programa chusmeamos los archivos. Corremos el archivo App.exe que es el programa principal, App2.exe es el administrador de producto (nos ocuparemos de él más tarde). ¿Qué ves? Carga algunas "cagaditas", y después tira un mensaje de error diciendo:

"This application requires that a Hardware key be installed, but none was found".

Este lindo mensaje nos anuncia que el programa busca en nuestros ports para ver si hay una mochila conectada, y por supuesto no la encuentra. Habiendo leído el manual de Sentinel  sabemos que el programa debe llamar la función sproFindFirstUnit() después de haber inicializado el "record paquet" con la función sproInitialize(). Cuando nos referimos a "record paquet", queremos decir una estructura que retiene y retendrá información sobre la mochila (dongle). Nuevamente usando info del manual de sentinel, sabemos que esta estructura debe ser puesta en el stack, antes de que cualquier API de la mochila sea usada. Esto es bueno para nosotros porque nos dá una marca de partida, para encontrar todos los llamados de API de la mochila.

También sabemos que cuando una API falla retorna 3 en el registro EAX. Otra cosa que nos ayuda es la demora que genera la mochila cuando el programa trata de accederla y no la encuentra, ésta hará como si la PC se colgara por unos pocos segundos; cuando esto sucede, es una señal inequívoca que se hizo un CALL a una API de la mochila. Antes de que este call suceda, el programa debe chequear que tenga una packet record válido, y es además lo que usaremos para encontrar el llamado a la mochila. En el código del programa luce más o menos así:

CMP WORD PTR [ESI], 7242
JE packet_record_valid
MOV AX, 0002
POP EDI
POP ESI
RET 000C

7242 es la firma del packet record, y debe ser chequeada; si falla el código que retorna en EAX es 2, que significa paquete incorrecto (Invalid Packet). Bien, basta de teoría por ahora, y vamos directo a nuestro negocio. Después de observar cuáles archivos son llamados por programa, los abrimos con el W32Dasm,y nos fijamos qué funciones importa y exporta. Comenzamos con un archivo llamado Sx32w.dll. Después de abrirlo chusmeamos que en el listado de exportación, exporta todas las funciones que vimos en el manual de Sentinel. Interesante ¿no?, si lo abrimos con un editor hexadecimal casi al final del archivo encontramos el string "SentinelSuperPro WIN32 DLL", que es la DLL que provee Rainbow a los desarrolladores para usarla en su aplicación.  Esto es realmente bueno para nosotros, porque ahora conocemos el lugar principal donde se hacen los llamados a la mochila; y además cuando el soft necesite usar la mochila se debe usar ésta DLL. 

Nuestro siguiente paso será poner este achivo en el debuger (SICE) y hacer todas las modificaciones a la mochila en un solo lugar. La clave para crackear un programa protegido con una mochila es tracear diferentes path hasta que encontrás el correcto. Bien, la gran pregunta es: ¿Cómo hacemos para poner un break en una mochila? Lo que hice, debido a que las APIs son llamadas de un montón de DLLs, fué abrir Sx32w.dll y fijarme el offset de la función sproFindFirstUnit(); después lo abrí con el editor hexadecimal, ubiqué el cursor en el offset y reemplacé el primer byte con CCh que es la INT 3; hay que acordarse del valor que tenía, de manera que eventualmente podamos restituirlo. 

En el SICE poné BPINT 3 (breakpoint en la INT3) y salí de la ventana presionando (F5). Ahora corré el programa y esperá el break, para editar el byte que apunta el puntero de instrucción, poné E EIP, entonces cambiá CCh al valor original. Siguiendo con  el SICE, pone el siguiente brekpoint "bpx EIP". Ahora anda al editor hexadecimal abrí el ejecutable y restaurá el valor CCh a al que tenía originalmente. Salí del editor y nuevamente corré App.exe. Como era de esperar, tenemos un break en la función sproFindFirstUnit(). Cuando esto ocurre luce de la siguiente forma:

Exported fn(): RNBOsproFindFirstUnit - Ord:000Bh
:004072B0 PUSH EBX
:004072B1 PUSH ESI
:004072B2 MOV EAX, DWORD PTR [ESP+0C]
:004072B6 OR EAX, EAX <-- verifica que el programa hay PUSHesdo el packet record.
:004072B8 JNE 004072C3
:004072BA MOV AX, 2
:004072BE POP ESI
:004072BF POP EBX
:004072C0 RET 8

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004072B8(C)

:004072C3 PUSH EAX
:004072C4 CALL 004010D0
:004072C9 MOV ESI, EAX
:004072CB CMP WORD PTR [ESI], 7242 <-- this is the trademark check for the
:004072D0 JE 004072E0 <-- validity of the packet record.
:004072D2 MOV AX, 2
:004072D6 POP ESI
:004072D7 POP EBX
:004072D8 RET 8

El código que sigue es el llamado a la mochila. No hay que preocuparse mucho por esto, porque en este punto el dongle no genera código de retorno, sino solamente dos valores en el registro EAX: 3 si no está presente, y cero si está presente. Lo que hacemos para emparcharlo, es poner NOP en donde estaba el JNE en la dirección 004072B8, y cambiamos MOV AX, 2 a MOV AX, 0 como se ve abajo:

:004072B8 90 NOP <-- evita el JNE.
:004072B9 90 NOP
:004072BA 66B80000 MOV AX, 0 <-- fuerza a retornar 0 - Encontro dongle.

Eso es todo lo que hay que hacer para emparachar el primer chequeo de la mochila. Bastante simple, ¿no?. Bueno, no nos pongamos tan contentos que resta mucho por hacer. Antes de que comencemos a realizar el trabajo de emparchado usando el editor Hexadecimal, (supongo que saben cómo hacerlo), vamos a continuar con el siguiente llamado  que se hace a la mochila, que debería ser sproRead(). Aquí es donde tenemos que hacer la mayoría del trabajo para reventar la protección, tampoco quiero que crean que es tan difícil. Comenzamos usando la técnica que expliqué anteriormente de BPINT 3 en la función  sproFindFirstUnit(). Una vez realizada la tarea en el SICE, y cuando el break se ejecuta luce de la siguiente forma:  

Exported fn(): RNBOsproRead - Ord:0002h
:00407480 PUSH ESI
:00407481 PUSH EDI
:00407482 MOV EAX, DWORD PTR [ESP+0C]
:00407486 OR EAX, EAX
:00407488 JNE 00407493
:0040748A MOV AX, 2
:0040748E POP EDI
:0040748F POP ESI
:00407490 RET 0C

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00407488(C)

:00407493 PUSH EAX
:00407494 CALL 004010D0
:00407499 MOV ESI, EAX
:0040749B CMP WORD PTR [ESI], 7242 <-- Verifica trademark nuevamente.
:004074A0 JE 004074B0
:004074A2 MOV AX, 2
:004074A6 POP EDI
:004074A7 POP ESI
:004074A8 RET 0C

:004074C5 MOV EDI, DWORD PTR [ESP+14] <-- EDI tiene la direccion del buffer para almacenar los datos.
:004074C9 OR EDI, EDI <-- La  word retornada de la mochila.

:004074E0 MOV [ESI+30], 000A
:004074E6 MOV AX, WORD PTR [ESP+10]
:004074EB MOV WORD PTR [ESI+34], AX
:004074EF PUSH ESI
:004074F0 CALL 00405E10 <-- Este call lleva a la funcion sproRead()l, que nosotros emularemost.
:004074F5 OR AL, AL <-- is AL = 0 ?.
:004074F7 JNE 00407510 <-- Si no da, chau vieja .
:004074F9 MOV AX, WORD PTR [ESI+36] <-- WORD que retorna la mochila.
:004074FD MOV WORD PTR [EDI], AX <-- pone esa WORD en el buffer.

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0040751E(C)

:00407500 MOV AX, WORD PTR [ESI+06] <-- Direccion de codigo de error.
:00407504 PUSH EAX
:00407505 CALL 00406AC0 <-- Verifica el codigo de error code, debe retornar 0 en el registro EAX para indicar OK.
:0040750A POP EDI
:0040750B POP ESI
:0040750C RET 0C

El código que está abajo lo origina  cm32.dll que es la DLL encargada de la protección. Se comunica con la mochila y resuelve los valores que ésta retorna. Abajo hay una explicación sobre esto:

:10002F60 CMP DWORD PTR [EBP-04], 17 <-- Verifica si llegamos a CELL 17.
:10002F64 JGE 10002FB0
:10002F66 MOV ECX, DWORD PTR [EBP-04] <-- Numero de CELL actual.
:10002F69 MOV EDX, DWORD PTR [EBP+0C] <-- Bueffer de retorno.
:10002F6C LEA EAX, DWORD PTR [EDX+2*ECX] <-- Hace espacio para la proxima WORD.
:10002F6F PUSH EAX
:10002F70 MOV ECX, DWORD PTR [EBP-04] <-- CELL a leer.
:10002F73 ADD ECX, 8 <-- Salta las CELLs 1 a 7.
:10002F76 PUSH ECX
:10002F77 MOV EDX, DWORD PTR [EBP+08] <-- packet record.
:10002F7A PUSH EDX

* Reference To: SX32W.RNBOsproRead, Ord:0001h

:10002F7B CALL 100281C7 <-- call sproRead().
:10002F80 AND EAX, 0000FFFF <-- Refina el codigo de error.
:10002F85 MOV DWORD PTR [EBP-08], EAX
:10002F88 CMP DWORD PTR [EBP-08], 0 <-- Fue correcta la lectura?.
:10002F8C JE 10002FAE <-- Buen cracker.
:10002F8E CMP DWORD PTR [EBP-08], 7
:10002F92 JNE 10002FA0
:10002F94 MOV EAX, DWORD PTR [EBP-0C]
:10002F97 MOV [EAX+04], 8
:10002F9E JMP 10002FAA

El código está en un lazo desde CELL 8 a CELL 17 (en hexadecimal). Después que tenemos toda la data de la mochila almacenada en la memoria, continuamos. El código de abajo es la función que llama el enredo de arriba:

:100029A5 CALL 10002F45 <-- Llamado por el loop de arriva (sproRead() loop).
:100029AA TEST EAX, EAX <-- El valor de retorno del CALL debe ser 1.
:100029AC JNE 100029B2 <-- good jump.
:100029AE XOR EAX, EAX
:100029B0 JMP 100029FA

:100029B2 MOV ECX, DWORD PTR [EBP-38] <-- Adquiere una de las WORDs retornadas por la mochila.
:100029B5 MOV EDX, DWORD PTR [EBP-2C]
:100029B8 MOV DWORD PTR [ECX+428], EDX
:100029BE MOV EAX, DWORD PTR [EBP-38] <-- buffer a la data retornada de la mochila.
:100029C1 MOV ECX, DWORD PTR [EAX+428]
:100029C7 CMP ECX, DWORD PTR [EBP+0C] <-- compara el USER NUMBER del programa con el que retorna la mochila.

:100029CA JNE 100029D3
:100029CC MOV EAX, 1 <-- good flag.
:100029D1 JMP 100029FA

En este tramo ya tenemos en ECX el valor retornado por la mochila para USER NUMBER, y en [EBP+0C] tenemos el valor correcto. Es el que entramos cuando instalamos el programa. Bueno, con solamente tipear en el SICE, D [EBP+0C], podemos ver el valor de retorno. Lo anotamos en algún lado, como también su posición desde la primera palabra (WORD ) desde la mochila. Después que salimos de este CALL, tenemos que chequear para ver si todo esto funciona correctamente:

:10002428 CALL 10002974 <-- the call we just came back from.
:1000242D TEST EAX, EAX <-- EAX debe ser igual a 1.
:1000242F JE 100024DB <-- bad jump.
:10002435 MOV [EBP-14], 00000000 <-- good flag.
:10002491 MOV ECX, DWORD PTR [EBP-28]
:10002494 CMP DWORD PTR [ECX+04], 8 <-- Verifica el valor de retorno del llamdo (CALL).
:10002498 JNE 100024A3 <-- bad jump.
:1000249A MOV [EBP-30], 1 <-- good flag.
:100024A1 JMP 100024B2 <-- good jump.

La verificacion de arriba, no es para chequear data de la mochila, es una verificación que hace el CALL para ver si no hubo problemas. Una vez que esta comparación es hecha, y el JMP 100024B2 es ejecutado, habremos hecho gran parte del emparchado. Bien, es tiempo de inspeccionar la función sproRead() para emularla. Cuando mirábamos la DLL Sx32w.dll,y llegábamos hasta la función sproRead(), veíamos que todo el chequeo que se hacía era para asegurarse de que el packet record había sido inicializado, y todo estaba bien antes de hacer la lectura de la mochila. La lectura comienza en la dirección 004074E0

:004074E0 MOV [ESI+30], 000A
:004074E6 MOV AX, WORD PTR [ESP+10]
:004074EB MOV WORD PTR [ESI+34], AX <-- "Developer ID" - (nota de CrackZ).
:004074EF PUSH ESI
:004074F0 CALL 00405E10 <-- Hace la lectura real, no es interesante.
:004074F5 OR AL, AL <-- AL debe ser 0, se es 3 falla.
:004074F7 JNE 00407510 <-- Salta si la lectura falla.
:004074F9 MOV AX, WORD PTR [ESI+36] <-- retorna la WORD de la mochila.
:004074FD MOV WORD PTR [EDI], AX <-- la salva en el buffer.

:00407500 MOV AX, WORD PTR [ESI+06] <-- Nuevamente el codigo de error.
:00407504 PUSH EAX
:00407505 CALL 00406AC0 <-- funcion para limpiar el codigo de error.
:0040750A POP EDI <-- Ahora EAX debe ser cero.
:0040750B POP ESI

:0040750C RET 0C

El código anterior hace la lectura de la mochila y almacena el WORD retornado en el registro EDI ,entoces verifica nuevamente el código de error y lo pone en el registro EAX para la función que lo llamó. De manera que cuando se ejecuta el código de operación RET, (retorno de un llamado a función) el registro AX debe estar en cero. Mirando alguno de los tutoriales de CrackZ's  sobre Sentinel, vemos que usa casi siempre la misma emulación para todas las aplicaciones que ha crackeado y que tengan Super SentinelPro. (Mira aquí here por el código) . Así que la tomé y la implementé como mi función sproRead(), pero le dí mucha difusión. Abajo está lo que hice:  

:004074E0 PUSH EBP <-- Salvamos EBP.
:004074E1 CALL $+5 <-- Obtenemos el Delta Offset.
:004074E6 POP EBP <-- Ponemos el Delta Offset en el registroEBP.
:004074E7 LEA EDX, [EBP+4C1Ah] <-- Aqui es donde la data de la mochila esta en el archivo.
:004074ED POP EBP <-- Corrige el stack nuevamente si no se cuelga.
:004074EE SHL ECX, 1 <-- ECX retiene la WORD que estamos leyendo.
:004074F0 MOVZX EAX, WORD PTR [ECX+EDX] <-- Lee la WORD desde la memoria simulada.
:004074F4 MOV DX, 400h <-- Ponemos a manos un codigo de retorno bueno.
:004074F8 MOV [ESI+6], DX
:004074FC NOP
:004074FD MOV [EDI], AX <-- Almacena el codigo de retorno en el registro EDI (buffer).
:00407500 MOV AX, [ESI+6]
:00407504 PUSH EAX

:00407505 CALL sub_406AC0 <-- Limpia el codigo de error, EAX debe ser cero cuando salimos.
:0040750A POP EDI
:0040750B POP ESI
:0040750C RETN 0Ch <-- returna a la funcion que llamadora.

Puesto que el archivo que estamos poniendo en el emulador, en realidad es una DLL, puede ser cargado en diferentes páginas de memoria cada vez que el programa corre. Cuando corremos App.exe lo carga en 10000000, y cuando corremos App2.exe (administrador de producto) lo carga en 70000000. Por eso no podemos tener una dirección fija donde está la data de la mochila en el archivo. Para que me entiendas, hago esto: Tomo la data de la mochila y la pongo en el archivo justo después del último byte de la sección ".RELOC". Desde 004074E0 a 004074E6 obtenemos nuestro delta offset, que es el EIP + la dirección de carga de la DLL. De este modo no importa dónde es cargada la DLL, el CALL $+5 will siempre calculará nuestra posición correcta. 

A 4074E7 la tomo como mi posición actual y le sumo 4C1Ah, esto me deja justo en la primer palabra (WORD ) de la data de mi mochila simulada, que quedó fija en el archivo (hard coded).

123A 223A 323A 423A 0000 0000 0000 0000 <-- Final de la seccion .RELOC.
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
FFFF FFFF 0100 0200 0100 0000 005E 1A00 <-- Comienza de la data de la mochila

El segundo POP EBP es requerido aquí, porque de otra manera el stack se pone loco y se va todo al carajo.  Cuando llegamos a SHL ECX, 1, (desplazar a la izquierda), tomá el número que viene como un parámetro a la función, (que le dice cuál número de la data de la mochila necesitamos leer), y lo multiplica por dos, (es lo mismo que desplazar un lugar), para obtener la palabra correcta, puesto que una palabra está compuesta por dos bytes. Después seguimos con MOVZX, lo que hace esta instrucción es la palabra (WORD) que está en la dirección que apunta la suma de  [ECX+EDX], al  registro EAX y completar con ceros la parte alta del registro. ECX es la palabra que necesitamos leer, y EDX es la dirección de la data de la mochila, sería como decir "quiero la primer palabra de la data que hay en la mochila".   

Después de esto, terminamos con el emulador; en la dirección 4074F4 ponemos en el código de retorno el valor 0400, después tomamos AX que contiene el WORD de la mochila que nos interesa, (obtenida un segundo atrás), y la ubicamos en el buffer que fue pusheado como parámetro a esta función. Ahora vamos al CALL at 407505 que pushea 0400 y los limpiamos hasta retornar solamente el registro AL que tendrá 00. Si la función hubiera fallado el código de error, de retorno, sería 0403, y después de este CALL el registro AL tendría 03, que nosotros conocemos de la documentación, que significa "Key not found". 

Nosotros ahora tenemos totalmente emparchada (crackeada) la función sproRead(), que funcionará correctamente sin importar dónde se cargue la DLL. Pero nos queda la función sproQuery(), para ésta es más difícil hacer una "emulación" genérica, de modo que la traceamos con el SICE y estudiamos qué hace. Después de analizar nos damos cuenta que es realmente simple, de lo único que hay que asegurarse es que la función siempre retorne 00 en el registro AL ,y eso es todo. No hay chequeos de los valores retornados. cm32.dll es bastante parecida a PMCore.dll que es la DLL principal para el administrador de producto, de manera que tenemos que hacer los mismos patches que le hicimos a la otra.

OK, después de hacer todo esto corremos App.exe, pero ¿qué está pasando aquí? Todo el código que hemos cambiado en cm32.dll y PMCore.dll desapareció! OK, es probable que hayan implementado algún tipo de chequeo de la integridad de los archivos. Lo que hacemos es poner un BPX CreateFileA  y corremos App.exe nuevamente, y cuando se produce el break presionamos F10 y tipeamos D *(ESP+08) para ver cuál archivo está siendo abierto. Se abren un montón de arhivos antes que lleguemos a ver cm32.dll, presionamos F12 unas cuantas veces hasta que llegamos a Fscore.dll, ahora presionamos F10 algunas más hasta que obtenemos algo como esto :-

MOV CL, [ESI]
MOV DL, [EDI]
CMP CL, DL
JZ good

No recuerdo exactamente el código, pero es más o menos así: Si hacés un D CL y D DL, en el SICE, te das cuenta que está comparando el archivo con una imagen que ha guardado en algún lado, de modo que solo hay que emparchar el JZ good a JMP y no cambiará tu archivo emparchado. Ahora corré App2.exe y hacé lo mismo para emparchar el chequeo que tiene en PMCore.dll.

Corremos y está funcionando :)-. Ahora cuando hacés Rip e imprimís todo funciona bien, excepto que hay algo que fuerza a la impresora a imprimir lineas blancas sobre la imagen del texto. Bueno, desensamblamos PMCore.dll y buscamos alguna pista; vemos algo llamado IsDemoVersion(), lo que tenemos que hacer es que esta función retorne OK ,y todo el mundo contento. Good flag (Bandera indicando OK) aqui es 01, realmente esto es fácil.    

Conclusion

Esta protección fué divertida, pero no  lo suficientemente difícil como esperaba que sea. Sentinel SuperPro es una mochila decente pero deja demasiadas huellas, y esto nos permite reventarla sin problemas. Este programa no usa sproQuery() correctamente, y usa escasamente sproRead(), para hacer apenas 2 cosas , lo cual hace que sea sumamente fácil emularla. La clave para hacer ingeniería inversa con mochilas es rastrear y seguir el camino (path). Si no tenés los nervios para hacer esto, las mochilas no son para vos. Me gustan las mochilas, es divertido crackearlas, tené en cuenta esto y verás que es muy fácil reventarlas.  

Saludo a mis compinches:- zip, CrackZ, GzA, int13h.

goatass.

Traduccion por: Aftosa

 

Karpoff Spanish Tutor: Pagina dedicada a la divulgacion de informacion en Castellano, sobre Ingenieria Inversa y Programacion. Email "Colabora con tus Proyectos"
www.000webhost.com