miércoles, 21 de abril de 2021

DOS cracking series VIII ~ PALESTRA v0.1, un TSR para traducir al castellano la aventura grafica/conversacional 'Les Manley in: Search for the King' v2.0

; PALESTRA v0.1, abr-2021, GPL-2.0-or-later, vlan7 <https://www.vlan7.org>
; un TSR para traducir al castellano la aventura grafica/conversacional
; Les Manley in: Search for the King v2.0
;
; PALESTRA is free software: you can redistribute it and/or modify
; it under the terms of the GNU General Public License as published by
; the Free Software Foundation, either version 2 of the License, or
; (at your option) any later version.
;
; PALESTRA is distributed in the hope that it will be useful,
; but WITHOUT ANY WARRANTY; without even the implied warranty of
; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
; GNU General Public License for more details.
;
; You should have received a copy of the GNU General Public License
; along with PALESTRA. If not, see <https://www.gnu.org/licenses/>.
;
; why PALESTRA?
; $ grep les /usr/share/dict/spanish |grep tra
; palestra

0. TL;DR
PALESTRA es un TSR que hace un man in the middle entre el juego y el jugador, para interceptar comandos y mensajes, y traducir comandos castellano->ingles y mensajes ingles->castellano. Todo esto en memoria y transparente para el juego.
jugador <--> TSR <--> juego
es importante destacar que es un man in the middle, es decir, el parser del juego no se toca, para el juego todo sigue estando en ingles, hay un flujo bidireccional constante, de comandos y mensajes en ingles y castellano, y el TSR traduciendo y dando cambiazos en memoria constantemente.

1. sobre el juego
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). + Info https://www.mobygames.com/game/dos/les-manley-in-search-for-the-king

2. sobre la traduccion
basicamente son dos partes a traducir.
1. comandos que introduce el jugador (parser) y
2. mensajes que muestra el juego, normalmente como respuesta a estos comandos.

3. creditos
traductores: pakolmo y danstructor
fuentes de letra: pakolmo
codigo: vlan7

4. descargas
1. Solo traduccion (binarios y codigo fuente) https://archive.org/details/palestra-0.1b.-7z
2. Juego ingles v2.0 original de Archive http://zen7.vlan7.org/file-cabinet/King2.7z
3. Juego con traduccion aplicada (v2.0 original de Archive + traduccion -binarios y codigo fuente-) http://zen7.vlan7.org/file-cabinet/King2_y_traduccion.7z

por supuesto se incluye el codigo fuente en ensamblador, los diccionarios y un par de scripts auxiliares. Todo un poco quick&dirty aun, pero el juego ya es completable en castellano. Para jugar la opcion mas rapida es bajarse el paquete 3, y en dosbox ejecutar RUNME.BAT y a jugar. + Info en el README.TXT

quiero dedicar la traduccion a mi gato Lino, que nos dejo el mismo dia que se subio la traduccion a internet, el 18 de abril de 2021. Al final encontre al Rey. Buen viaje y pechuga de pavo. Te queremos.

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. Programando un TSR para interceptar, en una aventura grafica/conversacional, los comandos que introduce el jugador (y cambiarlos al vuelo)
07. (otro) cracking de un juego DOS con DOSBox debugger (analisis dinamico)
08. PALESTRA v0.1

domingo, 18 de abril de 2021

pechuga de pavo

lunes, 11 de enero de 2021

DOS cracking series VII ~ (otro) cracking de un juego DOS con DOSBox debugger (analisis dinamico)

Vamos a hacer un descanso con un articulo introductorio.

Es un crack que hice hace algun tiempo para Breach 2, un juego de estrategia por turnos desarrollado por Omnitrend Software en 1990.

La proteccion es sencilla. Es suficiente con modificar un salto condicional a chico_bueno por un salto incondicional.

Ejecutamos el juego, y cuando nos pregunta por la clave entramos al debugger de DOSBox (ALT+PAUSA).

Para intentar caer lo mas cerca posible del punto donde el juego comprueba la clave introducida, ponemos un breakpoint a la INT 9 (INT de teclado de la BIOS). DOSBox debugger -> BPINT 9

Reanudamos la ejecucion (F5), y en el juego pulsamos Intro. Entonces saltara el debugger. Nos situamos en la primera instruccion de la INT 9 (debugger -> INTHAND 9) y vamos ejecutando instruccion por instruccion (F10).

Por alguna razon que no he analizado, al llegar a la instruccion IRET de la INT 9, podemos retornar a dos puntos distintos del codigo, segun la clave sea correcta o no. Bueno, vamos a probar con "la otra" INT de teclado: INT_16 (interrupcion de teclado del DOS). Salimos del juego y volvemos a empezar. debugger -> BPINT 16 y las cosas parecen mas favorables, ya que cuando retornamos con IRET, estamos en el mismo punto del codigo tanto si introducimos una clave correcta como incorrecta.

Bien, seguimos ejecutando paso a paso (F10) y bueno, si llegamos a algun CALL y, tras pulsar F10, el juego nos salta con el mensaje de "clave invalida", entonces significa que el chequeo de claves esta dentro de dicho CALL. Entonces saldremos de DOSBox y volveremos a empezar, y al llegar a ese CALL, en vez de pulsar F10 pulsaremos F11 para entrar en el. Esta es la parte aburrida, pero no tardamos en llegar al meollo de la cuestion: un bucle repeat-until donde se comprueba la clave introducida con la correcta, caracter a caracter. La condicion que rompe el bucle es:
339:FC50 3BCE   cmp cx,si       ;¿hemos comprobado todos los caracteres de la clave?
339:FC52 77D4 ja FC28 ($-2c) ;NO: seguir con el siguiente caracter
339:FC54 ... ;buen sitio para poner un breakpoint
Cuando estamos en "cmp cx,si", CX=num_chars_por_comprobar. Justo despues viene el salto condicional "ja FC28", que rompera el bucle cuando no queden caracteres por comprobar.

Si queremos avanzar hasta cuando se haya comprobado toda la clave, un buen sitio para poner un breakpoint es la primera instruccion que se ejecuta justo despues de salir del bucle. En esta ejecucion seria -> BP 339:FC54. Reanudamos la ejecucion con F5 y unas pocas instrucciones despues llegamos al punto en que el codigo comprueba si la clave entera ha sido correcta o no. El flujo de ejecucion se divide en dos caminos.
CS:FC60 263987A21C  cmp es:[bx+1CA2],ax ;¿chico bueno o chico malo?
CS:FC65 7443 je FCAA ($+43) ;chico_bueno (crack: JMP ~ EB 43)
CS:FC67 ... ;chico_malo

Si la clave es correcta -> al juego en si -> seguir ejecucion en CS:FCAA
Si no lo es -> poner mensaje de clave incorrecta y volver a pedir la clave, aqui no juega nadie hasta que la clave sea buena, y si fallas X veces, salir al DOS -> seguir ejecucion en CS:FC67
El salto condicional "je FCAA" se cumple para chico_bueno (clave introducida = clave correcta). Por lo tanto, para que chico_malo pueda jugar tambien, la solucion simple es modificar el salto condicional por un salto incondicional. Es decir, modificar "JE +43h" por "JMP +43h". Es decir, modificar los bytes "74 43" por "EB 43". En esta ejecucion seria -> SM 339:FC65 EB 43

Si seguimos la ejecucion paso a paso con unos cuantos F10, veremos que entramos al juego en si.

No nos hemos topado con ninguna comprobacion adicional, asi que parece que solo tenemos que modificar un byte para que el juego acepte cualquier clave como valida. :)

Abrimos BREACH2.EXE con un editor hexa y buscamos la cadena "263987A21C7443". Y... no la encontramos.

Resulta que el codigo en tiempo de ejecucion (en memoria) es distinto al codigo en el archivo ejecutable, pero no pasa nada, podemos seguir relajados, no hara falta hacer ningun cargador/TSR que modifique el codigo en tiempo de ejecucion. + Info 03. Haciendo un cargador (loader) para crackear un juego DOS en tiempo de ejecucion (I) y 04. Haciendo un cargador (loader) para crackear un juego DOS en tiempo de ejecucion (II)

El ejecutable esta empaquetado (comprimido) y cuando lo ejecutamos se desempaqueta (descomprime) en memoria. Asi que vamos a descomprimir el archivo. La herramienta UNP.EXE [1] es una buena primera opcion. No puede desempaquetar todo, pero es bastante todoterreno. Desde DOSBox:
UNP BREACH2.EXE
Y ya tenemos el ejecutable descomprimido. Volvemos a abrirlo con un editor hexa y buscamos la cadena "263987A21C7443", que ya si encontramos. La sustituimos por "263987A21CEB43" y ya esta. Ya hemos crackeado el juego. :)

PD: cuidado con el comando SM en dosbox-x. Si en debugger escribimos:
SM segment:offset CF
dosbox-vanilla escribira el byte CF (iret), pero dosbox-x escribira 0 ó 1, en funcion del valor del flag de carry (CF). Hay una colision entre el nombre de algunos flags (AC, AF, CF y DF) y los valores en hexa.

Para solucionarlo yo lo que hice fue eliminar unas pocas lineas del codigo fuente de dosbox-x, y dejar un pequeño parche en Vogons [2]

En versiones posteriores de dosbox-x, se añadio la posibilidad de escribir los bytes entre "" para forzar que la cadena se tome como valores hexa.

Feliz cracking.

[1] http://unp.bencastricum.nl/ UNP Executable file expander: uncompresses files compressed with DIET, EXEPACK, LZEXE, PKLITE and many other file compression utilities. UNP also allows you to convert files from COM to EXE and vice versa, optimize EXE headers, remove overlay data from EXE files and more.
[2] https://www.vogons.org/viewtopic.php?p=806403#p806403 evitar colisiones entre nombre de flags y valores hexa en comando SM en dosbox-x (patch)
---
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. Programando un TSR para interceptar, en una aventura grafica/conversacional, los comandos que introduce el jugador (y cambiarlos al vuelo)
07. esta entrada que estas leyendo

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.

lunes, 5 de octubre de 2020

Declaracion de Great Barrington

Epidemiologos y medicos del mundo lanzan una peticion internacional para poner fin al confinamiento indiscriminado, que puede hacer mas mal que bien.
+ Info https://gbdeclaration.org/

miércoles, 2 de octubre de 2019

DOS cracking series V ~ Dandole al DOSBox debugger para arreglar un bug en un juego MS-DOS

Y con esta ya son cinco las entradas que llevamos de oldschool cracking. Decir que no soy ningun experto y hacia años (decadas ya joder :/) que no cacharreaba con DOS a bajo nivel, por lo que cualquier correccion o comentario es bienvenido, yo aprendo.

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. esta entrada que estas leyendo.

Bien, comencemos. California Games (1987) [1] tiene un bug en la parte donde se introduce el nombre del jugador/es. El bug hace que solo se pueda introducir un nombre con un teclado de los de 84/85 teclas, si tienes un teclado extendido de 101/102 teclas el juego no detecta nada.

¿Que implica esto? que no podemos jugar a California Games en EGA/VGA, unicamente si usamos DOSBox en CGA y Tandy (machine=cga o machine=tandy) funcionara, ya que en estas dos DOSBox emula un teclado no extendido que si soporta el juego. Comentar que no es problema de DOSBox, el bug tambien existe en un DOS real.

Ante esta situacion nos asaltan dos preguntas: ¿por que? y ¿conseguiremos de alguna manera jugar en EGA/VGA? Bien, para resolverlo vamos a darle al debugger.

Lo que podemos observar es que tenemos un problema con el teclado. Comenzaremos usando los comandos:
INTHAND [intNum]          - Set code view to interrupt handler.
INTVEC [filename] - Writes interrupt vector table to file.
Vamos a inspeccionar la interrupcion de teclado del DOS.
INTHAND 16
y el debugger nos muestra el codigo que se ejecuta cuando se llama a la INT 16. A continuacion la parte relevante:
CS:6D5F 0AE4    or   ah,ah      ; ¿nos llaman para INT 16 funcion 0?
CS:6D61 740B je 6D6E ; SI: Saltamos a nuestra rutina de teclado a gestionar funcion 0
CS:6D63 FECC dec ah ; ¿funcion 1?
CS:6D65 7438 je 6D9F ; SI: Saltamos a nuestra rutina funcion 1
CS:6D67 FECC dec ah ; ¿funcion 2?
CS:6D69 7445 je 6DB0 ; SI: Saltamos a nuestra rutina funcion 2
Bien, ese codigo no se corresponde con la INT 16 del DOS, el juego ha capturado la INT 16 y redirige las funciones 0, 1 y 2 a su propia rutina de manejo de teclado.

Si quisieramos ver que interrupciones esta capturando el programa, podriamos volcar a un archivo el vector de interrupciones con DOSBox recien iniciado y volcarlo de nuevo en el punto del programa que queramos.
INTVEC orig
INTVEC juego
y el debugger nos responde con
DEBUG: Interrupt vector table written to ORIG.
DEBUG: Interrupt vector table written to JUEGO.
y si comparamos ambos archivos (son archivos de texto) los punteros que no coincidan seran las interrupciones que el juego ha capturado. Si lo hacemos veremos que el juego captura unas cuantas, entre ellas la INT 16
valor del puntero original -> F000:E9EC
valor del puntero a la rutina para la INT 16 del juego -> CS:6D57
Bien, si continuamos la ejecucion y volvemos a activar el debugger (ALT+PAUSA) en el punto donde el juego te pide tu nombre veremos que el juego llama a la INT 21/8 para leer caracteres del teclado, y el bug esta en que el juego asume que el DOS acabara llamando a INT 16/0. Con un teclado no extendido es asi y todo va bien, pero con un teclado extendido no, si nos fijamos en el valor de AX que muestra el debugger veremos que el DOS acaba llamando a INT 16 con AH=10h , que el juego no redirige, con lo cual no ocurre nada.

Comentar que podemos caer justo en la llamada a INT 21/8 poniendo un breakpoint de interrupcion con el comando:
BPINT 21 8
Y al llegar a la parte de introducir nombres el debugger nos deja aqui:
CS:9830 B608  mov  dh,08
(...)
CS:9841 92 xchg dx,ax
CS:9842 CD21 int 21 ; llamamos a INT 21/8
Para que funcione con teclado extendido (es decir, poder jugar en dosbox con EGA/VGA), el fix que se me ocurrio fue modificar la parte de codigo donde se llama a INT 21/8 para que llame a INT 16/0.
CS:9830 30F6  xor  dh,dh  ; ESTA LINEA ES EL CRACK
(...)
CS:9841 92 xchg dx,ax ; INT 16/0
CS:9842 CD16 int 16 ; ESTA LINEA ES EL CRACK
Para aplicar el fix abrimos CALGAMES.EXE con nuestro editor hexadecimal favorito y sustituimos:
b6 08 a1 5c 5e 0a e4 75 08 c7 06 5c 5e ff ff eb 05 92 cd 21
por
30 f6 a1 5c 5e 0a e4 75 08 c7 06 5c 5e ff ff eb 05 92 cd 16
Podemos traducir rapidamente instrucciones ASM a opcodes por ejemplo con la ayuda de algun ensamblador online. A mi me gusta el de shell-storm [2], ya que es sencillo, funcional y sobre todo porque admite tambien x86 en 16 bits. Simplemente escribimos la/s instruccion/es, marcamos las opciones x86 (16) e Inline y pulsamos el boton Assemble.

Ah, si aun queda alguien ahi fuera que lo vaya a jugar en un DOS puro, desde la version 5.0 puede añadir la siguiente linea al CONFIG.SYS (no soportado por DOSBox):
SWITCHES /K
/K - Causes an enhanced keyboard to act as though it were an older standard keyboard.

Feliz reversing.

[1] California Games https://www.mediafire.com/file/v1rbjsg5499ipc1/CGAMES.7z/file
[2] http://shell-storm.org/online/Online-Assembler-and-Disassembler/

miércoles, 3 de abril de 2019

DOS cracking series IV ~ Haciendo un cargador (loader) para crackear un juego DOS en tiempo de ejecucion (II)

NOTA: Esta entrada es un addendum. Si lo que buscas es un tutorial mas o menos detallado de como hacer un cargador/tsr aplicado al cracking... aqui esta la primera parte.

Comenzamos. En el cargador de la primera parte, para crackear La Colmena, parcheabamos en memoria asi:
mov Word Ptr [0097], 9090h  ; CRACK ~ escribimos los bytes 90 90 en DS:[0097]
En este caso es correcto, ya que cuando la instruccion MOV se ejecuta, los bytes que queremos parchear estan en DS:[0097], pero normalmente no tenemos el valor del segmento en ningun registro, en la practica la situacion es que las instrucciones a sobreescribir residen en un segmento distinto en cada ejecucion, y tendremos que buscar en la pila en que posicion se ha pusheado el segmento, o a malas siempre podriamos averiguarlo por fuerza bruta.

Vamos a poner un ejemplo, otro juego para el que que yo sepa nunca se ha publicado crack: GoGo Our Star [1], un juego coreano de naves de 1993 donde la pantalla de claves aparece al finalizar el primer nivel. Esta vez me voy a ahorrar el proceso y voy a poner un par de cargadores directamente. Primero por fuerza bruta, y despues intentaremos hacerlo mas limpio.

Fuerza bruta pues. Dandole al debugger podemos ver que el desplazamiento siempre es el mismo, asi que haremos fuerza bruta sobre el segmento, recorriendo todos los segmentos posibles desde [0001] hasta [FFFD] y buscando en ellos los bytes que queremos parchear. Si los encontramos, entonces parcheamos. La interrupcion que capturamos es la 10h (Video).
SIZE EQU 1024       ; Este programa y su pila caben en 1 KB
OVL segment para 'code'
assume cs:OVL, ds:OVL
org 100h ; Esto sera un programa .COM

START: jmp INITCODE ; Codigo de inicializacion

OLDINT10 dw 0,0 ; Espacio para el puntero a la INT_10h del sistema

NEWINT proc far
cmp ax, 0013h ; Se ha llamado a INT 10h con AX=0013 ?
jnz EXIT ; NO: saltamos a la INT 10h del sistema

push es ; Guardamos ES
push ax ; Guardamos AX
xor ax, ax ; AX=0

; fuerza bruta
; buscamos en todos los segmentos [0001]..[FFFD] los bytes que queremos parchear
INICIO:
inc ax
cmp ax, 0fffe ; Hemos llegado al ultimo segmento?
je NOTRIGHT ; SI: nos vamos sin parchear
mov es, ax ; ES=segmento a comprobar
cmp Word Ptr es:[032b], 0374h ; ¿ES:[032b] = 74 03?
jne INICIO ; NO: vamos a comprobar el segmento siguiente
cmp Word Ptr es:[034a], 3675h ; ¿ES:[034a] = 75 36?
jne INICIO ; NO: vamos a comprobar el segmento siguiente
cmp Word Ptr es:[0366], 1a75h ; ¿ES:[0366] = 75 1a?
jne INICIO ; NO: vamos a comprobar el segmento siguiente

; Hemos encontrado el segmento! :) Vamos a parchear
; ES:[032b] = JMP SHORT
mov Word Ptr es:[032b], 03ebh ; Crack ~ escribimos los bytes EB 03 en es:[032b]

; ES:[034a] = NOP NOP
mov Word Ptr es:[034a], 9090h ; Crack ~ escribimos los bytes 90 90 en es:[034a]

; ES:[0366] = NOP NOP
mov Word Ptr es:[0366], 9090h ; Crack ~ escribimos los bytes 90 90 en es:[0366]

NOTRIGHT:
pop ax ; Restauramos ax
pop es ; Restauramos es
EXIT:
jmp DWord Ptr cs:[OLDINT] ; Saltamos a la INT 10h del sistema
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, 3510h ; Queremos el valor del puntero a la INT 10h del sistema
int 21h
mov OLDINT[0], bx ; Guardamos puntero a la INT 10h del sistema
mov OLDINT[2], es ; (necesitaremos llamarla despues)

mov ax, 2510h ; Sobreescribimos en el vector de interrupciones el puntero a INT 10h por
lea dx, NEWINT ; un puntero a nuestra rutina NEWINT proc far
int 21h

push cs
pop es ; Necesitaremos ES:BX apuntando a EXEC_INFO

lea bx, EXEC_INFO
mov Word Ptr [bx], 0
mov Word 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
; ES:BX apuntando a EXEC_INFO
; DS:DX apuntando al nombre ASCIIZ del archivo a ejecutar
mov ax, 4b00h
int 21h ; cargar y ejecutar programa

mov dx, OLDINT[0]
mov ds, OLDINT[2]
mov ax, 2516h ; Restauramos la INT_10h del sistema
int 21h

push cs
pop ds ; DS = CS

mov ax, 4c00h ; terminar
int 21h

FILENAME db "OURSTAR.EXE",0 ; programa a ejecutar
EXEC_INFO db 22 DUP (0)
MSG db 0dh,0ah
db "Go! Go! OurStar floppy version crack.",0dh,0ah
db "2019, vlan7",0dh,0ah,"$"

OVL ends

end START
Funcional, pero un poco salvaje ¿no? Podria eliminar algunas direcciones reservadas donde seguro que no van a estar los bytes que queremos modificar, o quizas tener en cuenta el modelo de memoria segmentada del DOS, donde los segmentos se "solapan" parcialmente y hay multiples segmento:desplazamiento diferentes para apuntar al mismo byte. 7c0:0 , 0:7c , 204:5b0 ... todos esos punteros apuntan al mismo byte en memoria. Pero bueno no queria complicarlo, de todas formas seguiria siendo salvaje.

Si queremos hacerlo limpio podemos averiguar el segmento donde queremos parchear sabiendo como funcionan las instrucciones CALL/RET y que ocurre en el prologo/epilogo de una funcion. Sin entrar en detalle, cuando se ejecuta una instruccion (near) CALL se hace un push de IP (en un far CALL -> push de CS e IP), de tal forma que cuando se ejecute RET el flujo de ejecucion pueda continuar en la instruccion siguiente a dicha instruccion CALL. Es un poco enrevesado ponerlo por escrito, cosas del lenguaje, pero la idea es mas simple. En un video se veria mejor, seguro que hay alguno en internet que lo explica de forma simple.

Bien, los bytes que queremos parchear estan en una direccion de memoria [segmento]:[desplazamiento]. El desplazamiento ya lo hemos averiguado con el debugger. Para averiguar el segmento vamos a bucear en la pila.

Hacemos que BP apunte a la cima de la pila (SP) y al sumar cierto valor a BP estaremos apuntando algunos CALLs hacia atras. Dicho de otro modo, para algun valor de x, SS:[BP+x] apunta al segmento que buscamos, el segmento donde estan los bytes a parchear. Para averiguar x, bien, ver comentarios en el codigo.
SIZE EQU 1024       ; Este programa y su pila caben en 1 KB
OVL segment para 'code'
assume cs:OVL, ds:OVL
org 100h ; Esto sera un programa .COM

START: jmp INITCODE ; Codigo de inicializacion

OLDINT10 dw 0,0 ; Espacio para el puntero a la INT_10h del sistema

NEWINT10 proc far
cmp ax, 0013h ; Nos llaman para INT_10h con AX=0013 ?
jnz EXIT ; NO: saltamos a la INT_10h del sistema

;; Necesitamos el segmento donde estan los bytes que queremos parchear
push bp ; Guardamos BP
mov bp, sp ; BP apunta a la cima de la pila

push es ; Guardamos ES
push ax ; Guardamos AX

; En este punto, podemos cambiar la vista de datos a SS:BP
; (DOSBox debugger -> "d ss:bp")
; De todos esos valores, de dos en dos bytes, uno es el segmento que buscamos.
; Y no deberia estar muy lejos desde SS:BP

; Pero quizas sea mas rapido construir en memoria una lista de instrucciones tal que asi:
; mov ax,[bp] ; mov ax,[bp+2h] ; mov ax,[bp+4h] ; mov ax,[bp+6h] ...
; por ejemplo escribiendo opcodes en caliente con el comando SM de debugger

; Y con el debugger ejecutaremos instruccion por instruccion viendo que valor va
; tomando AX.

; Pongamos que estamos buscando el segmento donde estan los bytes 74 03.
; Pongamos que ejecutamos en el debugger mov ax,[bp+12h]. Ya conocemos el
; desplazamiento, digamos que es 032b, entonces iriamos a AX:32b (DOSBox
; debugger -> d ax:32b) y si vemos 7403 enhorabuena, hemos encontrado el
; segmento en la pila, podemos añadir al codigo la instruccion mov ax,[bp+12h]

mov ax, [bp+12h] ; SS:[bp+12h] son unos cuantos CALLs hacia atras
; y ahi tenemos el segmento donde estan los bytes a parchear
mov es, ax ; lo movemos a ES
;;

;; Ahora comprobamos que valor tienen los bytes del juego en memoria que
; queremos parchear, porque si ya los hemos parcheado en alguna llamada previa a
; INT_10/13, no queremos volver a hacerlo.
; Ojo!! x86 es little endian asi que los bytes van "al reves"
cmp Word Ptr es:[032b], 0374h ; ¿ES:[032b] = 74 03? (JE ($+3))
jne YAPARCHEADO ; NO: nos vamos sin parchear
cmp Word Ptr es:[034a], 3675h ; ¿ES:[034a] = 75 36? (JNE ($+36))
jne YAPARCHEADO ; NO: nos vamos sin parchear
cmp Word Ptr es:[0366], 1a75h ; ¿ES:[0366] = 75 1a? (JNE ($+1a))
jne YAPARCHEADO ; NO: nos vamos sin parchear
;;

;; Si estamos aqui es que aun no hemos parcheado. Vamos a parchear en memoria :)
; ES:[032b] = JMP SHORT
mov Byte Ptr es:[032b], 0ebh ; Crack ~ escribimos el byte EB en ES:[032b]

; ES:[034a] = NOP NOP
mov Word Ptr es:[034a], 9090h ; Crack ~ escribimos 90 90 en ES:[034a]

; ES:[0366] = NOP NOP
mov Word Ptr es:[0366], 9090h ; Crack ~ escribimos 90 90 en ES:[0366]
;;

YAPARCHEADO:
pop ax ; Restauramos AX
pop es ; Restauramos ES
pop bp ; Restauramos BP
EXIT:
jmp DWord Ptr cs:[OLDINT10] ; Saltamos a la INT_10h del sistema
NEWINT10 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, 3510h ; Queremos el puntero a la INT_10h del sistema
int 21h
mov OLDINT10[0], bx ; Guardamos puntero a la INT_10h del sistema
mov OLDINT10[2], es ; (necesitaremos llamarla despues)

push cs
pop es ; Necesitaremos ES:BX apuntando a EXEC_INFO

mov ax, 2510h ; Sobreescribimos en el vector de interrupciones el
; puntero a INT_10h por
lea dx, NEWINT10 ; un puntero a nuestra rutina NEWINT10 proc far
int 21h

;; Parameter block
lea bx, EXEC_INFO
mov Word Ptr [bx], 0
mov Word 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
; ES:BX apuntando a EXEC_INFO
; DS:DX apuntando al nombre ASCIIZ del archivo a ejecutar
mov ax, 4b00h
int 21h ; cargar y ejecutar programa

mov dx, OLDINT10[0]
mov ds, OLDINT10[2]
mov ax, 2516h ; Restauramos la INT_10h del sistema
int 21h

push cs
pop ds ; DS = CS

mov ax, 4c00h ; terminar
int 21h

FILENAME db "OURSTAR.EXE",0 ; programa a ejecutar
EXEC_INFO db 22 DUP (0)
MSG db 0dh,0ah
db "Go! Go! OurStar floppy version crack.",0dh,0ah
db "2019, vlan7",0dh,0ah,"$"

OVL ends

end START
Para ensamblar [2]:
a86 CRACK.ASM
Feliz reversing.

[1] GoGo Our Star https://www.mediafire.com/file/bbnak8waayg49d6/gogo.7z/file
[2] A86 http://www.eji.com/a86/
[+] GOGO.ASM http://zen7.vlan7.org/file-cabinet/GOGO.ASM