Curiosity is insubordination in its purest form. -Vladimir Nabokov

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, o ser un mensaje que devuelve el S.O., pero por algun sitio 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 como vimos en sub_31B9 espera un valor diferente al que le comunica DOSBox, con lo cual acaba saliendo al DOS 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 anular 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

Related Posts by Categories



0 comentarios :