Karpoff Spanish Tutor1999-2002

Programa:

Adobe PhotoShop v6.0


 

PROTECCION: Instalación: Unlock Code
Descripcion: Programa de retoque fotográfico
Dificultad: un poco lioso tal vez, pero nada complicado
DOWNLOAD: Ni idea
Herramientas: Softice y mi cabecita
CRACKER: AuspeX   FECHA: 018/03/2002

 

 INTRODUCCION

Aquí tenemos un programa que, al principio, me trajo un poco de cabeza por que no trabajaba como otros programas que había crackeado y que eran del mismo estilo. Este se dedica a marearnos haciendo una serie de tonterías con el código introducido y que al final todo se reduce a un simple cambio de flag en memoria (1 -> código correcto, 0 -> código erróneo). Bueno, pues me llevó unas cuantas horas, estudiando el código, darme cuenta de esto (hay que entender que yo aun soy bastante novatillo en este mundo).

El problema te lo encuentras únicamente en la instalación. Te sale la típica ventanita para registrarte, introduciendo nombre y demás, y luego el código unlock.

 

 AL ATAKE

Pues vamos allá... Arrancamos el programa de instalación y comenzamos a darle a siguiente, siguiente, etc... hasta que nos mánda a la ventanita de registro, así observamos algo como esto:

Pués nada, le metemos cualquier chorrada (no intenteis acertarlo a la primera, jeje), y le damos a siguiente, ¿y que es lo que sucede?, púes supongo que lo que tenía que suceder:

Lo primero que se nos puede ocurrir aquí, quizas sea estudiar el código de los alrededores de este mensajito (pues sice saltaba con MessageBoxA), pero he de decir que la cosa no era fácil, pués se llamaba a esta zona de código desde un CALL [EBP-24], instrucción que se ejecutaba muchísimas veces (y cuando digo muchísimas digo por lo menos 50 o más veces) antes y después del mensajito y por lo tanto me pareció un camino un tanto complicado, habiendo otros más sencillos (no se si me he explicado bien). Bueno, veamos entonces por donde meterle mano a esto. Intentemoslo atacando a alguna API que recoja nuestro código. Después de probar alguna que otra llamada, me funcionó GetWindowTextA, tambien la famosa HMEMCPY, pero pensé que mejor comenzaría probando la primera ya que esta otra tiene más traceo (ese día estaba muy flojo como habreis podido comprobar, jeje). He de decir que me saltó 6 veces, una por cada cuadro de texto, y por lo tanto el que nos interesa es el último, que es el que captura nuestro número de serie introducido. Veamos ahora este pequeño trozo de código, que no es demasiado importante, pero que quiero poner para luego explicar una cosa:

* Reference To: USER32.GetWindowTextA, Ord:013Fh
|
:0042F74E FF15CC034800 Call dword ptr [004803CC]
<-----Recoje el serial
:0042F754 8D8500FCFFFF lea eax, dword ptr [ebp+FFFFFC00]
<-----eax = dirección donde recoge el serial
:0042F75A 50 push eax
:0042F75B 8B4508 mov eax, dword ptr [ebp+08]
:0042F75E FF7008 push [eax+08]
:0042F761 E85B210300 call 004618C1
<-----Da como salida nuestro serial copiado en la dirección que hay en eax (y es 515E10h), en edx la longitud y en ecx la dirección donde se encuentra, por decirlo de alguna manera, la otra copia original del serial introducido

A partir de aquí, me tiré bastante tiempo estudiando el código que seguía para ver si realizaba alguna operación con el serial, no se, algo, pero no hacía nada de nada con él, parece ser que lo dejaba ahí olvidado y se encargaba de hacer otras cosas que no tenían nada que ver, por lo menos a mi entender. Así que decidí cambiar de extrategia: una vez copiado el número de serie en la dirección 515E10h (tras el último CALL) poner un BPM en dicho lugar para ver donde coño le metía mano, y así me dejaba de tantas historias y tanto traceo inútil. Volvemos pués a darle a siguiente y repetimos F5 hasta que nos coja el serial. Traceamos luego el código mostrado arriba hasta después del último CALL y hacemos: bd 0 (deshabilitamos el bpx de GetWindowTextA) y luego bpm eax r (recordad que en eax está la dirección de la copia, y "r" por que queremos saber cuando lee de esta dirección). Veamos ahora las llamadas más importantes a la que me llevó dicho break, donde se le metía mano al serial:

Exported fn(): CheckExpiringSerialNumber - Ord:0007h <-----Fijaos lo que dice aquí, parece ser que comprueba que el número de serie introducido no haya expirado, o sea, caducado
:10002BD0 83EC0C sub esp, 0000000C
:10002BD3 53 push ebx
:10002BD4 55 push ebp
:10002BD5 56 push esi
:10002BD6 8B74241C mov esi, dword ptr [esp+1C]
:10002BDA 6A03 push 00000003
<-----Longitud 3
:10002BDC 56 push esi
<-----Dirección del serial

* Possible StringData Ref from Data Obj ->"EXX"
|
:10002BDD 68E0820010 push 100082E0
<-----Dirección de la cadena "EXX"
:10002BE2 33ED xor ebp, ebp
:10002BE4 E8F73E0000 call 10006AE0
<-----Aquí es donde se le mete mano por primera vez al serial, y lo que hace es comprobar si los tres primeros caracteres de nuestro serial son EXX
:10002BE9 83C40C add esp, 0000000C
:10002BEC 85C0 test eax, eax
<-----Si eax <> 0 (si no cumple lo de arriba...)
:10002BEE 0F851C010000 jne 10002D10
<-----Salta fuera, y devolverá un 1 (el serial no ha expirado)

............

............

Lo que sigue después son una serie de comprobaciones más que hace para decidir si el número ha caducado o no, pero que realmente no nos interesa demasiado, pues lo que nosotros queremos es buscar un serial que valga y no uno que ya no sirva, ¿verdad?. Pués nada, nos salimos de la función con F12, y ahí podemos comprobar como mete un 1 en la dirección [EBP+FFFFFB70], significa que por ahora el número es bueno (no ha expirado).

Lo que sigue es un poco más complicado, por que se trata de comprobar ahora si nuestro serial es el correcto, realizando, para ello, una serie de operaciones con él que quizas mareen un poco. Volvemos a pulsar F5 para que se detenga cuando quiera meterle mano, de nuevo, al numerito. Tras un par de paradas, poco importantes, caemos en el siguiente trozo de código:

* Referenced by a CALL at Address:
|:10001E46
|

............

............

;Por aquí hace algunas operaciones absurdas como por ejemplo pasar nuestro serial a mayusculas, algo que no ;tiene mucho sentido si al introducir datos en el cuadro de texto del número de serie automáticamente este lo ;transforma a mayusculas...

............

............

:10001947 52 push edx <-----Dirección donde se encuentra el serial
:10001948 50 push eax
<-----Dirección a donde lo va a copiar

* Reference To: KERNEL32.lstrcpyA, Ord:0302h
|
:10001949 FF1520700010 Call dword ptr [10007020]
<-----Copia serial
:1000194F 8D8C249C010000 lea ecx, dword ptr [esp+0000019C]
:10001956 51 push ecx
<-----Nueva Dirección del serial
:10001957 E8E4030000 call 10001D40
<-----Comprueba si nuestro serial coincide con una serie de claves que hay en una zona de memoria, ¿aquí está lo que buscabamos?, ahora os explicaré que no es así
:1000195C 83C404 add esp, 00000004
:1000195F 85C0 test eax, eax
<-----¿Coincide?
:10001961 7418 je 1000197B
<-----Si no es así salta
:10001963 68D0070000 push 000007D0

* Reference To: KERNEL32.Sleep, Ord:0296h
|
:10001968 FF1528700010 Call dword ptr [10007028]
<-----¿Y esta tonteria pa que coño la hace?, Se queda, durante dos segundos, el programa parado, ¿ni idea, macho?
:1000196E 5F pop edi
:1000196F 5E pop esi
:10001970 5D pop ebp
:10001971 33C0 xor eax, eax
:10001973 5B pop ebx
:10001974 81C48C020000 add esp, 0000028C
:1000197A C3 ret

Bién, os explico un poco: ¿Veis ese CALL donde comprueba nuestro serial con otros que ya hay en memoria?, parece que la cosa ya esta clara ¿no?, pués resultó que no, jeje. Dentro lo que hace primero es transformal la clave pasando todos los caracteres por XOR eax,AA (eax contiene el caracter), y luego, como ya he dicho antes, compara con otras claves ya transformadas. Cuando me di cuenta de esto, lo que hice fue crearme un programilla que me volviera a transformar dichas claves que habia en memoria para poder copiarlas y comprobar que eran las correctas. Pero cual fue mi sorpresa, cuando observé que ninguno de esos seriales eran válidos, y entonces ¿por qué mierda comparaba mi clave con esos seriales si al final ninguno valía?, esto es un ejemplo de clave que utilizaba para comparar y que transformé: PWW250R3000406-282, claramente tiene to la pinta de número de serie, ¿verdad?, bueno, pués como esta había 20 o 30 claves más, y ninguna valía. La explicación de esto no la tengo clara, si alguien me lo puede decir... Nada, no había nada que hacer con esto, tendría que seguir traceando, a ver si encontraba de nuevo alguna pista, pero esta vez sin trampas.

Encontré dos cosas que me llamaron la atención: Una de ellas es que comprobaba si mi serial comenzaba por los dos caracteres PW:


............

:1000198D 0FBFC1 movsx eax, cx
:10001990 8A54041C mov dl, byte ptr [esp+eax+1C]
<-----esp+1c apunta a mi serial
:10001994 8A1C30 mov bl, byte ptr [eax+esi]
<-----esi apunta a "PW"
:10001997 3AD3 cmp dl, bl
:10001999 7527 jne 100019C2
:1000199B 41 inc ecx
:1000199C 6683F902 cmp cx, 0002
<-----Compara únicamente dos caracteres
:100019A0 7CEB jl 1000198D
:100019A2 EB04 jmp 100019A8

............

, y otra es que el cuarto caracter, empezando por el final, fuera un guión (-):

............

:100019DB 8A441C1C mov al, byte ptr [esp+ebx+1C] <-----al = cuarto caracter empezando por el final
:100019DF 8D7C1C1C lea edi, dword ptr [esp+ebx+1C]
:100019E3 3C2D cmp al, 2D
<-----Aquí tenemos la comparación (2D es el guión en ascii)
:100019E5 0F853B030000 jne 10001D26

............

Estas dos pistas junto con las claves fantasmas que vimos anteriormente, me hicieron suponer que realmente el serial correcto tendría la pinta que tenían dichas claves fantasmas, es decir, PW............-... También observé que los últimos tres caracteres debían de ser números. Esto lo hacía de una forma un tanto extraña, pero no lo pondré para no liaros más (además era bastante código), sólo deciros que utilizaba los codigos ascii de los caracteres como base de una dirección la cual devolvía un valor, concretamente el 84 si era número y un 1 si era letra, que al aplicarle un AND valor,4 daba resultados diferentes.

Lo que sigue a continuación son dos llamadas claves que operan con el número de serie. La primera no me costó demasiado trabajo darme cuenta que lo que hacía era devolver el valor del número compuesto por las tres últimas cifras pero transformada a hexadecimal (monta una impresionante sólo para hacer esta tontería). La segunda es un tanto más complicada, pués, realizan diversas operaciones matemáticas, luego usa los resultados parciales para, utilizándolos como base, acceder a zonas de memoria donde se encuentran una serie de valores, los cuales a su vez son utilizados para operar con el número de serie. La verdad, algo bastante complicado de seguir:

............

:10001AA6 52 push edx <-----Dirección donde se encuentran los tres últimos dígitos (número en decimal)
:10001AA7 E891200000 call 10003B3D
<-----Transforma a hexadecimal
:10001AAC 8BF0 mov esi, eax
<-----Devuelve el valor en hexadecimal y lo copia a esi
:10001AAE 8D442420 lea eax, dword ptr [esp+20]
:10001AB2 53 push ebx
<-----Longitud = nº caracteres serial - 4 (o sea, hasta el guión)
:10001AB3 50 push eax
<-----Dirección donde se encuentra el serial
:10001AB4 895C2424 mov dword ptr [esp+24], ebx
:10001AB8 E8D3F9FFFF call 10001490
<-----No tengo muy claro como trabaja exactamente (es complicado)
:10001ABD 0FBFC8 movsx ecx, ax
<-----Valor devuelto de la llamada se pasa a ecx
:10001AC0 83C40C add esp, 0000000C
:10001AC3 3BCE cmp ecx, esi
<-----comparamos y..
:10001AC5 0F855B020000 jne 10001D26
<-----Si todo va bién, continua, sino, salta a tomar por culo
:10001ACB 83FB07 cmp ebx, 00000007 <-----está mirando si hay un mínimo de 7 caracteres antes del guión
:10001ACE 896C2410 mov dword ptr [esp+10], ebp
<-----En principio está metiendo un 1
:10001AD2 C60700 mov byte ptr [edi], 00
<-----quita el 2D (guión) y lo pone a 00
:10001AD5 66892D308F0010 mov word ptr [10008F30], bp
:10001ADC 7C0C jl 10001AEA
<-----A tomar por culo si hay menos de 7 caracteres antes del guión
:10001ADE 8A442422 mov al, byte ptr [esp+22]
<-----al = 7º caracter, ¿para que?
:10001AE2 3C41 cmp al, 41
<-----comprueba si es mayor de A
:10001AE4 7C04 jl 10001AEA
<-----Si no es así a tomar viento
:10001AE6 3C5A cmp al, 5A
<-----¿Menor o igual que Z?
:10001AE8 7E08 jle 10001AF2
<-----salta si es así

............

Veamos que podemos sacar de este trozo de código: lo primero de todo, fijaos en lo último, está comprobando que el caracter que ocupa la posición 7 ha de ser una letra en mayúsculas, curioso, ¿verdad?. Por los dos primeros CALL´s podemos saber que tres últimos dígitos poner después del guión, y es el resultado en decimal del valor devuelto por CALL 10001490, por ejemplo: para PW123 se devuelve 2B2, por lo tanto si ese es el valor que queremos que devuelva CALL 10003B3D, hemos de poner 690 que equivale a 2B2 en decimal. Uniendo estas dos cosas obtendríamos algo como esto: PW1234A-351, pero esto sigue sin funcionar, falta algo más. Sigamos traceando, ya casi lo tenemos:

............

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:10001C8C(U)
|
:10001C96 83C3FA add ebx, FFFFFFFA
<-----ebx = nº caracteres hasta el guión - 6, en mi caso ebx = 7 - 6 = 1
:10001C99 8D4C241C lea ecx, dword ptr [esp+1C]
:10001C9D 53 push ebx
:10001C9E 6A00 push 00000000
:10001CA0 51 push ecx
<-----dirección del serial sólo hasta el guión
:10001CA1 E84AF8FFFF call 100014F0
<-----devuelve los 6 caracteres antes del guión, en mi caso: W1234A
:10001CA6 83C40C add esp, 0000000C
:10001CA9 EB07 jmp 10001CB2

............

............

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:10001CC1(C)
|
:10001D14 8D44241C lea eax, dword ptr [esp+1C]
:10001D18 50 push eax
<-----manda los 6 caracteres antes del guión
:10001D19 E81F1E0000 call 10003B3D
<-----comprueba si todos los caracteres son números
:10001D1E 83C404 add esp, 00000004
:10001D21 A3348F0010 mov dword ptr [10008F34], eax

* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:10001889(C), :10001892(C), :100019D5(C), :100019E5(C), :10001A36(C)
|:10001A69(C), :10001A9C(C), :10001AC5(C)
|
:10001D26 8B442410 mov eax, dword ptr [esp+10]
<-----1 -> correcto , 0 -> no es correcto

............

Fijaos este código, digamos que sólo coge los 6 caracteres que hay antes del guión y comprueba que sólo sean números, por lo tanto, podríamos probar con esta otra clave a ver si funciona: PW1234A123456-345

¡¡¡ Prueba superadaaaaaa !!!, fijaos que sencillo, ahora podemos sacar todas las claves que nos de la gana, eceptuando las que sean iguales a las claves fantasmas que ya vimos. El formato que sigue es el siguiente: PWccccLdddddd-nnn, siendo PW obligatorio ponerlo, las c´s significan cualquier caracter ya sea letra o número, la L ha de ser una letra (en mayúscula), las d´s sólo dígitos y las n´s se sacan a partir de la función CALL 10001490 que operaba con los caracteres que había antes del guión devolviendo así un valor hexadecimal y que es esas n´s pero en decimal. Un ejemplo de varias claves: PW1111P111111-129, PW3984D108379-969, PWXXXXG555555-218, PWHOLAT223344-906, etc... Creo que ya se ha entendido, ¿no?, pues ala, ya esta todo dicho.

NOTAS FINALES

Sólo quería decir una cosa: en la introducción dije que todo esto se solucionaba con un simple cambio de flag en memoria. Vereis, mientras estudiaba este código, observé que todo esto estaba dentro de una llamada que siempre devolvía un valor en eax, concretamente un 0, siempre, y luego metía este resultado en una zona de memoria. Observad:

............

:0042A3FE FF95A0FBFFFF call dword ptr [ebp+FFFFFBA0] <-----Aquí tenemos, digamos, la llamada padre
:0042A404 898570FBFFFF mov dword ptr [ebp+FFFFFB70], eax
<-----eax = 1 -> clave correcta, eax = 0 -> mal
:0042A40A EB78 jmp 0042A484

............

Luego, todo lo que os he contado se puede resumir en un simple cambio de bit, es decir, con sólo cambiar de 0 a 1 el valor que se devuelve en eax registramos correctamente el programa (digamos que cambiamos el flag en la dirección ebp+FFFFFB70), aunque la clave no sea la correcta, jeje, pero como comprendereis hubiera sido un tuto muy aburrido si sólo os hubiera contado esto, ¿verdad?, además, ya lo he dicho otras veces, el cracking es un arte y como tal no hay que dejarlo en un simple cambio.

AVISO

No me hago responsable del uso indebido o ilegal que cualquier persona haga de este tutorial. El estudio de la ingeniería inversa, en mi opinión, debería de hacerse con fines didácticos y no para beneficio de ella en cuanto a la desprotección de software y por consiguiente su uso ilegal. Si consideras que un programa, por su calidad, merece ser comprado al precio estipulado, hazlo, los programadores de dicho software lo merecen, sino desinstalalo. Si realmente te apasiona el mundo de la ingeniería inversa, dedícalo sólo para tu aprendizage y diversión personal.

Si piensas que estos tutoriales no deberían de publicarse, considero que estás muy equivocado, pués alguien debe de mostrar a los programadores encargados de proteger las aplicaciones donde están sus fallos de protección, para que lo corrijan y mejoren.

DEDICATORIA

Quiero dedicar este tutorial a mis colegas de facultad: Rafa, Migue, Jose miguel, Manolo. A mis amigos del Instituto: Reyes, Rincón, Jeremy, Dani, Marchena, Fernando, Santi... bueno, en general, a los que aguantan todos los días mis tonterías.

A todo a los cracker que escriben para la página de Karpoff, y por supuesto al propio Karpoff, los cuales me guían en este duro camino de la ingeniería inversa.

Nota2: Perdonad mis faltas de ortografía, soy un inculto de cojones, xDDD

CORREO DEL MENDA: auspex@wanadoo.es

 

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