"No creas de nosotros cuentos que otros cuenten." Eskorbuto

miércoles, 3 de abril de 2019

DOS cracking series IV ~ Haciendo un cargador (loader) para crackear un juego DOS en tiempo de ejecucion (II)

NOTA: Esta entrada es un addendum. Si lo que buscas es un tutorial mas o menos detallado de como hacer un cargador/tsr aplicado al cracking... aqui esta la primera parte.

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. Dandole al debugger podemos ver 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 Word Ptr [bx], 0
mov Word 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 START
Funcional, 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 construir en memoria una lista de instrucciones tal que asi:
; mov ax,[bp] ; mov ax,[bp+2h] ; mov ax,[bp+4h] ; mov ax,[bp+6h] ...
; por ejemplo escribiendo opcodes en caliente con el comando SM de debugger

; Y 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 Word Ptr [bx], 0
mov Word 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 START
Para ensamblar [2]:
a86 CRACK.ASM
Feliz reversing.

[1] GoGo Our Star https://www.mediafire.com/file/bbnak8waayg49d6/gogo.7z/file
[2] A86 https://www.eji.com/a86/
[+] GOGO.ASM https://zen7.vlan7.org/file-cabinet/GOGO.ASM

Related Posts by Categories



0 comentarios :

Publicar un comentario

Nota: solo los miembros de este blog pueden publicar comentarios.