"No creas de nosotros cuentos que otros cuenten." Eskorbuto

miércoles, 12 de octubre de 2022

What's the worst job you've ever had?

cuando la fiesta nacional yo me quedo en la cama igual, pero este blog ya hace telara~as, asi que vamos a hablar de un libro muy majo (se puede encontrar facilmente en la red :P) que empieza asi.

"Nadie debería trabajar.

El trabajo es la fuente de casi toda la miseria en el mundo. Casi todos los males que puedas mencionar provienen del trabajo, o de vivir en un mundo diseñado para el trabajo. Para dejar de sufrir, tenemos que dejar de trabajar."


la abolicion del trabajo
comparto una cancion inedita de La Polla Records, que esta sacada de un directo pirata en Tora (Lleida) del año 1986 y que dice asi.



"el que no trabaja es
porque no le da la gana
ni puta falta que hace
no queremos trabajar
siempre estaremos de juerga
este el sistema que este
ser esclavos no nos llama
aunque nos paguen muy bien
mantener a los patrones
no es nuestra debilidad
no queremos dar ni golpe
todo menos trabajar."

trabajo, del latin tri y palis (tripalium), instrumento de tortura que consistia en un yugo de tres estacas, donde amarraban a reos para[Informacion anulada por el sistema].

vender tu tiempo a tu jefe por un trabajo que muchas veces no solo no mejora el mundo sino que lo hace peor, ni es saludable ni nos deja tiempo para lo realmente importante. la putada es que aun hace falta dinero para sobrevivir, pero sobrevivir no es vivir, por eso el mundo necesita dinero gratis, para poder hacer todas las cosas que queremos hacer.

¿sabes programar? comparte codigo. ¿eres un manitas con la fontaneria, o quizas sabes arreglar una caldera? enseña a los demas como se hace. es saludable hacer cosas por el mundo. un puto banquero, un publicista de mierda... ¿que hacen?.

--
"has pensado alguna vez / si te toca la loteria / al cabron de tu jefe / la boca le partirias."
Cicatriz en la Matriz, "Por que" (Demo 1983)

domingo, 19 de septiembre de 2021

todo el poder para la gente

en internet hay proyectos admirables, comparto unos cuantos random. Ojala que haya mas cada vez y la informacion se expanda.

https://zoowoman.website/

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, 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 https://zen7.vlan7.org/file-cabinet/King2.7z
3. Juego con traduccion aplicada (v2.0 original de Archive + traduccion -binarios y codigo fuente-) https://zen7.vlan7.org/file-cabinet/King2_y_traduccion.7z

por supuesto se incluye el codigo fuente en ensamblador (programado desde cero), 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 Lino, El Gato. El dia que nos dejo 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
tripalium
https://www.google.com/search?q=%22la+abolicion+del+trabajo%22

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, 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 invidious.


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.

Bonus: pruebas con el parser en tradu les manley.
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] https://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 https://www.eji.com/a86/
[+] GOGO.ASM https://zen7.vlan7.org/file-cabinet/GOGO.ASM

sábado, 16 de febrero de 2019

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

Y con esta ya son tres las entradas que llevamos de oldschool cracking. En esta ocasion el candidato sera un viejo (1992) juego español: La Colmena version CGA/EGA/Hercules/VGA-16 [1], de la desaparecida compañia Opera.

Usaremos DOSBox debugger [2] para ejecutar el programa paso a paso, pondremos breakpoints, identificaremos varios puntos donde podremos cambiar el codigo en memoria para que acepte cualquier clave que introduzcamos y explicaremos por que. Aplicaremos los cracks en caliente desde DOSBox debugger y conseguiremos pasar de la pantalla de claves, hasta aqui nada nuevo con respecto a las anteriores entradas de la serie, pero a la hora de ir a parchear el ejecutable con un editor hexadecimal nos toparemos con que los bytes que hemos modificado desde DOSBox debugger no estan en el archivo. WTF!! ¿Quieres saber si finalmente lograremos crear un crack permanente? Entonces sigue leyendo.

Comenzamos. Cargamos el juego en DOSBox y al llegar a la pantalla de claves

pulsamos ALT+PAUSA para activar el debugger, tal como vimos en entregas anteriores. Si ejecutamos paso a paso (F10 step in/F11 step into) enseguida nos toparemos con el siguiente codigo:
0308:015C B401    mov  ah,01      ; ¿Se ha pulsado alguna tecla?
0308:015E CD16 int 16
0308:0160 7503 jne 0165 ($+3)
0308:0162 33C0 xor ax,ax ; NO: limpiamos ax
0308:0164 C3 ret ; y volvemos a empezar
0308:0165 B80000 mov ax,0000 ; SI: Obtenemos codigo tecla pulsada
0308:0168 CD16 int 16 ; Devuelve: AH = BIOS scan code
; Devuelve: AL = ASCII character
BP 0308:016A C3 ret
Es la parte encargada de esperar la pulsacion de alguna tecla. Ponemos un breakpoint en el segundo ret
BP 0308:016A
volvemos a ejecutar con F5 y no tardamos en llegar a la parte donde se comprueba que tecla ha sido pulsada.
(...)
0308:00E8 3D0D1C cmp ax,1C0D ; Se ha pulsado Intro?
0308:00EB 7433 je 0120 ($+33) ; SI: saltamos a gestionarlo
0308:00ED E9E1FF jmp 00D1 ($-1f) ; NO: vuelta a empezar
Ponemos un bp en la direccion de memoria a la que saltamos cuando se ha pulsado intro.
BP 0308:0120  ; Hemos pulsado intro
y no tardamos en llegar a la parte donde se comprueba si se han introducido los 4 caracteres de la clave, momento en que se llama a un call que compara la clave introducida con la clave que el juego espera. Cada simbolo se codifica con un byte (caracter) y ambas claves seran tratadas como cadenas.

0308:0043 A07B07 mov al,[077B] ; Numero de caracteres introducidos
0308:0046 3C04 cmp al,04 ; Se han introducido los 4 caracteres de la clave?
0308:0048 75F3 jne 003D ($-d) ; NO -> otra vuelta al bucle
BP 0308:004A E83D00 call 008A ($+3d) ; SI -> comprobamos la clave
0308:004D 75EE jne 003D ($-12) ; JNE = CHICO_MALO. **OPCION 1 DE CRACK->SM 0308:004D 90 90 NOP NOP
0308:004F E8DE02 call 0330 ($+2de) ; Camino CHICO_BUENO
0308:0052 CD12 int 12
Entramos con F11 en el primer CALL y enseguida vemos codigo que comprueba la clave introducida con la clave que espera el juego como valida.
0308:008A BE7C07  mov  si,077C     ; Puntero a clave introducida por usuario ; **OPCION 5 de CRACK ->SM 0308:008B 80 mov si,0780
0308:008D BF8007 mov di,0780 ; Puntero a clave buena ; **OPCION 6 de CRACK -> SM 0308:008E 7C mov di,077C
0308:0090 B90400 mov cx,0004 ; Numero de vueltas al bucle
0308:0093 8A04 mov al,[si] ; Guardamos en AL un caracter de la clave introducida por el usuario ; OPCION 3 DE CRACK->SM 0308:0093 XX XX MOV AL,[DI]
0308:0095 3A05 cmp al,[di] ; y lo comparamos con el caracter de la clave real ; OPCION 4 DE CRACK->SM 0308:0095 XX XX CMP AL,[SI]
0308:0097 750D jne 00A6 ($+d) ; JNE = CHICO_MALO ; **OPCION 2 DE CRACK->SM 0308:0097 90 90
0308:0099 47 inc di ; Siguiente caracter de clave buena
0308:009A 46 inc si ; Siguiente caracter de clave introducida por usuario
0308:009B E2F6 loop 0093 ($-a) ; Decrementamos CX en 1, y si CX aun no es 0 -> Saltamos al principio del bucle
0308:009D B90600 mov cx,0006 ; Camino CHICO_BUENO
0308:00A0 E82400 call 00C7 ($+24)
0308:00A3 32C0 xor al,al ; Ponemos al=0
0308:00A5 C3 ret ; Retornamos a 0308:004D 75EE jne 003D con codigo de retorno AL
; Como el codigo de retorno es 0 seguiremos en CHICO_BUENO
Podemos crackear el juego de multiples formas, he numerado varias, veamoslas.

A destacar las opciones 5 y 6 donde el juego quedaria crackeado cambiando UN solo byte. En la 5 hacemos que el puntero a la clave introducida por el usuario apunte a la clave buena, de tal forma que estaremos comparando la clave buena consigo misma y obviamente sera tomada como correcta. La 6 sigue la misma idea pero hacemos que el puntero a la clave buena apunte a la clave introducida por el usuario, con lo cual estaremos comparando la clave introducida consigo misma que obviamente tambien sera tomada como correcta.

En las opciones 3 y 4 haremos que el CMP compare [si] con [si] o bien [di] con [di], con lo que el resultado obviamente siempre sera que ambos caracteres coinciden.

Yo voy a elegir la opcion 2, es decir, anular este salto:
0308:0097 750D  jne  00A6 ($+d)
Podriamos anularlo cambiando UN solo byte. Haciendo que el salto sea de cero bytes. Es decir, cambiando "75 0D" por "75 00". Pero vamos a hacer lo que se suele hacer y parchearemos con dos NOP:
0308:0097 9090  nop nop
con lo cual seguiremos el camino CHICO_BUENO independientemente del resultado de la comprobacion cmp al,[di]. Vamos a ello, desde DOSBox debugger:
SM 0308:0097 90 90
Continuamos ejecucion con F5 y eureka! cualquier clave que introduzcamos

el juego la da por valida y pasamos de la pantalla de claves al juego en si.

Entonces, con todo el subidon por haberlo conseguido nos disponemos a cargar COLMENA.EXE en nuestro editor hexadecimal favorito y buscamos una ristra de bytes que incluya los bytes que queremos modificar. Y... no la encontramos. Pero que no cunda el panico. ¿Sera que estan en otro archivo? Podria ser. mmmm ¿Y si pudieramos saber los archivos que un ejecutable va abriendo? Pues podemos.

Una manera rapida seria usar KGB [3] (incluye codigo fuente en ASM!), un genial TSR que captura la INT 21h y escribe las llamadas que un ejecutable va haciendo a funciones de tratamiento de archivos de la INT 21h como abrir, crear, ejecutar, etc. Crea un archivo de texto c:\HISTORY.DAT donde va guardando estas llamadas. Su uso es muy sencillo:

C:\> KGB
Runtime saving of file actions.
ver. 1.04
Petr Hor�k, Praha 1992
Usage: kgb [file] [/Options] ..
Options:
/u unload
/dX drive X (A-Z)
/on (off)

C:\> COLMENA
Llegamos hasta la pantalla de claves, miramos el contenido de C:\HISTORY.DAT y tenemos:
Exec        : colmena.EXE 
OpenFile-R : col0.ovl
Vemos que COLMENA.EXE abre en modo lectura el archivo col0.ovl

Otra forma seria poner breakpoints en llamadas a interrupciones en el debugger de DOSBox. Tenemos practicamente toda la info posible sobre interrupciones (que funciones hay, en que registro se pasa cada argumento, si retorna algo en que registro lo hace...) en las dos biblias: Ralf Brown's Interrupt List [4] y HelpPC Interrupt List [5].

Llamadas interesantes que nos podrian interesar ahora son INT 21/3D (Open file using handle) e INT 21/4B (load and execute program), asi que ponemos los siguientes breakpoints:
BPINT 21 3d
BPINT 21 4b
Ejecutamos con F5 y llegamos a una llamada a INT 21h/3d. Justo antes de que se ejecute la instruccion INT 21h tenemos en DS:DX el nombre del archivo en ASCIIZ. Pulsamos ALT+X para cambiar la vista de datos a DS:DX y ahi tenemos el nombre del archivo que se va a abrir:
col0.ovl\00
Bien, vamos a abrir col0.ovl con un editor hexadecimal para cambiar bytes. Nada, tampoco encontramos la ristra de bytes que queremos parchear. WTF?

No desesperemos, volvamos a cargar el juego, volvamos a poner el BPINT y desde ahi sigamos ejecutando paso a paso (F10 step in/F11 step into). Acabaremos viendo que COLMENA.EXE va leyendo los bytes de col0.ovl y va tejiendo en memoria en tiempo de ejecucion los bytes que formaran el subprograma que nos pide las claves, y cuando termina el desempaquetado salta a la direccion de memoria donde comienza, que esta en otro segmento distinto. Y se ejecuta. Y aparece ante nosotros la pantalla de claves. Si la clave que introducimos es correcta, retornamos y COLMENA.EXE hace lo mismo con col1.ovl, que es el juego en si.

Sabiendo esto tenemos al menos dos opciones. Una seria localizar la rutina de descifrado, entenderla y parchear en la posicion correspondiente los bytes que queremos conseguir (90 90) pero _codificados_ de tal forma que cuando la rutina de descifrado desempaquete col0.ovl los bytes resultantes sean los que anulan la proteccion (90 90 en este caso).

La otra opcion, que es la que vamos a seguir, es crear un cargador (loader) en ASM que parchee los bytes que queremos modificar cuando col0.ovl haya sido desempaquetado. Para ello necesitamos capturar una interrupcion que llame el juego una vez estan en memoria los opcodes que queremos parchear.

Bueno, desde hace un par de parrafos los lectores mas avispados se estaran preguntando... y si...
COPY COL0.OVL TMP.OVL
COPY COL1.OVL COL0.OVL
COPY TMP.OVL COL1.OVL
jeje, pues si, simplemente renombrando COL1.OVL a COL0.OVL nos saltamos la pantalla de claves. :) Pero el articulo va de hacer un cargador, asi que sigamos.

Para encontrar la INT vamos a hacer un volcado del subprograma de claves, una vez ha sido desempaquetado, y con ayuda de IDA [6] sacaremos un listado de las interrupciones a las que llama. Necesitamos encontrar la primera instruccion de col0.ovl desempaquetado, y aqui tenemos al retf que hace que el flujo de ejecucion de COLMENA.EXE salte a la primera instruccion del subprograma col0.ovl
BP 9000:018B CB  retf
Ponemos un BP, ejecutamos el retf y veremos que retornamos a una instruccion que esta otro segmento distinto.
0308:0000 2EC6064C0600  mov  byte cs:[064C],00
Justo cuando estamos en esa instruccion, sin llegar a ejecutarla volcamos los opcodes desde ese punto asi:
MEMDUMPBIN CS:IP FFFF
Y se habra creado un archivo binario llamado MEMDUMP.BIN de FFFF bytes. Lo abrimos en IDA como si fuera un .COM y buscamos las ocurrencias del opcode CD, que es el que corresponde a la instruccion INT. De las interrupciones que aparecen yo he elegido una llamada a INT 16h/AH=1

Sigamos. Queremos sustituir esto:
0308:0097 750D  jne  00A6
por esto:
0308:0097 90  nop
0308:0098 90 nop
siempre y cuando en la direccion [0097] esten los opcodes 75 0D (no queremos parchear mas de una vez) y se haya entrado a nuestra INT 16h tras una llamada a INT 16h/AH=1. Manos a la obra. He llenado el codigo de comentarios, no os quejareis.
TSR segment para 'code'
assume cs:TSR, ds:TSR
org 100h ; Esto sera un programa .COM

; El codigo residente en memoria empieza aqui
START: jmp INITCODE ; Codigo de inicializacion

OLDINT dw 0,0 ; Espacio para el puntero a la INT 16h del sistema

NEWINT proc far
cmp ah, 1 ; Se ha llamado a INT 16h con la funcion AH=1 ?
jnz EXIT ; NO: saltamos a la INT 16h del sistema

cmp Word Ptr [0097], 0d75h ; ¿Tenemos en DS:[0097] los opcodes 75 0D? Ojo!! x86 es little endian, asi que van "al reves"
jnz EXIT ; NO: saltamos a la INT 16h del sistema

mov Word Ptr [0097], 9090h ; CRACK ~ escribimos los bytes 90 90 en DS:[0097]

EXIT:
jmp DWord Ptr cs:[OLDINT] ; Saltamos a la INT 16h del sistema
NEWINT endp

; El codigo residente en memoria acaba aqui

INITCODE:
push es

mov ax, 3516h ; Queremos el valor del puntero a la INT 16h del sistema
int 21h
mov OLDINT[0], bx ; Guardamos puntero a la INT 16h del sistema
mov OLDINT[2], es ; (necesitaremos llamarla despues)

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

mov es, ds:[2ch] ; Direccion del entorno
mov ah, 49h
int 21h ; Liberar espacio de entorno

pop es

lea dx, INITCODE ; Fin del codigo residente
add dx, 15 ; Redondeo a parrafo
mov cl, 4
shr dx, cl ; bytes -> parrafos
mov ax, 3100h ; Terminate and Stay Resident (TSR)
int 21h
TSR ends

end START
Guardamos como CRACK.ASM y lo ensamblamos. Usare A86 [7]
a86 CRACK.ASM
Y tendremos un ejecutable CRACK.COM de tamaño 75 bytes.

Si lo ejecutamos aparentemente no hara nada y volveremos a ver el prompt c:\> de MS-DOS, pero en ese punto el programa se habra quedado residente en memoria, y ahora la INT 16h sera nuestra rutina NEWINT proc far, lista para acechar la comprobacion de claves del juego. Cargaremos COLMENA.EXE, que como vimos con IDA acabara llamando a INT 16h/AH=1, llamada que sera interceptada por nuestro TSR que parcheara el codigo del juego en memoria y devolvera el control a la INT 16h del sistema. El juego entonces aceptara cualquier clave introducida como valida. Si vuelve a haber llamadas a INT 16h/AH=1 seran aun interceptadas por nuestro TSR, pero al detectar que el juego ya esta parcheado no hara nada y simplemente redirigira la peticion a la INT 16h original del sistema.

Ojo que el TSR es un poco quick&dirty, no comprueba si ya esta instalado, ni permite desinstalarse a si mismo... mientras solo lo ejecutemos una vez, la cosa ira bien. Lo suyo seria que el propio TSR comprobara al menos si el mismo ya esta residente en memoria, y solo se quedara residente en caso de no encontrarse a si mismo. Pero esto se esta haciendo ya muy largo, asi que si alguien tiene interes dejo unas palabras clave -> Interrupcion Multiplex (2Fh), BMB Compuscience, CiriSOFT, propuesta AMIS (INT 2Dh).

O un metodo mas simple (muchos virus TSR de DOS usaban tecnicas similares para comprobarse a si mismos): capturar una INT, crear una nueva funcion, que se active cuando llamemos con un valor que no este reservado ya por la INT, algo como AX=0xface , y devolver un valor, por ejemplo haciendo que ES:BX apunte en memoria al valor 0xdead, y sabremos que si devuelve ese valor estamos ya residentes y tendremos que salir al DOS sin hacer nada mas, y si no lo devuelve, es que es la primera vez que se ejecuta nuestro TSR y entonces si tendremos que quedarnos residentes (y escribir en memoria el valor 0xdead, y habra que reservar tambien 2 bytes en memoria -por ejemplo como hace el listado TSR con la variable OLDINT-).

o mas sencillo aun (pero fragil y ahora veremos por que) seria apoyarse en los vectores de interrupcion. Si capturamos una INT podriamos comprobar opcodes del codigo que escribamos para la INT capturada. Este metodo tiene un problema obvio, y es que si se vuelven a cambiar los vectores de la int que estamos chequeando, por otro TSR por ejemplo, cuando nuestro TSR compruebe si tiene sus bytes en memoria vera que no, e intentara quedarse residente de nuevo. De todos modos... ¿quizas mejor este asumir este riesgo que no comprobar nada? yo creo que si, y mas aun si es hoy en dia que probablemente se va a usar dosbox y en la mayoria de casos no va a haber mas TSR cargados. En un DOS puro... dependera de lo "rara" que sea la zona de memoria que queramos comprobar, no tiene por que ser una INT, podria ser alguna zona de la memoria donde el DOS en principio no vaya a escribir nunca, pero recuerda que en DOS todo el mundo puede escribir donde tu has escrito. + Info: 29A, VX Heavens, Minotauro magazine, NukeZ journal, Herm1t et al.

Sin ir mas lejos, en la entrada de las dinamitas sigo este ultimo metodo chequeando el segundo y tercer byte de INT_21, que el TSR captura.
;;autocomprobarse en memoria
mov ax,3521h ;get @int21
int 21h ;puntero en es:[bx]
cmp word es:[bx+1],48fch ;¿estoy ya residente?
jne @@isr_install
mov ah,4ch ;si, salir al DOS
int 21h
@@isr_install: ;no, instalar TSR
Pero... ¿y el cargador donde esta? esto es un TSR que no carga nada. ¿Seria posible que solo tuvieramos que ejecutar CRACK.COM y este ya cargara COLMENA.EXE? Pues si, haciendo uso de las funciones "Modify Allocated Memory Block" (INT 21h/4Ah) y "EXEC/Load and Execute Program" (INT 21h/4Bh). La captura de interrupciones, el parcheo del codigo en memoria de COLMENA.EXE, todo es igual, la unica diferencia es que esto ya no sera un TSR, sera un cargador en toda regla. Ahi va el listado.
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

OLDINT16 dw 0,0 ; Espacio para el puntero a INT_16h del sistema

NEWINT16 proc far
cmp ah, 1 ; Nos llaman para INT_16h funcion 0 ?
jnz EXIT ; NO: saltamos a la INT_16h del sistema

;; 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_16/0, no queremos volver a hacerlo.
cmp Word Ptr [0097], 0d75h ; ¿DS:[0097] = 75 0D?
jnz EXIT ; NO: saltamos a la INT_16h del sistema
;;

mov Word Ptr [0097], 9090h ; CRACK ~ escribimos los bytes 90 90 en DS:[0097]

EXIT:
jmp DWord Ptr cs:[OLDINT16] ; Saltamos a la INT_16h del sistema
NEWINT16 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, 3516h ; Queremos el puntero a la INT_16h del sistema
int 21h
mov OLDINT16[0], bx ; Guardamos puntero a la INT_16h del sistema
mov OLDINT16[2], es ; (necesitaremos llamarla despues)

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

mov ax, 2516h ; Sobreescribimos en el vector de interrupciones el
; puntero a INT_16h por
lea dx, NEWINT16 ; un puntero a nuestra rutina NEWINT16 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, OLDINT16[0]
mov ds, OLDINT16[2]
mov ax,2516h ; Volvemos a poner en el vector de interrupciones el puntero a la INT_16h del sistema
int 21h

push cs
pop ds ; DS = CS

mov ax, 4c00h ; terminar
int 21h

FILENAME db "COLMENA.EXE",0 ; programa a ejecutar
EXEC_INFO db 22 DUP (0)
MSG db 0dh,0ah
db "La Colmena CGA/EGA/Hercules/VGA-16 crack.",0dh,0ah
db "2019, vlan7",0dh,0ah,"$"

OVL ends

end START
Feliz reversing.

[1] La Colmena CGA/EGA/Hercules/VGA-16 https://www.mediafire.com/file/036ztp6dxslstsl/COLMENA.7z/file
[2] DOSBox Debugger https://www.vogons.org/viewtopic.php?t=7323
[3] KGB https://zen7.vlan7.org/file-cabinet/kgb.zip
[4] Ralf Brown's Interrupt List http://www.delorie.com/djgpp/doc/rbinter/ix/
[5] HelpPC Interrupt List https://stanislavs.org/helppc/idx_interrupt.html
[6] IDA 5.0 free https://wiki.scummvm.org/index.php/HOWTO-Reverse_Engineering
[7] A86 https://www.eji.com/a86/
[+] COLME.ASM https://zen7.vlan7.org/file-cabinet/COLME.ASM

viernes, 18 de enero de 2019

DOS cracking series II ~ Cracking de un juego DOS con DOSBox debugger (analisis dinamico)

El candidato es Megatraveller 2 [1], un viejo (1991) juego msdos de rol en su version en castellano con una proteccion bastante comun en su epoca: al iniciar el juego se nos pregunta por algo cuya respuesta viene en el manual. Si la respuesta es correcta jugamos, de lo contrario el juego termina y volvemos al DOS.

Para no repetirnos, a diferencia de la entrada anterior donde usamos IDA para hacer un analisis estatico, en esta ocasion usaremos el debugger de DOSBox para hacer un analisis dinamico, es decir, ejecutaremos el juego paso a paso, pondremos breakpoints hasta localizar la parte del codigo donde esta la proteccion y la anularemos modificando el codigo en memoria en caliente. Todo mientras vemos en la pantalla de DOSBox que es lo que va mostrando el juego por pantalla, lo cual es enormemente util. Finalmente haremos el crack permanente modificando el archivo responsable de la proteccion con un editor hexadecimal.

Lo primero necesitamos compilar una version de DOSBox con el debugger activado, ya que por defecto no lo esta.
./autogen.sh && ./configure --enable-debug=heavy && make
Yo he utilizado dosbox-x [2], que he compilado de una forma un poco diferente ejecutando ./build-debug que ya incluye --enable-debug=heavy

Bien, cargamos el juego con dosbox-x:
user@host:~/crack/MT2$ ~/emus/dosbox-x-0.82.14/src/dosbox-x -c "keyb sp" -c "mount c ." -c "c:" -c "MT2.COM"
Avanzamos hasta que nos pida las claves y activamos el modo debugger con ALT+PAUSA. Veremos algo parecido a:

Si tecleamos HELP en la ventana Output veremos una ayuda de los comandos aceptados por el debugger. Para este crack usaremos los siguientes:
F5                        - Run.
F10/F11                   - Step over / trace into instruction.
BP [segment]:[offset]     - Set breakpoint.
SM [seg]:[off] [val] [.]..- Set memory with following values.
Atencion: segun las asociaciones de teclas que tengas en tu programa emulador de terminal puede que necesites redefinir alguna tecla de funcion, ya que tendra preferencia sobre el debugger de DOSBox.

Si miramos el titulo de la ventana de DOSBox veremos que se esta ejecutando CHARGEN. DOSBox no muestra la extension del archivo en el titulo pero CHARGEN.DAT tiene todas las papeletas de ser realmente un ejecutable de DOS. Si miramos con file:
user@host:~/crack/MT2$ file CHARGEN.DAT
CHARGEN.DAT: MS-DOS executable, MZ for MS-DOS Self-extracting PKZIP archive
user@host:~/crack/MT2$
Efectivamente, es un ejecutable de MS-DOS, concretamente un .EXE (los magic-bytes MZ lo delatan) empaquetado con PKZIP. Pues antes de nada vamos a desempaquetarlo. Usare UNP [3].

Nunca un desempaquetado fue tan rapido. Volvemos a cargar el juego con MT2.COM, pulsamos ALT+PAUSA y para llegar al punto donde se comprueban las claves, iremos pulsando F10, prestando atencion a los CALL o los saltos Jxx. Si queremos entrar en alguna funcion llamada por CALL pulsaremos F11. Si vemos que pulsando repetidamente F10 entramos en un bucle podemos fijarnos en los saltos y ver si hay algun camino que no estamos siguiendo. Pondremos breakpoints en esos caminos y haremos F5, veremos si podemos teclear, si el codigo se para seguiremos con F10/F11. Yo suelo ir anotando los CALL por donde voy pasando, asi si tengo que volver a empezar porque me he pasado tengo el ultimo CALL donde poner un breakpoint y ejecutar con F5.

Bien, despues de poner breakpoints, ejecutar, breakpoints, ejecutar, paciencia con breakpoints cada vez mas avanzados en el flujo de ejecucion llegamos a ver la rutina de comprobacion de claves. Puede que acabe haciendo un video con todo el proceso desde inicio a final, pero mientras sugiero los siguientes breakpoints.
BP 0B74:0000506E -> Se activa cuando se introduce la clave y se pulsa intro.
BP 0B74:0000BB98 -> Comprobacion claves.
BP 0B74:0000506E -> Clave correcta, se carga INTRO.DAT y empieza el juego.
Una vez introducidos en la ventana Output del debugger pulsamos F5 para ejecutar el juego, avanzamos hasta que nos pida las claves y justo cuando introducimos una clave y pulsamos intro vemos que la ejecucion se pausa, es el primer breakpoint:
0B74:00005064 E8BDD4    call 00002524 ($-2b43)
0B74:00005067 8BF0      mov  si,ax
0B74:00005069 83FE0D    cmp  si,000D                         ; SE HA PULSADO INTRO? (EL CODIGO ASCII DE INTRO ES 0D)
0B74:0000506C 7503      jne  00005071 ($+3)     (no jmp)     ; NO
BP 0B74:0000506E E94102    jmp  000052B2 ($+241)   (down)    ; SI
Volvemos a pulsar F5 y el segundo breakpoint nos lleva a:
(...)
0B74:0000BB8B E8321A    call 0000D5C0 ($+1a32)
0B74:0000BB8E 83C402    add  sp,0002
0B74:0000BB91 8BC8      mov  cx,ax
0B74:0000BB93 8BC7      mov  ax,di
0B74:0000BB95 8BDA      mov  bx,dx
0B74:0000BB97 99        cwd
BP 0B74:0000BB98 3BC8   cmp  cx,ax                           ; ¿CLAVE CORRECTA?
0B74:0000BB9A 7504      jne  0000BBA0 ($+4)     (jmp)        ; CHICO_MALO
0B74:0000BB9C 3BDA      cmp  bx,dx                           ; CHICO_BUENO
0B74:0000BB9E 7411      je   0000BBB1 ($+11)    (down)       ; CHICO_BUENO
Es la parte de codigo donde se comprueban las claves. Nos encontramos en BP donde se compara la clave introducida con la clave que el juego espera. La linea siguiente "jne 0000BBA0" es el primer chequeo de claves. JNE es chico_malo. Lo anulamos sustituyendo JNE por NOP NOP, es decir, 75 04 por 90 90. El debugger de DOSBox nos permite modificar el codigo en memoria en caliente ya que si puedes cambiar la memoria puedes cambiar el codigo en ejecucion, recuerda: todo lo que se esta ejecutando esta en memoria. Asi:
SM 0B74:BB9A 90 90
y veremos que automaticamente el codigo cambia a:
BP 0B74:0000BB98 3BC8   cmp  cx,ax                           ; ¿CLAVE CORRECTA?
0B74:0000BB9A 90        nop
0B74:0000BB9B 90        nop
0B74:0000BB9C 3BDA      cmp  bx,dx                           ; CHICO_BUENO
0B74:0000BB9E 7411      je   0000BBB1 ($+11)    (down)       ; CHICO_BUENO
Despues de los NOP viene un segundo chequeo de claves. JE es chico_bueno. Para una clave corta se cumple el JE y no seria necesario cambiarlo, pero cuando el usuario introduce una clave con toda la longitud, por ejemplo 555555555555555555 _NO_ se cumple el JE (no saltamos) y el programa considera incorrecta la clave introducida. Como el camino chico_bueno es saltar cambiamos JE por JMP (74 11 por EB 11) y asi saltamos siempre independientemente de la longitud de la clave que introduzcamos. Lo modificamos con otro SM:
SM 0B74:BB9E EB 11
Y ahora tenemos:
BP 0B74:0000BB98 3BC8   cmp  cx,ax                           ; ¿CLAVE CORRECTA?
0B74:0000BB9A 90        nop
0B74:0000BB9B 90        nop
0B74:0000BB9C 3BDA      cmp  bx,dx                           ; CHICO_BUENO
0B74:0000BB9E EB11      jmp  0000BBB1 ($+11)    (down)       ; CHICO_BUENO
En este punto ya esta crackeado, no importara la clave que introduzcamos, todas seran validas. Volvemos a pulsar F5 (Run) y el programa se parara en el tercer y ultimo breakpoint que pusimos:
(...)
BP 0B74:00000CEB E85EC0  call FFFFCD4C ($-3fa2)        ; CARGAMOS INTRO.DAT Y EMPIEZA EL JUEGO
Este BP no es necesario para el crackeo pero sirve para mostrar cual es el CALL que carga INTRO.DAT. Es decir que CHARGEN.DAT ha tomado la clave como valida y empieza el juego en si.

Para hacer el crack permanente, editamos el archivo CHARGEN.DAT con un editor hexadecimal, buscamos una ristra de bytes que contenga los bytes que queremos cambiar y nos aseguramos de que sea unica (si no buscariamos con una cadena mas larga) y lo cambiamos por los nuevos opcodes.
hexedit CHARGEN.DAT (Ojo! primero hay que desempaquetarlo) y sustituir
3B C8 75 04 3B DA 74 11
por
3B C8 90 90 3B DA EB 11
y guardar cambios.
Feliz reversing.

[1] https://www.mediafire.com/file/id5q6tsgfve6ea5/MT2.7z/file
[2] https://github.com/joncampbell123/dosbox-x/releases
[3] https://bencastricum.nl/unp/

jueves, 20 de diciembre de 2018

DOS cracking series I ~ Cracking de un juego DOS con IDA (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 empaquetado, ni tiene protecciones antidebug, ni tiene codigo que se modifica a si mismo en tiempo de ejecucion (SMC), 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, o codificada 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 salta 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 llama a la INT de Video de la BIOS (INT_10), sino que se comunica con la CMOS mediante las instrucciones de acceso a puertos IN/OUT. out 70h/in 71h parece no estar correctamente emulado en DOSBox, ya que como vimos en sub_31B9, el juego 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 SMC, 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, aunque cuanto mas mejor. Saber como el sistema operativo en que nos movemos maneja la memoria, pila, interrupciones, BIOS, acceso a ficheros, etc es mas necesario que conocer todas las instrucciones ASM, pero lo mas importante de todo es tener claro el flujo de ejecucion del programa. Una vision global y no dejar de aprender.

¿Quieres practicar? bien, te contare un pequeño secreto: busca en la red PC Futbols /Baskets /Futbol Argentina... antiguos sin desproteger, y hasta la version 4 aprox, todos son iguales y se pueden desproteger en menos de un minuto aplicando lo aprendido en esta entrada. La rutina de proteccion es clavada, pero clavada en todos ellos y se puede localizar rapidamente en IDA o desensamblador similar. Malditos vagos, roto uno rotos todos ;/

Busca como hacer en IDA las cosas que he explicado y sigue tu camino.

Feliz reversing.

[1] Sauro https://www.mediafire.com/file/nyvd996o90ty107/SAURO.7z/file
[2] IDA 5.0 free https://wiki.scummvm.org/index.php/HOWTO-Reverse_Engineering
[+] HOW TO CRACK, by +ORC, A TUTORIAL https://zen7.vlan7.org/file-cabinet/howtocrk_.txt