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

domingo, 10 de enero de 2021

Ingenieria inversa del codigo fuente de la vacuna de BioNTech/Pfizer para el SARS-CoV-2

user a través de host.tld
sáb, 26 dic 2020 9:10
para rootedcon

https://berthub.eu/articles/posts/reverse-engineering-source-code-of-the-biontech-pfizer-vaccine/

----

y otra noticia importante y que no debemos dejar pasar sin al menos
reflexionar sobre lo que significa realmente...

"Blackstone compra Ancestry y su base de datos de ADN por $4700 millones. "

https://www.vice.com/amp/en/article/akzyq5/private-equity-firm-blackstone-bought-ancestry-dna-company-for-billions

----

La distopía continua. Por fin llega 2021. Ánimo!

;-)
He flipado. Es la explicacion mas simple de "la vacuna" que he visto.

miércoles, 16 de diciembre de 2020

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

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

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

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

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

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

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


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

org 100h                                    ;.COM

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

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

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

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

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

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

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

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

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

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

Esto se esta haciendo largo, asi que vamos resumiendo.

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

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

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

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

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

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

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

Hasta la proxima y feliz cracking.

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

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. Recomiendan focalizar la atencion en las personas vulnerables.

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 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 a la primera parte. Si lo que buscas es un tutorial mas o menos detallado 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. Vemos 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 DWord Ptr [bx], 0
    mov DWord 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 hacer una lista de instrucciones tal que asi:
; mov ax,[bp]      mov ax,[bp+2h]      mov ax,[bp+4h]      mov ax,[bp+6h] ...
; 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 DWord Ptr [bx], 0
    mov DWord 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

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]. Hint: INT 21h es muy importante.

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.

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 es 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-).

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 DWord Ptr [bx], 0
    mov DWord 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://www.mediafire.com/file/wbwtd69s3txcbt2/kgb.zip/file
[4] Ralf Brown's Interrupt List http://www.delorie.com/djgpp/doc/rbinter/ix/
[5] HelpPC Interrupt List http://stanislavs.org/helppc/idx_interrupt.html
[6] IDA 5.0 free https://www.scummvm.org/frs/extras/IDA/idafree50.exe
[7] A86 http://www.eji.com/a86/
[+] COLME.ASM http://zen7.vlan7.org/file-cabinet/COLME.ASM