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 ffffy en la shell:
xxd MEMDUMP.BIN |grep -i cadena_potencialmente_en_memoriapude 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.
0 comentarios :
Publicar un comentario