Curiosity is insubordination in its purest form. -Vladimir Nabokov

miércoles, 3 de abril de 2019

Haciendo un cargador (loader) para crackear un programa MS-DOS en ejecucion (II)

NOTA: Esta entrada es un addendum a la primera parte. Si lo que buscas es un tutorial mas o menos detallado te recomiendo que le eches un vistazo a la primera parte.

Comenzamos. En el cargador de la primera parte, en el crack de La Colmena, cuando escribiamos los bytes en memoria no especificabamos el segmento donde queriamos escribir. Teniamos:
mov Word Ptr [0097], 9090h  ; CRACK ~ escribimos los bytes 90 90 en [0097]
¿Y que segmento es ese? Pues resulta que si no lo especificamos el ensamblador asume que estamos escribiendo en DS. En este caso los bytes que queremos parchear estan en DS:[0097], pero normalmente no es asi 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.

En este caso las instrucciones a sobreescribir residen en un segmento distinto en cada ejecucion, aunque podemos observar que el desplazamiento siempre es el mismo, asi que averiguaremos el valor del segmento por fuerza bruta, recorriendo todos los segmentos posibles desde [0001] hasta [FFFD] y buscando en ellos los bytes que queremos parchear. Cuando los encontramos 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 START
Funcional, pero un poco salvaje ¿no? Podria eliminar algunos segmentos reservados donde seguro que no van a estar los bytes que queremos modificar, pero bueno no queria complicarlo.

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, call hace un push de CS e IP, de tal forma que cuando se ejecute RET podamos volver a la instruccion siguiente al CALL del que venimos.

Tras hacer backup de registros hacemos que el frame pointer (BP) apunte a la cima de la pila (SP) y le sumamos cierto valor con lo cual estamos apuntando algunos CALLs hacia atras y ahi tenemos el segmento donde estan los bytes a parchear. Para averiguar este valor, 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] y AX=03C0. Ya conocemos
; el desplazamiento, digamos que es 032b, entonces iriamos a 3c0:32b (DOSBox
; debugger -> d ax:32b) y si vemos 7403 enhorabuena, hemos encontrado el
; segmento en la pila, podemos añadir la linea de codigo mov ax,[bp+12h]

    mov ax, [bp+12h]    ; [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 START
Para ensamblar [2]:
a86 CRACK.ASM
Feliz 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

Related Posts by Categories



0 comentarios :