Copy Link
Add to Bookmark
Report

7A69 Issue 15 - Crackme #6 - sonkite

eZine's profile picture
Published in 
7A69
 · 20 Jul 2023

|-----------------------------------------------------------------------------| 
[ 7a69#15 ] [ 23-11-2003 ]
\ /
\-------------------------------------------------------------------------/
{ 5 - Crackme #6 - sonkite }{ esm-min }
|-----------------------------------------------------------------------|

Este crackme requiere un keyfile para estar registrado, se puede deducir facilmente echando un vistazo a las pocas importaciones que tiene. (CreateFile.. ReadFile..).

Esta programado en ensamblador y va directamente al grano, asi que no hay problema en analizarlo desde el inicio..

Localizacion de la rutina de validacion

El entry point del programa apunta directamente a la inicializacion de la ventana, que se crea con DialogBoxParamA. Esta api recibe como parametros entre otras cosas un puntero a la rutina que servira de filtro de mensajes.

.text:00401000                 public start 
.text:00401000 start proc near
.text:00401000 push 0 ; lpModuleName
.text:00401002 call GetModuleHandleA
.text:00401007 mov hInstance, eax
.text:0040100C push 0 ; dwInitParam
.text:0040100E push offset Main ; lpDialogFunc
; Es la rutina que filtra los mensajes

.text:00401013 push 0 ; hWndParent
.text:00401015 push offset aCrackme ; lpTemplateName
.text:0040101A push hInstance ; hInstance
.text:00401020 call DialogBoxParamA
.text:00401025 push eax ; uExitCode
.text:00401026 call ExitProcess
.text:00401026 start endp

El filtro de mensajes recibe como parametros hDlg, uMsg, wParam y lParam. El uMsg contiene informacion acerca del tipo de evento que ha recibido la ventana. La subrutina esta mirando si este toma el valor de WM_INITDIALOG o WM_CLOSE.

Cuando la ventana se carga, recibe una sola vez el evento WM_INITDIALOG y es logico que sea entonces cuando intente localizar el keyfile. Veamos como se comporta el programa entonces.

.text:0040102B ; ¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶ S U B R O U T I N E ¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶ 
.text:0040102B
.text:0040102B
.text:0040102B ; int __cdecl Main(HWND hDlg,int,int)
.text:0040102B ; Attributes: bp-based frame
.text:0040102B
.text:0040102B Main proc near ; DATA XREF: start+Eo
.text:0040102B
.text:0040102B hDlg = dword ptr 8
.text:0040102B uMsg = dword ptr 0Ch
.text:0040102B wParam = dword ptr 10h
.text:0040102B
.text:0040102B push ebp
.text:0040102C mov ebp, esp

Primero se inicializa el titulo de la ventana y luego se llama una o mas veces a la subrutina de validacion. Por ultimo se muestra SIEMPRE el mensaje de 'TRYAGAIN', lo cual hace pensar que si el keyfile es correcto debera modificar parte del codigo.

.text:0040102E                 cmp     [ebp+uMsg], WM_INITDIALOG 
.text:00401035 jnz short loc_401067
.text:00401037 push offset aCrackme6Sonkit ; lParam
.text:0040103C push 0 ; wParam
.text:0040103E push WM_SETTEXT ; Msg
.text:00401040 push [ebp+hDlg] ; hWnd
.text:00401043 call SendMessageA
.text:00401048
.text:00401048 loc_401048: ; CODE XREF: Main+29j
.text:00401048 call check
.text:0040104D cmp dword_40308C, 2
.text:00401054 jnz short loc_401048
.text:00401056 push offset aTryagain ; lpString
.text:0040105B push 64h ; nIDDlgItem
.text:0040105D push [ebp+hDlg] ; hDlg
.text:00401060 call SetDlgItemTextA
.text:00401065 jmp short loc_401089
.text:00401067 ; ---------------------------------------------------------------
.text:00401067
.text:00401067 loc_401067: ; CODE XREF: Main+Aj

En caso de recibir el evento WM_CLOSE o el identificador del boton Exit en el wParam, ser cierra la ventana.

.text:00401067                 cmp     [ebp+uMsg], WM_CLOSE 
.text:0040106B jnz short loc_401079
.text:0040106D push 0 ; nResult
.text:0040106F push [ebp+hDlg] ; hDlg
.text:00401072 call EndDialog
.text:00401077 jmp short loc_401089
.text:00401079 ; ---------------------------------------------------------------
.text:00401079
.text:00401079 loc_401079: ; CODE XREF: Main+40j
.text:00401079 cmp [ebp+wParam], 65h ; <-- ID del boton Exit
.text:0040107D jnz short loc_401089
.text:0040107F push 0 ; nResult
.text:00401081 push [ebp+hDlg] ; hDlg
.text:00401084 call EndDialog
.text:00401089
.text:00401089 loc_401089: ; CODE XREF: Main+3Aj
.text:00401089 ; Main+4Cj ...
.text:00401089 xor eax, eax
.text:0040108B leave
.text:0040108C retn 10h
.text:0040108C Main endp

La subrutina

.text:0040108F ; ¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶ S U B R O U T I N E ¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶ 
.text:0040108F
.text:0040108F
.text:0040108F check proc near ; CODE XREF: Main+1Dp
.text:0040108F pusha
.text:00401090 inc dword_40308C
.text:00401096 push 0 ; hTemplateFile
.text:00401098 push 80h ; dwFlagsAndAttributes
.text:0040109D push 3 ; dwCreationDisposition
.text:0040109F push 0 ; lpSecurityAttributes
.text:004010A1 push 1 ; dwShareMode
.text:004010A3 push 80000000h ; dwDesiredAccess
.text:004010A8 push offset aMopz ; lpFileName
.text:004010AD call CreateFileA
.text:004010B2 cmp eax, 0FFFFFFFFh
.text:004010B5 jz loc_40114E
.text:004010BB cmp dword_40308C, 1
.text:004010C2 jz loc_40114E

...

.text:0040114E ; ---------------------------------------------------------------
.text:0040114E
.text:0040114E loc_40114E: ; CODE XREF: check+26j
.text:0040114E ; check+33j ...
.text:0040114E xor dword ptr aMopz, 111B1A0Bh ; "mopz"
.text:00401158
.text:00401158 loc_401158: ; CODE XREF: check+8Ej
.text:00401158 popa
.text:00401159 retn
.text:00401159 check endp

Cada vez que se llama a la rutina de validar el keyfile se incrementa la variable que esta en 0040308Ch y vale inicialmente 0. Si no se encuentra el archivo 'mopz' o la variable vale 1 se xorea 'mopz' con 111B1A0Bh que da 'fukk' y se retorna.

Una vez fuera solo se seguira la ejecucion normal del programa si la variable incrementada vale 2, en caso contrario se vuelve a llamar a esta subrutina.

De aqui se deduce que el keyfile debe llamarse 'fukk' por el siguiente motivo:
La primera vez que se llame a esta subrutina es imposible pasar por 004010C2h sin saltar, con lo cual el keyfile NO tiene porque llamarse 'mopz'.
La segunda vez (y ultima) se buscara el fichero 'fukk'. En esta ocasion deberemos tenerlo listo, puesto que SI se puede pasar de 004010C2h.

Asi pues creamos el archivo 'fukk' y colocamos dentro cualquier cosa.

Tamaño que debe tener el keyfile:

.text:004010C8                 mov     hFile, eax 
.text:004010CD push 0 ; lpFileSizeHigh
.text:004010CF push hFile ; hFile
.text:004010D5 call GetFileSize
.text:004010DA mov esi, eax
.text:004010DC mov ecx, 0Ah
.text:004010E1
.text:004010E1 loc_4010E1: ; CODE XREF: check+57j
.text:004010E1 add eax, 3
.text:004010E4 add eax, ecx
.text:004010E6 loop loc_4010E1
.text:004010E8 imul eax, 3039h
.text:004010EE cmp eax, 160A0Dh
.text:004010F3 jnz short loc_40114E

Al tamaño de nuestro keyfile se le suma un valor fijo que viene determinado por un loop y luego se multiplica por 3039h. Eso debe dar como resultado 160A0Dh. El loop suma 3 + 10 + 3 + 9 + ... + 3 + 1. Es decir el 3 lo suma 10 veces y luego un sumatorio de 1 a 10, el resultado del cual es:
((primero+ultimo)*numero_sumandos)/2
En resumen el loop esta sumando 3*10 + (10+1)*10/2 = 85 = 55h

Luego deberiamos aislar la variable 'size' de la siguiente ecuacion:

(size + 55h) * 3039h = 160A0Dh
size = (160A0Dh / 3039h) - 55h = 20h = 32

El tamaño del keyfile debe ser de 32 bytes.

En este punto se procedera a la lectura del archivo y lo almacenara en el buffer que esta en 403034h.

.text:004010F5                 push    0                  ; lpOverlapped 
.text:004010F7 push offset unk_403088 ; lpNumberOfBytesRead
.text:004010FC push esi ; nNumberOfBytesToRead
.text:004010FD push offset unk_403034 ; lpBuffer
.text:00401102 push hFile ; hFile
.text:00401108 call ReadFile

Luego se verifica si el buffer contiene algun byte nulo. Esto suele ocurrir con las shellcodes, puesto que muchas veces la lectura de una cadena se realiza hasta el primer byte nulo que actua de "centinela".

.text:0040110D                 mov     ecx, esi 
.text:0040110F mov edi, offset unk_403034
.text:00401114 mov esi, edi
.text:00401116 mov al, 0
.text:00401118 repne scasb
.text:0040111A cmp ecx, 0
.text:0040111D jnz short loc_401158

Seguidamente se calcula un puntero al cual se saltara a partir de las primeras 4 dwords del keyfile. La pregunta que nos viene a todos a la mente es: øA que sitio se debe saltar? Esto me dejo pensando un tiempo y estube buscando algun codigo oculto en el programa que modificara la cadena de "TRYAGAIN" en runtime (øRecordais que siempre se mostraba esta?). Sin embargo al poco tiempo de buscar me di cuenta de que no habia tal rutina. Por eso es logico, y no hay otra manera, que sea el propio keyfile el que tenga la rutina que modifique esa cadena. Y esta hay que hacerla en tansolo 32 bytes sin poder usar ni un solo byte nulo!. Quiero recordar que ya con usar por ejemplo "mov eax, offset aTryagain" estariamos violando una de estas condiciones porque los RVAs se codifican como xxxx40[00].

Con respecto al modo de calcular la direccion de destino en eax, reservare la primera dword del keyfile para equilibrar la ecuacion y lo dejare para el final, cuando este todo el codigo escrito.

.text:0040111F                 mov     eax, [esi] 
.text:00401121 xor eax, 12344321h
.text:00401126 mov ebx, [esi+4]
.text:00401129 xor ebx, 23455432h
.text:0040112F mov ecx, [esi+8]
.text:00401132 xor ecx, 34566543h
.text:00401138 mov edx, [esi+0Ch]
.text:0040113B xor edx, 45677654h
.text:00401141 add eax, ebx
.text:00401143 add eax, ecx
.text:00401145 add eax, edx
.text:00401147 add eax, 0DEADh
.text:0040114C jmp eax
.text:0040114E ; ---------------------------------------------------------------
.text:0040114E
.text:0040114E loc_40114E: ; CODE XREF: check+26j
.text:0040114E ; check+33j ...
.text:0040114E xor dword ptr aMopz, 111B1A0Bh ; "mopz"
.text:00401158
.text:00401158 loc_401158: ; CODE XREF: check+8Ej
.text:00401158 popa
.text:00401159 retn
.text:00401159 check endp

Antes de nada se tendran en cuenta los siguientes puntos que ayudaran a la hora de reducir el numero de bytes al maximo:

  • eax apuntara al inicio de la rutina del keyfile cuando lleguemos a este, asi luego solo hay que sumar o restar desplazamientos para no gastar 4 bytes en un RVA nuevo cada vez.
  • Se pueden xorear todos los bytes para poder trabajar luego tranquilamente con bytes nulos.
  • Como la instruccion "loop" requiere el valor entero de ECX y la inicializacion de este conlleva un inmediato muy grande, mejor usar un bucle convencional y asi inicializar solo CL.

Con todo esto podemos fabricar lo siguiente:

.00403034: FFFF                         ??? 
.00403036: FFFF ???
.00403038: B10F mov cl,00F
.0040303A: 83C00D add eax,00D
.0040303D: 803069 xor b,[eax],069
.00403040: 40 inc eax
.00403041: FEC9 dec cl
.00403043: 75F8 jne .00040303D
.00403045: 2C32 sub al,032
.00403047: C7004F4B0000 mov d,[eax],000004B4F ;" KO"
.0040304D: 6858114000 push 000401158
.00403052: C3 retn
( Y me sobra un byte :D )

Ahora con HIEW basta encriptar la zona entre 403045h y 403053h para su posterior desencriptado con XOR 69h. Ello se puede hacer con F3 y luego F8 para xorear.

Volviendo al problema anterior sobre como se calcula la direccion de destino del "jmp eax".

Con el siguiente keyfile:

?? ?? ?? ?? B1 0F 83 C0 0D 80 30 69 40 FE C9 75  ; llamando la primera dword que 
F8 45 5B AE 69 26 22 69 69 01 31 78 29 69 AA AA ; desconocemos "X"

Y dada la ecuacion:

destino = (X xor 12344321h) + (dword2 xor 23455432h) + (dword3 xor 34566543h) + (dword4 xor 45677654h) + 0DEADh

en nuestro caso:
403038h = (X xor 12344321h) + (0C0830FB1h xor 23455432h) + (6930800Dh xor 34566543h) + (75C9FE40h xor 45677654h) + 0DEADh

o simplificando:
403038h = (X xor 12344321h) + 71DCA792h

solo queda aislar la X:
X = (403038h - 71DCA792h) xor 12344321h = 9C57CB87h


Con lo cual el keyfile resultante es el siguiente:

   +- 'fukk' ----------------------------------------+ 
| 87 CB 57 9C B1 0F 83 C0 0D 80 30 69 40 FE C9 75 |
| F8 45 5B AE 69 26 22 69 69 01 31 78 29 69 AA AA |
+-------------------------------------------------+

Por supuesto que esta no es la unica solucion ni la mejor.. A alguien se le ocurre algo nuevo? :P

Finalizando

Espero que este tutorial sirva un poco para ayudar a analizar las protecciones solo con listado muerto. Aunque hay que reconocer que en ocasiones, sin la ayuda de un debugger no podria haber avanzado demasiado.

*EOF*

← previous
next →
loading
sending ...
New to Neperos ? Sign Up for free
download Neperos App from Google Play
install Neperos as PWA

Let's discover also

Recent Articles

Recent Comments

Neperos cookies
This website uses cookies to store your preferences and improve the service. Cookies authorization will allow me and / or my partners to process personal data such as browsing behaviour.

By pressing OK you agree to the Terms of Service and acknowledge the Privacy Policy

By pressing REJECT you will be able to continue to use Neperos (like read articles or write comments) but some important cookies will not be set. This may affect certain features and functions of the platform.
OK
REJECT