Usaremos DOSBox debugger [2] para ejecutar el programa paso a paso, pondremos breakpoints, identificaremos varios puntos donde podremos cambiar el codigo en memoria para que acepte cualquier clave que introduzcamos y explicaremos por que. Aplicaremos los cracks en caliente desde DOSBox debugger y conseguiremos pasar de la pantalla de claves, hasta aqui nada nuevo con respecto a las anteriores entradas de la serie, pero a la hora de ir a parchear el ejecutable con un editor hexadecimal nos toparemos con que los bytes que hemos modificado desde DOSBox debugger no estan en el archivo. WTF!! ¿Quieres saber si finalmente lograremos crear un crack permanente? Entonces sigue leyendo.
Comenzamos. Cargamos el juego en DOSBox y al llegar a la pantalla de claves
pulsamos ALT+PAUSA para activar el debugger, tal como vimos en entregas anteriores. Si ejecutamos paso a paso (F10 step in/F11 step into) enseguida nos toparemos con el siguiente codigo:
0308:015C B401 mov ah,01 ; ¿Se ha pulsado alguna tecla? 0308:015E CD16 int 16 0308:0160 7503 jne 0165 ($+3) 0308:0162 33C0 xor ax,ax ; NO: limpiamos ax 0308:0164 C3 ret ; y volvemos a empezar 0308:0165 B80000 mov ax,0000 ; SI: Obtenemos codigo tecla pulsada 0308:0168 CD16 int 16 ; Devuelve: AH = BIOS scan code ; Devuelve: AL = ASCII character BP 0308:016A C3 retEs la parte encargada de esperar la pulsacion de alguna tecla. Ponemos un breakpoint en el segundo ret
BP 0308:016Avolvemos a ejecutar con F5 y no tardamos en llegar a la parte donde se comprueba que tecla ha sido pulsada.
(...) 0308:00E8 3D0D1C cmp ax,1C0D ; Se ha pulsado Intro? 0308:00EB 7433 je 0120 ($+33) ; SI: saltamos a gestionarlo 0308:00ED E9E1FF jmp 00D1 ($-1f) ; NO: vuelta a empezarPonemos un bp en la direccion de memoria a la que saltamos cuando se ha pulsado intro.
BP 0308:0120 ; Hemos pulsado introy no tardamos en llegar a la parte donde se comprueba si se han introducido los 4 caracteres de la clave, momento en que se llama a un call que compara la clave introducida con la clave que el juego espera. Cada simbolo se codifica con un byte (caracter) y ambas claves seran tratadas como cadenas.
0308:0043 A07B07 mov al,[077B] ; Numero de caracteres introducidos 0308:0046 3C04 cmp al,04 ; Se han introducido los 4 caracteres de la clave? 0308:0048 75F3 jne 003D ($-d) ; NO -> otra vuelta al bucle BP 0308:004A E83D00 call 008A ($+3d) ; SI -> comprobamos la clave 0308:004D 75EE jne 003D ($-12) ; JNE = CHICO_MALO. **OPCION 1 DE CRACK->SM 0308:004D 90 90 NOP NOP 0308:004F E8DE02 call 0330 ($+2de) ; Camino CHICO_BUENO 0308:0052 CD12 int 12Entramos con F11 en el primer CALL y enseguida vemos codigo que comprueba la clave introducida con la clave que espera el juego como valida.
0308:008A BE7C07 mov si,077C ; Puntero a clave introducida por usuario ; **OPCION 5 de CRACK ->SM 0308:008B 80 mov si,0780 0308:008D BF8007 mov di,0780 ; Puntero a clave buena ; **OPCION 6 de CRACK -> SM 0308:008E 7C mov di,077C 0308:0090 B90400 mov cx,0004 ; Numero de vueltas al bucle 0308:0093 8A04 mov al,[si] ; Guardamos en AL un caracter de la clave introducida por el usuario ; OPCION 3 DE CRACK->SM 0308:0093 XX XX MOV AL,[DI] 0308:0095 3A05 cmp al,[di] ; y lo comparamos con el caracter de la clave real ; OPCION 4 DE CRACK->SM 0308:0095 XX XX CMP AL,[SI] 0308:0097 750D jne 00A6 ($+d) ; JNE = CHICO_MALO ; **OPCION 2 DE CRACK->SM 0308:0097 90 90 0308:0099 47 inc di ; Siguiente caracter de clave buena 0308:009A 46 inc si ; Siguiente caracter de clave introducida por usuario 0308:009B E2F6 loop 0093 ($-a) ; Decrementamos CX en 1, y si CX aun no es 0 -> Saltamos al principio del bucle 0308:009D B90600 mov cx,0006 ; Camino CHICO_BUENO 0308:00A0 E82400 call 00C7 ($+24) 0308:00A3 32C0 xor al,al ; Ponemos al=0 0308:00A5 C3 ret ; Retornamos a 0308:004D 75EE jne 003D con codigo de retorno AL ; Como el codigo de retorno es 0 seguiremos en CHICO_BUENOPodemos crackear el juego de multiples formas, he numerado varias, veamoslas.
A destacar las opciones 5 y 6 donde el juego quedaria crackeado cambiando UN solo byte. En la 5 hacemos que el puntero a la clave introducida por el usuario apunte a la clave buena, de tal forma que estaremos comparando la clave buena consigo misma y obviamente sera tomada como correcta. La 6 sigue la misma idea pero hacemos que el puntero a la clave buena apunte a la clave introducida por el usuario, con lo cual estaremos comparando la clave introducida consigo misma que obviamente tambien sera tomada como correcta.
En las opciones 3 y 4 haremos que el CMP compare [si] con [si] o bien [di] con [di], con lo que el resultado obviamente siempre sera que ambos caracteres coinciden.
Yo voy a elegir la opcion 2, es decir, anular este salto:
0308:0097 750D jne 00A6 ($+d)Podriamos anularlo cambiando UN solo byte. Haciendo que el salto sea de cero bytes. Es decir, cambiando "75 0D" por "75 00". Pero vamos a hacer lo que se suele hacer y parchearemos con dos NOP:
0308:0097 9090 nop nopcon lo cual seguiremos el camino CHICO_BUENO independientemente del resultado de la comprobacion cmp al,[di]. Vamos a ello, desde DOSBox debugger:
SM 0308:0097 90 90Continuamos ejecucion con F5 y eureka! cualquier clave que introduzcamos
el juego la da por valida y pasamos de la pantalla de claves al juego en si.
Entonces, con todo el subidon por haberlo conseguido nos disponemos a cargar COLMENA.EXE en nuestro editor hexadecimal favorito y buscamos una ristra de bytes que incluya los bytes que queremos modificar. Y... no la encontramos. Pero que no cunda el panico. ¿Sera que estan en otro archivo? Podria ser. mmmm ¿Y si pudieramos saber los archivos que un ejecutable va abriendo? Pues podemos.
Una manera rapida seria usar KGB [3] (incluye codigo fuente en ASM!), un genial TSR que captura la INT 21h y escribe las llamadas que un ejecutable va haciendo a funciones de tratamiento de archivos de la INT 21h como abrir, crear, ejecutar, etc. Crea un archivo de texto c:\HISTORY.DAT donde va guardando estas llamadas. Su uso es muy sencillo:
C:\> KGB Runtime saving of file actions. ver. 1.04 Petr Hor�k, Praha 1992 Usage: kgb [file] [/Options] .. Options: /u unload /dX drive X (A-Z) /on (off) C:\> COLMENALlegamos hasta la pantalla de claves, miramos el contenido de C:\HISTORY.DAT y tenemos:
Exec : colmena.EXE OpenFile-R : col0.ovlVemos que COLMENA.EXE abre en modo lectura el archivo col0.ovl
Otra forma seria poner breakpoints en llamadas a interrupciones en el debugger de DOSBox. Tenemos practicamente toda la info posible sobre interrupciones (que funciones hay, en que registro se pasa cada argumento, si retorna algo en que registro lo hace...) en las dos biblias: Ralf Brown's Interrupt List [4] y HelpPC Interrupt List [5].
Llamadas interesantes que nos podrian interesar ahora son INT 21/3D (Open file using handle) e INT 21/4B (load and execute program), asi que ponemos los siguientes breakpoints:
BPINT 21 3d BPINT 21 4bEjecutamos con F5 y llegamos a una llamada a INT 21h/3d. Justo antes de que se ejecute la instruccion INT 21h tenemos en DS:DX el nombre del archivo en ASCIIZ. Pulsamos ALT+X para cambiar la vista de datos a DS:DX y ahi tenemos el nombre del archivo que se va a abrir:
col0.ovl\00Bien, vamos a abrir col0.ovl con un editor hexadecimal para cambiar bytes. Nada, tampoco encontramos la ristra de bytes que queremos parchear. WTF?
No desesperemos, volvamos a cargar el juego, volvamos a poner el BPINT y desde ahi sigamos ejecutando paso a paso (F10 step in/F11 step into). Acabaremos viendo que COLMENA.EXE va leyendo los bytes de col0.ovl y va tejiendo en memoria en tiempo de ejecucion los bytes que formaran el subprograma que nos pide las claves, y cuando termina el desempaquetado salta a la direccion de memoria donde comienza, que esta en otro segmento distinto. Y se ejecuta. Y aparece ante nosotros la pantalla de claves. Si la clave que introducimos es correcta, retornamos y COLMENA.EXE hace lo mismo con col1.ovl, que es el juego en si.
Sabiendo esto tenemos al menos dos opciones. Una seria localizar la rutina de descifrado, entenderla y parchear en la posicion correspondiente los bytes que queremos conseguir (90 90) pero _codificados_ de tal forma que cuando la rutina de descifrado desempaquete col0.ovl los bytes resultantes sean los que anulan la proteccion (90 90 en este caso).
La otra opcion, que es la que vamos a seguir, es crear un cargador (loader) en ASM que parchee los bytes que queremos modificar cuando col0.ovl haya sido desempaquetado. Para ello necesitamos capturar una interrupcion que llame el juego una vez estan en memoria los opcodes que queremos parchear.
Bueno, en este momento los lectores mas avispados se estaran preguntando... y si...
COPY COL0.OVL TMP.OVL COPY COL1.OVL COL0.OVL COPY TMP.OVL COL1.OVLjeje, pues si, simplemente renombrando COL1.OVL a COL0.OVL nos saltamos la pantalla de claves. :) Pero el articulo va de hacer un cargador, asi que sigamos.
Para encontrar la INT vamos a hacer un volcado del subprograma de claves, una vez ha sido desempaquetado, y con ayuda de IDA [6] sacaremos un listado de las interrupciones a las que llama. Necesitamos encontrar la primera instruccion de col0.ovl desempaquetado, y aqui tenemos al retf que hace que el flujo de ejecucion de COLMENA.EXE salte a la primera instruccion del subprograma col0.ovl
BP 9000:018B CB retfPonemos un BP, ejecutamos el retf y veremos que retornamos a una instruccion que esta otro segmento distinto.
0308:0000 2EC6064C0600 mov byte cs:[064C],00Justo cuando estamos en esa instruccion, sin llegar a ejecutarla volcamos los opcodes desde ese punto asi:
MEMDUMPBIN CS:IP FFFFY se habra creado un archivo binario llamado MEMDUMP.BIN de FFFF bytes. Lo abrimos en IDA como si fuera un .COM y buscamos las ocurrencias del opcode CD, que es el que corresponde a la instruccion INT. De las interrupciones que aparecen yo he elegido una llamada a INT 16h/AH=1
Sigamos. Queremos sustituir esto:
0308:0097 750D jne 00A6por esto:
0308:0097 90 nop 0308:0098 90 nopsiempre y cuando en la direccion [0097] esten los opcodes 75 0D (no queremos parchear mas de una vez) y se haya entrado a nuestra INT 16h tras una llamada a INT 16h/AH=1. Manos a la obra. He llenado el codigo de comentarios, no es quejareis.
TSR segment para 'code' assume cs:TSR, ds:TSR org 100h ; Esto sera un programa .COM ; El codigo residente en memoria empieza aqui START: jmp INITCODE ; Codigo de inicializacion OLDINT dw 0,0 ; Espacio para el puntero a la INT 16h del sistema NEWINT proc far cmp ah, 1 ; Se ha llamado a INT 16h con la funcion AH=1 ? jnz EXIT ; NO: saltamos a la INT 16h del sistema cmp Word Ptr [0097], 0d75h ; ¿Tenemos en DS:[0097] los opcodes 75 0D? Ojo!! x86 es little endian, asi que van "al reves" jnz EXIT ; NO: saltamos a la INT 16h del sistema mov Word Ptr [0097], 9090h ; CRACK ~ escribimos los bytes 90 90 en DS:[0097] EXIT: jmp DWord Ptr cs:[OLDINT] ; Saltamos a la INT 16h del sistema NEWINT endp ; El codigo residente en memoria acaba aqui INITCODE: push es mov ax, 3516h ; Queremos el valor del puntero a la INT 16h del sistema int 21h mov OLDINT[0], bx ; Guardamos puntero a la INT 16h del sistema mov OLDINT[2], es ; (necesitaremos llamarla despues) mov ax,2516h ; Sobreescribimos en el vector de interrupciones el puntero a INT 16h por lea dx, NEWINT ; un puntero a nuestra rutina NEWINT proc far int 21h mov es, ds:[2ch] ; Direccion del entorno mov ah, 49h int 21h ; Liberar espacio de entorno pop es lea dx, INITCODE ; Fin del codigo residente add dx, 15 ; Redondeo a parrafo mov cl, 4 shr dx, cl ; bytes -> parrafos mov ax, 3100h ; Terminate and Stay Resident (TSR) int 21h TSR ends end STARTGuardamos como CRACK.ASM y lo ensamblamos. Usare A86 [7]
a86 CRACK.ASMY tendremos un ejecutable CRACK.COM de tamaño 75 bytes.
Si lo ejecutamos aparentemente no hara nada y volveremos a ver el prompt c:\> de MS-DOS, pero en ese punto el programa se habra quedado residente en memoria, y ahora la INT 16h sera nuestra rutina NEWINT proc far, lista para acechar la comprobacion de claves del juego. Cargaremos COLMENA.EXE, que como vimos con IDA acabara llamando a INT 16h/AH=1, llamada que sera interceptada por nuestro TSR que parcheara el codigo del juego en memoria y devolvera el control a la INT 16h del sistema. El juego entonces aceptara cualquier clave introducida como valida. Si vuelve a haber llamadas a INT 16h/AH=1 seran aun interceptadas por nuestro TSR, pero al detectar que el juego ya esta parcheado no hara nada y simplemente redirigira la peticion a la INT 16h original del sistema.
Ojo que el TSR es un poco quick&dirty, no comprueba si ya esta instalado, ni permite desinstalarse a si mismo... mientras solo lo ejecutemos una vez, la cosa ira bien. Lo suyo seria que el propio TSR comprobara al menos si el mismo ya esta residente en memoria, y solo se quedara residente en caso de no encontrarse a si mismo. Pero esto se esta haciendo ya muy largo, asi que si alguien tiene interes dejo unas palabras clave -> Interrupcion Multiplex (2Fh), BMB Compuscience, CiriSOFT, propuesta AMIS (INT 2Dh).
O un metodo mas simple (muchos virus TSR de DOS usaban tecnicas similares para comprobarse a si mismos): capturar una INT, crear una nueva funcion, que se active cuando llamemos con un valor que no este reservado ya por la INT, algo como AX=0xface , y devolver un valor, por ejemplo haciendo que ES:BX apunte en memoria al valor 0xdead, y sabremos que si devuelve ese valor estamos ya residentes y tendremos que salir al DOS sin hacer nada mas, y si no lo devuelve, es que es la primera vez que se ejecuta nuestro TSR y entonces si tendremos que quedarnos residentes (y escribir en memoria el valor 0xdead, y habra que reservar tambien 2 bytes en memoria -por ejemplo como hace el listado TSR con la variable OLDINT-).
Pero... ¿y el cargador donde esta? esto es un TSR que no carga nada. ¿Seria posible que solo tuvieramos que ejecutar CRACK.COM y este ya cargara COLMENA.EXE? Pues si, haciendo uso de las funciones "Modify Allocated Memory Block" (INT 21h/4Ah) y "EXEC/Load and Execute Program" (INT 21h/4Bh). La captura de interrupciones, el parcheo del codigo en memoria de COLMENA.EXE, todo es igual, la unica diferencia es que esto ya no sera un TSR, sera un cargador en toda regla. Ahi va el listado.
SIZE EQU 1024 ; Este programa y su pila caben en 1 KB OVL segment para 'code' assume cs:OVL, ds:OVL org 100h ; Esto sera un programa .COM START: jmp INITCODE ; Codigo de inicializacion OLDINT16 dw 0,0 ; Espacio para el puntero a INT_16h del sistema NEWINT16 proc far cmp ah, 1 ; Nos llaman para INT_16h funcion 0 ? jnz EXIT ; NO: saltamos a la INT_16h del sistema ;; Ahora comprobamos que valor tienen los bytes del juego en memoria que ; queremos parchear, porque si ya los hemos parcheado en alguna llamada previa a ; INT_16/0, no queremos volver a hacerlo. cmp Word Ptr [0097], 0d75h ; ¿DS:[0097] = 75 0D? jnz EXIT ; NO: saltamos a la INT_16h del sistema ;; mov Word Ptr [0097], 9090h ; CRACK ~ escribimos los bytes 90 90 en DS:[0097] EXIT: jmp DWord Ptr cs:[OLDINT16] ; Saltamos a la INT_16h del sistema NEWINT16 endp INITCODE: mov sp, SIZE ; redefinimos la pila mov bx, SIZE/16 mov ah, 4ah ; redimensionamos bloque memoria int 21h lea dx, MSG mov ah, 9 ; Escribimos texto por pantalla int 21h xor ah, ah ; Esperamos pulsacion de tecla int 16h mov ax, 3516h ; Queremos el puntero a la INT_16h del sistema int 21h mov OLDINT16[0], bx ; Guardamos puntero a la INT_16h del sistema mov OLDINT16[2], es ; (necesitaremos llamarla despues) push cs pop es ; Necesitaremos ES:BX apuntando a EXEC_INFO mov ax, 2516h ; Sobreescribimos en el vector de interrupciones el ; puntero a INT_16h por lea dx, NEWINT16 ; un puntero a nuestra rutina NEWINT16 proc far int 21h ;; Parameter block lea bx, EXEC_INFO mov DWord Ptr [bx], 0 mov DWord Ptr [bx+2], 80h ; PSP mov Word Ptr [bx+4], cs mov Word Ptr [bx+6], 5ch ; FCB 0 mov Word Ptr [bx+8], cs mov Word Ptr [bx+0ah], 6ch ; FCB 1 mov Word Ptr [bx+0ch], cs ;; lea dx, FILENAME ; ES:BX apuntando a EXEC_INFO ; DS:DX apuntando al nombre ASCIIZ del archivo a ejecutar mov ax, 4b00h int 21h ; cargar y ejecutar programa mov dx, OLDINT16[0] mov ds, OLDINT16[2] mov ax,2516h ; Volvemos a poner en el vector de interrupciones el puntero a la INT_16h del sistema int 21h push cs pop ds ; DS = CS mov ax, 4c00h ; terminar int 21h FILENAME db "COLMENA.EXE",0 ; programa a ejecutar EXEC_INFO db 22 DUP (0) MSG db 0dh,0ah db "La Colmena CGA/EGA/Hercules/VGA-16 crack.",0dh,0ah db "2019, vlan7",0dh,0ah,"$" OVL ends end STARTFeliz reversing.
[1] La Colmena CGA/EGA/Hercules/VGA-16 https://www.mediafire.com/file/036ztp6dxslstsl/COLMENA.7z/file
[2] DOSBox Debugger https://www.vogons.org/viewtopic.php?t=7323
[3] KGB https://www.mediafire.com/file/wbwtd69s3txcbt2/kgb.zip/file
[4] Ralf Brown's Interrupt List http://www.delorie.com/djgpp/doc/rbinter/ix/
[5] HelpPC Interrupt List http://stanislavs.org/helppc/idx_interrupt.html
[6] IDA 5.0 free https://www.scummvm.org/frs/extras/IDA/idafree50.exe
[7] A86 http://www.eji.com/a86/
[+] COLME.ASM http://zen7.vlan7.org/file-cabinet/COLME.ASM
0 comentarios :
Publicar un comentario