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)

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 Byte Ptr [0097], 90h   ; Crack: escribimos el byte 90 en [0097]
mov Byte Ptr [0098], 90h   ; Crack: escribimos el byte 90 en [0098]
¿Y que segmento es ese? Pues resulta que si no ponemos nada el ensamblador asume que estamos escribiendo en DS. Pero queremos sobreescribir codigo, entonces... ¿no deberiamos estar escribiendo en CS? Pues si, pero resulta que en ese punto DS=CS (podeis ver el valor de DS y CS con el debugger), con lo cual acabamos escribiendo en CS:[0097] y CS:[0098]

Bien, ¿y si resulta que DS no es igual a CS? ¿como sabemos el valor del segmento donde queremos escribir? Pues 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.

Aqui el segmento cambia a cada ejecucion. Esta vez me voy a ahorrar el proceso y voy a poner un par de cargadores directamente.

En el primero averiguamos el segmento por fuerza bruta, recorriendo todos los segmentos posibles desde [0001] hasta [FFFD] y buscando en ellos los bytes que queremos parchear, ya que el desplazamiento no cambia. Cuando los encontramos parcheamos. La interrupcion que capturamos es la 10h.
SIZE EQU 1024       ; este programa y su pila caben en 1 KB
OVERLAY segment para 'code'
        assume cs:OVERLAY, ds:OVERLAY
        org 100h    ; Esto sera un programa .COM

START: jmp INITCODE     ; Codigo de inicializacion

OLDINT dw 0,0           ; Espacio para el puntero a la INT 10h del sistema

NEWINT proc far
    pushf           ; Guardamos flags
    cmp ax, 0013h   ; Se ha llamado a INT 10h con AX=0013 ?
    jnz EXIT        ; NO: saltamos a la antigua INT 10h

    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
    mov Byte Ptr es:[032b], 0ebh    ; Crack: escribimos el byte EB en es:[032b]
    mov Byte Ptr es:[032c], 03h     ; Crack: escribimos el byte 03 en es:[032c]

; ES:[034a] = NOP NOP
    mov Byte Ptr es:[034a], 90h     ; Crack: escribimos el byte 90 en es:[034a]
    mov Byte Ptr es:[034b], 90h     ; Crack: escribimos el byte 90 en es:[034b]

; ES:[0366] = NOP NOP
    mov Byte Ptr es:[0366], 90h     ; Crack: escribimos el byte 90 en es:[0366]
    mov Byte Ptr es:[0367], 90h     ; Crack: escribimos el byte 90 en es:[0367]

notright:
    pop ax      ; Restauramos ax
    pop es      ; Restauramos es
EXIT:
    popf                        ; Restauramos flags
    jmp DWord Ptr cs:[OLDINT]   ; Saltamos a la antigua INT 10h
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

    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
    mov ax, 4b00h
    int 21h             ; cargar y ejecutar programa

    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,"$"

OVERLAY ends

end START
Funcional, pero un poco salvaje ¿no? Podria eliminar algunos segmentos reservados donde seguro 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.
SIZE EQU 1024       ; este programa y su pila caben en 1 KB
OVERLAY segment para 'code'
        assume cs:OVERLAY, ds:OVERLAY
        org 100h    ; Esto sera un programa .COM

START: jmp INITCODE     ; Codigo de inicializacion

OLDINT dw 0,0           ; Espacio para el puntero a la INT 10h del sistema

NEWINT proc far
    pushf           ; Guardamos flags
    cmp ax, 0013h   ; Se ha llamado a INT 10h con AX=0013 ?
    jnz EXIT        ; NO: saltamos a la antigua INT 10h

    push bp         ; Guardamos bp
    mov bp, sp      ; bp apunta a la cima de la pila

    push es         ; Guardamos ES
    push ax         ; Guardamos AX

    mov ax, [bp+14h]    ; [bp+14h] son unos cuantos CALLs hacia atras
                        ; y ahi tenemos el segmento donde estan los bytes a parchear
    mov es, ax      ; lo movemos a ES
    cmp Word Ptr es:[032b], 0374h   ; ¿es:[032b] = 74 03?
    jne NOTRIGHT                    ; NO: nos vamos sin parchear
    cmp Word Ptr es:[034a], 3675h   ; ¿es:[034a] = 75 36?
    jne NOTRIGHT                    ; NO: nos vamos sin parchear
    cmp Word Ptr es:[0366], 1a75h   ; ¿es:[0366] = 75 1a?
    jne NOTRIGHT                    ; NO: nos vamos sin parchear

; Hemos encontrado el segmento! :) Vamos a parchear
; ES:[032b] = JMP
    mov Byte Ptr es:[032b], 0ebh    ; Crack: escribimos el byte EB en es:[032b]
    mov Byte Ptr es:[032c], 03h     ; Crack: escribimos el byte 03 en es:[032c]

; ES:[034a] = NOP NOP
    mov Byte Ptr es:[034a], 90h     ; Crack: escribimos el byte 90 en es:[034a]
    mov Byte Ptr es:[034b], 90h     ; Crack: escribimos el byte 90 en es:[034b]

; ES:[0366] = NOP NOP
    mov Byte Ptr es:[0366], 90h     ; Crack: escribimos el byte 90 en es:[0366]
    mov Byte Ptr es:[0367], 90h     ; Crack: escribimos el byte 90 en es:[0367]

NOTRIGHT:
    pop ax      ; Restauramos ax
    pop es      ; Restauramos es
    pop bp      ; Restauramos bp
EXIT:
    popf                        ; Restauramos flags
    jmp DWord Ptr cs:[OLDINT]   ; Saltamos a la antigua INT 10h
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

    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
    mov ax, 4b00h
    int 21h             ; cargar y ejecutar programa

    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,"$"

OVERLAY ends

end START
Para ensamblar:
a86 CRACK.ASM
Feliz reversing.

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

Related Posts by Categories



0 comentarios :