Curiosity is insubordination in its purest form. -Vladimir Nabokov

miércoles, 16 de diciembre de 2020

DOS cracking series VI: Programando un TSR para interceptar, en una aventura grafica/conversacional, los comandos que introduce el jugador (y cambiarlos al vuelo)

Les Manley in: Search for the King es una vieja (1990) aventura grafica/conversacional para DOS, con una interfaz similar a las primeras aventuras de Sierra, donde los comandos se introducen por teclado, y hay un parser que los interpreta, y movemos al protagonista por la pantalla con los cursores (o el raton). La aventura fue desarrollada por Accolade, y tiene un pequeño bug en una habitacion, en un hotel, donde hay una cama en la que te puedes sentar.

Si nos sentamos con "SIT BED" y luego nos levantamos con "GET UP", Les se sienta y se levanta.

Pero si nos sentamos con "SIT DOWN" o "SIT", cuando vamos a levantarnos, Les no se levanta, y nos quedamos atascados sin poder mover al personaje.

El bug afecta a todas las versiones que creo que existen: v1.0, v1.1, v2.0

Bueno, pues resulta que estoy programando un TSR para traducir el juego, y tengo una rutina (colgando de int60) que intercepta los comandos del jugador, y sabe en que habitacion estamos. Teniendo esto hecho, la solucion simple es añadir codigo para que, cuando intercepte un "sit" en la habitacion del hotel, lo cambie por "sit bed".

Como primer paso, seria bueno intentar solucionar el bug desde el debugger, y despues añadir el fix al TSR. Ahi va un video dandole al debugger.


Bien, despues de añadir el fix al codigo, y ya que el trabajo esta hecho, he sacado la parte de codigo que corresponde, y he hecho un fix independiente del TSR gordo, y de paso esta entrada que estas leyendo :D Ahi va el listado.
;A86 LESFIX.ASM
;Fix para: Les Manley in: Search for the King
;testeado en: v2.0, v1.1
;vlan7, diciembre 2020

org 100h                                    ;.COM

;****************************************
;*                                      *
;*   C O D I G O    R E S I D E N T E   *
;*                                      *
;****************************************
@@start:    jmp near @@init                 ;saltar a instalacion/old handler
    dw  0                                   ;extra word para far jmp segment

new21   proc    far
;¿es la v1.1 o v2.0 del juego?
;si, parchear en memoria, una instruccion del juego por la instruccion "INT 60"
;no, continuar
    cmp ah,48h                              ;¿allocate memory?
    jne @@start
    push    es
    push word ss:[bp+4]
    pop es
    cmp word es:[_offset_v2],0fa8bh         ;¿mov di,dx?
    jne @@v11
    cmp word es:[_offset_v2-2],0f38bh       ;¿mov si,bx?
    jne @@v11
    mov word es:[_offset_v2],60cdh          ;patch!
    jmp short @@done
@@v11:  cmp word es:[_offset_v11],0fa8bh    ;¿mov di,dx?
    jne @@done
    cmp word es:[_offset_v11-2],0f38bh      ;¿mov si,bx?
    jne @@done
    mov word es:[_offset_v11],60cdh         ;patch!
@@done: pop es
    jmp	short @@start                       ;continuar
new21   endp

new60   proc    far
;¿estamos en la habitacion del hotel y se ha introducido "sit"?
;si, sustituir en memoria "sit\x00" por "sit\x20bed\x00"
;no, bye
    mov di,dx                               ;instruccion parcheada por CD 60
    push    es,di
    les di,[bp+6]                           ;el segmento donde parchear
    cmp di,_offset_comando                  ;¿es un comando?
    jne @@exit60
;54 79 70 69 63 61 6C 20 52 6F 6F 6D 00
;T  y  p  i  c  a  l  20 R  o  o  m  0
    cmp word es:[_offset_room],7954h        ;¿estamos en habitacion hotel?
    jne @@exit60
;73 69 74 20 64 6F 77 6E 00
;s  i  t  20 d  o  w  n  0
    cmp word es:[di],6973h                  ;¿"sit\x00"?
    jne @@exit60
    cmp word es:[di+2],0074h
    je @@patch60
    cmp Word es:[di+3],6420h                ;¿"sit\x20down\x00"?
    jne @@exit60
    cmp Word es:[di+5],776fh
    jne @@exit60
    cmp Word es:[di+7],6eh
    jne @@exit60
;73 69 74 20 62 65 64 00
;s  i  t  20 b  e  d  0
@@patch60:  mov word es:[di+3],6220h        ;write "sit\x20bed\x00" to memory
    mov word es:[di+5],6465h
    mov byte es:[di+7],0
@@exit60:   pop di,es
    iret                                    ;volver al juego
new60   endp

;***************************************
;*                                     *
;*   I N S T A L A C I O N    T S R    *
;*                                     *
;***************************************
;;autocomprobarse en memoria
@@init: mov ax,3521h                ;get @int21
    int 21h                         ;puntero en es:bx
    cmp word es:[bx+1],48fch        ;¿estoy ya residente?
    jne @@isr_install
    mov dx,offset msgresidente      ;si, bye
    mov ah,9                        ;print string
    int 21h
    mov ah,4ch                      ;salir al DOS
    int 21h
@@isr_install:  inc byte @@start    ;crear salto a antigua int21
    mov word @@start+1,bx
    mov word @@start+3,es
    mov dx,offset new21
    mov ah,25h                      ;set @int21, ojo! asegurate de que AL=21
    int 21h
    mov al,60h                      ;set @int60
    mov dx,offset new60
    int 21h
    mov es,ds:[2ch]                 ;@entorno
    mov ah,49h
    int 21h                         ;liberar espacio entorno
    mov dx,offset @@init            ;fin codigo residente
    add dx,0fh                      ;redondeo a parrafo
    mov cl,4
    shr dx,cl                       ;bytes->parrafos
    mov ax,3100h                    ;terminar, dejar DX parrafos residentes
    int 21h

;***********************************************
;*                                             *
;*   D A T O S    N O    R E S I D E N T E S   *
;*                                             *
;***********************************************
_offset_comando     equ 1eah
_offset_room        equ 29eah
_offset_v2          equ 6d0fh
_offset_v11         equ 6d07h
msgresidente        db  "ya estoy en memoria, nothing todo. bye",0dh,0ah,"$"
He comentado lo que me parecia mas relevante, pero vamos a añadir un poco mas de info sobre como funciona el artefacto.

El TSR coloca en INT_60 una rutina que interceptara los comandos que introduce el jugador, y si detecta que estamos en la habitacion del bug, y el comando es "sit\x00", lo modifica por "sit\x20\bed\x00". Todo esto en memoria.

Muy bien, tenemos el payload, pero para que se ejecute, necesitamos algo que lo active. La solucion que yo segui fue parchear alguna instruccion del juego en memoria, con una llamada a nuestra INT_60. Idealmente buscaba un punto en el flujo de ejecucion del juego, donde el comando del jugador estuviera en memoria, y el juego estuviera a punto de leerlo.

Una cosa a tener en cuenta es que el juego espera que se ejecute la instruccion que precisamente parcheamos. En este caso es mov di,dx. Lo resolvemos poniendo mov di,dx como primera instruccion de nuestra int.

No es una buena idea machacar una instruccion como cmp, o test, porque aunque preservaramos los flags con un pushf al principio y un popf antes de iret, resulta que iret (en modo real) hace un popf justo antes de retornar. Pero si no nos quedara otra opcion que machacar una instruccion que afecte a flags, entonces justo antes de iret tendriamos que escribir el contenido del registro FLAGS en la pila, en la posicion donde iret espera encontrarlo cuando retorne.

Es mejor buscar una instruccion que no modifique los flags, y si podemos elegir, una de dos bytes, para que \xcd\x60 calce perfecto (para una instruccion de tres bytes o mas: nop-padding).

Esto se esta haciendo largo, asi que vamos resumiendo.

Para encontrar un punto donde el juego este a punto de leer el comando del jugador, lo que hice fue darle al debugger, poniendo especial atencion a las instrucciones de tratamiento de cadenas (lodsb, movsb, stosb, cmpsb y las equivalentes para word: lodsw, etc), o instrucciones que lean/escriban de/en ds:[si] y es:[di]).

A base de combinar en el debugger:
BPINT 9
BPM segment:offset
MEMDUMPBIN segment:0 ffff
y en la shell:
xxd MEMDUMP.BIN |grep -i cadena_potencialmente_en_memoria
pude localizar las direcciones de memoria donde el juego guarda:
- el ultimo comando introducido por el jugador y
- el nombre de la habitacion actual.

y decidir en que punto del codigo del juego inyectaremos una llamada a int60 (\xcd\x60). Esta llamada es la mecha.

Tenemos la dinamita y la mecha. Ya solo nos falta un artificiero que plante la mecha en el codigo del juego. Podriamos parchear el EXE del juego con un editor hexa, pero vamos a ir un poco mas alla: no tocar el EXE, y cuando el juego este en memoria, que sea el propio TSR quien cambie el codigo del juego en memoria, sin cambiar en ningun momento el archivo ejecutable. Y ahi entra en juego nuestra rutina int21, que sera la rutina que cambiara la instruccion "mov di,dx" del juego por la instruccion "int 60".

Vamos terminando ya. Resumiendo:
- la dinamita o payload es nuestra int60.
- la mecha son los opcodes \xcd\x60, que se meteran en el codigo del juego, y llamaran a nuestra int60 cuando el juego los ejecute.
- y el artificiero que pone la mecha en el codigo del juego es nuestra int21.
El juego funciona bien en dosbox. La version 2.0 del juego puede bajarse de
https://www.mediafire.com/file/f5j5u8xnfb84uy1/King2.7z/file

Para probarlo sin tener que jugar hasta la habitacion del hotel, comparto un archivo LESFIX.7z con una partida guardada. Instrucciones en el README.TXT. Por supuesto, se incluye el codigo fuente.

+ Info: Proyecto de traduccion en Abandonsocios (saludos a los traductores pakolmo y danstructor, al Guardián de las aventuras cireja, y a todos los socios molones :D)

Hasta la proxima y feliz cracking.

01. Cracking de un juego DOS con IDA (analisis estatico)
02. Cracking de un juego DOS con DOSBox debugger (analisis dinamico)
03. Haciendo un cargador (loader) para crackear un juego DOS en tiempo de ejecucion (I)
04. Haciendo un cargador (loader) para crackear un juego DOS en tiempo de ejecucion (II)
05. Dandole al debugger para arreglar un bug en un juego DOS
06. De dinamitas, mechas y artificieros.

Related Posts by Categories



0 comentarios :