7A69 Issue 15 - Un loader y un crack
|-----------------------------------------------------------------------------|
[ 7a69#15 ] [ 23-11-2003 ]
\ /
\-------------------------------------------------------------------------/
{ 12 - Un loader y un crack. }{ Marconi }
|-----------------------------------------------------------------------|
/************************************************************************/
/* */
/* acehide v1.22 */
/* www.zslab.com */
/* Ejemplo de un loader y un crack */
/* */
/* dificultad: baja */
/* herramientas usadas: W32Dasm, PEditor,PDump32,Masm32 */
/* */
/* Marconi */
/* inocraM@yahoo.com */
/* */
/* Miercoles, 5 de febrero del 2003 */
/* */
/* 1™ revision: Jueves, 6 de febrero del 2003 */
/* */
/************************************************************************/
0.- Acerca de esta revision...
- Se ha escrito correctamente, usando "qu"'s en vez de k's, usando y's donde corresponde en vez de abusar del uso de la "i" latina, etc. Si se me ha escapado alguna falta de ortografia lo siento, no era mi intencion. No pongo acentos, y ya sabeis todos el porque. Este conato de escribir con "buena ortografia" se ha hecho a instancias de Esn-min. Un saludo y gracias chavalote ;)
- Se han añadido algunos comentarios que se me habian olvidado y sin los cuales creo que no todo se entiende tan bien como deberia.
- El crack ha sido modificado, debido a la estupidez que era comenzar con un jmp en vez de pasar el EP a ese punto. Uno de mis muchos momentos brillantes :_(
- Lo primero de todo es que al hacer copy-paste del archivo debio haber algun error y habia un salto condicional mal. Y tambien habia dos pequeños errores que me cole y puse dos addr's donde no tenian que estar. Y tambien se me olvido el alineamiento de los contextos... Vamos, que yo no se en que estaba pensando porque eso no funcionaba ni bien ni mal. Lo que pasaba era que se quedaba en un bucle infinito recibiendo eventos de debug por intentar ejecutar una instruccion privilegiada ( codigo de excepcion STATUS_PRIVILEGED_INSTRUCTION). Yo que no vi salir ninguna ventana me confie y pense que todo iba sobre ruedas... jejeje
De toda formas decidi reescribir el bucle del WaitForDebugEvent usando la sintaxis de alto nivel que proporciona el masm intentando asi que quedase un codigo mas legible. Y ahora si, todo funciona a la perfeccion.
1.- Introduccion...
La historia de como se crea este peke§o tutorial es bien singular. Lo cierto es que yo llevaba ya mucho tiempo sin crackear y toda mi actividad en el mundo de la informatica se reducia a ocupar una peque§a parte de mi tiempo libre en programar. Aun asi sigo siendo un habitual del canal #crackers del irc-hispano, ademas de serlo de otros canales como #asm, #win32asm o #ensamblador. Justo ayer, 4 de febrero, habia acabado mis examenes parciales y tenia al fin un poco de tiempo libre, y tambien justo ayer entro en el canal #crackers alguien que se consideraba un ex-cracker y que pedia ayuda con la victima objetivo de este tuto, el acehide. Como no tenia nada mejor que hacer y el individuo en cuestion aseguraba que era cosa de ni§os ( lo que no entiendo si era tan sencillo era porque no lo hacia el) me puse manos a la obra.
Hay que tener en cuenta alguna cosa importante a la hora de leer el tuto. La primera es que lo estoy escribiendo guiandome por las notas que tome ayer noche mientras le hacia a la victima todo tipo de perrerias. Intentare dar una vision real de lo que fue el proceso de investigacion en si, sin omitir errores, perdidas de tiempo, etc. Intentando excusarme dire que llevaba mas de un a§o sin crackear nada, seguramente cualquiera que este un poco mas en forma le habria dado jaque mate mucho mas rapido y de una forma mucho mas elegante. Otra cosa importante a tener en cuenta es el conjunto de herramientas del que se disponia para llevar a cabo la tarea. Solo tenia instalado el W32dasm, el Peditor y el Masm32, y en un cd que tenia sobre mi mesa estaba el PDump32. No eran ciertamente las herramientas idoneas para llevar a cabo el trabajo que se avecinaba,pero creedme si os digo que habria tardado mas en encontrar los CD's con utilidades para crackear que en hacerlo usando lo que tenia a mano. Una desventaja de ser desordenado ;)
Sin mas dilaciones...
2.- Investigando...
Al ejecutar el programa se pone el iconcillo del programa al lado del reloj, y haciendo click sobre el con el boton dcho del raton se despliega un menu con una serie de opciones entre las que se encuentra la de registrar la aplicacion. Vamos directamente a ella y podemos ver que tenemos campos para introducir nuestro nombre y serial, asi como de checkear dicho serial. Tambien nos dice que para que tenga efecto hay que reiniciar la aplicacion. Introduciendo un nombre y serial podemos ver que sale una ventana de error con la frase "The registration code is INCORRECT". Desensamblo el ejecutable con el W32Dasm pero este no es capaz a desensamblar nada, asi que me imagino que esta empacado y le hecho un vistazo con el PEditor, pudiendo ver una seccion .aspack que dentro de la cual esta el punto de entrada.
Lo primero en lo que me voy a centrar es en conseguir un listado muerto del codigo del programa, aunque no sea un ejecutable funcinal. Para ello uso el PDump32 para hacer un volcado total del proceso en memoria y seguidamente con el PEditor modifico las caracteristicas de la primera seccion a E0000020 ( ni siquiera cambiaron los nombres de las secciones y se sigue llamando .text). Una vez hecho esto, y aunque no tengo el punto de entrada original y no tengo la tabla de funciones exportadas e importadas si que cuento con un listado de codigo valido, asi como informacion de recursos y cadenas de texto.
Encontramos las cadenas con ID's 00138 y 00139, que son: "The registration code is CORRECT" y "The registration code is INCORRECT". La ultima de ellas se referencia desde 00407179 y 00406e5f, y la primera de ellas desde 004071d4, 0041090c y 00406d0f. De todas estas VA's, podemos ver que 0041090c no se puede emparejar con otra VA cercana de la otra cadena asi que la eliminamos. 00406e5f esta dentro de una funcion que es llamada desde 00409ad5 y 0040a506, con lo que la pareja de VA's mas probable para comenzar el estudio es 00407179 y 004071d4.
Estudiando el codigo por encima de estas VA's podemos ver cosas interesantes, incluido un salto condicional con el que se escoje una de las dos opciones. Vamos a tener que tracear para conseguir mas informacion, y para ello vamos a usar el depurador del W32Dasm, y tracearemos sobre el .exe orginal al que previamente modifique las caracteristicas de las secciones .text y .aspack a E0000020, para poder asi en la medida de lo posible poner bpx's. La informacion que conseguimos (resumida) es la siguiente:
; introduciendo como nombre = Marconi
; y como serial = 1234567890
:00407098 call 0041BF3B ; eax= tamaño del serial
:0040709D mov esi, dword ptr [esp+10] ; esi->serial
:004070A1 mov eax, dword ptr [esi-08] ; eax= tamaño del serial
:004070A4 cmp eax, 0000001D ; tamserial < 29?
:004070A7 jl 00407167 ; la cagamos.
Usamos un serial de 30 caracteres, por ejemplo:
452095782509826028571034161456
Cambiando el serial que habiamos introducido por este pasamos el primer filtro. Seguimos traceando en busca de informacion que nos interese... y llegamos a:
:004070DF mov al, byte ptr [esi+1B] ;esi->serial
:004070E2 pop ebp
:004070E3 cmp al, 30 ;al=caracter 0x1b
:004070E5 jl 004070F3
:004070E7 cmp al, 39
:004070E9 jg 004070F3 ;saltamos si no es un num.
:004070EB movsx eax, al
:004070EE sub eax, 00000030 ;eax=ese digito
:004070F1 jmp 00407105
ese salto incodicional nos lleva a:
:00407105 mov cl, byte ptr [esi+1C] ;idemp con la pos 0x1c
:00407108 shl al, 04 ; al*=16
:0040710B cmp cl, 30
:0040710E jl 0040711D
:00407110 cmp cl, 39
:00407113 jg 0040711D
:00407115 movsx ecx, cl
:00407118 sub ecx, 00000030
:0040711B jmp 00407131
y con ese otro salto incondicional llegamos a:
:00407131 or al, cl
:00407133 mov cl, byte ptr [esp+0F]
:00407137 xor cl, al ; si no son iguales
:00407139 jne 00407163 ; la cagamos
Los slatos condicionales de las dos primeras partes no se ejecutan puesto que en nuestro serial solo hay numeros.
Si tenemos en cuenta que en [esp+0f] hemos leido un valor 6, y presuponiendo que los cambios de las posiciones 0x1b y 0x1c no influyen en este valor el serial tiene que cumplir que:
pos(0x1b)*16+pos(0x1c)==6 ---> pos(0x1b)=0 y pos(0x1c)=6
y nuestro serial quedaria ahora:
452095782509826028571034161066
Y efectivamente nos sale ese pedazo de mensaje diciendonos que el serial es correcto, asi que pasamos a reinciar el programa.
Y aqui algo falla porque seguimos sin estar registrados, y asi nos lo indica la ventana de "about". Esa ventana tambien nos dice que estamos en el dia 0 de 30 de uso, que es el tiempo que funcionara si no estamos registrados. Adelantando el reloj dos meses no hacemos nada, pues el programa asegura que solo ha pasado un dia. Obviamente no nos vamos a parar a hacer esto 30 veces. Asi que forzaremos la caducidad del programa para ver a donde nos lleva. Mirando en la lista de cadenas de texto de nuestro archivo dumpeado encontramos tres que son:
- ID=00131: "You are now on day %u of your 30 day evaluation period."
- ID=00134: "You are now on day %i of your 30 day evaluation period. Do y"
- ID=00135: "Evaluation period had been expired. Do you want to Register "
La primera se trataria de la que se usa para rellenar el about asi que si investigamos las otras dos... y encontramos a su alrededor algo tan interesante como esto
:0040A466 cmp eax, 0000000A ; eax= numero de dias
:0040A469 jbe 0040A4C6 ; a los 10 dias pasa algo
y en otro sitio:
:0040A4C6 cmp dword ptr [00433994], 0000001E ; a los 30 dias...
:0040A4CD jbe 0040A55F
Con estos dos puntos podemos hacer caducar el programa. Nos falta comprobar que estamos en lo cierto traceando el programa. Y aqui nos encontramos con un problema. Si creamos el proceso como suspendido, le ponemos los bpx's y continuamos su ejecucion el W32dasm produce una excepcion y se cierra. Esto puede ser debido a que el aspack introduzca codigo antidebuggin. La solucion es tracear el codigo de la seccion .aspack, intentando evitar ese problema. Aqui no voy a entrar en detalles, traceando durante un rato y pasando una serie de bucles llego a 0043f3bf (un ret) que nos llevara a la 0040cbc4, con lo que ya tenemos OEP. Poniendo un bpx en 0043f3bf y desde ahi poniendo bpx's en los lugares donde queramos nos saltamos el antidebuggin. Ahora que ya tenemos la forma de poder poner bpx's al principio de la ejecucion del programa original podemos tambien simular su caducidad y seguidamente podemos comprobar como eso no llega a ningun sitio. Cuando caduca aparece una ventana que nos ofrece la posibilidad de registrarnos, pero ese boton nos lleva hasta la pagina web de la empresa para que copremos el programa y no hay salida a esta situacion. Tambien descubrimos jugando con este trozo de codigo que lo que pasa a partir del dia 10 de uso es que el programa nos enseñara una horrible ventana indicando lo que usamos el programa y lo poco que hemos pagado por el.
Entonces, contamos ahora con dos puntos de ataque que aun nos quedan. Por un lado la ventana que aparece de vez en cuando al intentar usar algunas opciones y que nos dice algo asi como "shareware restriction bla bla bla" y tenemos tambien el texto "NOT REGISTERED", ID: 00133. y que aparece en la ventana de "about". Este texto esta referenciado en dos sitios: 0040522c y 004108dc. Poniendo un bpx en ambas posiciones vemos que la ultima queda descartada, con lo que pasamos a estudiar el codigo de la zona en la que esta la primera aparicion. Encontramos lo siguiente:
:004051AF call 0041DFBE
:004051B4 mov al, byte ptr [0043388D] ; comprobacion
:004051B9 test al, al ; al==0?
:004051BB je 004051EA ; la cagamos
Ademas de esto podemos ver como esa misma comparacion se hace en varios sitios, lo que induce a pensar que byte ptr [0043388D] es una flag que indica si estamos o no registrados.
Una de las cosas que hay que tener en cuenta llegado a este punto es que haciendo las pruebas pertinentes hasta llegar aqui ya he ejecutado unas cuantas veces el programa con lo que ahora ya aparece por derecho propio la ventana que indica la superacion de los 10 dias.
(*** nota sobre el debugger del w32dasm ***)
La superacion de esos 10 dias se ha producido entre otras cosas por lo que parece ser un fallo en el debugger del w32dasm. Al parchear el codigo para simular la caducidad del programa me he dado cuenta que si tenemos el programa parado y hacemos modificaciones de alguna posicion de memoria y/o registro, en la mayoria de las ocasiones al usar f7 o f8 para seguir traceando en vez de parar en la instruccion siguiente lo que hace es continuar ejecutandose. La unica solucion que he encontrado es poner un bpx en la instruccion siguiente para despues si poder seguir traceando.
(*** nota sobre el debugger del w32dasm ***)
Pasemos a hacer la siguiente prueba:
- bpx en 0043f3bf ( el ret que se ejecuta para llegar al OEP)
- Poner en [0043388D] un 1
Pasan dos cosas interesantes:
- aparece la mierda de ventana diciendome que me quedan no se cuantos dias para que el programa deje de funcionar-> la direccion de memoria en la que antes he puesto un valor se ha borrado.
- Viendo esto vuelvo a poner en el byte ptr [0043388d] un 1, y de repente el programa pasa a funcionar como si estuviese perfectamente registrado.
Parece obvio que lo que hay qu hacer es buscar cuando se toca esa posicion de memoria y hacer que se rellene con lo que nos interesa. Despues de tracear un ratillo encontramos un posible punto para cambiar y arrinconamos el codigo malefico en una funcion. La posicion es 0040a43d y la funcion 00403c50 y traceando dentro de ella finalmente encontramos:
:00404CCC mov byte ptr [eax+0000010D], 00
cambiar el ultimo valor por 1 y el programa pensara que esta registrado.
Con esto llego al final de la parte de investigacion del software. Obviamente no es una investigacion muy pormenorizada y ni siquiera me he parado a estudiar en detalle el sistema de proteccion. Tampoco he explicado detenidamente partes que con las herramientas adecuadas seguramente habrian sido trivialidades.
3.- Loader...
Pues si contamos con que tenemos el punto de entrada original, y sabemos donde hay que parchear, si se dispusiese de las herramientas necesarias pareceria mas que logico desempacar el ejecutable, parchearlo y ya tendriamos el programa crackeado. Pero no es eso lo que pretendo yo ahora. Puesto que hemos seguido unos pasos para conseguir simular el registro lo que vamos a hacer un programa que haga de forma automatica todos esos pasos.
Lo primero que se ve claramente es que se tiene que tratar de un loader, pero que sea casi como un pequeño debugger. El proceso tiene que lanzar el ejecutable victima, esperar a que llegue al punto en el que va a entrar en el OEP, en ese momento parar el programa, parchear la direccion en cuestion, y seguir la ejecucion normal.
Para hacer todo esto yo en principio propongo que el loader actue como debugger del programa victima y ponga una int 3h en el punto en el que va a saltar al OEP. Una vez se ejecute el bpx podemos quitarlo, parchear la segunda direccion, y esperar a que termine. Un programa que hace todo esto podria ser algo asi como:
/********************************acehide.asm - (loader) *****************
;
; loader para el acehide v1.22
; version 1.1
;
; Marconi
; Febrero del 2003
; inocram@yahoo.com
;
;
; ml /c /coff acehide.asm
; link /SUBSYSTEM:WINDOWS acehide.obj
;
.386
.MODEL flat,STDCALL
option casemap:none
include c:\masm32\include\windows.inc
include c:\masm32\include\kernel32.inc
include c:\masm32\include\user32.inc
includelib c:\masm32\lib\kernel32.lib
includelib c:\masm32\lib\user32.lib
.data
error db "Error en la aplicacion...",0
procsi dd 0
archivo db "c:\Archivos de programa\aceHide\aceHide.exe",0
nval db 1
nbpx db 0cch
noldbpx db 0c3h
vapatch dd 00404cd2h
vabpx dd 0043f3bfh
ALIGN 4
contexto CONTEXT<CONTEXT_CONTROL,0>
devento DEBUG_EVENT <0>
.data?
infoinicio STARTUPINFO <?>
procinfo PROCESS_INFORMATION <?>
cad dd ?
.code
inicio:
;---------
; crear el proceso
;---------
invoke GetStartupInfo,addr infoinicio
invoke CreateProcess,NULL,addr archivo,NULL,NULL,FALSE,\
DEBUG_ONLY_THIS_PROCESS+NORMAL_PRIORITY_CLASS,\
NULL,NULL,addr infoinicio, addr procinfo
test eax,eax
je fallo
inc procsi
;---------
; poner el bpx
;---------
invoke WriteProcessMemory, procinfo.hProcess,vabpx,addr nbpx,\
1,NULL
test eax,eax
je fallo
;---------
; Esperar por las peticiones
;---------
.WHILE TRUE
invoke WaitForDebugEvent, addr devento, INFINITE
.IF devento.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT
.break
.ELSEIF devento.dwDebugEventCode == EXCEPTION_DEBUG_EVENT
.IF devento.u.Exception.pExceptionRecord.ExceptionCode \
== EXCEPTION_BREAKPOINT
mov eax, \
devento.u.Exception.pExceptionRecord.ExceptionAddress
.IF eax == vabpx
;---------
; quitar el punto de ruptura
;---------
invoke WriteProcessMemory,procinfo.hProcess,vabpx,\
addr noldbpx, 1,NULL
mov contexto.ContextFlags, CONTEXT_CONTROL
invoke GetThreadContext, procinfo.hThread,\
addr contexto
dec contexto.regEip
invoke SetThreadContext, procinfo.hThread,\
addr contexto
;---------
; parchear el .exe
;---------
invoke WriteProcessMemory,procinfo.hProcess,\
vapatch, addr nval,1,NULL
.ENDIF
.ENDIF
invoke ContinueDebugEvent, devento.dwProcessId , \
devento.dwThreadId, DBG_CONTINUE
.ENDIF
invoke ContinueDebugEvent, devento.dwProcessId , \
devento.dwThreadId, DBG_CONTINUE
.ENDW
jmp salida
fallo:
invoke GetLastError
invoke FormatMessage,FORMAT_MESSAGE_ALLOCATE_BUFFER + \
FORMAT_MESSAGE_FROM_SYSTEM,NULL,eax,0,addr cad,0\
,NULL
cmp procsi,0
je mensaje
invoke TerminateProcess,procinfo.hProcess,0
mensaje:
invoke MessageBox,0,cad,addr error,MB_OK+MB_ICONINFORMATION
invoke LocalFree,cad
salida:
invoke ExitProcess,NULL
end inicio
/***************************** fin del archivo *************************
4.- crack...
Seguramente a nadie se le ha pasado el hecho de que tenemos la ultima direccion que se ejecuta antes de pasar el control al codigo original del programa. Aun estando empacado y puesto que no tiene ningun tipo de control de integridad, hacer el crack es practicamente cosa de niños.
Lo unico que hay que tener en cuenta es que el push 0 que hay antes del ret que nos lleva al codigo original se rellena en tiempo de ejecucion con la direccion valida. Esta direccion es calculada por el trozo de codigo que hay justo antes del ret. Por ejemplo en 0043f39a, que mueve a eax el valor 0x0cbc4, ese valor es la parte baja del OEP. El punto en el que la direccion de retorno es escrita en el codigo es:
:0043F3A9 mov dword ptr [ebp+000003A8], eax
Por lo tanto habra que tener cuidado con esta instruccion.
/********************************acehide.asm - (crack) *****************
;
; crack para el acehide v1.22
; Marconi
; Febrero del 2003
; inocram@yahoo.com
;
;
; ml /c /coff acehide.asm
; link /SUBSYSTEM:WINDOWS acehide.obj
;
.386
.MODEL flat,STDCALL
option casemap:none
include c:\masm32\include\windows.inc
include c:\masm32\include\kernel32.inc
includelib c:\masm32\lib\kernel32.lib
.data
;---------
; archivo a parchear y offsets donde parchear
;---------
archivo db "c:\Archivos de programa\AceHide\AceHide.exe",0
offset1 dd 0001b66bh
offset2 dd 00019dbah
offset3 dd 00019da9h
.data?
handle HANDLE ?
cuantos dd ?
.code
;---------
; Desde aqui hasta fincodigo son los datos con los que hay que parchear
;---------
codigo1:
push eax
mov eax, 00404cd2h
mov byte ptr [eax],1
pop eax
push 0040cbc4h
ret
codigo2:
push 00440c6bh
ret
codigo3:
dec eax
inc eax
dec eax
inc eax
dec eax
inc eax
fincodigo:
inicio:
;---------
; punto de entrada
; abrimos el archivo
; y vamos escribiendo los datos
;---------
invoke CreateFile, addr archivo,GENERIC_WRITE,FILE_SHARE_READ,NULL, \
OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL
cmp eax, INVALID_HANDLE_VALUE
je fallo
mov handle,eax
invoke SetFilePointer,handle,offset2,NULL,FILE_BEGIN
cmp eax,-1
je falloaa
invoke WriteFile, handle, codigo2, codigo3-codigo2, addr cuantos, NULL
invoke SetFilePointer,handle,offset1,NULL,FILE_BEGIN
cmp eax,-1
je falloaa
invoke WriteFile, handle, codigo1, codigo2-codigo1, addr cuantos, NULL
invoke SetFilePointer,handle,offset3,NULL,FILE_BEGIN
cmp eax,-1
je falloaa
invoke WriteFile, handle, codigo3, fincodigo-codigo3, addr cuantos, NULL
;---------
; salida del programa ( tanto si hubo errores como si no)
;---------
falloaa:
invoke CloseHandle,handle
fallo:
invoke ExitProcess,eax
end inicio
/***************************** fin del archivo *************************
La verdad es que hacer un crack sin inteface ni nada de nada es una chapuza como dios manda, pero bueno, no estoy intentando escribir aqui un tutorial sobre como hacer un carck perfecto. Este codigo es lo que corresponderia al corazon del crack.
4.- Saludos y despedida...
Con esto termino este peque§o tuto, que espero a alguien sirva para algo. El loader que se propone esta probado en XP y funciona perfectamente, pero aun asi no es una solucion buena al problema. Esta puesto mas que nada para ense§ar a quien no sepa como se hace un programita de ese estilo y tambien, por que no decirlo, para rellenar un poco ;)
En cuanto al parche, tambien funciona a la perfeccion, esta probado en XP y a mi esta si que me parece una solucion minimamente decente al problema.
No creo que haga falta decir que no me hago responsable del mal uso que se pueda hacer de la informacion aqui expuesta, que este documento esta escrito con fines estricta y unicamente educativos, y todas esas cosas.
Saludos a los asiduos y no tan asiudos de los canales #crackers, #asm, #win32asm y #ensamblador.
Por supuesto cualquier comentario, critica, puntualizacion, o lo que sea, se puede mandar un mail a: inocraM@yahoo.com
Y respondo al nombre de Marconi ;)
Saludos.
Marconi
Lo normal es aburrido
*EOF*