StruCad Drawing Viewer v2.05 - Tutorial by CrackZ

Traducción de ^[G]oLe[E]^

Archivos disponibles a petición.

StruCad Drawing Viewer (spfview.exe) es como los visores de programas de Microsoft, los cuales son usados por las personas que no tiene el software usado para crear el archivo original. Aunque StruCad ha escogido proteger su Visor (Viewer) con una Dongle Centinela y Código de Autorización Máquina-Dependiente, dudo que la publicación de este documento los dañe en alguna forma significativa. Nuestro interés en este objetivo, está relacionado solamente con los aspectos de la implementación de Centinela, Específicamente la lectura y los algoritmos de palabra Dongle.

Usando IDA y las técnicas que describiré en otra parte, podemos identificar un tanto fácilmente el Record de Paquetes de fabricación del Centinela y comenzar nuestro intento de etiquetar las funciones varias y sus referencias, solamente las 7 siguientes son de interés:-

sub_46A77C, sub_46A92C, sub_46A9C0, sub_46AAB4, sub_46ABF4, sub_46AD98 & sub_46AE24.

Seguimos usando IDA y adoptamos una teoría de acercamiento a la verificación, sub_46A77C toma un parámetro (un PUSH EBX), justo sobre esta referencia, podemos ver sub_46A714 el cual toma EBX y 404h (1028 bytes), una mirada rápida a la guía API, confirma que esto debe ser sproFormatPacket(), con un razonable grado de seguridad, podemos asumir que EBX es un puntero al Record de paquetes, esto sugiere que sub_46A77C es sproFindNextUnit(), lo extraño acá, es que supuestamente llamas a sproFindFirstUnit() con el ID del desarrollador que quieres encontrar antes de esto, asumiremos por ahora, que esto no necesita ser parcheado.

sub_46A92C está referido en la dirección 464F6A y toma 2 parámetros vía EAX & EDX, usando SoftICE colocamos un bpx para este código y vemos que son estos. Como pasa EAX, está la dirección de la estructura de Record de Paquetes y EDX es 00483B5E, la función por si misma retorna EAX=3 (unit not found, en español, unidad no encontrada) entonces esto se ve como sproFindFirstUnit(), la ID de la Dongle está por lo tanto asumida de ser 3B5Eh, en la mayoría de Centinelas que he visto, el ID es empujado directo o a los bytes de ordenes altas de los registros, siendo pulidos, por ejemplo AND EDX, 0FFFFh.

Usando IDA, seguimos la ejecución del código (esto es en alto nivel) :-

:0046CBB2 CALL sproFindFirstUnit()
:0046CBB7 MOV [EBX], AX <-- Almacena el estado del código.
:0046CBBA CMP WORD PTR [EBX], 0 <-- Éxito?.
:0046CBBE JZ 46CBD1 <-- Buen Salto
:0046CBC0 CMP WORD PTR [EBX], 2 <-- Paquete Invalido?.

Si parcheas sproFindFirstUnit() para que retorne AX=0 StruCad se colgará en un loop y probablemente tendrás que darle un saludo de 3 dedos al programa. Relajémonos tranquilamente en IDA y sigamos el camino de la ejecución un poco mas lejos :-

:0046DC72 MOV EAX, 16h <-- Será empujado como un parámetro.
:0046DC77 CALL 0046CC90 <-- Abajo de aquí.

:00464F70 PUSH EBX <-- 0x7B.
:00464F71 PUSH ECX <-- Dirección donde WORD será almacenada eventualmente.
:00464F72 MOV EBX, ECX
:00464F74 PUSH ESP <-- Retorna una dirección de WORD para leer.
:00464F75 PUSH EDX <-- Dirección de Word.
:00464F76 PUSH EAX <-- Estructura del Record de Paquetes.
:00464F77 CALL 0046AAB4 <-- sproRead().
:00464F7C MOV DX, [ESP+0] <-- Word de la Dongle.
:00464F80 MOV [EBX], DX <-- La almacena aquí.

Como siempre, tomaremos esta oportunidad para rescribir sproRead(), acá esta como se verá el nuevo código :-

:0046AAD7 JZ 0046AAD9 (74 00).
:0046AAD9 PUSH EBP (55).
:0046AADA MOVZX EAX, WORD PTR [ESP+18] (0F B7 44 24 18).
:0046AADF SHL EAX, 1 (D1 E0).
:0046AAE1 CALL $+5 (E8 00 00 00 00).
:0046AAE6 POP EBP (5D) <-- Colocar el offset del Delta.
:0046AAE7 LEA EDI, [EBP+14] (8D 7D 14).
:0046AAEA MOVZX EAX, WORD PTR [EAX+EDI] (0F B7 04 38).
:0046AAEE MOV EDI, [ESP+1C] (8B 7C 24 1C).
:0046AAF2 MOV [EDI], AX (66 89 07).
:0046AAF5 XOR EAX, EAX (33 C0).
:0046AAF7 POP EBP (5D).
:0046AAF8 JMP 0046AB43 (EB 49).

Este código podría verse un poco intimidatorio al principio, pero en realidad es muy simple y puede ser pegado (con unos pocos ajustes) justo cerca en cualquier sproRead() que encontraras. Comenzamos empujando EBP para colocar nuestra llamada al retorno del offset del Delta, en EAX retornamos la Word para leer desde la pila (MOVZX para extender los ceros), multiplicamos la dirección de la Word por 2, como estamos leyendo una palabra desde nuestra dongle simula y después usa el offset del delta, para apuntar EDI al comienzo de nuestra memoria simulada de la Dongle, antes leyendo y almacenando en la dirección de retorno (de nuevo desde la pila) y finalmente aclarando EAX (el estado).

Ahora es tiempo para hacer el Debugging con el SoftICE. Colocaremos algunos breakpoints en nuestra nueva función sproRead() como en las otras localidades que no identificamos arriba . Acá esta el log de los eventos :-

Lee las Words :- 16, 17, 0, 16, 17, 8, 9, A, B, C, D, E, F, 10, 11, 12, 13, bpx @ 0046AE24 (loop del sistema).

El break final nos interesa, ya que estas son las manipulaciones realizadas en un loop a través de las word 8-13 (siento que regresaremos aquí), esto es de lo que realmente trata la Ingeniería Inversa de DoNgLeS, seguir el camino, cuando procedes a través de los nuevos caminos que deben abrirse delante de ti, únicamente estarás desesperado cuando todas tus veredas te lleven a un callejón sin salida, en cuyo caso deberás rastrear de nuevo tus pasos. sub_46AE24 está referido desde sub_464FC0, los parámetros empujados parecen indicar que esto es un sproQuery() :-

:00464FD3 PUSH EDX <-- Número de bytes en la cadena de query (0x1C).
:00464FD4 PUSH EAX <-- Estructura de Record de Paquetes.

Cuando rastreamos sproQuery() StruCad se cuelga, el problema real con el rango de Dongles Sentinel SuperPro es que los algoritmos internos de query son diferentes para cada cliente, así que no existen 'queries genéricos' para invertir, solo un algoritmo para un cliente especifico, o al menos eso tiene que hacerte creer el Centinela. Debes diseñar una ASIC (exclusivamente desarrollar una) el rango del coste va de $30,000 a $100,000 básicamente la economía dice que ese Centinela no usa una ASIC, seguramente están usando componentes off-the-shelf, eso significa casi ciertamente que la fábrica quemó el PROM.

Por supuesto el Centinela ha hecho un 'trade off', a cambio de una producción con costos bajos, han dejado vulnerables sus DoNgLeS a la copia de hardware, virtualmente cualquiera podría copiar un Centinela en su posesión, usando equipo bastante rudimentario (o por lo menos con el equipo que podría usar en una escuela). Un parcheo rápido del query, despliega el dialogo de Registro del Visor, desplegando el Número de Serio (word 0), el identificador (desconocido en este momento) y basura escrita en el apartado User Name, un útil (y mal escrito) tooltip informándote que no puedes cambiar esto, es muy obvio que la cadena construida desde las words 8-13h leen desde la DoNgLe. Veamos como es que trabaja :-

:0046D13A MOV DI, 0ABCDh
:0046D13E MOV SI, 1h <-- Contador.
:0046D146 LEA EBP, [ESI+7h] <-- Comenzar en la word 8h.
:0046D149 MOVZX EAX, EBP <-- Extender los Ceros de la word de la dongle para leer en EAX.
:0046D14C CALL sproRead()
:0046D151 XOR AX, DI
:0046D154 MOV EDI, EAX
:0046D156 MOVZX EDX, AX
:0046D159 SHR EDX, 8h
:0046D15C MOV [EBX], DL <-- Primer Byte del pass del loop.
:0046D15E AND AX, 0FFh <-- únicamente el Byte más bajo.
:0046D162 MOV [EBX-1], AL <-- Segundo byte del pass del loop.
:0046D165 INC ESI <-- Incrementar el contador del loop.
:0046D166 ADD EBX, 2h <-- Shift store (creo que es algo como un lugar de intercambio).
:0046D169 CMP SI, 0Dh
:0046D16D JNZ 0046D146 <-- Loop de las words de la dongle .

sproRead() retorna en EAX/EDX el valor de la palabra leída, en ECX está la dirección de la word. Este loop produce una estructura de 24 bytes que son copiados primeramente a una nueva localidad, antes de ser alimentado en un byte-loop comenzando en 004+AFC7, este resultado constituye el User Name. Para revertir esto completamente, debemos escribir una función para producir bytes que necesitaremos como resultado del primer loop mostrado arriba, solo entonces podemos revertir este loop para generar contenidos validos en la DoNgLe para nuestro User Name deseado.

Rápidamente encontré que definitivamente esto no es tan fácil como parece, el problema con la segunda fase es que un rango de bytes adyacentes generaran las mismas letras. Por ejemplo, asumimos que mi nombre real comienza con la letra 'P' o 50h, usamos algún código trivial para encontrar el primer byte que pasaría a través de la 2da. fase y dar 0x50 al final, i.e. 0xC4, no obstante, un poco de intuición (o por prueba y error), te dice que 0xC5, 0xC6, 0xC7 también trabajarían, la diferencia estriba en el valor de ESI al final, esto es critico porque ESI determinara que valores pueden generarse en las subsecuentes pasadas por el loop, si asumimos que 0xC4 es la única posibilidad para nuestro ejemplo, entonces el valor máximo que podemos generar en la siguiente pasada es 0x2E, nadie que yo conozca querrá el nombre 'P.'.

Esto significa que cualquier key generator debe incorporar código para manipular esta anomalía potencia, suena más fácil hacerlo manualmente, no lo es? :-). Lo que descubrí actualmente cuando reaplicaba este esquema en assembler es que la rutina de la fase 2 no puede generar ningún carácter mayor que 0x5A (i.e. 'Z'), esto significa que User Names en minúsculas son imposibles, noté también que nuestro User Name es de 32 caracteres de longitud aunque fue generado solamente de 24 bytes, ¿cómo puede ser esto? bien, la 3era sección del loop (la dirección 46B044 genera 2 bytes de nuestro user name). Puse un poco de código junto para que eventualmente hiciera esto (incluyendo el paso 1 el cual recupera las words reales de la dongle, código mostrado arriba-----> ojo, esto es en ingles y no esta incluido en esta traducción), probablemente podrías hacer un millón y más modificaciones. El programa escribe el archivo Strucad.dat de 24 Bytes, el cual contiene los contenidos requeridos por las words 8-13h de la DoNgLe, solo pégalo usando un buen HEX editor, soy demasiado perezoso para escribir un parcheador automático completo, ya que estamos acá para aprender acerca del algoritmo y NO para descargar cRaCkS ya hechos.

StruCad Viewer v2.0 Name Generator (código fuente incluido).

Ahora solo nos quedan 2 obstáculos, la misteriosa Cadena Identificadora y el Código de Acción. Sospechamos que la Cadena Identificadora está hecha de las words 16 & 17 de la DoNgLe (por simple proceso de eliminación), usando bpr podemos verificar fácilmente que este es el caso (dirección 0046DF7B y sub_46BEB0 hacen la generación actual), abajo de aquí hay algunos cálculos matemáticos muy serios llevándose a cabo, solamente toma una mirada a sub_46B32C el cual hace un montón de trabajo. No veo la necesidad de revertir esto, de hecho el único acercamiento que seria practicado seria fuerza bruta y sospecho que contar la longitud de las matemáticas sería prohibida.

Calentemos un poco más IDA. Podemos pescar algunas referencias muy útiles, asegurarte de generar un archivo .MAP y usar Msym del /Util16 directorio para generar un archivo .sym para usar con SoftICE. Primeramente tenemos el "This Product is not licensed !" en 0046D814, el código decisivo recupera un DWORD en EAX antes de chequear un valor local, esto se ve sospechosamente como un checksum, fijaremos un bpx acá. También podemos ver referencias a mensajes de error relacionados a la expiración del tiempo de prueba, también podemos fijar un bpx aquí. Te podrías estar preguntando por qué rápidamente he abandonado la demanda de generar un código de autorización valido, bien, encontré el principio de las rutinas que hacen esto (sub_46B7CC) y déjame asegurarte que esto llevaría muchas horas para revertirse, recuerda también que no seria un chance real de hacer fuerza bruta a un código de 16 bytes.

U sumario de los parches sucesivos requeridos son dados acá (con una pequeña explicación):-

:0046E103 TEST AL, AL <-- Parchea esto a MOV AL, 1
:0046E105 JZ 0046E10B <-- Esto es el resultado de la validación de la fecha.

:0046D80C CMP EAX, DS:DWORD_48A0C4 <-- Checksum.
:0046D812 JZ 0046D82B <-- Parchear a JMP.

:0046AC0E CMP WORD PTR [EBX], 7242h <-- Esta es la función CALL (llamada) a sproOverWrite().
:0046AC13 JZ 0046AC1B <-- Cambiar a modo que la función siempre retorne AX=0.

:0047D9AC CALL 00403E58 <-- Retorna EAX= -3 (lo sientes :-) ).
:0047D9B1 JZ 0047DA01 <-- Cambialo por JMP (EAX será limpiado después).

Notas Finales

Justamente esto es una Protección difícil, la cantidad (no la calidad) de las matemáticas fue suficiente para ponerme fuera de tratar de revertir el algoritmo de autorización, no había requerimientos para quebrar el Identificador, aunque pienso que seria más tedioso cortar y pegar..... Entonces, que es lo malo con esta protección?, bien, StruCad tenia la escogencia de usar el query Centinela y la Shell Centinela y no lo hizo, casi de seguro, si ellos hubieran hecho este programa, seria inquebrantable sin la DoNgLe original (pudieron dejar el esquema que describí arriba intacto).

El problema para StruCad sin embargo, y supongo que todo desarrollador de DoNgLe usando estas envolturas, es que no ofrecen protección contra crackers con la Dongle original (muchos grupos de warez también tienen acceso a ellas en estos días), tomaría 2 minutos amontonar el programa desencriptado en un disco y te quedarías con el programa abierto a todos los ataques como las per status quo. No hay CrAcKs ya hechos aquí, elogio StruCad por tan inteligente protección.

Licensed to me!, but not for distribution.


Dongles Return to Main Index


© 1998, 1999, 2000 CrackZ. 21st February 2000.
www.000webhost.com