Curiosity is insubordination in its purest form. -Vladimir Nabokov

sábado, 16 de febrero de 2019

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

Y con esta ya son tres las entradas que llevamos de oldschool cracking. En esta ocasion el candidato sera un viejo (1992) juego español: La Colmena version CGA/EGA/Hercules/VGA-16 [1], de la desaparecida compañia Opera.

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:0000015C B401      mov  ah,01            ; ¿Se ha pulsado alguna tecla?
0308:0000015E CD16      int  16
0308:00000160 7503      jne  00000165 ($+3)
0308:00000162 33C0      xor  ax,ax            ; NO: limpiamos ax
0308:00000164 C3        ret                   ;     y volvemos a empezar
0308:00000165 B80000    mov  ax,0000          ; SI: Obtenemos codigo tecla pulsada
0308:00000168 CD16      int  16               ; Devuelve: AH = BIOS scan code
                                              ; Devuelve: AL = ASCII character
BP 0308:0000016A C3     ret
Es la parte encargada de esperar la pulsacion de alguna tecla. Ponemos un breakpoint en el segundo ret
BP 0308:016A
volvemos a ejecutar con F5 y no tardamos en llegar a la parte donde se comprueba que tecla ha sido pulsada.
(...)
0308:000000E8 3D0D1C    cmp  ax,1C0D          ; Se ha pulsado Intro?
0308:000000EB 7433      je   00000120 ($+33)  ; SI: saltamos a gestionarlo
0308:000000ED E9E1FF    jmp  000000D1 ($-1f)  ; NO: vuelta a empezar
Ponemos un bp en la direccion de memoria a la que saltamos cuando se ha pulsado intro.
BP 0308:0120  ; Hemos pulsado intro
y 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:00000043 A07B07        mov  al,[077B]            ; Numero de caracteres introducidos
0308:00000046 3C04          cmp  al,04                ; Se han introducido los 4 caracteres de la clave?
0308:00000048 75F3          jne  0000003D ($-d)       ; NO -> otra vuelta al bucle
BP 0308:0000004A E83D00     call 0000008A ($+3d)   ; SI -> comprobamos la clave
0308:0000004D 75EE          jne  0000003D ($-12)      ; JNE = CHICO_MALO. **OPCION 1 DE CRACK->SM 0308:0000004D 90 90 NOP NOP
0308:0000004F E8DE02        call 00000330 ($+2de)     ; Camino CHICO_BUENO
0308:00000052 CD12          int  12
Entramos 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:0000008A BE7C07    mov  si,077C          ; Puntero a clave introducida por usuario ; **OPCION 5 de CRACK ->SM 0308:008B 80 mov si,0780
0308:0000008D BF8007    mov  di,0780          ; Puntero a clave buena ; **OPCION 6 de CRACK -> SM 0308:008E 7C mov di,077C
0308:00000090 B90400    mov  cx,0004          ; Numero de vueltas al bucle
0308:00000093 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:00000095 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:00000097 750D      jne  000000A6 ($+d)   ; JNE = CHICO_MALO ; **OPCION 2 DE CRACK->SM 0308:0097 90 90
0308:00000099 47        inc  di               ; Siguiente caracter de clave buena
0308:0000009A 46        inc  si               ; Siguiente caracter de clave introducida por usuario
0308:0000009B E2F6      loop 00000093 ($-a)   ; Decrementamos CX en 1, y si CX aun no es 0 -> Saltamos al principio del bucle
0308:0000009D B90600    mov  cx,0006          ; Camino CHICO_BUENO
0308:000000A0 E82400    call 000000C7 ($+24)
0308:000000A3 32C0      xor  al,al            ; Ponemos al=0
0308:000000A5 C3        ret                   ; Retornamos a 0308:0000004D 75EE jne 0000003D con codigo de retorno AL
                                              ; Como el codigo de retorno es 0 seguiremos en CHICO_BUENO
Podemos 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 ambas cadenas coinciden.

Yo voy a elegir la opcion 2, es decir, anular este salto:
0308:00000097 750D      jne  000000A6 ($+d)
cambiandolo por dos NOP:
0308:00000097 9090      nop nop
con 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 90
Continuamos 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:\> COLMENA
Llegamos hasta la pantalla de claves, miramos el contenido de C:\HISTORY.DAT y tenemos:
Exec        : colmena.EXE 
OpenFile-R  : col0.ovl
Vemos 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]. Hint: INT 21h es muy importante.

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 4b
Ejecutamos 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\00
Bien, 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 el punto exacto de 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.

Bien, 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:0000018B CB     retf
Si ponemos un BP y lo ejecutamos veremos que la instruccion siguiente ya se encuentra en otro segmento distinto. Es la primera instruccion del subprograma de claves:
0308:00000000 2EC6064C0600      mov  byte cs:[064C],00
Justo cuando estamos en esa instruccion, sin llegar a ejecutarla volcamos los opcodes desde ese punto asi:
MEMDUMPBIN CS:IP 50000
Y se habra creado un archivo binario llamado MEMDUMP.BIN de 50000 bytes. Habra muchos 0's al final, podriamos quitarlos pero no importa, 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:00000097 750D      jne  000000A6
por esto:
0308:00000097 90      nop
0308:00000098 90      nop
siempre 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.
OVERLAY segment para 'code'
        assume cs:OVERLAY, ds:OVERLAY
        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
    pushf               ; Guardamos flags. Si modificaramos algun registro habria que guardarlo tambien.
    cmp ah, 1           ; Se ha llamado a INT 16h con la funcion AH=1 ?
    jnz EXIT            ; NO: saltamos a la antigua INT 16h

    cmp Word Ptr [0097], 0d75h  ; ¿Tenemos en [0097] los opcodes 75 0D? Ojo!! x86 es little endian, asi que van "al reves" 
    jnz EXIT            ; NO: saltamos a la antigua INT 16h

    mov Byte Ptr [0097], 90h   ; Crack: escribimos el byte 90 en [0097]
    mov Byte Ptr [0098], 90h   ; Crack: escribimos el byte 90 en [0098]

EXIT:
    popf                ; Restauramos flags
    jmp DWord Ptr cs:[OLDINT]     ; Saltamos a la antigua INT 16h
NEWINT endp

FINAL equ $
; El codigo residente en memoria acaba aqui

INITCODE:
    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 ax, 3100h           ; Terminate and Stay Resident (TSR)
    lea dx, FINAL           ; Guardamos offset de FINAL en DX
    int 21h

OVERLAY ends

end START
Guardamos como CRACK.ASM y lo ensamblamos. Usare A86 [7]
a86 CRACK.ASM
Y tendremos un ejecutable CRACK.COM de tamaño 66 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 habra una nueva INT 16h en el sistema 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.

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
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 16h del sistema

NEWINT proc far
    pushf               ; Guardamos flags. Si modificaramos algun registro habria que guardarlo tambien.
    cmp ah, 1           ; Se ha llamado a INT 16h con la funcion AH=1 ?
    jnz EXIT            ; NO: saltamos a la antigua INT 16h

    cmp Word Ptr [0097], 0d75h  ; ¿Tenemos en [0097] los opcodes 75 0D? Ojo!! x86 es little endian, asi que van "al reves" 
    jnz EXIT            ; NO: saltamos a la antigua INT 16h

    mov Byte Ptr [0097], 90h   ; Crack: escribimos el byte 90 en [0097]
    mov Byte Ptr [0098], 90h   ; Crack: escribimos el byte 90 en [0098]

EXIT:
    popf                ; Restauramos flags
    jmp DWord Ptr cs:[OLDINT]     ; Saltamos a la antigua INT 16h
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, 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

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

OVERLAY ends

end START
Feliz 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/

sábado, 19 de enero de 2019

Cracking de un juego MS-DOS usando DOSBox debugger (analisis dinamico)

El candidato es Megatraveller 2 [1], un viejo (1991) juego msdos de rol en su version en castellano con una proteccion bastante comun en su epoca: al iniciar el juego se nos pregunta por algo cuya respuesta viene en el manual. Si la respuesta es correcta jugamos, de lo contrario el juego termina y volvemos al DOS.

Para no repetirnos, a diferencia de la entrada anterior donde usamos IDA para hacer un analisis estatico, en esta ocasion usaremos el debugger de DOSBox para hacer un analisis dinamico, es decir, ejecutaremos el juego paso a paso, pondremos breakpoints hasta localizar la parte del codigo donde esta la proteccion y la anularemos modificando el codigo en memoria en caliente. Todo mientras vemos en la pantalla de DOSBox que es lo que va mostrando el juego por pantalla, lo cual es enormemente util. Finalmente haremos el crack permanente modificando el archivo responsable de la proteccion con un editor hexadecimal.

Lo primero necesitamos compilar una version de DOSBox con el debugger activado, ya que por defecto no lo esta. Para windows hay precompilados en Vogons [2]. Para compilar en Linux:
./autogen.sh && ./configure --enable-debug=heavy && make
Aunque yo he utilizado dosbox-x [3], que he compilado de una forma un poco diferente ejecutando ./build-debug que ya incluye --enable-debug=heavy

Bien, cargamos el juego:
user@host:~/crack/MT2$ ~/emus/dosbox-x-0.82.14/src/dosbox-x -c "keyb sp" -c "mount c ." -c "c:" -c "MT2.COM"
Avanzamos hasta que nos pida las claves y activamos el modo debugger con ALT+PAUSA. Veremos algo parecido a:

Si tecleamos HELP en la ventana Output veremos una ayuda de los comandos aceptados por el debugger. Para este crack usaremos los siguientes:
F5                        - Run.
F10/F11                   - Step over / trace into instruction.
BP [segment]:[offset]     - Set breakpoint.
SM [seg]:[off] [val] [.]..- Set memory with following values.
Atencion: segun las asociaciones de teclas que tengas en tu programa emulador de terminal puede que necesites redefinir alguna tecla de funcion, ya que tendra preferencia sobre el debugger de DOSBox.

Si miramos el titulo de la ventana de DOSBox veremos que se esta ejecutando CHARGEN. DOSBox no muestra la extension del archivo en el titulo pero CHARGEN.DAT tiene todas las papeletas de ser realmente un ejecutable de DOS. Si miramos con file:
user@host:~/crack/MT2$ file CHARGEN.DAT
CHARGEN.DAT: MS-DOS executable, MZ for MS-DOS Self-extracting PKZIP archive
user@host:~/crack/MT2$
Efectivamente, es un ejecutable de MS-DOS, concretamente un .EXE (los magic-bytes MZ lo delatan) empaquetado con PKZIP. Pues antes de nada vamos a desempaquetarlo. Usare UNP [4].

Un empaquetado conocido es un desempaquetado instantaneo. Volvemos a cargar el juego con MT2.COM, pulsamos ALT+PAUSA y para llegar al punto donde se comprueban las claves, iremos pulsando F10, prestando atencion a los CALL o los saltos Jxx. Si queremos entrar en alguna funcion llamada por CALL pulsaremos F11. Si vemos que pulsando repetidamente F10 entramos en un bucle podemos fijarnos en los saltos y ver si hay algun camino que no estamos siguiendo. Pondremos breakpoints en esos caminos y haremos F5, veremos si podemos teclear, si el codigo se para seguiremos con F10/F11.

Bien, despues de poner breakpoints, ejecutar, breakpoints, ejecutar, paciencia con breakpoints cada vez mas avanzados en el flujo de ejecucion llegamos a ver la rutina de comprobacion de claves. Puede que acabe haciendo un video con todo el proceso desde inicio a final, pero mientras sugiero los siguientes breakpoints.
BP 0B74:0000506E -> Se activa cuando se introduce la clave y se pulsa intro.
BP 0B74:0000BB98 -> Comprobacion claves.
BP 0B74:0000506E -> Clave correcta, se carga INTRO.DAT y empieza el juego.
Una vez introducidos en la ventana Output del debugger pulsamos F5 para ejecutar el juego, avanzamos hasta que nos pida las claves y justo cuando introducimos una clave y pulsamos intro vemos que la ejecucion se pausa, es el primer breakpoint:
      0B74:00005064 E8BDD4    call 00002524 ($-2b43)
      0B74:00005067 8BF0      mov  si,ax
      0B74:00005069 83FE0D    cmp  si,000D                         ; SE HA PULSADO INTRO? (EL CODIGO ASCII DE INTRO ES 0D)
      0B74:0000506C 7503      jne  00005071 ($+3)     (no jmp)     ; NO
BP    0B74:0000506E E94102    jmp  000052B2 ($+241)   (down)       ; SI
Volvemos a pulsar F5 y el segundo breakpoint nos lleva a:
      (...)
      0B74:0000BB8B E8321A    call 0000D5C0 ($+1a32)
      0B74:0000BB8E 83C402    add  sp,0002
      0B74:0000BB91 8BC8      mov  cx,ax
      0B74:0000BB93 8BC7      mov  ax,di
      0B74:0000BB95 8BDA      mov  bx,dx
      0B74:0000BB97 99        cwd
BP    0B74:0000BB98 3BC8      cmp  cx,ax                           ; ¿CLAVE CORRECTA?
      0B74:0000BB9A 7504      jne  0000BBA0 ($+4)     (jmp)        ; CHICO_MALO
      0B74:0000BB9C 3BDA      cmp  bx,dx                           ; CHICO_BUENO
      0B74:0000BB9E 7411      je   0000BBB1 ($+11)    (down)       ; CHICO_BUENO
Es la parte de codigo donde se comprueban las claves. Nos encontramos en BP donde se compara la clave introducida con la clave que el juego espera. La linea siguiente "jne 0000BBA0" es el primer chequeo de claves. JNE es chico_malo. Lo anulamos sustituyendo JNE por NOP NOP, es decir, 75 04 por 90 90. El debugger de DOSBox nos permite modificar el codigo en memoria en caliente ya que si puedes cambiar la memoria puedes cambiar el codigo en ejecucion, recuerda: todo lo que se esta ejecutando esta en memoria. Asi:
SM 0B74:BB9A 90 90
y veremos que automaticamente el codigo cambia a:
BP    0B74:0000BB98 3BC8      cmp  cx,ax                           ; ¿CLAVE CORRECTA?
      0B74:0000BB9A 90        nop
      0B74:0000BB9B 90        nop
      0B74:0000BB9C 3BDA      cmp  bx,dx                           ; CHICO_BUENO
      0B74:0000BB9E 7411      je   0000BBB1 ($+11)    (down)       ; CHICO_BUENO
Despues de los NOP viene un segundo chequeo de claves. JE es chico_bueno. Para una clave corta se cumple el JE y no seria necesario cambiarlo, pero cuando el usuario introduce una clave con toda la longitud, por ejemplo 555555555555555555 _NO_ se cumple el JE (no saltamos) y el programa considera incorrecta la clave introducida. Como el camino chico_bueno es saltar cambiamos JE por JMP (74 11 por EB 11) y asi saltamos siempre independientemente de la longitud de la clave que introduzcamos. Lo modificamos con otro SM:
SM 0B74:BB9E EB 11
Y ahora tenemos:
BP    0B74:0000BB98 3BC8      cmp  cx,ax                           ; ¿CLAVE CORRECTA?
      0B74:0000BB9A 90        nop
      0B74:0000BB9B 90        nop
      0B74:0000BB9C 3BDA      cmp  bx,dx                           ; CHICO_BUENO
      0B74:0000BB9E EB11      jmp  0000BBB1 ($+11)    (down)       ; CHICO_BUENO
En este punto ya esta crackeado, no importara la clave que introduzcamos, todas seran validas. Volvemos a pulsar F5 (Run) y el programa se parara en el tercer y ultimo breakpoint que pusimos:
      (...)
BP    0B74:00000CEB E85EC0    call FFFFCD4C ($-3fa2)               ; CARGAMOS INTRO.DAT Y EMPIEZA EL JUEGO
Este BP no es necesario para el crackeo pero sirve para mostrar cual es el CALL que carga INTRO.DAT. Es decir que CHARGEN.DAT ha tomado la clave como valida y empieza el juego en si.

Para hacer el crack permanente editamos el archivo CHARGEN.DAT con un editor hexadecimal, buscamos una ristra de bytes que contenga los bytes que queremos cambiar y nos aseguramos de que sea unica (si no buscariamos con una cadena mas larga) y lo cambiamos por los nuevos opcodes.
hexedit CHARGEN.DAT (Ojo! primero hay que desempaquetarlo) y sustituir
3B C8 75 04 3B DA 74 11
por
3B C8 90 90 3B DA EB 11
y guardar cambios.
Feliz reversing.

[1] Megatraveller 2 https://www.mediafire.com/file/id5q6tsgfve6ea5/MT2.7z/file
[2] DOSBox Debugger https://www.vogons.org/viewtopic.php?t=7323
[3] DOSBox-xhttps://github.com/joncampbell123/dosbox-x/releases
[4] UNP http://unp.bencastricum.nl

jueves, 20 de diciembre de 2018

Reversing de un juego MS-DOS antiguo que no carga en DOSBox (analisis estatico)

Disclaimer: Este articulo es un crack basico de un ejecutable DOS practicamente paso a paso, asi que si sabes de esto no vas a aprender nada, si no sigue leyendo.

Bien, hice este parche para una gente que queria jugar a este juego y es un ejemplo muy simple de un cracking en la practica: un viejo (1997) juego argentino para msdos llamado Sauro [1] que no arranca en DOSBox. Al cargar SAURO.EXE en DOSBox el programa nos devuelve al DOS con el mensaje "Atencion el sistema necesita una placa VGA valida, instale una. GRACIAS por usar este producto RAL SOFT".

Comenzamos cambiando en la configuracion de DOSBox la tarjeta grafica que DOSBox presenta al juego y seguimos con el mismo problema, asi que decidimos acudir al debugger.

El ejecutable SAURO.EXE es un ejecutable de MSDOS, por lo que necesitamos un debugger que pueda leer binarios de DOS. Yo he usado la version free de IDA 5.0 [2], ya que versiones free posteriores eliminaron soporte DOS. Manos a la obra.

Arranco IDA y cargo SAURO.EXE indicando que es un EXE de DOS. Espero unos segundos a que se complete el analisis y como en este caso el codigo no esta "packed" ni tiene protecciones antidebug ni esta ofuscado ni nada raro, en cuanto IDA termina el analisis tengo ante mi el codigo en ensamblador de SAURO.EXE

Es muy largo por lo que necesito acotarlo a la seccion que me interesa. Bien, sabemos que al ejecutar SAURO.EXE el programa finaliza y nos devuelve al DOS con el mensaje "Atencion el sistema necesita una placa VGA compatible". Esa cadena esta en algun lado, podria estar en texto claro u ofuscada o no estar en SAURO.EXE sino en algun archivo que lee SAURO.EXE pero por aqui tenemos que empezar, la buscamos en IDA y nos encontramos con el siguiente codigo ensamblador:
loc_31DD:
push    ds
push    offset aAtencionElSist ; "\nAtencion el sistema necesita una placa"...
call    sub_1BD5
pop     cx
pop     cx
push    ds
push    offset aGraciasPorUsar ; "GRACIAS por usar este producto RAL SOFT"...
call    sub_1BD5
pop     cx
pop     cx
push    0
call    sub_261
pop     cx
loc_31DD: es una etiqueta puesta por IDA porque en algun momento del codigo el flujo de ejecucion nos lleva hasta ahi. Bien, pedimos a IDA que nos muestre desde donde se llama a loc_31DD y el debugger nos muestra esto:
sub_31B9 proc far

var_1= byte ptr -1

enter   2, 0
push    14h
call    sub_1AED
pop     cx
mov     [bp+var_1], al
test    [bp+var_1], 30h
jnz     short loc_31DD
Ya viendo esto sabemos que muy probablemente ya lo tenemos y solo necesitariamos invalidar el salto JNZ. Justo antes de llegar al salto condicional JNZ vemos que se compara con un TEST el valor que hay en la direccion de memoria [bp+var_1] con el valor 30 en hexadecimal. Solo si es 30h el TEST devolvera un 0. Luego viene el salto JNZ que dice que si no es 0 (JNZ = Jump if Not Zero) saltamos a loc_31DD, que equivaldria a que el programa no ha detectado una placa VGA valida y finalizaria, que es lo que nos esta pasando.

Si vemos la funcion sub_1AED a la que llama CALL tenemos esto:
; Attributes: bp-based frame

sub_1AED proc far

var_1= byte ptr -1
arg_0= byte ptr  6

enter   2, 0
mov     dx, 70h ; 'p'
mov     al, [bp+arg_0]
out     dx, al          ; CMOS Memory:
                        ; used by real-time clock
inc     dx
in      al, dx          ; CMOS Memory
mov     [bp+var_1], al
mov     al, [bp+var_1]
leave
retf
sub_1AED endp
Esta es la rutina que chequea si tenemos una VGA o compatible y vemos que no comprueba la BIOS sino que usa el CMOS y espera un valor diferente al que le comunica DOSBox, con lo cual finaliza con el mensaje de que no encuentra VGA valida.

Para "crackearlo" tenemos al menos dos opciones. Una seria modificar la funcion sub_1AED para que siempre devolviera el valor 30h, que es el que comprueba el TEST que vimos, pero quizas la solucion mas simple y por tanto la mejor seria invalidar el salto jnz short loc_31DD de tal forma que no saltaramos al codigo que nos muestra por pantalla "no hay vga valida" sino que el programa continuara y pudieramos jugar a SAURO.

Bien, el codigo ensamblador es mas bajo nivel que un lenguaje como C pero aun no es lo que entiende la CPU, la CPU solo entiende de ceros y unos. Y para que los humanos podamos entenderlo un poco mejor representamos los ceros y unos en hexadecimal. Cada instruccion ensamblador se traduce a lo que se conoce como opcodes. Si pedimos a IDA que nos muestre los opcodes al lado de las instrucciones para el salto jnz short y las instrucciones inmediatamente anteriores tenemos:
0BCF call    sub_1AED
0BD4 pop     cx
0BD5 mov     [bp+var_1], al
0BD8 test    [bp+var_1], 30h
0BDC jnz     short loc_31DD
La columna de la izquierda son los opcodes en hexadecimal para cada instruccion (1 opcode=1 byte, 0B es un byte, CF otro byte). El salto JNZ se traduce como los dos bytes en hexadecimal "0B DC". Para invalidar el salto existe una instruccion en ensamblador x86 que es NOP, y simplemente no hace nada, cuando la CPU ejecuta un NOP se consume algun ciclo de cpu pero no hace nada y continua con la siguiente instruccion. El opcode de NOP en arquitectura x86 es 90 en hexadecimal.

Pues manos al crakeo. Si sustituimos el fragmento de codigo anterior por...
0BCF call    sub_1AED
0BD4 pop     cx
0BD5 mov     [bp+var_1], al
0BD8 test    [bp+var_1], 30h
90   nop
90   nop
...cuando el programa haga la comprobacion con TEST no importara el resultado de esa comprobacion porque luego ejecutara los dos NOP que siguen y continuara hacia el flujo de ejecucion de que ha encontrado una placa VGA valida, es decir, veremos el juego en si.

Para parchearlo podemos por ejemplo abrir SAURO.EXE con un editor hexadecimal y buscar la cadena de bytes "0B CF 0B D4 0B D5 0B D8 0B DC", asegurarnos de que sea unica (si no buscariamos con una cadena mas larga) y sustituirla por "0B CF 0B D4 0B D5 0B D8 90 90"

Y ya estaria crackeado. Digo crackeado porque imagina que en vez de comprobar un valor que devuelve la memoria CMOS el programa estuviera comprobando si la clave que has puesto es valida o no. La logica seria la misma o muy similar, invalidar el salto hacia chico_malo para que siempre siguiera el flujo de ejecucion por el camino chico_bueno.

Este era un ejemplo simple: no empaquetado, no ofuscado, no antidebugging, nada, solo invalidar un salto, pero puede complicarse y mucho y ahi pues paciencia y determinacion.

No es necesario conocer todas las instrucciones ensamblador, pero cuanto mas mejor. Saber como el sistema operativo en que nos movemos maneja la memoria, pila, interrupciones, BIOS, acceso a ficheros, etc ayuda, pero lo mas importante es tener claro el flujo de ejecucion del programa. Una vision global y no dejar de aprender.

Feliz reversing.

[1] Sauro https://www.mediafire.com/file/nyvd996o90ty107/SAURO.7z/file
[2] IDA 5.0 free https://www.scummvm.org/frs/extras/IDA/idafree50.exe

jueves, 28 de agosto de 2014

libro hackstory.es ya disponible

Hola a todos, ya esta disponible el libro Hackstory.es La historia nunca contada del underground hacker en la península Ibérica. Mi enhorabuena a la autora y a todos los demas.

sábado, 5 de abril de 2014

Phrack news

Hola a todos,

La ezine mas respetada en este mundo probablemente sea Phrack.

Pues bien, Phrack cambia a mejor el ritmo de publicacion de contenidos, y el cambio comienza con The Fall of Hacker Groups.

domingo, 30 de marzo de 2014

Exploiting para niños [revised]

Hola Exploiters,

Llevo unos dias revisando un documento que llevaba ya demasiado tiempo dormido y este es el resultado.

Se que el enfoque puede parecerle extraño a algunas personas, pero ese es el estilo que he querido darle, y ademas, el titulo del documento me sirve como la excusa perfecta para ponerme a escribir y que mi propia ignorancia no sea un estorbo, sin falsa modestia, todos ignoramos algo.

He intentado que no hubiera cosas incorrectas en el texto. Lo de siempre, cualquier comentario al respecto es bienvenido.

No os cuento nada nuevo si os digo que una vez pasados los 90, el mundo del Exploiting ha estado fuertemente marcado por el eterno juego del gato y el raton y que hasta ahora los Exploiters no han dejado de darle emocion a la carrera y se han mantenido en su sitio siempre desplazando la meta hacia adelante.

Quien sabe si alguien algun dia lograra inmovilizar la meta definitivamente. Por el bien comun de la diversion esperemos que ese dia este muy lejano.

Aunque tal y como estan desarrollandose los acontecimientos en el mundo, cada vez parece menos descabellado pensar que todo va a explotar pronto de todas formas, pero antes de que ocurra vamos a disfrutar, por si acaso, y si al final esto no explota, nos habremos divertido por el camino. Exploiting para niños pues.

sábado, 24 de agosto de 2013

Elevacion de privilegios en Debian y derivadas



More info: http://unaaldia.hispasec.com/2013/08/elevacion-de-privilegios-en-debian-y.html

martes, 6 de agosto de 2013

Linux Heap Exploiting Revisited



Aupa ahi mr heap!

"lo que vamos a suponer es... que ocurre si nosotros pudieramos controlar el contenido del puntero forward y del puntero backward..."

A partir de ahi la creme de la creme amigo... Keep on rockin' y haxta otra :)

domingo, 16 de junio de 2013

Basic UDP session Hijacking with Scapy

Ask him no questions he'll tell you no lies
That's why a rocker never dies
He's the rocker ♪♪
Cockney Rejects - The Rocker