Comenzamos. En el cargador de la primera parte, para crackear La Colmena, parcheabamos en memoria asi:
mov Word Ptr [0097], 9090h ; CRACK ~ escribimos los bytes 90 90 en DS:[0097]En este caso es correcto, ya que cuando la instruccion MOV se ejecuta, los bytes que queremos parchear estan en DS:[0097], pero normalmente no tenemos el valor del segmento en ningun registro, en la practica la situacion es que las instrucciones a sobreescribir residen en un segmento distinto en cada ejecucion, y tendremos que buscar en la pila en que posicion se ha pusheado el segmento, o a malas siempre podriamos averiguarlo por fuerza bruta.
Vamos a poner un ejemplo, otro juego para el que que yo sepa nunca se ha publicado crack: GoGo Our Star [1], un juego coreano de naves de 1993 donde la pantalla de claves aparece al finalizar el primer nivel. Esta vez me voy a ahorrar el proceso y voy a poner un par de cargadores directamente. Primero por fuerza bruta, y despues intentaremos hacerlo mas limpio.
Fuerza bruta pues. Vemos que el desplazamiento siempre es el mismo, asi que haremos fuerza bruta sobre el segmento, recorriendo todos los segmentos posibles desde [0001] hasta [FFFD] y buscando en ellos los bytes que queremos parchear. Si los encontramos, entonces parcheamos. La interrupcion que capturamos es la 10h (Video).
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 OLDINT10 dw 0,0 ; Espacio para el puntero a la INT_10h del sistema NEWINT proc far cmp ax, 0013h ; Se ha llamado a INT 10h con AX=0013 ? jnz EXIT ; NO: saltamos a la INT 10h del sistema push es ; Guardamos ES push ax ; Guardamos AX xor ax, ax ; AX=0 ; fuerza bruta ; buscamos en todos los segmentos [0001]..[FFFD] los bytes que queremos parchear INICIO: inc ax cmp ax, 0fffe ; Hemos llegado al ultimo segmento? je NOTRIGHT ; SI: nos vamos sin parchear mov es, ax ; ES=segmento a comprobar cmp Word Ptr es:[032b], 0374h ; ¿ES:[032b] = 74 03? jne INICIO ; NO: vamos a comprobar el segmento siguiente cmp Word Ptr es:[034a], 3675h ; ¿ES:[034a] = 75 36? jne INICIO ; NO: vamos a comprobar el segmento siguiente cmp Word Ptr es:[0366], 1a75h ; ¿ES:[0366] = 75 1a? jne INICIO ; NO: vamos a comprobar el segmento siguiente ; Hemos encontrado el segmento! :) Vamos a parchear ; ES:[032b] = JMP SHORT mov Word Ptr es:[032b], 03ebh ; Crack ~ escribimos los bytes EB 03 en es:[032b] ; ES:[034a] = NOP NOP mov Word Ptr es:[034a], 9090h ; Crack ~ escribimos los bytes 90 90 en es:[034a] ; ES:[0366] = NOP NOP mov Word Ptr es:[0366], 9090h ; Crack ~ escribimos los bytes 90 90 en es:[0366] NOTRIGHT: pop ax ; Restauramos ax pop es ; Restauramos es EXIT: jmp DWord Ptr cs:[OLDINT] ; Saltamos a la INT 10h del sistema NEWINT 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, 3510h ; Queremos el valor del puntero a la INT 10h del sistema int 21h mov OLDINT[0], bx ; Guardamos puntero a la INT 10h del sistema mov OLDINT[2], es ; (necesitaremos llamarla despues) mov ax, 2510h ; Sobreescribimos en el vector de interrupciones el puntero a INT 10h por lea dx, NEWINT ; un puntero a nuestra rutina NEWINT proc far int 21h push cs pop es ; Necesitaremos ES:BX apuntando a EXEC_INFO 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, OLDINT[0] mov ds, OLDINT[2] mov ax, 2516h ; Restauramos la INT_10h del sistema int 21h push cs pop ds ; DS = CS mov ax, 4c00h ; terminar int 21h FILENAME db "OURSTAR.EXE",0 ; programa a ejecutar EXEC_INFO db 22 DUP (0) MSG db 0dh,0ah db "Go! Go! OurStar floppy version crack.",0dh,0ah db "2019, vlan7",0dh,0ah,"$" OVL ends end STARTFuncional, pero un poco salvaje ¿no? Podria eliminar algunas direcciones reservadas donde seguro que no van a estar los bytes que queremos modificar, o quizas tener en cuenta el modelo de memoria segmentada del DOS, donde los segmentos se "solapan" parcialmente y hay multiples segmento:desplazamiento diferentes para apuntar al mismo byte. 7c0:0 , 0:7c , 204:5b0 ... todos esos punteros apuntan al mismo byte en memoria. Pero bueno no queria complicarlo, de todas formas seguiria siendo salvaje.
Si queremos hacerlo limpio podemos averiguar el segmento donde queremos parchear sabiendo como funcionan las instrucciones CALL/RET y que ocurre en el prologo/epilogo de una funcion. Sin entrar en detalle, cuando se ejecuta una instruccion (near) CALL se hace un push de IP (en un far CALL -> push de CS e IP), de tal forma que cuando se ejecute RET el flujo de ejecucion pueda continuar en la instruccion siguiente a dicha instruccion CALL. Es un poco enrevesado ponerlo por escrito, cosas del lenguaje, pero la idea es mas simple. En un video se veria mejor, seguro que hay alguno en internet que lo explica de forma simple.
Bien, los bytes que queremos parchear estan en una direccion de memoria [segmento]:[desplazamiento]. El desplazamiento ya lo hemos averiguado con el debugger. Para averiguar el segmento vamos a bucear en la pila.
Hacemos que BP apunte a la cima de la pila (SP) y al sumar cierto valor a BP estaremos apuntando algunos CALLs hacia atras. Dicho de otro modo, para algun valor de x, SS:[BP+x] apunta al segmento que buscamos, el segmento donde estan los bytes a parchear. Para averiguar x, bien, ver comentarios en el codigo.
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 OLDINT10 dw 0,0 ; Espacio para el puntero a la INT_10h del sistema NEWINT10 proc far cmp ax, 0013h ; Nos llaman para INT_10h con AX=0013 ? jnz EXIT ; NO: saltamos a la INT_10h del sistema ;; Necesitamos el segmento donde estan los bytes que queremos parchear push bp ; Guardamos BP mov bp, sp ; BP apunta a la cima de la pila push es ; Guardamos ES push ax ; Guardamos AX ; En este punto, podemos cambiar la vista de datos a SS:BP ; (DOSBox debugger -> "d ss:bp") ; De todos esos valores, de dos en dos bytes, uno es el segmento que buscamos. ; Y no deberia estar muy lejos desde SS:BP ; Pero quizas sea mas rapido hacer una lista de instrucciones tal que asi: ; mov ax,[bp] mov ax,[bp+2h] mov ax,[bp+4h] mov ax,[bp+6h] ... ; Con el debugger ejecutaremos instruccion por instruccion viendo que valor va ; tomando AX. ; Pongamos que estamos buscando el segmento donde estan los bytes 74 03. ; Pongamos que ejecutamos en el debugger mov ax,[bp+12h]. Ya conocemos el ; desplazamiento, digamos que es 032b, entonces iriamos a AX:32b (DOSBox ; debugger -> d ax:32b) y si vemos 7403 enhorabuena, hemos encontrado el ; segmento en la pila, podemos añadir al codigo la instruccion mov ax,[bp+12h] mov ax, [bp+12h] ; SS:[bp+12h] son unos cuantos CALLs hacia atras ; y ahi tenemos el segmento donde estan los bytes a parchear mov es, ax ; lo movemos a ES ;; ;; 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_10/13, no queremos volver a hacerlo. ; Ojo!! x86 es little endian asi que los bytes van "al reves" cmp Word Ptr es:[032b], 0374h ; ¿ES:[032b] = 74 03? (JE ($+3)) jne YAPARCHEADO ; NO: nos vamos sin parchear cmp Word Ptr es:[034a], 3675h ; ¿ES:[034a] = 75 36? (JNE ($+36)) jne YAPARCHEADO ; NO: nos vamos sin parchear cmp Word Ptr es:[0366], 1a75h ; ¿ES:[0366] = 75 1a? (JNE ($+1a)) jne YAPARCHEADO ; NO: nos vamos sin parchear ;; ;; Si estamos aqui es que aun no hemos parcheado. Vamos a parchear en memoria :) ; ES:[032b] = JMP SHORT mov Byte Ptr es:[032b], 0ebh ; Crack ~ escribimos el byte EB en ES:[032b] ; ES:[034a] = NOP NOP mov Word Ptr es:[034a], 9090h ; Crack ~ escribimos 90 90 en ES:[034a] ; ES:[0366] = NOP NOP mov Word Ptr es:[0366], 9090h ; Crack ~ escribimos 90 90 en ES:[0366] ;; YAPARCHEADO: pop ax ; Restauramos AX pop es ; Restauramos ES pop bp ; Restauramos BP EXIT: jmp DWord Ptr cs:[OLDINT10] ; Saltamos a la INT_10h del sistema NEWINT10 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, 3510h ; Queremos el puntero a la INT_10h del sistema int 21h mov OLDINT10[0], bx ; Guardamos puntero a la INT_10h del sistema mov OLDINT10[2], es ; (necesitaremos llamarla despues) push cs pop es ; Necesitaremos ES:BX apuntando a EXEC_INFO mov ax, 2510h ; Sobreescribimos en el vector de interrupciones el ; puntero a INT_10h por lea dx, NEWINT10 ; un puntero a nuestra rutina NEWINT10 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, OLDINT10[0] mov ds, OLDINT10[2] mov ax, 2516h ; Restauramos la INT_10h del sistema int 21h push cs pop ds ; DS = CS mov ax, 4c00h ; terminar int 21h FILENAME db "OURSTAR.EXE",0 ; programa a ejecutar EXEC_INFO db 22 DUP (0) MSG db 0dh,0ah db "Go! Go! OurStar floppy version crack.",0dh,0ah db "2019, vlan7",0dh,0ah,"$" OVL ends end STARTPara ensamblar [2]:
a86 CRACK.ASMFeliz reversing.
[1] GoGo Our Star https://www.mediafire.com/file/bbnak8waayg49d6/gogo.7z/file
[2] A86 http://www.eji.com/a86/
[+] GOGO.ASM http://zen7.vlan7.org/file-cabinet/GOGO.ASM
0 comentarios :
Publicar un comentario