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"/"sit down" 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. jTL_7Dhv6ww en youtube.


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 ds,si
push word ss:[bp+4] ;el segmento donde parchear
pop ds
mov si,_offset_v2
cmp word [si],0fa8bh ;¿mov di,dx?
jne @@v11
cmp word [si-2],0f38bh ;¿mov si,bx?
jne @@v11
mov word [si],60cdh ;patch!
jmp short @@done
@@v11: mov si,_offset_v11
cmp word [si],0fa8bh ;¿mov di,dx?
jne @@done
cmp word [si-2],0f38bh ;¿mov si,bx?
jne @@done
mov word [si],60cdh ;patch!
@@done: pop si,ds
jmp short @@start ;continuar
new21 endp

new60 proc far
;¿estamos en la habitacion del hotel y se ha introducido "sit"/"sit down"?
;si, sustituir en memoria "sit\x00" o "sit\x20down\x00" por "sit\x20bed\x00"
;no, bye
mov di,dx ;instruccion parcheada con CD 60
push ds,si
lds si,[bp+6] ;el segmento donde parchear
cmp si,_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 [_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 [si],6973h ;¿"sit\x00"?
jne @@exit60
cmp word [si+2],0074h
je @@patch60
cmp word [si+3],6420h ;¿"sit\x20down\x00"?
jne @@exit60
cmp word [si+5],776fh
jne @@exit60
cmp word [si+7],6eh
jne @@exit60
;73 69 74 20 62 65 64 00
;s i t 20 b e d 0
@@patch60: mov word [si+3],6220h ;write "\x20bed\x00" to memory
mov word [si+5],6465h
mov byte [si+7],0
@@exit60: pop si,ds
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 ;liberar espacio entorno
int 21h
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\x20bed\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.

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 :

Publicar un comentario

Nota: solo los miembros de este blog pueden publicar comentarios.