COMO ROMPER LLAVES DE HARDWARE
· Nuestro aliado: El Microprocesador
· Rastreo de las operaciones en los puertos de E/S
· Capturando los accesos a LPT1
· Análisis de los datos capturados
· Capturando los accesos a LPT1 de un programa protegido con llave de hardware
· Emulando la llave de hardware
· Mensajitos para los más chiquitos
Las empresas y los productos mencionados en el
presente texto NO EXISTEN.
Todo lo publicado en esta nota es absolutamente FALSO.
Lo que Ud. está a punto de leer es resultado de la decadente y absurda
imaginación del autor que, al no tener otra cosa que hacer, se puso a escribir
esto.
CUALQUIER PARECIDO CON LA REALIDAD ES PURA COINCIDENCIA.
Muchos desarrolladores de
software utilizan, para proteger sus productos de eventuales copias, distintos
tipos de llaves de hardware. Esto no es muy lógico en este país ya que el
software argentino, principalmente aquellos programas orientados a gestión de
empresas, son algo que dejan mucho que desear, pero bueno, la mayoría de la
gente no sabe nada de computación y es bastante fácil engañarla y cobrarle
sumas de dinero completamente absurdas. Con esto no quiero decir que todos los
programas de gestión son truchos, aunque sí la gran mayoría (digamos un 99%) de
los que corren en DOS y/o WINDOORS.
Todo el material que sigue es pura y exclusivamente para uso educativo. El
autor no se hace responsable de los perjuicios que alguien pueda causar
haciendo mal uso de las herramientas y métodos que aquí se dan.
La información no es mala, mala es la gente que la usa mal.
Pasemos a una descripción
etérea sobre el funcionamiento del tipo de llave de la que habla el presente
texto (no todas se comportan de la manera indicada):
Lo primero que hace un programa protegido cuando empieza a correr es cargar y
ejecutar a otro programa de 'protección', el cuál se encarga de desencriptar al
programa original (con datos que lee de la llave) y luego le devuelve el
control.
Seguramente, el programa de protección debe tener bastantes vueltas como para
confundir a cualquiera que se anime a querer abrirlo, si está medianamente bien
hecho tendrá muchos artilugios para tratar de evitar su vulnerabilidad, pero
tarde o temprano tiene que leer y/o escribir datos en la llave y es justamente
ése momento el que nosotros aprovecharemos.
Lo que se propone es, simple y sencillamente, emular la llave.
Los temas que se tratan con mayor profundidad son los que hacen a la emulación
de la llave. Si el lector desea profundizar sobre otros aspectos puede
consultar la bibliografía que se presenta al final del texto.
FLAGS:
Al estar en DOS, el microprocesador se encuentra en lo que se llama 'modo
real': está funcionando como un 8088 muy rápido (este micro es el que tenían
las viejas XT), por lo tanto, el desarrollo que sigue se va a basar en el
funcionamiento de ese micro.
Como es sabido, el microprocesador posee varios registros, uno de ellos es el
'Flag Register' (tambien llamado 'Status Register') que posee 16 bits y se
detallan a continuación:
15 14
13 12 11 10 9
8 7 6 5 4
3 2 1 0
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| |
| | | O | D | I | T | S | Z
| | A | | P | | C |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
bit 0: C: Carry
bit 2: P: Parity
bit 4: A: Auxiliar Carry
bit 6: Z: Zero
bit 7: S: Sign
bit 8: T: Trap
bit 9: I: Interrupt Enable
bit 10: D: Direction
bit 11: O: Overflow
Nos centraremos en solo dos de
estos bits: el Interrupt Enable y el Trap.
INTERRUPCIONES:
Daremos una rápida mirada al concepto de interrupciones:
Las interrupciones se usan, generalmente, en la comunicación con dispositivos
periféricos, por ejemplo: el teclado genera una interrupción cada vez que se
pulsa una tecla, en este caso, el uso de interrupciones permite al
microprocesador hacer otras tareas mientras uno piensa si va o no a escribir
algo y, solamente cuando se presiona una tecla, procesarla. A este tipo de
interrupciones se las llama de 'HARDWARE' ya que es un dispositivo externo el
que las produce, pero tambien existen las de 'SOFTWARE' que se generan por
programa (utilizando la instrucción INT). Todas las interrupciones se catalogan
por tipos. Un tipo es un número entre 0 y 255 (Ej.: Para poder utilizar las
rutinas del DOS hay que generar una interrupción de software tipo 21h -INT
21h-). Hay tipos que son de uso exclusivo del microprocesador, a saber:
Tipo 0: 'Divide Error': Se produce cada vez que aparece una división por
0.
Tipo 1: 'Single-Step' o 'Trace': Se produce cada vez que se ejecuta una
instrucción (solamente cuando el Trap bit del flag register está activo).
Tipo 2: 'Nonmaskable Hardware Interrupt': Se genera cuando el pin NMI
del microprocesador se pone en 1. Esta interrupción no puede obviarse ni
desactivarse, por lo tanto se procesa sí o sí.
Tipo 3: 'Byte Interrupt Instruction': La provoca una instrucción
especial.
Tipo 4: 'Overflow': Se genera cuando se ejecuta la instrucción INTO y
además, el bit O del flag register está activo.
Cada vez que ocurre una interrupción se suspende la ejecución normal de un
programa y el micro pasa a ejecutar el código de la misma; cuando termina, se
vuelve al punto exacto donde estaba antes de producirse ésta. Para saber dónde
se encuentra el código asociado a una interrupción existe el 'Vector de
Interrupciones' que consta de una zona de 1K byte que contiene las direcciones
de memoria a las que el micro debe saltar cada vez que se produce una de algún
tipo. Este vector se comienza en el offset 0 del segmento 0 (0000:0000). Hay 4
bytes por cada tipo de interrupción (dos indican el offset y los otros dos el
segmento donde se encuentra el código a ejecutar). Por ejmplo, el vector que
indica la dirección de la interrupción tipo 21h se encuentra en 0000:4 * 21h =
0000:0084h.
OPERACION DE UNA INTERRUPCION:
Cuando el 8088 termina la ejecución de una instrucción se fija si debe o no
procesar alguna interrupción observando, entre otras cosas, algunos bits del
flag register en el siguiente orden:
Si debe procesar alguna interrupción realiza los siguientes pasos:
Una implementación de estos pasos podría ser:
paso 1: pushf
paso 2: push ax
pushf
pop ax
and ax, ( 65535 - ( bit I | bit T ) )
push ax
popf
pop ax
pasos 3 y 4: call far [ 0000 : 4*X ]
Cabe aclarar que el micro no los realiza de esta manera.
Cuando el bit I está activo están permitidas las interrupciones de hardware y
se deshabilitan apagando este bit. Para encenderlo podemos usar la instrucción
STI y para apagarlo CLI. Cuando el bit T está activo se produce una
interrupción tipo 1 cada vez que se ejecuta una instrucción (siempre y cuando
no se esté ejecutando el código de una interrupción -ver paso 2-), de aquí el
nombre de 'single-step' (paso simple). A diferencia del bit I, no existen
instrucciones para activar o desactivar el bit T, por lo tanto debemos
rebuscarnoslá de otra manera.
Seguramente, el lector ya se habrá dado cuenta de que la niña bonita es la interrupción SINGLE-STEP o TRAP.
El programa TRAP.ASM muestra como se puede utilizar la interrupción TRAP para rastrear las instrucciones que se van ejecutando (este es el principio básico de funcionamiento de un debugger).
--------------------BEGIN TRAP.ASM-------------------------------------
;
;TRAP.ASM - (c) 1998 - Maruja
;
;Ejemplo de funcionamiento de la interrupcion TRAP
;
.model tiny
.code
org 100h
inicio: jmp Arranque
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
settrap_msg db 'TRAP ON', 13, 10, '$'
untrap_msg db 'TRAP OFF', 13, 10, '$'
trap_msg db 'TRAP!', 13, 10, '$'
vieja90 dd ?
vieja91 dd ?
vieja01 dd ?
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
SetTrap: push ax
push bp
mov bp, sp
mov ax, [bp+8] ;Obtener flags de la pila
or ah, 1 ;Activar bit T
mov [bp+8], ax ;Colocar nuevos flags en la pila
push dx ;Imprimir mensaje 'TRAP ON'
push ds
push cs
pop ds
lea dx, settrap_msg
mov ah, 9
int 21h
pop ds
pop dx
pop bp
pop ax
iret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
UnTrap: push ax
push bp
mov bp, sp
mov ax, [bp+8] ;Obtener flags de la pila
and ah, 0FEh ;Desactivar bit T
mov [bp+8], ax ;Colocar nuevos flags en la pila
push dx ;Imprimir mensaje 'TRAP OFF'
push ds
push cs
pop ds
lea dx, untrap_msg
mov ah, 9
int 21h
pop ds
pop dx
pop bp
pop ax
iret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Trap: push dx ;Imprimir mensaje 'TRAP!'
push ds
push ax
push cs
pop ds
lea dx, trap_msg
mov ah, 9
int 21h
pop ax
pop ds
pop dx
iret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
SetearVectores: mov ax, 3501h ;Obtener vector de interrupcion TRAP
int 21h ;(tipo 1) y guardarlo
mov word ptr vieja01, bx
mov bx, es
mov word ptr vieja01+2, bx
mov ax, 3590h ;Obtener vector de interrupcion 90h
int 21h ;y guardarlo
mov word ptr vieja90, bx
mov bx, es
mov word ptr vieja90+2, bx
mov ax, 3591h ;Obtener vector de interrupcion 91h
int 21h ;y guardarlo
mov word ptr vieja91, bx
mov bx, es
mov word ptr vieja91+2, bx
mov ax, 2590h ;Hacer que una INT 90h ejecute el
lea dx, SetTrap ;codigo 'SetTrap'
int 21h
mov ax, 2591h ;Hacer que una INT 91h ejecute el
lea dx, UnTrap ;codigo 'UnTrap'
int 21h
mov ax, 2501h ;Hacer que la interrupcion TRAP
lea dx, Trap ;provoque la ejecucion del codigo
int 21h ;'Trap'
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
RestaurarVectores:
mov ax, 2501h ;Restaurar vector anterior
lds dx, dword ptr vieja01 ;interrupcion TRAP
int 21h
mov ax, 2590h ;Restaurar vector anterior
lds dx, dword ptr cs:vieja90 ;interrupcion 90h
int 21h
mov ax, 2591h ;Restaurar vector anterior
lds dx, dword ptr cs:vieja91 ;interrupcion 91h
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Arranque: call SetearVectores
int 90h ;Activar Trap
mov ax, 1
mov dx, 2
nop
int 91h ;Desactivar Trap
Call RestaurarVectores
mov ax, 4C00h ;Terminar programa
int 21h
end inicio
--------------------END TRAP.ASM---------------------------------------
Este programa se compila (usando el Turbette Assembler) y se ejecuta de la siguiente manera:
C:\>TASM TRAP.ASM
...
C:\>TLINK /t TRAP
...
C:\>TRAP.COM
TRAP ON
TRAP!
TRAP!
TRAP!
TRAP OFF
C:\>
No voy a entrar en detalles
sobre la inicialización del programa ya que se asume que el lector tiene los
conocimientos necesarios de assembler como para entenderlo por sí mismo (solo
se recuerda que cuando comienza la ejecución de un archivo .COM es: CS = DS =
ES).
En la rutina 'Arranque', la instrucción 'INT 90h' provoca la ejecución de
'SetTrap' que, luego de activar el bit T de los flags que se encuentran en la
pila (ver 'Operación de una Interrrupción'), muestra el
mensaje 'TRAP ON' utilizando la función 9 del DOS. Cuando esta rutina termina,
el micro recupera los flags 'cambiados' de la pila quedando activo el bit T.
Luego aparecen 3 instrucciones cualquiera. Como el bit T está activo, la
ejecución de cada una de estas instrucciones genera una interrupción tipo 1 que
provoca ejecución de la rutina 'Trap', ésta muestra el mensaje 'TRAP!' y
termina.
Finalmente, la instrucción 'INT 91h' llama a la rutina 'UnTrap' que desactiva
el bit T y muestra el mensaje correspondiente.
Observar que los tres mensajes 'TRAP!' que se visualizan en la pantalla se
corresponden con cada una de las tres instrucciones que se encuentran
entre las dos INTs.
Las llaves de hardware se conectan, por lo general, en el
puerto paralelo, principalmente en LPT1 y tienen dos conectores: uno para la PC
y otro para la impresora.
Cuando el programa protegido accede a la llave lo hace a través de dicho
puerto, esto se logra con las instrucciones IN y OUT (la primera se utiliza
para leer datos y la segunda para escribirlos). Lo que debemos hacer es
rastrear la ejecución de estas instrucciones para poder capturar los datos que
se leen y/o escriben en el puerto.
Para llevar a cabo nuestro cometido nos aprovecharemos de la forma en que se
procesan las interrupciones:
Como ya se comentó, antes de pasarle el control al código de una interrupción,
el microprocesador coloca en la pila los flags y la dirección actual de
ejecución del programa principal (dirección de la instrucción que todavía no
fué ejecutada), por lo tanto, la instrucción trapeada es la anterior a la que
apunta el CS:IP que se encuentra en la pila.
Ejemplo: (Se considera bit T activo)
0960:0001: xor ax, ax
0960:0002: inc ax
Luego de ejecutarse el 'XOR' el CS:IP contiene 0960:0002, como el bit T está activo, en vez de ejecutarse el 'INC' se genera una interrupción TRAP. El estado de la pila dentro de la rutina Trap es el siguiente:
+------+------+-------+-------------...--------------------+
| 0002 | 0960 | FLAGS | XXXX XXXX XX...XXXX XXXX XXXX XXXX |
+------+------+-------+-------------...--------------------+
SP
El SP (stack pointer) está
apuntando al último elemento guardado en la pila (al IP del programa original).
En SP+2 se encuentra el CS y en SP+4 estan los flags (no olvidar que en la pila
se guardan elementos de 16 bits). Lo que debemos hacer es obtener de la pila la
dirección del código interrumpido y restarle a su IP el valor necesario para
ubicarlo en la instrucción anterior (para que apunte al 'XOR'). Una vez hecho
esto podemos leer el código de operación (opcode) de la instrucción trapeada y
saber si es o no un IN o un OUT.
Ahora tenemos un nuevo problema: ¿ Cuáles son los opcodes de IN y de OUT ?
Todas sus posibles combinaciones son:
La 1 y 5 leen un byte de un puerto;
La 3 y 7 escriben un byte en un puerto;
La 2 y 6 leen un word (2 bytes);
La 4 y 8 escriben un word;
La 1, 2, 3, y 4 direccionan directamente al puerto (solamente se puede utilizar
esta forma si el puerto es uno entre 0 y 0FFh);
La 5, 6, 7, y 8 acceden al puerto en forma indirecta a través del registro DX
(el puerto en DX puede ser uno entre 0 y 0FFFFh).
Los puertos paralelos se encuentran en direcciones mayores a 0FFh, por lo
tanto, la única manera de acceder a los mismos es por medio de alguna de las
formas indicadas en 5, 6, 7, u 8. Son entonces 4 opcodes los que debemos
conocer, para lo cuál utilizamos el programa OPCODES.ASM.
--------------------BEGIN OPCODES.ASM----------------------------------
;
;OPCODES.ASM - (c) 1998 - Maruja
;
;Programa para conocer los codigos de operacion de las
;instrucciones que permiten el acceso a los puertos de E/S
;
.model tiny
.code
org 100h
inicio: in al, dx
in ax, dx
out dx, al
out dx, ax
nop
nop
nop
nop
nop
nop
mov ax, 4C00h
int 21h
end inicio
--------------------END OPCODES.ASM------------------------------------
Compilando y usando el debug del DOS:
C:\>TASM OPCODES.ASM
...
C:\>TLINK /t OPCODES
...
C:\>DEBUG OPCODES.COM
-d
144F:0100 EC ED EE EF 90 90 90 90-90 90 B8 00 4C CD 21 00 ............L.!.
144F:0110 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
144F:0120 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
144F:0130 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
144F:0140 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
144F:0150 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
144F:0160 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
144F:0170 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
-q
C:\>
Aquí se observan claramente los códigos (en hexadecimal) de cada una de las instrucciones:
IN AL, DX = EC
IN AX, DX = ED
OUT DX, AL = EE
OUT DX, AX = EF
NOP = 90
MOV AX, 4C00h = B8 00 4C
INT 21h = CD 21
Basandosé en los datos recién obtenidos, el programa IOTRAP.ASM se encarga de rastrear cada una de las operaciones de E/S que puedan ocurrir. Las diferencias entre este programa y el programa TRAP.ASM se encuentran en las rutinas 'Trap' y 'Arraque', es por esto que solamente se muestran estas dos:
--------------------BEGIN IOTRAP.ASM - TRAP & ARRANQUE-----------------
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Trap:
SP_INICIAL EQU $
push dx ;Salvar registros utilizados
push ds
push ax
push si
push bp
SP_FINAL EQU $
mov bp, sp ;Obtener CS:IP Interrumpido
mov si, [bp + (SP_FINAL-SP_INICIAL)*2 + 2 ]
mov ds, si
mov si, [bp + (SP_FINAL-SP_INICIAL)*2 ]
dec si ;DS:SI = CS:IP Instruccion previa
mov al, byte ptr [si] ;Obtener opcode instruccion previa
cmp al, 0ECh ;El opcode es 0ECh ?
jb Trap_salir ;Si es menor salir
cmp al, 0EFh ;El opcode es 0EFh ?
ja Trap_salir ;Si es mayor salir
push cs ;Como el opcode es uno entre
pop ds ;0ECh y 0EFh imprimir mensaje TRAP!
lea dx, trap_msg
mov ah, 9
int 21h
Trap_salir: pop bp ;Recuperar registros y salir
pop si
pop ax
pop ds
pop dx
iret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Arranque: call SetearVectores
int 90h ;Activar Trap
mov ax, 1
mov dx, 60h
nop
in al, 60h
in al, dx
in ax, dx
in ax, 60h
int 91h ;Desactivar Trap
Call RestaurarVectores
mov ax, 4C00h ;Terminar programa
int 21h
end inicio
--------------------END IOTRAP.ASM - TRAP & ARRANQUE-------------------
Compilando y ejecutando el programa se obtiene:
C:\>TASM IOTRAP.ASM
...
C:\>TLINK /t IOTRAP
...
C:\>IOTRAP.COM
TRAP ON
TRAP!
TRAP!
TRAP OFF
C:\>
Explicación de las rutinas:
Trap:
Lo primero que llama la atención son las definiciones 'SP_INICIAL' y
'SP_FINAL', están solamente para no tener que calcular el espacio que ocupan
los registros en la pila cada vez que se agrega o quita algún 'PUSH' durante
alguna etapa de desarrollo de esta rutina. Luego de esto, obtiene de la pila el
CS:IP de la instrucción que se debe ejecutar cuando la interrupción TRAP
termina, decrementa el IP en uno para otener el CS:IP de la instrucción
anterior (más adelante se demuestra que esto está mal) y compara el opcode que
se encuentra en esa dirección con los códigos de OUT e IN que se obtuvieron con
anterioridad. Si esta instrucción previa posee alguno de estos códigos se
imprime el mensaje 'TRAP!'.
Arranque:
La diferencia con la que se encuentra en TRAP.ASM es que aquí se agregaron
cuatro instrucciones IN.
Si uno observa la salida de este programa luego de la ejecución del mismo se vé
que el mensaje 'TRAP!' aprece solo dos veces, esto está bien ya que son dos las
instrucciones IN que acceden al puerto en forma indirecta (a través del
registro DX): 'IN AL,DX' e 'IN AX,DX'.
Durante la explicación de la rutina Trap se ha adelantado que en realidad el
hecho de decrementar el IP en uno no necesariamente lo coloca sobre la
instrucción anterior, por ejemplo, si trapeamos la siguiente sección de código:
mov ax, 0EE00h
xor dx, dx
Luego del 'MOV' se ejecuta la Trap con el CS:IP del 'XOR' en la pila, cuando nuestra rutina decrementa en uno el IP va a parar sobre la última parte del dato que se carga en 'AX', osea sobre 0EEh, con lo cuál la Trap asumirá, erroneamente, que la instrucción reciente fué un 'OUT DX,AL' en vez de un 'MOV'. Lo verificamos en el programa IOTRAP2.ASM:
--------------------BEGIN IOTRAP2.ASM - ARRANQUE-----------------------
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Arranque: call SetearVectores
int 90h ;Activar Trap
mov ax, 1
mov dx, 60h
nop
in al, 60h
in al, dx
in ax, dx
in ax, 60h
mov ax, 0EE00h
xor dx, dx
int 91h ;Desactivar Trap
Call RestaurarVectores
mov ax, 4C00h ;Terminar programa
int 21h
end inicio
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
--------------------END IOTRAP2.ASM - ARRANQUE-------------------------
Las diferencias con IOTRAP.ASM están solamente en la rutina 'Arranque'. Compilando y ejecutando obtenemos:
C:\>TASM IOTRAP2.ASM
...
C:\>TLINK /t IOTRAP2
...
C:\>IOTRAP2.COM
TRAP ON
TRAP!
TRAP!
TRAP!
TRAP OFF
C:\>
¡Un mensaje más que en el ejemplo anterior! Esto significa
que la Trap confundió el 'MOV AX,0EE00h' con un 'OUT DX,AL' ya que no calculó
correctamente la dirección exacta de esa instrucción. Una posible solución a
este problema puede ser:
Antes que nada:
En la Trap:
Esto parece funcionar lindo pero pagamos un precio: la
primera instrucción que se ejecuta luego de activar el 'traping' no será
rastreada (punto 2). El precio es irrisorio ya que lo podemos solucionar
poniendo un 'NOP' luego de activar el 'traping' con 'INT 90h'.
(NOTA: Para evitar el punto 2 se puede hacer que la rutina de 'INT 90h' se
encargue de setear el CS:IP de la siguiente instrucción en las variables. Se
deja este trabajo al lector).
Las modificaciones que hay que hacerle al programa anterior ( IOTRAP2.ASM) están solamente en la rutina Trap.
Tenemos entonces el programa IOTRAP3.ASM:
--------------------BEGIN IOTRAP3.ASM - TRAP---------------------------
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
IPanterior dw 0
CSanterior dw 0
Trap:
SP_INICIAL EQU $
push dx ;Salvar registros utilizados
push ds
push ax
push si
push bp
SP_FINAL EQU $
cmp cs:CSanterior, 0 ;CSanterior tiene un valor incorrecto ?
jz Trap_salir ;Si: salir
mov si, cs:CSanterior
mov ds, si
mov si, cs:IPanterior ;DS:SI = CS:IP instruccion anterior
mov al, byte ptr [si] ;Obtener opcode
cmp al, 0ECh ;El opcode es 0ECh ?
jb Trap_salir ;Si es menor salir
cmp al, 0EFh ;El opcode es 0EFh ?
ja Trap_salir ;Si es mayor salir
push cs ;Como el opcode es uno entre
pop ds ;0ECh y 0EFh imprimir mensaje TRAP!
lea dx, trap_msg
mov ah, 9
int 21h
Trap_salir: mov bp, sp ;Guardar CS:IP de proxima instruccion
mov si, [bp + (SP_FINAL-SP_INICIAL)*2 + 2 ]
mov cs:CSanterior, si
mov si, [bp + (SP_FINAL-SP_INICIAL)*2 ]
mov cs:IPanterior, si
pop bp ;Recuperar registros y salir
pop si
pop ax
pop ds
pop dx
iret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
--------------------END IOTRAP3.ASM - TRAP-----------------------------
Al compilar y ejecutar se tiene:
C:\>TASM IOTRAP3.ASM
...
C:\>TLINK /t IOTRAP3
...
C:\>IOTRAP3.COM
TRAP ON
TRAP!
TRAP!
TRAP OFF
C:\>
¡Perfecto! Solamente hay 2
instrucciones IN válidas.
Para poder capturar los datos que vienen o que van al puerto
paralelo debemos conocer las direcciones donde está mapeado, por lo general, la
dirección base del puerto LPT1 se encuentra en 278h, pero esto no siempre es
así ya que la mayoría de los mothers permiten cambiarla.
El BIOS escribe las direcciones base de cada LPTx en una zona particular de
memoria:
Dirección Contenido
-------------------------------------------
0040:0008h Dirección base de LPT1
0040:000Ah Dirección base de LPT2
0040:000Ch Dirección base de LPT3
0040:000Eh Dirección base de LPT4
Si bien existe la posibilidad
de tener un cuarto puerto paralelo, el DOS no lo reconoce.
Cada LPTx está formado por tres registros mapeados en tres puertos consecutivos
a partir de la dirección base (por ejemplo: si la dirección base de LPT1 es
278h entonces tenemos al primer registro en 278h, al segundo en 279h, y al
tercero en 27Ah). Esto significa que si queremos rastrear los accesos a LPT1
nuestra rutina Trap debe capturar las lecturas y/o escrituras que se produzcan
en cualquiera de estos tres puertos.
El programa LPT1CAP1.ASM captura las operaciones en LPT1 y las
baja al archivo 'C:\LPT.DAT'.
Cada acceso al puerto genera una estructura de 8 bytes que contiene:
Descripción de los flags:
bit 15 (32768):
Si está activo indica que la operación es un OUT (caso contrario un IN). Osea
que 'Dato' contiene el dato que se escribe en el puerto.
bit 14 (16384):
Si está activo indica que el 'Dato' leído o escrito en el puerto es un word
(caso contrario un byte).
bits 13 a 11 (8192 a 2048):
No se utilizan.
bits 10 a 0 (1024 a 1):
Aquí se almacena el puerto accedido.
--------------------BEGIN LPT1CAP1.ASM---------------------------------
;
;LPT1CAP1.ASM - (c) 1998 - Maruja
;
;Ejemplo de rastreo de operaciones en el puerto LPT1
;
.model tiny
.code
org 100h
inicio: jmp Arranque
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
IN_BYTE EQU 0ECh ;IN AL, DX
IN_WORD EQU 0EDh ;IN AX, DX
OUT_BYTE EQU 0EEh ;OUT DX, AL
OUT_WORD EQU 0EFh ;OUT DX, AX
ES_OUT EQU 32768 ;Bit 15 flags
ES_WORD EQU 16384 ;Bit 14 flags
vieja90 dd ? ;Direccion original INT 90h
vieja91 dd ? ;Direccion original INT 91h
vieja01 dd ? ;Direccion original TRAP
IPanterior dw 0 ;CS:IP Instruccion anterior
CSanterior dw 0
lpt11 dw ? ;Direccion base (1er registro) de LPT1
lpt13 dw ? ;Direccion 3er registro de LPT1
TAMBUF EQU 256*8 ;Longitud buffer
buffer db TAMBUF dup (0) ;Buffer temporal para datos capturados
index dw 0 ;Posicion en buffer
filename db 'C:\LPT.DAT', 0 ;Archivo con datos capturados
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
SetTrap: push ax
push bp
mov bp, sp
mov ax, [bp+8] ;Obtener flags de la pila
or ah, 1 ;Activar bit T
mov [bp+8], ax ;Colocar nuevos flags en la pila
pop bp
pop ax
iret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
UnTrap: push ax
push bp
mov bp, sp
mov ax, [bp+8] ;Obtener flags de la pila
and ah, 0FEh ;Desactivar bit T
mov [bp+8], ax ;Colocar nuevos flags en la pila
cmp cs:index, 0 ;El buffer esta vacio ?
jz UnTrap_salir ;Si: salir
push bx
push cx
push dx
push ds
call GrabarBuffer ;Forzar la grabacion del buffer
pop ds
pop dx
pop cx
pop bx
UnTrap_salir: pop bp
pop ax
iret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Trap:
SP_INICIAL EQU $
push ax ;Salvar registros utilizados
push bx
push cx
push dx
push si
push ds
push bp
SP_FINAL EQU $
cmp cs:CSanterior, 0 ;CSanterior tiene un valor incorrecto ?
jz Trap_salir ;Si: salir
mov si, cs:CSanterior
mov ds, si
mov si, cs:IPanterior ;DS:SI = CS:IP instruccion anterior
mov cl, byte ptr [si] ;Obtener opcode
cmp cl, IN_BYTE ;El opcode es alguno entre
jb Trap_salir ;IN_BYTE u OUT_WORD ?
cmp cl, OUT_WORD
ja Trap_salir ;No: salir
push cs
pop ds
cmp dx, lpt11 ;Acceso a alguno de los puertos LPT1 ?
jb Trap_salir
cmp dx, lpt13
ja Trap_salir ;No: salir
call CapturarAcceso
Trap_salir: mov bp, sp ;Guardar CS:IP de proxima instruccion
mov si, [bp + (SP_FINAL-SP_INICIAL)*2 + 2 ]
mov cs:CSanterior, si
mov si, [bp + (SP_FINAL-SP_INICIAL)*2 ]
mov cs:IPanterior, si
pop bp ;Recuperar registros y salir
pop ds
pop si
pop dx
pop cx
pop bx
pop ax
iret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
CapturarAcceso: ;AX = Dato; DX = Puerto; CL = Opcode
cmp cl, OUT_WORD ;Es un OUT ?
je CA_setout
cmp cl, OUT_BYTE
jne CA_verdata
CA_setout: or dx, ES_OUT ;Si: setear bit 15
CA_verdata: cmp cl, IN_WORD ;El dato es word ?
je CA_setword
cmp cl, OUT_WORD
jne CA_push
CA_setword: or dx, ES_WORD ;Si: setear bit 14
CA_push: lea si, buffer ;Guardar estructura en buffer
add si, index
mov cx, CSanterior
mov [si], cx ;Guardar CS
mov cx, IPanterior
mov [si+2], cx ;Guardar IP
mov [si+4], dx ;Guardar Flags
mov [si+6], ax ;Guardar Dato
add index, 8 ;Actualizar indice
cmp index, TAMBUF ;El buffer esta lleno ?
je GrabarBuffer ;Si: grabar buffer en disco
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
GrabarBuffer: mov ax, 3D02h ;Abrir archivo para L/E
lea dx, filename
int 21h
jnc GB_append
mov ah, 3Ch ;Si no existe crearlo
xor cx, cx
int 21h
jc GB_salir ;Si hubo error salir
GB_append: mov bx, ax ;Poner archivo en modo append
mov ax, 4202h
xor dx, dx
xor cx, cx
int 21h
mov ah, 40h ;Grabar buffer
mov cx, index
lea dx, buffer
int 21h
mov ah, 3Eh ;Cerrar archivo
int 21h
mov index, 0 ;Resetear indice
GB_salir: ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
SetearVectores: mov ax, 3501h ;Obtener vector de interrupcion TRAP
int 21h ;(tipo 1) y guardarlo
mov word ptr vieja01, bx
mov bx, es
mov word ptr vieja01+2, bx
mov ax, 3590h ;Obtener vector de interrupcion 90h
int 21h ;y guardarlo
mov word ptr vieja90, bx
mov bx, es
mov word ptr vieja90+2, bx
mov ax, 3591h ;Obtener vector de interrupcion 91h
int 21h ;y guardarlo
mov word ptr vieja91, bx
mov bx, es
mov word ptr vieja91+2, bx
mov ax, 2590h ;Hacer que una INT 90h ejecute el
lea dx, SetTrap ;codigo 'SetTrap'
int 21h
mov ax, 2591h ;Hacer que una INT 91h ejecute el
lea dx, UnTrap ;codigo 'UnTrap'
int 21h
mov ax, 2501h ;Hacer que la interrupcion TRAP
lea dx, Trap ;provoque la ejecucion del codigo
int 21h ;'Trap'
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
RestaurarVectores:
mov ax, 2501h ;Restaurar vector anterior
lds dx, dword ptr vieja01 ;interrupcion TRAP
int 21h
mov ax, 2590h ;Restaurar vector anterior
lds dx, dword ptr cs:vieja90 ;interrupcion 90h
int 21h
mov ax, 2591h ;Restaurar vector anterior
lds dx, dword ptr cs:vieja91 ;interrupcion 91h
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
GetLPT1: mov di, 40h
mov es, di
mov di, 8 ;ES:DI = 0040:0008h
mov ax, word ptr es:[di]
mov lpt11, ax
add ax, 2
mov lpt13, ax
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Arranque: call GetLPT1 ;Obtener direcciones de LPT1
call SetearVectores
int 90h ;Activar Trap
xor ax, ax
mov dx, lpt11 ;DX = LPT1 registro 1
out dx, al
inc dx ;DX = LPT1 registro 2
in ax, dx
out dx, ax
inc dx ;DX = LPT1 registro 3
in al, dx
inc dx ;DX = LPT1Base+4: NO es LPT1
in ax, dx
int 91h ;Desactivar Trap
Call RestaurarVectores
mov ax, 4C00h ;Terminar programa
int 21h
end inicio
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
--------------------END LPT1CAP1.ASM-----------------------------------
Compilando y ejecutando:
C:\>TASM LPT1CAP1.ASM
...
C:\>TLINK /t LPT1CAP1
...
C:\>LPT1CAP1.COM
C:\>DIR *.DAT
Volumen en unidad C es PINDONGA
Número de serie de volumen es 2D4B-1CD6
Directorio de C:\
LPT DAT 32 01/09/98 23:44
1 archivo(s) 32 bytes
804421632 bytes libres
C:\>
Se ha creado un archivo de 32
bytes de longitud, esto significa que se capturaron 4 accesos al puerto LPT1. Antes
de examinar este archivo se comentará el programa ya que hubo algunos cambios
importantes con respecto a los anteriores:
Variables:
Se han colocado todas las variables y definiciones al comienzo. Las que merecen
explicación son:
'lpt11' y 'lpt13': aquí se guardan los registros 1 y 3 del LPT1;
'buffer': es un buffer donde se guardan momentaneamente los datos capturados,
se utiliza para no tener que hacer un acceso a disco cada vez que se encuentra
una operación en el puerto (de lo contrario se alentaría mucho la ejecución
normal del programa trapeado);
'index': se utiliza para indicar en qué posición del buffer hay que poner los
datos.
SetTrap y UnTrap:
Cumplen la misma función que en los programas anteriores salvo que ahora no
imprimen ningún mensaje. Observar que UnTrap fuerza la grabación del buffer en
el archivo en caso que este no se haya llenado por completo.
Trap:
Se eliminó el mensaje 'TRAP!' que mostraban los programas anteriores. En caso
de accederse a alguno de los registros de LPT1 se ejecuta la rutina
'CapturarAcceso'.
Como Trap se ejecuta cada vez que se termina una instrucción, entonces, luego
de una operación de E/S (Ej.: OUT AX,DX) el registro AX contiene el dato y DX
el puerto.
CapturarAcceso:
Esta rutina necesita tres parámetros en los registros AX, DX, y CL.
AX debe contener el dato escrito o leído del puerto;
DX debe contener el puerto al que se tuvo acceso;
CL debe contener el opcode de la instrucción ejecutada;
Con esta información 'CapturarAcceso' genera los flags y pone CS, IP, Flags, y
Dato en el buffer. Si el buffer está lleno llama a 'GrabarBuffer'.
GrabarBuffer:
Abre el archivo 'C:\LPT.DAT' para lectura/escritura (si no existe lo crea) y lo
pone en modo 'append', luego graba el buffer y cierra el archivo.
GetLPT1:
Obtiene la dirección base del puerto LPT1 y coloca en las variables 'lpt11' y
'lpt13' las direcciones de los registros 1 y 3.
Arranque:
A diferencia con el programa anterior ahora se llama a 'GetLPT1' y, entre las
'INT 90h' e 'INT 91h', se provocan 5 accesos a varios puertos (los cuatro
primeros son en LPT1).
Como se dijo antes, se capturaron 4 accesos y, justamente, son 4 las
operaciones que hace nuestro programa en LPT1.
Para poder analizar sín problemas la información capturada debemos convertir los datos del archivo en algo que se entienda a simple vista. Este es el objetivo del programa IOVIEW.C:
--------------------BEGIN IOVIEW.C-------------------------------------
/*
** IOVIEW.C - (c) 1998 - Maruja
** Muestra (en forma entendible) los datos capturados en operaciones de E/S
**
** Modo de uso: IOVIEW file
** Donde file es el nombre del archivo con los datos capturados
*/
#include <>stdio.h>
#include <>stdlib.h>
/* Teseteo de Flags */
#define TEST_OUT(flag) (flag & 32768) /* bit 15 = OUT */
#define TEST_WORD(flag) (flag & 16384) /* bit 14 = WORD */
#define GET_PORT(flag) (flag & 2047)
/* Estructura basica */
typedef struct {
unsigned int cs,
ip,
flags,
data;
} TRAP;
int main (int can, char *arg[])
{
FILE *f;
TRAP i;
/* Verificar que se encuentre el nombre del file */
if (can < 2) {
fprintf (stderr,
"%s: Falta el nombre del archivo con los datos capturados\n\r",
arg[0]);
exit (-1);
}
/* Abrir archivo de datos */
if (! (f = fopen (arg[1], "rb")) ) {
fprintf (stderr, "%s: ", arg[0]);
perror (arg[1]);
exit (-1);
}
/* Mostrar informacion */
printf ("\nArchivo: '%s'", arg[1]);
for (;;) {
/* Leer estructura */
if (!fread (&i, sizeof (TRAP), 1, f)) break;
/* Mostrar en forma humana */
printf ("\n%04X:%04X\t%s\t%03X, ", i.cs, i.ip,
TEST_OUT(i.flags) ? "OUT" : "IN", GET_PORT(i.flags));
if (TEST_WORD(i.flags))
printf ("%04X\t(WORD)", i.data);
else printf ("%02X \t(BYTE)", i.data & 255);
}
printf ("\n");
return 0;
}
--------------------END IOVIEW.C---------------------------------------
Compilando con cualquier compilador de C y luego ejecutando...
C:\>CC IOVIEW.C
...
C:\>IOVIEW LPT.DAT
Archivo: 'LPT.DAT'
0E61:0AA5 OUT 278, 00 (BYTE)
0E61:0AA7 IN 279, CC87 (WORD)
0E61:0AA8 OUT 279, CC87 (WORD)
0E61:0AAA IN 27A, CC (BYTE)
C:\>
Esto se lee así:
En la dirección 0E61:0AA5h se ejecutó un 'OUT DX, AL': se escribió el byte 00
en el puerto 278h;
En la dirección 0E61:0AA7h se ejecutó un 'IN AX, DX': se leyó el word 0CC87h
del puerto 279h;
En la dirección 0E61:0AA8h se ejecutó un 'OUT DX, AX': se escribió el word
0CC87h en el puerto 279h;
En la dirección 0E61:0AAAh se ejecutó un 'IN AL, DX': se leyó el byte 0CCh del
puerto 27Ah;
Comparar con la rutina 'Arranque' de LPT1CAP1.ASM.
A partir de este momento el
lector podrá observar un cambio mas o menos abrupto del vocabulario utilizado,
motivado éste, por el pésimo nivel de los 'programadores/analistas/ingenieros'
que hicieron el soft y la llave aquí analizados (tanto el programa protegido
como el protector).
El primer paso fué gastar unos
$600 por un programa de facturación berreta que no valía (ni vale) para nada
esa plata. Este coso fué 'creado' por la empresa VeniQueTeCagoBien Software
(un pésimo y mal diseñado programa originado en clipper y portado a clarion),
estaba protegido con una llave de fabricación argentina marca SOStuare Look.
(A diferencia de lo que piensan algunos asquerosos e inmundos cerdos, el autor
considera cualquier suma de dinero destinada a la educación y aprendizaje como
inversión y no como gasto -se pide disculpas al reino porcino por haber llamdo
cerdos a estos repugnantes entes-).
Para poder emular la llave primero hay que obtener los datos
que están grabados en esta, como no conocemos los chips raros que puede tener
dejaremos que el programa que la utiliza haga lo suyo mientras que nosotros capturamos
todos los accesos al puerto paralelo.
Si bien todo lo que se ha dicho hasta ahora se puede aplicar para esto, no hay
que subestimar a los fabricantes de la llave que, seguramente, habrán tomado
algún tipo de recaudo para evitar nuestra forma de ataque, por lo tanto hay que
analizar cada caso en particular (Aquí es donde el cracking pasa a ser más un
arte que una ciencia).
Luego de colocar la llave en LPT1 y, con algún buen debugger en mano (como ser
el debugger freeware de GNU), iniciamos la ejecución del programa:
C:\>LDR GESTION.EXE
Loading C:\GESTION.EXE
...
...
:bpio 278 ;Breakpoint on I/O access
:bpio 279
:bpio 27a
[ Suspender ejecución si hay algún acceso al puerto LPT1 ]
:bpint 21 ah=4b ;Breakpoint on Interrupt
[ Suspender ejecución cuando se carga y/o ejecuta un programa ]
:bpint 21 ah=4c
[ Suspender ejecución cuando finaliza un programa ]
:x ;Run
Break Point at 0E1B:03BB
0E1B:03B8 B8004B MOV AX, 4B00
> 0E1B:03BB CD21 INT 21 ;Load and execute program
0E1B:03BD 7236 JB 03F5
...
[ El programa protegido (gestion.exe) carga y ejecuta al programa de protección (protect.exe) ]
:t ;One Step
> 0123:109E 90 NOP
0123:109F 90 NOP
0123:10A0 E8CC00 CALL 116F
...
[ Inicio del código de la interrupción tipo 21h ]
:p ret ;Run until return
> FDC9:423C CF IRET
...
[ Punto final de la funcion 4Bh del DOS ]
:t
> 1AE3:0000 BA3E1C MOV DX, 1C3E
1AE3:0003 8EDA MOV DS, DX
1AE3:0005 8C066801 MOV [0168], ES
1AE3:0009 33ED XOR BP, BP
1AE3:000B 8BC4 MOV AX, BP
...
[ Punto de entrada del programa de protección ]
:x
Break Point at 18B4:0855
18B4:0854 EC IN AL, DX
> 18B4:0855 EB01 JMP 0858
18B4:0857 28 .
18B4:0858 2EA20200 MOV CS:[0002], AL
18B4:085C BB0100 MOV BX, 0001
18B4:085F B055 MOV AL, 55
18B4:0861 EE OUT DX, AL
...
[ Rutina que accede a la llave ]
Luego de observar durante un buen rato el comportamiento de este muchachín se
obtuvieron ciertas conclusiones que nos permitirán actuar en forma:
El programa de facturación (que se encuentra parcialmente encriptado) carga y
ejecuta a otro programa de protección.
El programa de protección realiza unas cuantas interesantes, pero abusrdas
payasadas, para tratar de evitar, inútilmente, su debugging:
Por ejemplo, durante un tiempo cambia el stack segment y el stack pointer
(SS:SP) a la zona que contiene los vectores de interrupción tipos 1, 2, y 3
(Trap, NMI, y BII), luego genera unos cuantos calls sín sentido para que se modifique
el contenido de esta zona (con esto se puede colgar fácilmente cualquier
debugger viejo, cosa que no pasa con un debugger moderno); hacer esto con la
Trap y la BII podría resultar útil, pero hacerlo con la NMI es una animalada,
es algo que no tiene nombre y no es digno de programador alguno que se jacte de
serlo, menos aún de un analista y menos aún todavía de un ingeniero (el nivel
de las facultades argentinas -tanto privadas como estatales- es lamentable, por
lo tanto no hay que sorprenderse si algunos de los idiotas que hicieron esta
cagada tienen algún título por el estilo), la NMI es una interrupción que se
reserva para fallas fatales del sistema y no debe tocarse... salvo que haya
algún tipo de hardware especial (como ser alguna hipotética UPS) que también la
utilice.
Se han observado ciertas rutininas que calculan el tiempo que tardan en
ejecutarse otras rutinitas: si el tiempo calculado es muy grande (cracker
utilizando el comando ONE STEP de un debugger que no se cuelga), el programa de
protección entra en un bucle infinito de calls.
Otra de las estupideces que se manda este particular programita es setear sus
propios vectores para esa zona utilizando la función 25h del DOS. La pegunta
es: ¿Para qué carajo hizo el quilombo que hizo cambiando el stack a la zona de
los vectores de interrupción si ahora utiliza una simple función del DOS
recontra re visible y super re debugueable?... Aparentemente los creadores del
coso este pensaron que sus estúpidos métodos son infalibles para detener a cualquiera...
Bue.. sigamos...
Una de las cosas más interesantes de este programilla es que el código que
accede a la llave (la sección de código que realiza los INs y OUTs) está
encriptada. Este señorito desencripta este código para, en algún momento,
ejecutarlo y así poder acceder a los puertos de LPT1 y leer de la llave los
datos escritos en ésta para desencriptar el código del programa protegido y
pasarle, finalmente, el control.
Lo que vamos a hacer es desviar la ejecución normal del programa y activar nuestro
sistema, esto se puede lograr generando una interrupción en alguna parte
después de los desastres hechos sobre el vector de interrupciones. Además de
inicializar el sistema de captura de datos, nuestra rutina debe encargarse de
ejecutar la instrucción que fué cambiada por nuestra modificación del código
original.
Lo ideal sería hacer esto en la zona de código que realiza los INs y OUTs,
lamentablemente no puede hacerse porque, como ya se dijo, esta parte se
encuentra encriptada, lo haremos entonces sobre la rutina que desencripta este
código. Para encontrarla tenemos que realizar unos pasos bastantes simples:
Gracias a los breakpoints de accesos a los puertos conocemos la dirección de
memoria que realiza los accesos a LPT1, con este dato colocamos otro breakpoint
para que se suspenda la ejecución cuando algo (o alguien) intenta escribir en
esta zona (osea, ponemos un breakpoint que paraliza la ejecución normal en el
momento en que la rutina de desencripción desencripta el código que accede a la
llave), en nuestro caso la dirección es 18B4:0854:
C:\>LDR GESTION.EXE
Loading C:\GESTION.EXE
...
...
:bc * ;Clear all breakpoints
:bpm 18b4:0854 ;Breakpoint on memory access
:x
...
:x
Break Point at 179D:021C
> 179D:021C C47E8F LES DI, [BP-08]
179D:021F 03F8 ADD DI, AX
179D:0221 268B15 MOV DX, ES:[DI]
179D:0224 8B46FE MOV AX, [BP-02]
179D:0227 D1E0 SHL AX, 1
179D:0229 C47E8F LES DI, [BP-08]
179D:022C 03F8 ADD DI, AX
179D:022E 268B05 MOV AX, ES:[DI]
179D:0231 33C2 XOR AX, DX
179D:0233 8BD0 MOV DX, AX
179D:0235 8B46FE MOV AX, [BP-02]
179D:0238 D1E0 SHL AX, 1
179D:023A C47E8F LES DI, [BP-08]
179D:023D 03F8 ADD DI, AX
179D:023F 268915 MOV ES:[DI], DX
179D:0242 837EFE01 CMP WORD PTR [BP-02], 1
179D:0246 75CB JNZ 0213
...
[ Rutina que desencripta el codigo que accede a la llave ]
Pondremos nuestro desvío en la dirección 179D:023Dh y quedará con la siguiente
forma:
...
C47EF8 LES DI, [BP-08]
179D:023D CD90 INT 90
268915 MOV ES:[DI], DX
...
Podemos hacerlo desde el debugger pero para un cambio permanente tenemos que modificar directamente al ejecutable, esto lo podemos hacer con algún tipo de editor de archivos como ser el 'disquetitor' del 'MarronOrton Utilitis':
C:\>COPY PROTECT.EXE ORIGINAL.EXE
C:\>DISQUETITOR PROTECT.EXE /W
Pedimos a este utilitario que busque la secuencia:
C4 7E 8F 03 F8 26 8B 15 8B 46 FE D1 E0 C4 7E 8F 03 F8 26
8B 05 33 C2 8B D0 8B 46 FE D1 E0 C4 7E 8F 03 F8 26 89 15
y cambiamos la última ocurrencia del '03 F8' con un 'CD 90' de tal manera que quede así:
C4 7E 8F 03 F8 26 8B 15 8B 46 FE D1 E0 C4 7E 8F 03 F8 26
8B 05 33 C2 8B D0 8B 46 FE D1 E0 C4 7E 8F CD 90 26 89 15
El 'CD' es el código de operación de la instrucción INT, el
siguiente byte es el tipo de interrupción a generar, por eso 'CD 90' es el
código de máquina de la instrucción 'INT 90h'. (Observar la salida generada por
el debug del DOS cuando se lo utilizó para obtener los códigos
de operación de los INs y OUTs).
Si ejecutamos el programa luego de grabar los cambios, el sistema se va a
colgar ya que no hay ningún vector válido para la INT 90h. Este problemita lo
solucionamos con el programa residente LPT1CAP.ASM:
--------------------BEGIN LPT1CAP.ASM----------------------------------
;
;LPT1CAP.ASM - (c) 1998 - Maruja
;
;Programa residente que captura todos los accesos al puerto LPT1
;Se activa con 'INT 90h' y se desactiva con 'INT 91h'
;
.model tiny
.code
org 100h
inicio: jmp Arranque
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
IN_BYTE EQU 0ECh ;IN AL, DX
IN_WORD EQU 0EDh ;IN AX, DX
OUT_BYTE EQU 0EEh ;OUT DX, AL
OUT_WORD EQU 0EFh ;OUT DX, AX
ES_OUT EQU 32768 ;Bit 15 flags
ES_WORD EQU 16384 ;Bit 14 flags
IPanterior dw 0 ;CS:IP Instruccion anterior
CSanterior dw 0
lpt11 dw ? ;Direccion base (1er registro) de LPT1
lpt13 dw ? ;Direccion 3er registro de LPT1
TAMBUF EQU 4096*8 ;Longitud buffer (grande)
buffer db TAMBUF dup (0) ;Buffer temporal para datos capturados
index dw 0 ;Posicion en buffer
filename db 'C:\LPT.DAT', 0 ;Archivo con datos capturados
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
SetTrap: push ax
push bp
mov bp, sp
mov ax, [bp+8] ;Obtener flags de la pila
or ah, 1 ;Activar bit T
mov [bp+8], ax ;Colocar nuevos flags en la pila
push dx
push ds
push cs
pop ds
mov ax, 2501h ;Setear nuestro vector de int TRAP
lea dx, Trap
int 21h
pop ds
pop dx
pop bp
pop ax
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; INSTRUCCION ORIGINAL EN EL ARCHIVO DE PROTECCION
; QUE FUE CAMBIADA POR EL 'INT 90h' (CD 90)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
ADD DI, AX
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
iret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
UnTrap: push ax
push bp
mov bp, sp
mov ax, [bp+8] ;Obtener flags de la pila
and ah, 0FEh ;Desactivar bit T
mov [bp+8], ax ;Colocar nuevos flags en la pila
cmp cs:index, 0 ;El buffer est vac¡o ?
jz UnTrap_salir ;Si: salir
push bx
push cx
push dx
push ds
push cs
pop ds
call GrabarBuffer ;Forzar la grabacion del buffer
pop ds
pop dx
pop cx
pop bx
UnTrap_salir: pop bp
pop ax
iret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Trap:
SP_INICIAL EQU $
push ax ;Salvar registros utilizados
push bx
push cx
push dx
push si
push ds
push bp
SP_FINAL EQU $
cmp cs:CSanterior, 0 ;CSanterior tiene un valor incorrecto ?
jz Trap_salir ;Si: salir
mov si, cs:CSanterior
mov ds, si
mov si, cs:IPanterior ;DS:SI = CS:IP instrucci¢n anterior
mov cl, byte ptr [si] ;Obtener opcode
cmp cl, IN_BYTE ;El opcode es alguno entre
jb Trap_salir ;IN_BYTE u OUT_WORD ?
cmp cl, OUT_WORD
ja Trap_salir ;No: salir
push cs
pop ds
cmp dx, lpt11 ;Acceso a alguno de los puertos LPT1 ?
jb Trap_salir
cmp dx, lpt13
ja Trap_salir ;No: salir
call CapturarAcceso
Trap_salir: mov bp, sp ;Guardar CS:IP de proxima instruccion
mov si, [bp + (SP_FINAL-SP_INICIAL)*2 + 2 ]
mov cs:CSanterior, si
mov si, [bp + (SP_FINAL-SP_INICIAL)*2 ]
mov cs:IPanterior, si
pop bp ;Recuperar registros y salir
pop ds
pop si
pop dx
pop cx
pop bx
pop ax
iret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
CapturarAcceso: ;AX = Dato; DX = Puerto; CL = Opcode
cmp cl, OUT_WORD ;Es un OUT ?
je CA_setout
cmp cl, OUT_BYTE
jne CA_verdata
CA_setout: or dx, ES_OUT ;Si: setear bit 15
CA_verdata: cmp cl, IN_WORD ;El dato es word ?
je CA_setword
cmp cl, OUT_WORD
jne CA_push
CA_setword: or dx, ES_WORD ;Si: setear bit 14
CA_push: lea si, buffer ;Guardar estructura en buffer
add si, index
mov cx, CSanterior
mov [si], cx ;Guardar CS
mov cx, IPanterior
mov [si+2], cx ;Guardar IP
mov [si+4], dx ;Guardar Flags
mov [si+6], ax ;Guardar Dato
add index, 8 ;Actualizar indice
cmp index, TAMBUF ;El buffer esta lleno ?
je GrabarBuffer ;Si: grabar buffer en disco
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
GrabarBuffer: mov ax, 3D02h ;Abrir archivo para L/E
lea dx, filename
int 21h
jnc GB_append
mov ah, 3Ch ;Si no existe crearlo
xor cx, cx
int 21h
jc GB_salir ;Si hubo error salir
GB_append: mov bx, ax ;Poner archivo en modo append
mov ax, 4202h
xor dx, dx
xor cx, cx
int 21h
mov ah, 40h ;Grabar buffer
mov cx, index
lea dx, buffer
int 21h
mov ah, 3Eh ;Cerrar archivo
int 21h
mov index, 0 ;Resetear indice
GB_salir: ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
FINRESID EQU $ ;Aca termina el residente
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
SetearVectores: mov ax, 2590h ;Hacer que una INT 90h ejecute el
lea dx, SetTrap ;codigo 'SetTrap'
int 21h
mov ax, 2591h ;Hacer que una INT 91h ejecute el
lea dx, UnTrap ;codigo 'UnTrap'
int 21h
mov ax, 2501h ;Hacer que la interrupcion TRAP
lea dx, Trap ;provoque la ejecucion del codigo
int 21h ;'Trap'
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
GetLPT1: push es ;Obtener registros de LPT1
mov di, 40h
mov es, di
mov di, 8 ;ES:DI = 0040:0008h
mov ax, word ptr es:[di]
mov lpt11, ax ;lpt11 = Registro 1 LPT1
add ax, 2
mov lpt13, ax ;lpt13 = Registro 3 LPT1
pop es
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
mok db 13, 10, 'OK', 13, 10, '$'
myaestoy db 13, 10, 'Ya estaba residente', 13, 10, '$'
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Arranque: push ds ;El offset del vector de
xor si, si ;interrupcion 90h es igual
mov ds, si ;al offset de la SetTrap ?
mov si, 240h
mov ax, word ptr [si]
pop ds
cmp ax, offset SetTrap
jne A_instalar ;No: instalar residente
lea dx, myaestoy ;Como el residente ya estaba
mov ah, 9 ;instalado mostrar mensaje
int 21h ;y salir
mov ax, 4C00h
int 21h
A_instalar: call GetLPT1 ;Obtener direcciones de LPT1
call SetearVectores ;Setear nuevos vectores de ints
lea dx, mok ;Mostrar mensaje 'OK'
mov ah, 9
int 21h
mov ax, 3100h ;Terminar y quedar residente
lea dx, FINRESID
shr dx, 4
inc dx
int 21h
end inicio
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
--------------------END LPT1CAP.ASM------------------------------------
Operamos:
C:\>TASM LPT1CAP.ASM
...
C:\>TLINK /t LPT1CAP
...
C:\>DEL LPT.DAT
C:\>LPT1CAP
OK
C:\>LPT1CAP
Ya estaba residente
C:\>GESTION.EXE
...
...
C:\>DIR *.DAT
Volumen en unidad C es PINDONGA
Número de serie de volumen es 2D4B-1CD6
Directorio de C:\
Archivo no se encontró
C:\>
¿Qué pasó que no apareció el archivo con los datos capturados? Nada malo: el nuevo buffer del residente tiene capacidad para 4096 accesos y el programa de protección no realizó tantos, para vaciarlo usamos al programa FLUSH.ASM:
--------------------BEGIN FLUSH.ASM------------------------------------
;
;FLUSH.ASM - (c) 1998 - Maruja
;
;Baja el contenido del buffer de la rutina TRAP residente a disco
;
.model tiny
.code
org 100h
inicio: int 91h
mov ax, 4C00h
int 21h
end inicio
--------------------END FLUSH.ASM--------------------------------------
Luego:
C:\>TASM FLUSH.ASM
...
C:\>TLINK /t FLUSH
...
C:\>FLUSH.COM
C:\>DIR *.DAT
Volumen en unidad C es PINDONGA
Número de serie de volumen es 2D4B-1CD6
Directorio de C:\
LPT DAT 14032 06/09/98 23:10
1 archivo(s) 2048 bytes
804409628 bytes libres
C:\>
¡Muejejee! Se han hecho 1754
accesos en LPT1.
Explicación de las rutinas de LPT1CAP.ASM:
SetTrap:
Activa el TRAP bit, setea el propio vector para la interrupción Trap y ejecuta
el código original del programa de protección que fué alterado por el 'INT 90h'
en la rutina de desencripción ('ADD DI, AX').
Arranque:
Se fija si el vector de la interrupción 90h tiene el mismo offset que el de la
rutina 'SetTrap', si es así, asume que el residente ya está instalado y termina
la ejecución del programa. En caso contrario, obtiene los puertos de LPT1,
cambia los vectores de las interrupciones utilizadas y sale al DOS quedando
residente.
NOTA: Una forma más sencilla de provocar la interrupción 90h puede hacerse en
el momento en que el programa de protección setea los vectores de las
interrupciones TRAP, NMI, y BII luego de producir esa 'colitis' utilizando la
pila: ¡Basta solamente con cambiar el INT 21h por un INT 90h modificando
solamente 1 byte del código original! Se optó por ponerla en otra parte ya que
este tipo de estupidismos por parte de los creadores de ese archivo no debería
ser común, además se demuestra que cualquier lugar estratégico es bueno,
incluso dentro de un bucle.
En este momento disponemos de las herramientas necesarias para capturar varios accesos a LPT1 y hacer un análisis mas o menos profundo del contenido de la llave y de la forma con la que el programa de protección accede a ella:
C:\>REN LPT.DAT 1.DAT
C:\>GESTION.EXE
...
C:\>FLUSH.COM
C:\>REN LPT.DAT 2.DAT
C:\>GESTION.EXE
...
C:\>FLUSH.COM
C:\>REN LPT.DAT 3.DAT
C:\>GESTION.EXE
...
C:\>FLUSH.COM
C:\>REN LPT.DAT 4.DAT
C:\>GESTION.EXE
...
C:\>FLUSH.COM
C:\>REN LPT.DAT 5.DAT
C:\>DIR C:\*.DAT
Volumen en unidad C es PINDONGA
Número de serie de volumen es 2D4B-1CD6
Directorio de C:\
1 DAT 14032 10/09/98 18:09
2 DAT 14912 10/09/98 18:10
3 DAT 15488 10/09/98 18:10
4 DAT 14048 10/09/98 18:11
5 DAT 15792 10/09/98 18:11
5 archivo(s) 74272 bytes
757170176 bytes libres
C:\>
¡Ooohh! ¡Parece que los muchachos quieren guerra! Por cada ejecución se hicieron 1754, 1864, 1936, 1756, y 1974 accesos respectivamente, veamos el contenido del primero:
C:\>IOVIEW 1.DAT
Archivo: '1.dat'
1BCB:0854 IN 278, 00 (BYTE)
1BCB:0861 OUT 278, 55 (BYTE)
1BCB:0865 IN 278, 55 (BYTE)
1BCB:0874 OUT 278, AA (BYTE)
1BCB:0878 IN 278, AA (BYTE)
1BCB:0887 OUT 278, 00 (BYTE)
1BCB:00E2 IN 278, 00 (BYTE)
1BCB:00F9 IN 27A, CC (BYTE)
1BCB:00FF OUT 27A, CC (BYTE)
1BCB:010D OUT 278, 92 (BYTE)
1BCB:011D OUT 278, B2 (BYTE)
1BCB:014A IN 279, 96 (BYTE)
1BCB:0195 OUT 278, 40 (BYTE)
1BCB:01B8 OUT 278, 00 (BYTE)
1BCB:0195 OUT 278, FA (BYTE)
1BCB:01B8 OUT 278, BA (BYTE)
1BCB:0195 OUT 278, F8 (BYTE)
1BCB:01B8 OUT 278, B8 (BYTE)
1BCB:0195 OUT 278, F2 (BYTE)
1BCB:01B8 OUT 278, B2 (BYTE)
1BCB:0195 OUT 278, F0 (BYTE)
1BCB:01B8 OUT 278, B0 (BYTE)
1BCB:0195 OUT 278, EA (BYTE)
1BCB:01B8 OUT 278, AA (BYTE)
1BCB:0195 OUT 278, E8 (BYTE)
1BCB:01B8 OUT 278, A8 (BYTE)
1BCB:0195 OUT 278, E2 (BYTE)
1BCB:01B8 OUT 278, A2 (BYTE)
1BCB:0195 OUT 278, E0 (BYTE)
1BCB:01B8 OUT 278, A0 (BYTE)
1BCB:0195 OUT 278, DA (BYTE)
1BCB:01B8 OUT 278, 9A (BYTE)
1BCB:0195 OUT 278, D8 (BYTE)
1BCB:01B8 OUT 278, 98 (BYTE)
1BCB:0195 OUT 278, D2 (BYTE)
1BCB:01B8 OUT 278, 92 (BYTE)
1BCB:0195 OUT 278, D0 (BYTE)
1BCB:01B8 OUT 278, 90 (BYTE)
1BCB:0195 OUT 278, CA (BYTE)
1BCB:01B8 OUT 278, 8A (BYTE)
1BCB:0195 OUT 278, C8 (BYTE)
1BCB:01B8 OUT 278, 88 (BYTE)
1BCB:0195 OUT 278, C2 (BYTE)
1BCB:01B8 OUT 278, 82 (BYTE)
1BCB:0195 OUT 278, C0 (BYTE)
1BCB:01B8 OUT 278, 80 (BYTE)
1BCB:0195 OUT 278, 7A (BYTE)
1BCB:01B8 OUT 278, 3A (BYTE)
1BCB:0195 OUT 278, 78 (BYTE)
1BCB:01B8 OUT 278, 38 (BYTE)
1BCB:0195 OUT 278, 72 (BYTE)
1BCB:01B8 OUT 278, 32 (BYTE)
1BCB:0195 OUT 278, 70 (BYTE)
1BCB:01B8 OUT 278, 30 (BYTE)
1BCB:0195 OUT 278, 6A (BYTE)
1BCB:01B8 OUT 278, 2A (BYTE)
1BCB:0195 OUT 278, 68 (BYTE)
1BCB:01B8 OUT 278, 28 (BYTE)
1BCB:0195 OUT 278, 62 (BYTE)
1BCB:01B8 OUT 278, 22 (BYTE)
1BCB:0195 OUT 278, 60 (BYTE)
1BCB:01B8 OUT 278, 20 (BYTE)
1BCB:0195 OUT 278, 5A (BYTE)
1BCB:01B8 OUT 278, 1A (BYTE)
1BCB:01C5 OUT 278, A2 (BYTE)
1BCB:01EC OUT 278, E2 (BYTE)
1BCB:020F OUT 278, A2 (BYTE)
1BCB:0219 OUT 278, B2 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:0244 OUT 278, F2 (BYTE)
1BCB:0267 OUT 278, B2 (BYTE)
1BCB:0272 IN 279, 96 (BYTE)
1BCB:02FD OUT 278, 40 (BYTE)
1BCB:0320 OUT 278, 00 (BYTE)
1BCB:02FD OUT 278, FA (BYTE)
1BCB:0320 OUT 278, BA (BYTE)
1BCB:02FD OUT 278, F8 (BYTE)
1BCB:0320 OUT 278, B8 (BYTE)
1BCB:02FD OUT 278, F2 (BYTE)
1BCB:0320 OUT 278, B2 (BYTE)
1BCB:02FD OUT 278, F0 (BYTE)
1BCB:0320 OUT 278, B0 (BYTE)
1BCB:02FD OUT 278, EA (BYTE)
1BCB:0320 OUT 278, AA (BYTE)
1BCB:02FD OUT 278, E8 (BYTE)
1BCB:0320 OUT 278, A8 (BYTE)
1BCB:02FD OUT 278, E2 (BYTE)
1BCB:0320 OUT 278, A2 (BYTE)
1BCB:02FD OUT 278, E0 (BYTE)
1BCB:0320 OUT 278, A0 (BYTE)
1BCB:02FD OUT 278, DA (BYTE)
1BCB:0320 OUT 278, 9A (BYTE)
1BCB:02FD OUT 278, D8 (BYTE)
1BCB:0320 OUT 278, 98 (BYTE)
1BCB:02FD OUT 278, D2 (BYTE)
1BCB:0320 OUT 278, 92 (BYTE)
1BCB:02FD OUT 278, D0 (BYTE)
1BCB:0320 OUT 278, 90 (BYTE)
1BCB:02FD OUT 278, CA (BYTE)
1BCB:0320 OUT 278, 8A (BYTE)
1BCB:02FD OUT 278, C8 (BYTE)
1BCB:0320 OUT 278, 88 (BYTE)
1BCB:02FD OUT 278, C2 (BYTE)
1BCB:0320 OUT 278, 82 (BYTE)
1BCB:02FD OUT 278, C0 (BYTE)
1BCB:0320 OUT 278, 80 (BYTE)
1BCB:02FD OUT 278, 7A (BYTE)
1BCB:0320 OUT 278, 3A (BYTE)
1BCB:02FD OUT 278, 78 (BYTE)
1BCB:0320 OUT 278, 38 (BYTE)
1BCB:02FD OUT 278, 72 (BYTE)
1BCB:0320 OUT 278, 32 (BYTE)
1BCB:02FD OUT 278, 70 (BYTE)
1BCB:0320 OUT 278, 30 (BYTE)
1BCB:02FD OUT 278, 6A (BYTE)
1BCB:0320 OUT 278, 2A (BYTE)
1BCB:02FD OUT 278, 68 (BYTE)
1BCB:0320 OUT 278, 28 (BYTE)
1BCB:02FD OUT 278, 62 (BYTE)
1BCB:0320 OUT 278, 22 (BYTE)
1BCB:02FD OUT 278, 60 (BYTE)
1BCB:0320 OUT 278, 20 (BYTE)
1BCB:02FD OUT 278, 5A (BYTE)
1BCB:0320 OUT 278, 1A (BYTE)
1BCB:02FD OUT 278, 58 (BYTE)
1BCB:0320 OUT 278, 18 (BYTE)
1BCB:02FD OUT 278, 52 (BYTE)
1BCB:0320 OUT 278, 12 (BYTE)
1BCB:02FD OUT 278, 50 (BYTE)
1BCB:0320 OUT 278, 10 (BYTE)
1BCB:02FD OUT 278, 4A (BYTE)
1BCB:0320 OUT 278, 0A (BYTE)
1BCB:02FD OUT 278, 48 (BYTE)
1BCB:0320 OUT 278, 08 (BYTE)
1BCB:02FD OUT 278, 42 (BYTE)
1BCB:0320 OUT 278, 02 (BYTE)
1BCB:02FD OUT 278, 40 (BYTE)
1BCB:0320 OUT 278, 00 (BYTE)
1BCB:02FD OUT 278, FA (BYTE)
1BCB:0320 OUT 278, BA (BYTE)
1BCB:02FD OUT 278, F8 (BYTE)
1BCB:0320 OUT 278, B8 (BYTE)
1BCB:02FD OUT 278, F2 (BYTE)
1BCB:0320 OUT 278, B2 (BYTE)
1BCB:032D OUT 278, 92 (BYTE)
1BCB:0338 IN 279, 96 (BYTE)
1BCB:0357 OUT 27A, 0C (BYTE)
1BCB:07CD OUT 278, 00 (BYTE)
1BCB:00E2 IN 278, 00 (BYTE)
1BCB:040B IN 278, 00 (BYTE)
1BCB:0423 OUT 278, 73 (BYTE)
1BCB:0435 OUT 27A, 0C (BYTE)
1BCB:043F IN 27A, CC (BYTE)
1BCB:0474 OUT 278, 05 (BYTE)
1BCB:0484 OUT 278, 85 (BYTE)
1BCB:04E8 OUT 278, 95 (BYTE)
1BCB:04F8 OUT 278, B5 (BYTE)
1BCB:04E8 OUT 278, 95 (BYTE)
1BCB:04F8 OUT 278, B5 (BYTE)
1BCB:04E8 OUT 278, 85 (BYTE)
1BCB:04F8 OUT 278, A5 (BYTE)
1BCB:04E8 OUT 278, 95 (BYTE)
1BCB:04F8 OUT 278, B5 (BYTE)
1BCB:04E8 OUT 278, 95 (BYTE)
1BCB:04F8 OUT 278, B5 (BYTE)
1BCB:04E8 OUT 278, 95 (BYTE)
1BCB:04F8 OUT 278, B5 (BYTE)
1BCB:04E8 OUT 278, 95 (BYTE)
1BCB:04F8 OUT 278, B5 (BYTE)
1BCB:04E8 OUT 278, 95 (BYTE)
1BCB:04F8 OUT 278, B5 (BYTE)
1BCB:04E8 OUT 278, 95 (BYTE)
1BCB:04F8 OUT 278, B5 (BYTE)
1BCB:0498 OUT 278, 85 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0398 OUT 278, 85 (BYTE)
1BCB:03AA OUT 27A, 04 (BYTE)
1BCB:03B8 OUT 278, C5 (BYTE)
1BCB:03C4 OUT 278, 85 (BYTE)
1BCB:03D6 OUT 27A, 0C (BYTE)
1BCB:04E8 OUT 278, 95 (BYTE)
1BCB:04F8 OUT 278, B5 (BYTE)
1BCB:04E8 OUT 278, 95 (BYTE)
1BCB:04F8 OUT 278, B5 (BYTE)
1BCB:04E8 OUT 278, 85 (BYTE)
1BCB:04F8 OUT 278, A5 (BYTE)
1BCB:04E8 OUT 278, 95 (BYTE)
1BCB:04F8 OUT 278, B5 (BYTE)
1BCB:04E8 OUT 278, 95 (BYTE)
1BCB:04F8 OUT 278, B5 (BYTE)
1BCB:04E8 OUT 278, 95 (BYTE)
1BCB:04F8 OUT 278, B5 (BYTE)
1BCB:04E8 OUT 278, 95 (BYTE)
1BCB:04F8 OUT 278, B5 (BYTE)
1BCB:04E8 OUT 278, 95 (BYTE)
1BCB:04F8 OUT 278, B5 (BYTE)
1BCB:04E8 OUT 278, 95 (BYTE)
1BCB:04F8 OUT 278, B5 (BYTE)
1BCB:0498 OUT 278, 85 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 86 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 86 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 86 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 86 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 86 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 86 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 86 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 86 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0474 OUT 278, 05 (BYTE)
1BCB:0484 OUT 278, 85 (BYTE)
1BCB:0398 OUT 278, 85 (BYTE)
1BCB:03AA OUT 27A, 04 (BYTE)
1BCB:03B8 OUT 278, C5 (BYTE)
1BCB:03C4 OUT 278, 85 (BYTE)
1BCB:03D6 OUT 27A, 0C (BYTE)
1BCB:04E8 OUT 278, 97 (BYTE)
1BCB:04F8 OUT 278, B7 (BYTE)
1BCB:04E8 OUT 278, 97 (BYTE)
1BCB:04F8 OUT 278, B7 (BYTE)
1BCB:04E8 OUT 278, 87 (BYTE)
1BCB:04F8 OUT 278, A7 (BYTE)
1BCB:04E8 OUT 278, 87 (BYTE)
1BCB:04F8 OUT 278, A7 (BYTE)
1BCB:04E8 OUT 278, 87 (BYTE)
1BCB:04F8 OUT 278, A7 (BYTE)
1BCB:04E8 OUT 278, 87 (BYTE)
1BCB:04F8 OUT 278, A7 (BYTE)
1BCB:04E8 OUT 278, 87 (BYTE)
1BCB:04F8 OUT 278, A7 (BYTE)
1BCB:04E8 OUT 278, 87 (BYTE)
1BCB:04F8 OUT 278, A7 (BYTE)
1BCB:04E8 OUT 278, 87 (BYTE)
1BCB:04F8 OUT 278, A7 (BYTE)
1BCB:05A5 OUT 278, 85 (BYTE)
1BCB:05B1 OUT 278, 95 (BYTE)
1BCB:05C4 IN 279, 86 (BYTE)
1BCB:05F7 OUT 278, A5 (BYTE)
1BCB:0602 IN 279, 86 (BYTE)
1BCB:0616 OUT 278, 95 (BYTE)
1BCB:0638 IN 279, 96 (BYTE)
1BCB:05F7 OUT 278, A5 (BYTE)
1BCB:0602 IN 279, 96 (BYTE)
1BCB:0616 OUT 278, 95 (BYTE)
1BCB:0638 IN 279, 96 (BYTE)
1BCB:05F7 OUT 278, A5 (BYTE)
1BCB:0602 IN 279, 96 (BYTE)
1BCB:0616 OUT 278, 95 (BYTE)
1BCB:0638 IN 279, 96 (BYTE)
1BCB:05F7 OUT 278, A5 (BYTE)
1BCB:0602 IN 279, 96 (BYTE)
1BCB:0616 OUT 278, 95 (BYTE)
1BCB:0638 IN 279, 96 (BYTE)
1BCB:05F7 OUT 278, A5 (BYTE)
1BCB:0602 IN 279, 96 (BYTE)
1BCB:0616 OUT 278, 95 (BYTE)
1BCB:0638 IN 279, 96 (BYTE)
1BCB:05F7 OUT 278, A5 (BYTE)
1BCB:0602 IN 279, 96 (BYTE)
1BCB:0616 OUT 278, 95 (BYTE)
1BCB:0638 IN 279, 86 (BYTE)
1BCB:05F7 OUT 278, A5 (BYTE)
1BCB:0602 IN 279, 86 (BYTE)
1BCB:0616 OUT 278, 95 (BYTE)
1BCB:0638 IN 279, 86 (BYTE)
1BCB:0474 OUT 278, 05 (BYTE)
1BCB:0484 OUT 278, 85 (BYTE)
1BCB:0398 OUT 278, 85 (BYTE)
1BCB:03AA OUT 27A, 04 (BYTE)
1BCB:03B8 OUT 278, C5 (BYTE)
1BCB:03C4 OUT 278, 85 (BYTE)
1BCB:03D6 OUT 27A, 0C (BYTE)
1BCB:04E8 OUT 278, 95 (BYTE)
1BCB:04F8 OUT 278, B5 (BYTE)
1BCB:04E8 OUT 278, 95 (BYTE)
1BCB:04F8 OUT 278, B5 (BYTE)
1BCB:04E8 OUT 278, 85 (BYTE)
1BCB:04F8 OUT 278, A5 (BYTE)
1BCB:04E8 OUT 278, 95 (BYTE)
1BCB:04F8 OUT 278, B5 (BYTE)
1BCB:04E8 OUT 278, 95 (BYTE)
1BCB:04F8 OUT 278, B5 (BYTE)
1BCB:04E8 OUT 278, 95 (BYTE)
1BCB:04F8 OUT 278, B5 (BYTE)
1BCB:04E8 OUT 278, 95 (BYTE)
1BCB:04F8 OUT 278, B5 (BYTE)
1BCB:04E8 OUT 278, 85 (BYTE)
1BCB:04F8 OUT 278, A5 (BYTE)
1BCB:04E8 OUT 278, 85 (BYTE)
1BCB:04F8 OUT 278, A5 (BYTE)
1BCB:0498 OUT 278, 85 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 86 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 86 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 86 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 86 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 86 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 86 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 86 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 86 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 86 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 86 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 86 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 86 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 86 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 86 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 86 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 86 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 86 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 86 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 86 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0474 OUT 278, 05 (BYTE)
1BCB:0484 OUT 278, 85 (BYTE)
1BCB:040B IN 278, 85 (BYTE)
1BCB:0423 OUT 278, 73 (BYTE)
1BCB:0435 OUT 27A, 0C (BYTE)
1BCB:043F IN 27A, CC (BYTE)
1BCB:0474 OUT 278, 05 (BYTE)
1BCB:0484 OUT 278, 85 (BYTE)
1BCB:04E8 OUT 278, 95 (BYTE)
1BCB:04F8 OUT 278, B5 (BYTE)
1BCB:04E8 OUT 278, 95 (BYTE)
1BCB:04F8 OUT 278, B5 (BYTE)
1BCB:04E8 OUT 278, 85 (BYTE)
1BCB:04F8 OUT 278, A5 (BYTE)
1BCB:04E8 OUT 278, 95 (BYTE)
1BCB:04F8 OUT 278, B5 (BYTE)
1BCB:04E8 OUT 278, 95 (BYTE)
1BCB:04F8 OUT 278, B5 (BYTE)
1BCB:04E8 OUT 278, 95 (BYTE)
1BCB:04F8 OUT 278, B5 (BYTE)
1BCB:04E8 OUT 278, 95 (BYTE)
1BCB:04F8 OUT 278, B5 (BYTE)
1BCB:04E8 OUT 278, 95 (BYTE)
1BCB:04F8 OUT 278, B5 (BYTE)
1BCB:04E8 OUT 278, 95 (BYTE)
1BCB:04F8 OUT 278, B5 (BYTE)
1BCB:0498 OUT 278, 85 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0398 OUT 278, 85 (BYTE)
1BCB:03AA OUT 27A, 04 (BYTE)
1BCB:03B8 OUT 278, C5 (BYTE)
1BCB:03C4 OUT 278, 85 (BYTE)
1BCB:03D6 OUT 27A, 0C (BYTE)
1BCB:03EE OUT 278, C5 (BYTE)
1BCB:03FA OUT 278, 85 (BYTE)
1BCB:04E8 OUT 278, 95 (BYTE)
1BCB:04F8 OUT 278, B5 (BYTE)
1BCB:04E8 OUT 278, 95 (BYTE)
1BCB:04F8 OUT 278, B5 (BYTE)
1BCB:04E8 OUT 278, 85 (BYTE)
1BCB:04F8 OUT 278, A5 (BYTE)
1BCB:04E8 OUT 278, 95 (BYTE)
1BCB:04F8 OUT 278, B5 (BYTE)
1BCB:04E8 OUT 278, 95 (BYTE)
1BCB:04F8 OUT 278, B5 (BYTE)
1BCB:04E8 OUT 278, 95 (BYTE)
1BCB:04F8 OUT 278, B5 (BYTE)
1BCB:04E8 OUT 278, 95 (BYTE)
1BCB:04F8 OUT 278, B5 (BYTE)
1BCB:04E8 OUT 278, 95 (BYTE)
1BCB:04F8 OUT 278, B5 (BYTE)
1BCB:04E8 OUT 278, 95 (BYTE)
1BCB:04F8 OUT 278, B5 (BYTE)
1BCB:0498 OUT 278, 85 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:040B IN 278, 95 (BYTE)
1BCB:0423 OUT 278, 73 (BYTE)
1BCB:0435 OUT 27A, 0C (BYTE)
1BCB:043F IN 27A, CC (BYTE)
1BCB:0474 OUT 278, 05 (BYTE)
1BCB:0484 OUT 278, 85 (BYTE)
1BCB:04E8 OUT 278, 95 (BYTE)
1BCB:04F8 OUT 278, B5 (BYTE)
1BCB:04E8 OUT 278, 95 (BYTE)
1BCB:04F8 OUT 278, B5 (BYTE)
1BCB:04E8 OUT 278, 85 (BYTE)
1BCB:04F8 OUT 278, A5 (BYTE)
1BCB:04E8 OUT 278, 85 (BYTE)
1BCB:04F8 OUT 278, A5 (BYTE)
1BCB:04E8 OUT 278, 85 (BYTE)
1BCB:04F8 OUT 278, A5 (BYTE)
1BCB:04E8 OUT 278, 85 (BYTE)
1BCB:04F8 OUT 278, A5 (BYTE)
1BCB:04E8 OUT 278, 85 (BYTE)
1BCB:04F8 OUT 278, A5 (BYTE)
1BCB:04E8 OUT 278, 85 (BYTE)
1BCB:04F8 OUT 278, A5 (BYTE)
1BCB:04E8 OUT 278, 85 (BYTE)
1BCB:04F8 OUT 278, A5 (BYTE)
1BCB:0498 OUT 278, 85 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0398 OUT 278, 85 (BYTE)
1BCB:03AA OUT 27A, 04 (BYTE)
1BCB:03B8 OUT 278, C5 (BYTE)
1BCB:03C4 OUT 278, 85 (BYTE)
1BCB:03D6 OUT 27A, 0C (BYTE)
1BCB:03EE OUT 278, C5 (BYTE)
1BCB:03FA OUT 278, 85 (BYTE)
1BCB:04E8 OUT 278, 95 (BYTE)
1BCB:04F8 OUT 278, B5 (BYTE)
1BCB:04E8 OUT 278, 95 (BYTE)
1BCB:04F8 OUT 278, B5 (BYTE)
1BCB:04E8 OUT 278, 85 (BYTE)
1BCB:04F8 OUT 278, A5 (BYTE)
1BCB:04E8 OUT 278, 85 (BYTE)
1BCB:04F8 OUT 278, A5 (BYTE)
1BCB:04E8 OUT 278, 85 (BYTE)
1BCB:04F8 OUT 278, A5 (BYTE)
1BCB:04E8 OUT 278, 85 (BYTE)
1BCB:04F8 OUT 278, A5 (BYTE)
1BCB:04E8 OUT 278, 85 (BYTE)
1BCB:04F8 OUT 278, A5 (BYTE)
1BCB:04E8 OUT 278, 85 (BYTE)
1BCB:04F8 OUT 278, A5 (BYTE)
1BCB:04E8 OUT 278, 85 (BYTE)
1BCB:04F8 OUT 278, A5 (BYTE)
1BCB:0498 OUT 278, 85 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:0515 OUT 278, A5 (BYTE)
1BCB:0521 OUT 278, 95 (BYTE)
1BCB:0537 OUT 278, 85 (BYTE)
1BCB:0543 OUT 278, 95 (BYTE)
1BCB:0561 IN 279, 96 (BYTE)
1BCB:07CD OUT 278, 00 (BYTE)
¡Que lo parió! Si hacemos lo mismo con los otros archivos
observaremos cosas similarmente asquerosas. ¡Pero no os desesperéis! ¡Pensad!
¡Pensad un cachito!
Por un lado se observa que la cantidad de accesos a la llave es variable por
cada ejecución del programa de protección, pero por otro lado se sabe que los
datos de la llave se utilizan para desencriptar al programa protegido, podemos
inferir entonces que lo que se lee es siempre lo mismo (pueden haber
variaciones con los OUTs pero los INs deberían ser siempre iguales).
Verificamos esta sospecha con el programa IOINS.C:
--------------------BEGIN IOINS.C--------------------------------------
/*
** IOINS.C - (c) 1998 - Maruja
** Muestra (en forma entendible) los datos capturados en operaciones IN
**
** Modo de uso: IOINS file
** Donde file es el nombre del archivo con los datos
*/
#include <>stdio.h>
#include <>stdlib.h>
/* Teseteo de Flags */
#define TEST_OUT(flag) (flag & 32768) /* bit 15 = OUT */
#define TEST_WORD(flag) (flag & 16384) /* bit 14 = WORD */
#define GET_PORT(flag) (flag & 2047)
/* Estructura basica */
typedef struct {
unsigned int cs,
ip,
flags,
data;
} TRAP;
int main (int can, char *arg[])
{
FILE *f;
TRAP i;
/* Verificar que se encuentre el nombre del file */
if (can < 2) {
fprintf (stderr,
"%s: Falta el nombre del archivo con los datos capturados\n\r",
arg[0]);
exit (-1);
}
/* Abrir archivo de datos */
if (! (f = fopen (arg[1], "rb")) ) {
fprintf (stderr, "%s: ", arg[0]);
perror (arg[1]);
exit (-1);
}
/* Mostrar informacion */
printf ("\nArchivo: '%s'", arg[1]);
for (;;) {
/* Leer estructura */
if (!fread (&i, sizeof (TRAP), 1, f)) break;
/* Mostrar en forma humana */
if (!TEST_OUT(i.flags)) {
printf ("\n%04X:%04X\tIN\t%03X, ", i.cs, i.ip,
GET_PORT(i.flags));
if (TEST_WORD(i.flags))
printf ("%04X\t(WORD)", i.data);
else printf ("%02X \t(BYTE)", i.data & 255);
}
}
printf ("\n");
return 0;
}
--------------------END IOINS.C----------------------------------------
Hagamos lo siguiente:
C:\>CC IOINS.C
..
C:\>IOINS 1.DAT > IN1.ASC
C:\>IOINS 2.DAT > IN2.ASC
C:\>IOINS 3.DAT > IN3.ASC
C:\>IOINS 4.DAT > IN4.ASC
C:\>IOINS 5.DAT > IN5.ASC
C:\>DIR C:\IN*.ASC
Volumen en unidad C es PINDONGA
Número de serie de volumen es 2D4B-1CD6
Directorio de C:\
IN1 ASC 13784 15/09/98 0:15
IN2 ASC 13784 15/09/98 0:16
IN3 ASC 13784 15/09/98 0:16
IN4 ASC 13784 15/09/98 0:17
IN5 ASC 13784 15/09/98 0:17
5 archivo(s) 68920 bytes
757334016 bytes libres
C:\>
[ Jejejejeje... La cantidad de INs es siempre la misma. Veamos si los datos tambien son los mismos... ]
C:\>FC IN1.ASC IN2.ASC
Comparando archivos IN1.ASC y IN2.ASC
***** IN1.ASC
Archivo: '1.DAT'
1BCB:0854 IN 278, 00 (BYTE)
***** IN2.ASC
Archivo: '2.DAT'
1BCB:0854 IN 278, 00 (BYTE)
*****
C:\>FC IN1.ASC IN3.ASC
Comparando archivos IN1.ASC y IN3.ASC
***** IN1.ASC
Archivo: '1.DAT'
1BCB:0854 IN 278, 00 (BYTE)
***** IN3.ASC
Archivo: '3.DAT'
1BCB:0854 IN 278, 00 (BYTE)
*****
C:\>FC IN1.ASC IN4.ASC
Comparando archivos IN1.ASC y IN4.ASC
***** IN1.ASC
Archivo: '1.DAT'
1BCB:0854 IN 278, 00 (BYTE)
***** IN4.ASC
Archivo: '4.DAT'
1BCB:0854 IN 278, 00 (BYTE)
*****
C:\>FC IN1.ASC IN5.ASC
Comparando archivos IN1.ASC y IN5.ASC
***** IN1.ASC
Archivo: '1.DAT'
1BCB:0854 IN 278, 00 (BYTE)
***** IN5.ASC
Archivo: '5.DAT'
1BCB:0854 IN 278, 00 (BYTE)
*****
C:\>
Jurujujája... Sabemos que los 5 archivos tienen los mismos datos leídos y que las operaciones son de a bytes (osea: 'IN AL,DX' y 'OUT DX,AL'). Para poner los datos de la llave en un programa emulador necesitamos un conversor .DAT a .ASM, el programa DBIN.C se encarga de esto:
--------------------BEGIN DBIN.C---------------------------------------
/*
** DBIN.C - (c) 1998 - Maruja
** Muestra (en forma de codigo para ensamblador) los datos
** capturados en operaciones IN en orden de aparicion
**
** Modo de uso: DBIN file
** Donde file es el nombre del archivo con los datos
**
** IMPORTANTE: LOS DATOS DEBEN SER TODOS BYTES!! (IN AL, XX)
*/
#include <>stdio.h>
#include <>stdlib.h>
/* Bytes por linea */
#define COLUMNAS 10
/* Teseteo de Flags */
#define TEST_OUT(flag) (flag & 32768) /* bit 15 = OUT */
#define TEST_WORD(flag) (flag & 16384) /* bit 14 = WORD */
#define GET_PORT(flag) (flag & 2047)
/* Estructura basica */
typedef struct {
unsigned int cs,
ip,
flags,
data;
} TRAP;
int main (int can, char *arg[])
{
FILE *f;
TRAP i;
int cols,
datos;
/* Verificar que se encuentre el nombre del file */
if (can < 2) {
fprintf (stderr,
"%s: Falta el nombre del archivo con los datos capturados\n\r",
arg[0]);
exit (-1);
}
/* Abrir archivo de datos */
if (! (f = fopen (arg[1], "rb")) ) {
fprintf (stderr, "%s: ", arg[0]);
perror (arg[1]);
exit (-1);
}
/* Mostrar informacion */
datos = 0;
cols = 0;
for (;;) {
/* Leer estructura */
if (!fread (&i, sizeof (TRAP), 1, f)) break;
/* Solamente si es IN */
if (!TEST_OUT(i.flags) ) {
/* Contador de cantidad de INs */
datos++;
/* Iniciar linea */
if (!cols) printf ("\n\t\tdb\t");
/* Mostrar en decimal */
printf ("%03u", i.data & 255);
if (++cols == COLUMNAS) cols = 0;
else printf (", ");
}
}
/* Mostrar cantidad de datos */
printf ("\n\nKEYBUF\t\tEQU\t%u\n", datos);
return 0;
}
--------------------END DBIN.C-----------------------------------------
Ojo: Este programa asume que los INs son de la forma 'IN
AL,DX'. Si el lector está experimentando con otra implementación del programa
de protección debe hacer los cambios pertinentes.
Al compilar y ejecutar obtenemos:
C:\>CC DBIN.C
..
C:\>DBIN 1.DAT > 1.ASM
C:\>DBIN 2.DAT > 2.ASM
C:\>DBIN 3.DAT > 3.ASM
C:\>DBIN 4.DAT > 4.ASM
C:\>DBIN 5.DAT > 5.ASM
C:\>DIR C:\?.ASM
Volumen en unidad C es PINDONGA
Número de serie de volumen es 2D4B-1CD6
Directorio de C:\
1 ASM 2468 16/09/98 22:14
2 ASM 2468 16/09/98 22:14
3 ASM 2468 16/09/98 22:14
4 ASM 2468 16/09/98 22:14
5 ASM 2468 16/09/98 22:14
5 archivo(s) 12340 bytes
756776960 bytes libres
C:\>FC 1.ASM 2.ASM
Comparando archivos 1.ASM y 2.ASM
FC: no se encontraron diferencias
C:\>FC 1.ASM 3.ASM
Comparando archivos 1.ASM y 3.ASM
FC: no se encontraron diferencias
C:\>FC 1.ASM 4.ASM
Comparando archivos 1.ASM y 4.ASM
FC: no se encontraron diferencias
C:\>FC 1.ASM 5.ASM
Comparando archivos 1.ASM y 5.ASM
FC: no se encontraron diferencias
C:\>TYPE 1.ASM
db 000, 085, 170, 000, 204, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 000, 000, 204, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 134, 134, 150, 134, 134, 150
db 134, 150, 134, 134, 134, 150, 150, 134, 134, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 134
db 134, 134, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 134, 134, 134, 134, 134, 134
db 134, 134, 150, 150, 150, 134, 150, 134, 134, 150
db 150, 150, 150, 134, 134, 150, 134, 134, 150, 134
db 150, 134, 134, 134, 150, 150, 133, 204, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 149, 204, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150,
KEYBUF EQU 444
C:\>
Sabiendo que:
el programa KEYEMU1.ASM puede engañar perfectamente al archivo de protección:
--------------------BEGIN KEYEMU1.ASM-----------------------------------
;
;KEYEMU1.ASM - (c) 1998 - Maruja
;
;Programa residente de prueba que emula la llave de harware
;marca 'SOStuare Look' que protege a un programa berreta de facturacion
;
;Confunde al programa de proteccion pero no al programa protegido
;
.model tiny
.code
org 100h
inicio: jmp Arranque
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
IN_BYTE EQU 0ECh ;IN AL, DX
IPanterior dw 0 ;CS:IP Instruccion anterior
CSanterior dw 0
lpt11 dw ? ;Direccion base (1er registro) de LPT1
lpt13 dw ? ;Direccion 3er registro de LPT1
KEYBUF EQU 444
keyindex dw 0
keydata db 000, 085, 170, 000, 204, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 000, 000, 204, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 134, 134, 150, 134, 134, 150
db 134, 150, 134, 134, 134, 150, 150, 134, 134, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 134
db 134, 134, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 134, 134, 134, 134, 134, 134
db 134, 134, 150, 150, 150, 134, 150, 134, 134, 150
db 150, 150, 150, 134, 134, 150, 134, 134, 150, 134
db 150, 134, 134, 134, 150, 150, 133, 204, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 149, 204, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
SetTrap: push ax
push bp
mov bp, sp
mov ax, [bp+8] ;Obtener flags de la pila
or ah, 1 ;Activar bit T
mov [bp+8], ax ;Colocar nuevos flags en la pila
push dx
push ds
push cs
pop ds
mov ax, 2501h ;Setear nuestro vector de int TRAP
lea dx, Trap
int 21h
pop ds
pop dx
pop bp
pop ax
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; INSTRUCCION ORIGINAL EN EL ARCHIVO DE PROTECCION
; QUE FUE CAMBIADA POR EL 'INT 90h' (CD 90)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
ADD DI, AX
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
iret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
UnTrap: push ax
push bp
mov bp, sp
mov ax, [bp+8] ;Obtener flags de la pila
and ah, 0FEh ;Desactivar bit T
mov [bp+8], ax ;Colocar nuevos flags en la pila
mov cs:keyindex, 0 ;Resetear posicion en buffer de llave
pop bp
pop ax
iret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Trap:
SP_INICIAL EQU $
push cx ;Salvar registros utilizados
push si
push ds
push bp
SP_FINAL EQU $
cmp cs:CSanterior, 0 ;CSanterior tiene un valor incorrecto ?
jz Trap_salir ;Si: salir
mov si, cs:CSanterior
mov ds, si
mov si, cs:IPanterior ;DS:SI = CS:IP instruccion anterior
mov cl, byte ptr [si] ;Obtener opcode
cmp cl, IN_BYTE ;El opcode es IN_BYTE ?
jne Trap_salir ;No: salir
push cs
pop ds
cmp dx, lpt11 ;Acceso a alguno de los puertos LPT1 ?
jb Trap_salir
cmp dx, lpt13
ja Trap_salir ;No: salir
call EmularKey ;Si: emular llave
Trap_salir: mov bp, sp ;Guardar CS:IP de proxima instruccion
mov si, [bp + (SP_FINAL-SP_INICIAL)*2 + 2 ]
mov cs:CSanterior, si
mov si, [bp + (SP_FINAL-SP_INICIAL)*2 ]
mov cs:IPanterior, si
pop bp ;Recuperar registros y salir
pop ds
pop si
pop cx
iret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
EmularKey: ;En AL pone el mismo dato que pondria la llave
lea si, keydata ;Posicionarse en el buffer
add si, keyindex
mov al, byte ptr [si] ;Obtener dato de la llave ;)
inc keyindex
cmp keyindex, KEYBUF ;Ya llego al ultimo dato ?
jne EK_salir
mov keyindex, 0 ;Si: resetear posicion en el buffer
EK_salir: ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
FINRESID EQU $ ;Aca termina el residente
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
SetearVectores: mov ax, 2590h ;Hacer que una INT 90h ejecute el
lea dx, SetTrap ;codigo 'SetTrap'
int 21h
mov ax, 2591h ;Hacer que una INT 91h ejecute el
lea dx, UnTrap ;codigo 'UnTrap'
int 21h
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
GetLPT1: push es ;Obtener registros de LPT1
mov di, 40h
mov es, di
mov di, 8 ;ES:DI = 0040:0008h
mov ax, word ptr es:[di]
mov lpt11, ax ;lpt11 = Registro 1 LPT1
add ax, 2
mov lpt13, ax ;lpt13 = Registro 3 LPT1
pop es
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
mok db 13, 10, 'OK', 13, 10, '$'
myaestoy db 13, 10, 'Ya estaba residente', 13, 10, '$'
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Arranque: push ds ;El offset del vector de
xor si, si ;interrupcion 90h es igual
mov ds, si ;al offset de la SetTrap ?
mov si, 240h
mov ax, word ptr [si]
pop ds
cmp ax, offset SetTrap
jne A_instalar ;No: instalar residente
lea dx, myaestoy ;Como el residente ya estaba
mov ah, 9 ;instalado mostrar mensaje
int 21h ;y salir
mov ax, 4C00h
int 21h
A_instalar: call GetLPT1 ;Obtener direcciones de LPT1
call SetearVectores ;Setear nuevos vectores de ints
lea dx, mok ;Mostrar mensaje 'OK'
mov ah, 9
int 21h
mov ax, 3100h ;Terminar y quedar residente
lea dx, FINRESID
shr dx, 4
inc dx
int 21h
end inicio
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
--------------------END KEYEMU1.ASM-------------------------------------
Descripción de las variables:
keydata:
Es un buffer con datos que el programa de protección necesita para desencriptar
al archivo protegido (aquí están los mismos datos que se leerían de la llave si
estuviera conectada).
keyindex:
Esta variable indica la posición dentro del buffer (número de órden de
lectura).
KEYBUF:
Definición que contiene la longitud del buffer.
Descripción de las rutinas:
Trap:
Rastrea la ejecución de todas las instrucciones y, cuando encuentra un 'IN AL,
DX' llama a la rutina 'EmularKey'.
EmularKey:
Pone en AL el dato que corresponde para que el programa de protección se 'coma'
que la instrucción recién ejecutada (la IN) tuvo acceso a la llave.
Este programa funciona de la siguiente manera:
Antes de realizar los accesos a LPT1, el programa de protección modificado
activa el sistema de rastreo con la 'INT 90h'. Si la llave no está conectada,
luego de ejecutarse una instrucción 'IN AL,DX' el registro AL contiene basura,
en ése momento la rutina Trap toma el control y ejecuta un EmularKey que coloca
en AL el mismo valor que se hubiera leído del puerto si la llave hubiese estado
conectada.
Un ejemplo gráfico de este procedimiento puede ser:
EJECUCION NORMAL
EJECUCION UTILIZANDO KEYEMU1
Si el programa LPT1CAP1 está residente debemos quitarlo, ya sea resetenado la máquina o utilizando algún programa preparado para tal fín (como ser el par MARK/RELEASE), luego sacamos la llave y realizamos:
C:\>TASM KEYEMU1.ASM
...
C:\>TLINK /t KEYEMU1
...
C:\>REN PROTECT.EXE MODIFIC.EXE
C:\>COPY ORIGINAL.EXE PROTECT.EXE
C:\>GESTION.EXE
* El módulo 'SOStuare Look' con número de serie: 00-00000 no está instalado.
C:\>DEL PROTECT.EXE
C:\>COPY MODIFIC.EXE PROTECT.EXE
C:\>KEYEMU1
OK
C:\>GESTION.EXE
...
...
...
...
...
...
...
+---------------------------------------------------------------+
| El módulo SOStuare Look ha sido retirado |
| Oprima cualquier tecla para reiniciar el sistema |
+---------------------------------------------------------------+
Se ha engañado totalmente al archivo de protección pero el
programa protegido dijo: '¡No contaban con mi astucia!'.
Calma calma que no panda el cúnico. El chipote chillón es nuestro.
Si ejecutamos el programa original (sín las modificaciones en el archivo de
protección) y, en algún momento sacamos la llave, después de un instante
aparecerá el mensaje anterior, esto significa que el programa protegido accede
al puerto paralelo en forma periódica verificando la existencia de la llave. No
sabemos para qué utiliza estos datos pero, utilizando el método KISS (Keep It
Simple, Stupid! -técnica que deberían adoptar los infelices que hicieron el
programa de factucarión-) asumamos, como primera medida, que el programa
protegido no usa para un pito los datos de ésta.
Con esto último en mente, tenemos que debuggear la rutina del programa de
facturación que accede a la llave y cancelarla (si esto no llega a funcionar
hay que emular de la misma forma que se hizo con el archivo de protección).
Sín el residente KEYEMU1.COM, con la llave colocada en el puerto, y con el
programa original sín modificaciones, corremos el debugger:
C:\>DEL PROTECT.EXE
C:\>COPY ORIGINAL.EXE PROTECT.EXE
C:\>LDR GESTION.EXE
Loading C:\GESTION.EXE
...
...
:bpint 21 ah=4c
:x
Break Point at 1AE3:01A3
1AE3:018F E85500 CALL 01E7
1AE3:0192 A16401 MOV AX, [0164]
1AE3:0195 E83500 CALL 01CD
1AE3:0198 BB1502 MOV BX, 0215
1AE3:019B E80700 CALL 01A5
1AE3:019E A16201 MOV AX, [0162]
1AE3:01A1 B44C MOV AH, 4C
> 1AE3:01A3 CD21 INT 21 ;Terminate process
...
[ El programa de protección termina y le devuelve el control al programa protegido ]
:t
> 0123:109E 90 NOP
0123:109F 90 NOP
0123:10A0 E8CC00 CALL 116F
...
[ Inicio del código de la interrupción tipo 21h ]
:p ret
> FDC9:423C CF IRET
...
[ Punto final de la función 4Ch del DOS ]
:t
> 0E0B:05D6 7303 JAE 05BD
...
[ Inicio del código del programa de facturación ya desencriptado ]
:bpio 278
:bpio 279
:bpio 27a
:x
Break Point at 0E0B:0AF9
0E0B:0AF8 EC IN AL, DX
> 0E0B:0AF9 EB01 JMP 0AFC
...
[ Código que accede al puerto paralelo ]
:p ret
> 0E0B:12D9 C3 RET
...
[ Termina la ejecución del código que accede a la llave ]
:t
0E0B:1300 2E8C0E0105 MOV CS:[0501], CS
0E0B:1307 E8CEF7 CALL 0ADD
> 0E0B:130F 2E803EFE0401 CMP BYTE PTR CS:[04FE], 01 ;CS:[04FE]=01
0E0B:1315 7502 JNZ 1319
0E0B:1317 EB0B JMP 1324
0E0B:1319 E8C1F7 CALL 0ADD
...
[ 0E0B:0ADD es la dirección de la rutina que accede a la llave directamente, pero esta sección de código genera dos accesos, por lo tanto, asumimos que es parte de un sistema de verificación de los datos de ésta, veamos como termina... ]
:p ret
:t
0E0B:0892 2EFF1EDD03 CALL FAR CS:[03DD]
> 0E0B:0897 2E803ED50300 CMP BYTE PTR CS:[03D5], 00 ;CS:[03D5]=00
0E0B:089D 7409 JZ 08A8
0E0B:089F 2EC706D8031200 MOV WORD PTR CS:[03D8], 0012
0E0B:08A6 EB07 JMP 08AF
0E0B:08A8 2EC706D8038F00 MOV WORD PTR CS:[03D8], 008F
0E0B:08AF 2EC606D40300 MOV BYTE PTR CS:[03D4], 00
0E0B:08B5 2E833EF80300 CMP WORD PTR CS:[03D8], 00
0E0B:08BB 7405 JZ 08C2
0E0B:08BD 2EFF0ED803 DEC WORD PTR CS:[03D8]
0E0B:08C2 CF IRET
[ Era de esperar: el sistema que accede a la llave está dentro de una interrupción -la última instrucción es un IRET-, veamos cuál es... ]
:p ret
:t
3799:00F0 55 PUSH BP
3799:00F1 CD16 INT 16
> 3799:00F3 5D POP BP
[ ¡Já! En vez de utilizar la obvia interrupción Timer Tick
usa la 16h del BIOS que se encarga de casi todo lo que tiene que ver con el
teclado.
Todo lo que está desde 0E0B:0892 hasta el IRET es sumamente sospechoso, por lo
tanto, vamos a finalizar la interrupción justamente en ése punto... ]
:a e0b:892
0E0B:0892 iret
0E0B:0893
[ Ahora limpiamos todos los breakpoints, sacamos la llave, y dejamos correr al programa como si nada... ]
:bc *
:x
...
...
...
...
...
C:\>
Hemos solucionado el problema: el programa protegido ya no accede a la llave. Tenemos que hacer algo para que el IRET se ponga en forma automática. No podemos modificar al programa de facturación porque está encriptado, por lo tanto, tendremos que provocar otro desvío en el archivo de protección en algún lugar después de la desencripción. El mejor lugar para esto es el momento en que dicho programa termina su ejecución. Recordando esa parte del código:
1AE3:018F E85500 CALL 01E7
1AE3:0192 A16401 MOV AX, [0164]
1AE3:0195 E83500 CALL 01CD
1AE3:0198 BB1502 MOV BX, 0215
1AE3:019B E80700 CALL 01A5
1AE3:019E A16201 MOV AX, [0162]
1AE3:01A1 B44C MOV AH, 4C
1AE3:01A3 CD21 INT 21
Con la gran ayuda del 'disquetitor':
C:\>DISQUETITOR MODIFIC.EXE /W
Buscamos la secuencia:
E8 55 00 A1 64 01 E8 35 00 BB 15 02 E8 07 00 A1 62 01 B4 4C CD 21
y reemplazamos el 'B4 4C' con un 'CD 92' quedando:
E8 55 00 A1 64 01 E8 35 00 BB 15 02 E8 07 00 A1 62 01 CD 92 CD 21
luego se graban los cambios y, después de modificar al programa de emulación anterior, obtenemos a KEYEMU.ASM:
--------------------BEGIN KEYEMU.ASM------------------------------------
;
;KEYEMU.ASM - (c) 1998 - Maruja
;
;Programa residente que emula a la perfeccion a la llave de hardware
;marca 'SOStuare Look' que protege a un programa berreta de facturacion
;
.model tiny
.code
org 100h
inicio: jmp Arranque
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
IN_BYTE EQU 0ECh ;IN AL, DX
IPanterior dw 0 ;CS:IP Instruccion anterior
CSanterior dw 0
lpt11 dw ? ;Direccion base (1er registro) de LPT1
lpt13 dw ? ;Direccion 3er registro de LPT1
IRET_CODE EQU 0CFh ;Opcode de 'IRET'
OFF_PROTEGIDO EQU 0892h ;Offset del programa protegido en el
;que se anula el llamado a la rutina
;de la llave
KEYBUF EQU 444
keyindex dw 0
keydata db 000, 085, 170, 000, 204, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 000, 000, 204, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 134, 134, 150, 134, 134, 150
db 134, 150, 134, 134, 134, 150, 150, 134, 134, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 134
db 134, 134, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 134, 134, 134, 134, 134, 134
db 134, 134, 150, 150, 150, 134, 150, 134, 134, 150
db 150, 150, 150, 134, 134, 150, 134, 134, 150, 134
db 150, 134, 134, 134, 150, 150, 133, 204, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 149, 204, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150, 150, 150, 150, 150, 150, 150
db 150, 150, 150, 150
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
SetTrap: push ax
push bp
mov bp, sp
mov ax, [bp+8] ;Obtener flags de la pila
or ah, 1 ;Activar bit T
mov [bp+8], ax ;Colocar nuevos flags en la pila
push dx
push ds
push cs
pop ds
mov ax, 2501h ;Setear nuestro vector de int TRAP
lea dx, Trap
int 21h
pop ds
pop dx
pop bp
pop ax
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; INSTRUCCION ORIGINAL EN EL ARCHIVO DE PROTECCION
; QUE FUE CAMBIADA POR EL 'INT 90h' (CD 90)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
ADD DI, AX
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
iret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Trap:
SP_INICIAL EQU $
push cx ;Salvar registros utilizados
push si
push ds
push bp
SP_FINAL EQU $
cmp cs:CSanterior, 0 ;CSanterior tiene un valor incorrecto ?
jz Trap_salir ;Si: salir
mov si, cs:CSanterior
mov ds, si
mov si, cs:IPanterior ;DS:SI = CS:IP instruccion anterior
mov cl, byte ptr [si] ;Obtener opcode
cmp cl, IN_BYTE ;El opcode es IN_BYTE ?
jne Trap_salir ;No: salir
push cs
pop ds
cmp dx, lpt11 ;Acceso a alguno de los puertos LPT1 ?
jb Trap_salir
cmp dx, lpt13
ja Trap_salir ;No: salir
call EmularKey ;Si: emular llave
Trap_salir: mov bp, sp ;Guardar CS:IP de proxima instruccion
mov si, [bp + (SP_FINAL-SP_INICIAL)*2 + 2 ]
mov cs:CSanterior, si
mov si, [bp + (SP_FINAL-SP_INICIAL)*2 ]
mov cs:IPanterior, si
pop bp ;Recuperar registros y salir
pop ds
pop si
pop cx
iret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
EmularKey: ;En AL pone el mismo dato que pondria la llave
lea si, keydata ;Posicionarse en el buffer
add si, keyindex
mov al, byte ptr [si] ;Obtener dato de la llave ;)
inc keyindex
cmp keyindex, KEYBUF ;Ya llego al ultimo dato ?
jne EK_salir
mov keyindex, 0 ;Si: resetear posicion en el buffer
EK_salir: ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
SetJMP: push si
push ds
push bx
push ax
mov ah, 51h ;Obtener direccion del PSP
int 21h
mov ds, bx
mov si, 0Ch ;El offset 0Ch del PSP contiene el
;segmento del codigo al que se debe
;volver cuando termine el programa de
;proteccion
mov bx, word ptr [si] ;Cancelar llamada a la rutina de
mov ds, bx ;lectura de la llave
mov si, OFF_PROTEGIDO
mov byte ptr [si], IRET_CODE
pop ax
pop bx
pop ds
pop si
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; INSTRUCCION ORIGINAL EN EL ARCHIVO DE PROTECCION
; QUE FUE CAMBIADA POR EL 'INT 92h'
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
MOV AH, 4Ch
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
iret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
FINRESID EQU $ ;Aca termina el residente
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
SetearVectores: mov ax, 2590h ;Hacer que una INT 90h ejecute el
lea dx, SetTrap ;codigo 'SetTrap'
int 21h
mov ax, 2592h ;Hacer que una INT 92h ejecute el
lea dx, SetJMP ;codigo 'SetJMP'
int 21h
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
GetLPT1: push es ;Obtener registros de LPT1
mov di, 40h
mov es, di
mov di, 8 ;ES:DI = 0040:0008h
mov ax, word ptr es:[di]
mov lpt11, ax ;lpt11 = Registro 1 LPT1
add ax, 2
mov lpt13, ax ;lpt13 = Registro 3 LPT1
pop es
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
mok db 13, 10, 'OK', 13, 10, '$'
myaestoy db 13, 10, 'Ya estaba residente', 13, 10, '$'
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Arranque: push ds ;El offset del vector de
xor si, si ;interrupcion 90h es igual
mov ds, si ;al offset de la SetTrap ?
mov si, 240h
mov ax, word ptr [si]
pop ds
cmp ax, offset SetTrap
jne A_instalar ;No: instalar residente
lea dx, myaestoy ;Como el residente ya estaba
mov ah, 9 ;instalado mostrar mensaje
int 21h ;y salir
mov ax, 4C00h
int 21h
A_instalar: call GetLPT1 ;Obtener direcciones de LPT1
call SetearVectores ;Setear nuevos vectores de ints
lea dx, mok ;Mostrar mensaje 'OK'
mov ah, 9
int 21h
mov ax, 3100h ;Terminar y quedar residente
lea dx, FINRESID
shr dx, 4
inc dx
int 21h
end inicio
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
--------------------END KEYEMU.ASM--------------------------------------
Explicación de las variables:
IRET_CODE:
Definición que contiene el código de operación de la instrucción IRET.
OFF_PROTEGIDO:
Definición que contiene el offset dentro del programa protegido donde se pondrá
el IRET (0892h).
Explicación de las rutinas:
SetJMP:
La ejecuta el programa de protección a través de un 'INT 92h' colocado antes de
terminar la ejecución del mismo. Obtiene el segmento del programa protegido a
través del PSP, coloca el IRET en el offset que corresponde de ése segmento, y
ejecuta la instrucción original que fué 'tapada' por el 'INT 92h' (en este caso
un 'MOV AH, 4Ch').
NOTA: Observar que el segmento del punto de entrada del programa protegido
luego de la desencripción es el mismo que el segmento de la rutina de
interrupción que accede a la llave.
Si sacamos la llave, activamos al programa de protección modificado, compilamos
y ejecutamos al programa de emulación...
C:\>DEL PROTECT.EXE
C:\>REN MODIFIC.EXE PROTECT.EXE
C:\>TASM KEYEMU.ASM
...
C:\>TLINK /t KEYEMU
...
C:\>KEYEMU
OK
C:\>GESTION
...
...
...
...
...
...
...
...
C:\>
El programa funciona sín llave de hardware.
Para finalizar, se ilustra el comportamiento de KEYEMU:
En Inglés:
En Castellano: