You are on page 1of 34

Assembly y Cracking Elemental 1

Original en Inglés por Greythorne the Technomancer


Traducido al castellano por eidan yoson

Introducción a numeración binaria y hexadecimal

En general, la matemática diaria es base 10 (decimal), aunque la


tendencia de los constructores de PCs fue usar base 2 (binario).

El binario fue la elección simplemente porque OFF y ON son términos


fáciles en electrónica y este modelo encaja bien con 1's y 0's.
En algún momento, alguien decidió que continuar con numeración
binaria era algo tedioso para los humanos y se propuso que los
números se vean parecidos a los de la aritmética decimal, pero
conservando la progresión con potencias de 2 para que la conversión a
binario sea fácil.

De esta forma se popularizó el hexadecimal (base 16).


Qué tiene esto que ver con crackers o programadores assembly?
TODO. Si no se comprende cómo operar con hexadecimales y cómo
convertir entre binario y hexa, es imposible depurar (reversar)
cualquier programa.
En cualquier sistema de numeración, siempre se sigue esta simple
regla: en una base B, los dígitos se numeran desde cero hasta B-1

Que significa lo anterior?, por ejemplo que en base 10 tenemos diez


dígitos, del 0 al 9. En binario tenemos sólo dos: 0 y 1, y para base 16
(hexa) tenemos 16 dígitos. Por simplicidad, se usan los números del 0 a
9 y las seis primeras letras del alfabeto.

0 1 2 3 4 5 6 7 8 9AB C DE F

(estos dígitos valen 0-15 al convertirlos al sistema decimal)

la cuenta es similar a la del sistema decimal:

... E F 10 11 12 ... 18 19 1A 1B 1C 1D 1E 1F 20 21 22 ...

10 en hexa es 16 decimal, 20 hexa es 32, 30h es 48, 40h es 64 etc.

Una cuenta en sistema binario sería como sigue:

0 1 10 11 100 101 110 111 1000...


Asi 10 (en cualquier base) siempre es igual a B, la base misma -
Equivale a decir que 10 (binario) is 2 decimal, 10 (octal) es 8 decimal,
etc

Bien, si yo tuviese 16 dedos podría sacar la cuenta como lo hacen los


niños, pero como no los tengo, cómo calcular cuánto es A9h en base
10?

Ya sabemos que base 16 se amolda a potencias de 2, lo que no es difícil


de manejar. Una vez que uno aprende a convertir un número a
binario, es fácil cambiar de base a cualquier número, haciéndolo en
dos etapas: primero se convierte de una base a binario y luego de
binario a la otra.

Conversión de Decimal a Binario

A esto me gusta llamarlo 'matemática del resto'.

Básicamente, en lugar de una suma repetida, contando hasta un dígito


hexa, usaremos división repetida para acelerar el proceso.

Nuestros amigos DIV y MOD

En computación, los datos se almacenan como números enteros, ya sea


como una larga serie de dígitos en cualquier base más la ubicación del
punto decimal (o para hablar generalmente para cualquier base, el
punto raíz), o como partes de una fracción (numerador, denominador
y cantidad a sumar en tres partes separadas). Hay por cierto otros
métodos con números imaginarios, pero esto cae fuera de los límites de
esta lección.

Usando números enteros, la división se hace tal como lo hemos


aprendido en base 10, de a un dígito por vez y recordando el resto de
cada etapa. Para cada operación de división, tenemos 2 respuestas: el
cociente (DIV) y el resto (MOD). Aunque estamos más familiarizados
con DIV, MOD tiene interesantes propiedades usadas en programas de
computación, específicamente en randomización y scroll de menús.

Usaremos como notación 47 MOD 4, o 47%4 cuando queramos decir


"dividir 47 por 4 y obtener el resto", ya que el signo "%" se usa como
símbolo para MOD en lenguajes de alto nivel como el C.
para nuestro caso: 47/4 = 11, resto = 3

DIV = 11, MOD = 3

y también: 47%4 = 3 (47 MOD 4 es igual a 3)

Con estos conocimientos podemos comenzar con la conversión de


bases entre decimal y binario (no es tan feo, no se preocupe).
Quisiera que primero conozca que 47 en binario es 101111. Ahora voy
a mostrarle como deducirlo matemáticamente.
Básicamente, dividimos en forma repetitiva nuestro número por la
base binaria (2) y tomamos cada MOD (resto) como el próximo dígito
binario.

47 / 2 nos dá DIV=23 MOD=1, string binario = 1


23 / 2 nos dá DIV=11 MOD=1, string binario = 11
11/2 nos dá DIV=5 MOD=1, string binario = 111
5/2 nos dá DIV=2 MOD=1, string binario = 1111
2/2 nos dá DIV=1 MOD=0, string binario = 01111
1/2 nos dá DIV=0 MOD=1, string binario = 101111

Note que el string se construye de derecha a izquierda, al revés de


cómo uno lee. Esta es una característica del sistema de numeración
arábigo que incrementa el valor de los dígitos de derecha a izquierda
Esto puede parecer tonto en un principio, pero las máquinas requieren
tal nivel de instrucción para hacer aquello que nosotros sabemos hacer
desde hace tanto que olvidamos los basamentos de lo que es un
número: supongamos el 2041; lo tomamos en su totalidad, pero
sabemos muy bien que el 2 tiene mucho más valor que el 4 o el 1. En
cambio las computadoras requieren hacer esto de a pasos.

Escribiremos un programa en pseudocódigo (una mezcla de lenguaje


cotidiano con lenguaje de computación). Es de gran ayuda escribir en
pseudocódigo antes de pasar el programa a un lenguaje concreto.

Viendo nuestro anterior ejemplo, podemos determinar cuándo se


cumplió la operación consultando si DIV = 0. Nuestro programa sería:

1. Obtener el valor DIV (del usuario o del mismo programa)


2. Dividir DIV por 2, dejar el cociente en DIV y el resto en MOD
3. Almacenar MOD como próximo dígito de un string RES
4. Repetir las acciones 2 y 3 hasta que DIV = 0 (inclusive)
5. Informar el resultado RES
Conversión de un número binario a hexadecimal

Como 24 = 16 cada 4 dígitos del string binario, tendremos un dígito


hexa. Notar que también aquí hay que ir de derecha a izquierda como
en las operaciones con números decimales. Usando nuestro ejemplo del
47:

101111

Lo separamos en grupos de a 4:

10 | 1111

Y ahora consultamos la siguiente tabla de conversión:

0000 = 0 ........ 1000 = 8


0001 = 1 ........ 1001 = 9
0010 = 2 ........ 1010 = A
0011 = 3 ........ 1011 = B
0100 = 4 ........ 1100 = C
0101 = 5 ........ 1101 = D
0110 = 6 ........ 1110 = E
0111 = 7 ........ 1111 = F

Por lo que nuestro número (10 | 1111) , se convierte en:


0010 = 2h y 1111 = Fh

por demás simple, 101111 es 2F en hexadecimal o, más rigurosamente:

47 (dec) = 101111 (bin) = 2F (hex)

Si por ejemplo quisiésemos convertir a base octal, debemos separar de


a tres bits, o sea que para el 47 decimal hacemos:

101 | 111

111 = 7
101 = 5

47 (dec) = 101111 (bin) = 2F (hex) = 57 (oct)

Ahora que usted conoce las relaciones entre las bases, le será mucho
más fácil leer código assembly, y posiblemente en un futuro próximo,
comenzar a entender qué está leyendo
Se aprende mejor con ejemplos

Frecuentemente, los buenos ejemplos son breves y van justo al punto.


Puede ser muy difícil para un programador assembly novato obtener
alguna conclusión valedera de un programa largo e indocumentado (o
malamente comentado) que parece más una sopa de letras que código.

Estos ejemplos pueden cortarse y pegarse en sus propios programas.

Se me ha preguntado recientemente cuál es el mejor lugar para


encontrar información sobre Assembly. Un lugar (mala respuesta) es
Internet. Sin embargo, mi información favorita proviene de un medio
más tradicional: los libros. Y los mejores libros sobre Assembly son sin
duda los más viejos, los que muestran cómo optimizar código de 8086.
Yo prefiero comprar manuales de segunda mano por un dólar que
contienen un tesoro en código, siempre necesario, como por ejemplo el
legendario manual de Peter Norton.

En las siguientes páginas veremos el código necesario para


realizar las siguientes funciones:

Operaciones con strings


Mostrando Numeros en Assembly
Operaciones con Archivos
Search Funciones de Búsqueda
Otros Códigos útiles

Lo básico con Strings


(HELLO WORLD)

La primer cosa que un instructor de programación debe mostrar es


cómo sacar mensajes por pantalla (esto se denomina
"representación"). El mensaje más popularmente usado es "Hello,
World".

Ahora mismo vamos a ver un par de formas de cómo hacerlo. No hay


necesidad de entender ahora cómo funciona, sino cuáles son las reglas
básicas para su utilización en futuros programas.

También se mostrará como parte de un programa para que se vea lo


fácil que es incorporar esta pieza de código a otras de mayor tamaño.

En DOS

Llamado a la Rutina:

message db 'hello world','$'


mov dx,offset message
call DisplayString

La Rutina: (Pequeña porque DOS se encarga casi de todo)

; muestra strigs apuntados por dx usando: int 21h, ah=9

DisplayString:
mov ax,cs
mov ds,ax
mov ah,9 ; Función DOS: mostrar display
int 21h ; Llama la interrupción del DOS
ret

En BIOS

Otra manera es usar la interrupción 10h del BIOS en lugar de la


función 9 de la interrupción 21h (DOS). El motivo para hacer esto es
doble: por un lado, muchos de los programas a crackear no se apoyan
en los simples métodos del DOS y por otro, si no conocemos el BIOS,
no podremos escribir código para sistemas no-DOS como el UNIX.

Llamado a la Rutina:

message db'hello world','$'


mov dx,offset message
call BiosDisplayString

La Rutina:

; muestra string apuntados por dx usando: int 10h, ah=14

BiosDisplayString:

mov si,dx ; el bios necesita si en lugar de dx


mov ax,cs ; usar segmento actual (de código)
mov ds,ax ; para los datos a ser mostrados

bnxtchar:
lodsb ; buscar el próximo carácter a mostrar
push ax ; preservar ax de cualquier cambio
cmp al,'$' ; marca de final de string?
Jz endbprtstr
Pop ax ; restaura ax
Call BiosDisplayChar
Jmp bnxtchar

endbprtstr:
pop ax ; limpiar
ret

; Observe que usando el BIOS debemos mostrar de a un


carácter por vez.

BiosDisplayChar: ; muestra el caracter que hay en al


Mov ah,0Eh ;Código de la función disp-char de BIOS
Xor bx, bx
Xor dx, dx
Int 10h ; Llamado a la funcion BIOS
Ret

Aunque hay otras maneras de mostrar caracteres (por ejemplo la INT


21h, ah=02 imprime igualmente un carácter en la pantalla), por lo
general todo el mundo usa la INT 10h función 0Eh aquí mostrada. El
conocimiento de las interrupciones es muy útil para el cracking .

Y LOS NUMEROS?
(Código Assembly para mostrar números en cualquier base)

Llamado a la Rutina:

mov ax, 0402h


call DisplayWord

La Rutina: ; muestra la Word que hay en AX

DigitBase dw 010h ; usando base 16 dígitos


;cambiar lo anterior por 10h por 0Ah para ver números
decimales

DisplayWord proc near

mov si,offset DigitBase


mov di,offset TempNum
NextDgt:
xor dx,dx
div si
add dx,30h ; convertir a dígito ascii
mov [di],dl
dec di
cmp ax,0 ; falta algún dígito?
ja NextDgt
inc di
mov dx,di
mov ah,9
int 21h ; mostrar string apuntado por DX (DOS)
retn

DisplayWord endp

db 4 dup (20h) ; número máximo de dígitos


TempNum db 20h
db 24h,90h

Con esto último, reservamos espacio en memoria.


Es para almacenamiento temporario del string a mostrar.
Nótese que en el ejemplo anterior podríamos haber llamado a la int
10h del BIOS en lugar de haberlo resuelto con la función 9 de la Int
21h.

Algunos modelos útiles


Nota del Traductor: Los siguientes modelos contienen las directivas de ensamblador
necesarias para poder compilar exitosamente programas simples en lenguaje assembly.
Pueden ser copiados y pegados como comienzo de edición de un programa. Para que el
principiante tenga una idea de la importancia de contar con estos modelos en lo que a
ahorro de tiempo se refiere, le sugiero que trate de hacer uno que compile sin errores.

MODELO DE ARCHIVO .COM

;**********************************************
;
; MODELO DE ARCHIVO EJECUTABLE .COM (COM.ASM)
;
; Compilar con:
;
; TASM COM.ASM
; TLINK /t COM.OBJ
;
; +gthorne'97
;
;**********************************************

.model small
.code
.386

org 100h

start: jmp MAIN_PROGRAM


;----------------------
; Zona para datos
;----------------------

;----------------------

MAIN_PROGRAM:

;---------------
; Código de programa
;---------------

;---------------

mov al, 0h ; código de retorno 0 (0 = no error)

exit_program:

mov ah,4ch ; salir al DOS


int 21h

end start

MODELO DE ARCHIVO .COM #2 (ALTERNATIVO)

;**********************************************
;
; MODELO DE ARCHIVO EJECUTABLE.COM #2(COM_B.ASM)
;
; Lo incluimos para poderlo comparar con
; el modelo .EXE mostrado más abajo
;
; Compilar con:
;
; TASM COM_B.ASM
; TLINK /t COM_B.OBJ
;
; +gthorne'97
;
;**********************************************

COM_PROG segment byte public

assume cs:COM_PROG

org 100h

start: jmp MAIN_PROGRAM


;----------------------
; Zona de datos
;----------------------

;----------------------

MAIN_PROGRAM:

;---------------
; Código de programa
;---------------

;---------------

mov al, 0h ; código de retorno (0 = no error)

exit_program:

mov ah,4ch ; salir al DOS


int 21h

COM_PROG ends

end start

MODELO DE ARCHIVO .EXE

;**********************************************
;
; MODELO DE ARCHIVO EJECUTABLE .EXE (EXE.ASM)
;
; Compilar con:
;
; TASM EXE.ASM
; TLINK EXE.OBJ
;
; +gthorne'97
;
;**********************************************

.model small ; normalmente small, medium o large


.stack 200h
.code

EXE_PROG segment byte public


assume cs:EXE_PROG,ds:EXE_PROG,es:EXE_PROG,ss:EXE_PROG

start: jmp MAIN_PROGRAM


;----------------------
; Zona de datos
;----------------------

;----------------------

MAIN_PROGRAM:

;---------------
; Código de programa
;---------------

;---------------

mov al, 0h ; código de retorno (0 = no error)

exit_program:

mov ah,4ch ; salir al DOS


int 21h

EXE_PROG ends

end start

MODELO DE ARCHIVO .EXE #2 (ALTERNATIVO)

;**********************************************
;
; MODELO DE ARCHIVO EJECUTABLE .EXE 2 (EXE2.ASM)
; (Comparar con el primer modelo .COM)
; Probado con TASM 4.1
; Donado por Eyes22, Modificado para semejarse
; los otros modelos
; Compilar con :
;
; TASM EXE2.ASM
; TLINK EXE2.OBJ
;
; +gthorne'97
;
;**********************************************

;dosseg ; directiva que es ignorada en tasm 4,


; descomentar en caso de errores

.model small
.stack 200h
.data
.code
start: jmp MAIN_PROGRAM

;----------------------
; Zona de datos
;----------------------

;---------------
; Código de programa
;---------------

;---------------

mov al, 0h ; código de retorno (0 = no error)

exit_program:

mov ah,4ch ; salir al DOS


int 21h

end start

Cita textual del libro Assembly Language for the IBM-PC

Programas COM:

Hay dos tipos de programas transitorios, dependiendo de la extensión usada:


COM y EXE. Recuerde que usamos DEBUG para crear y salvar un pequeño
programa COM. Un programa COM es una imagen binario de un programa
en lenguaje de máquina. El DOS lo carga en memoria en la dirección de
segmento más baja disponible, creando un PSP en offset 0. El código, datos
y stack se almacenan todos en el mismo segmento físico (y lógico). El
programa no puede superar los 64 kB menos el largo del PSP y dos bytes
reservados en el tope del stack. Todos los registros de segmento se cargan
con la dirección base del programa, el código comienza en el offset 100h y el
área de datos sigue al código. El stack está al final del segmento ya que el
DOS inicializa al registro SP en 0FFFEh.

Hello.asm

Programa ejemplo "hello world" escrito en formato .COM

Note que las directivas DOSSEG, DATA y STACK son innecesarias, y la


directiva ORG (que inicializa al contador de direcciones en 100h) se
antepone a toda instrucción assembly para dejar espacio para el PSP, que
ocupa desde la dirección 0 hasta la 0FFh.

.model tiny
.code
org 100h

maine proc
mov ah,9
mov dx, offset helo_msg
int 21h
mov ax, 4c00h
int 21h
maine endp

helo_msg db 'Hello, world!' '$'


end Maine

Desarrollo de programas
(Se aplica para cualquier lenguaje)

Hace mucho tiempo


En una Galaxia no tan lejana

Alguien inventó el diagrama de flujo. Este dispositivo fue una sucia


herramienta que requería nivel universitario para ser escrita, la
entendía sólo uno mismo y era como un enredo de espaghettis a la
hora de usarse para dirigir la escritura de un programa.

Hace también mucho tiempo


En la tierra de la ficción interactiva
(Que puede muy bien estar en otra galaxia lejana...)

Alguien más se dio cuenta que diagramas de cajas simples mostrando


la ubicación en un fantasioso texto de aventuras permitía un mapeado
fácil y rápido que permitía retomar el trabajo al tiempo, sin perder la
ilación debido a la simplicidad de las cajas y sus descripciones.

Más Recientemente
En la tierra del sentido común
(El cual tiende a no ser tan común...)

Algunos astutos individuos se dieron cuenta de que las computadoras


no tenían por qué ser tan confusas que no eran necesarios unos tontos
que agregaran frustración con sus diagramas.

De aquí en adelante comienza IPO


Con sus diagramas de caja simples
Para facilitar el planeamiento y desarrollo de programas

Qué es IPO?
Del Inglés: INPUT - PROCESS - OUTPUT
En castellano: ENTRADA - PROCESO - SALIDA

Es por lejos la más simple y usable forma para planear la


programación jamás desarrollada. Tarda un gran tiempo la gente en
aprender la idea que complejidad no significa necesariamente
superioridad. (Lea algún texto que compare las arquitecturas CISC y
RISC y entenderá la idea).

La manera en que funciona es realmente clara, todo lo que hay que


hacer es comenzar con un plan básico... Todos los programas tienen
algún grado de entrada (desde un usuario, dispositivo o programa),
algún grado de procesamiento de aquella entrada y algún tipo de
salida, la cual puede ser una pantalla, una impresora, otro programa,
etc

Sabiendo esto, podemos desarrollar todos un programa o parte de


ellos con alguna mutación de este proceso

Aquí hay una más amplia descripción de un programa típico.

La ENTRADA incluye estas etapas:

 Leer la línea de comando para ver si hay algún argumento


especial tal como un nombre de archivo o una directiva a la
manera de los comandos del DOS (por ejemplo dir /w *.txt)
donde los argumentos /w y *.txt deben ser leídos e
interpretados
 Leer datos desde un archivo de configuración (.CFG)
 Pedirle al usuario el ingreso de algún dato (p. ej. su nombre)
 Leer datos que ingresan desde un scanner, cámara o cualquier
otro dispositivo de entrada presente en el sistema.

El PROCESAMIENTO Involucra todo aquello que se hace


para manipular o alterar los datos de entrada recibidos (por
ejemplo ordenarlos, operarlos matemáticamente, etc). Esto es
por lo general la mayor parte del programa.

La SALIDA Es como la fase inversa de la entrada. Podemos


grabar la configuración actual, mostrar al usuario algún
mensaje, imprimir algo, o enviar los datos a disco o a la entrada
de otro programa.

Tuve que programar en algún lenguaje o en otro por más de 15


años (aunque tampoco tengo mucha edad), y perdí
innumerables veces la pista del código de programa, qué estaba
haciendo y la dirección a seguir para completar un producto.
Siguiendo este lineamiento para la programación e incluyendo
pequeños comentarios en todo el desarrollo del código es la
única forma de garantizar que una semana o un mes después de
ser escrito uno pueda reinterpretar el código y aquél trabajo
pueda ser de alguna utilidad. Si no lo creyese así, no hubiese
escrito esta parte del tutorial, que por otra parte, será usado
como guía a lo largo de todas las lecciones en cada programa o
subrutina que escribamos de aquí en adelante.

Comienzos en Assembly

Habiendo leído en el capítulo anterior cómo se desarrollan los


programas, es probable que se pregunte si ahora vamos a comenzar a
escribir código. Nuestra primera lección será sobre cómo construir la
caparazón de un programa para que maneje varios tipos de entradas y
se inscriba dentro del modelo IPO.

Qué tan bueno puede ser un programa si no es interactivo? Incluso


programas de baja interactividad como los patches requieren la
lectura de datos desde archivos. Si ya está familiarizado en la técnica
de cómo se hacen las llamadas al DOS, saltee esta parte. El texto que
sigue es para asegurarse que nadie quede a oscuras aún si ha
comenzado desde cero. Después de todo, hemos dirigido este tutorial a
los principiantes.

La primera cosa que uno desea de un programa es que sea capaz de


mostrar un mensaje tonto como "hello, world". Eso haremos.

Primero necesito que usted comprenda qué son los registros (las hiper-
rápidas variables construidas dentro del procesador x86 de su PC).

En los viejos lenguajes de programación en alto nivel, el BASIC fue el


más fácil de todos los que se hayan conocido (no confundir con Visual
Basic, esa monstruosidad de nuestros amigos de Microsquash,
tampoco Qbasic, ni BASICA, sino el viejo y llano BASIC que cada
máquina emuló a través de los años para que si una persona que desea
aprender un lenguaje de programación se atasque con éste, para nada
útil).

En BASIC, había una manera de ingresar información y mostrarla al


usuario, usando comandos semejantes a los que siguen:

DATA "Hello Planet Hollywood";


READ D$; (from data)
PRINT D$;

o más simplemente:

LET D$ = "Hello Planet Hollywood";


PRINT D$;

y la computadora debería haber mostrado nuestro mensaje en la


pantalla, en el supuesto que lo hayamos escrito bien.

En Assembly las cosas no son diferentes. Nos quedamos con el primero


de los dos modelos BASIC, y escribimos algo como lo que sigue:

MYSTRING DB 'Hello Planet Hollywood";


MOV DX, OFFSET MYSTRING

y cuando lo tengamos que imprimir, usaremos una llamada al DOS


con las siguientes sentencias:

MOV AH, 09h


INT 21h

Poniéndolo todo junto, tenemos:


MYSTRING DB 'Hello Planet Hollywood','$'
MOV DX, OFFSET MYSTRING
MOV AH, 09h
INT 21h

No está del todo mal, verdad?

Note el signo '$' al final de la cadena. El DOS necesita que de alguna


manera se le señale el final del string, o sea cuándo debe dejar de sacar
caracteres a la pantalla. Sin él, DOS seguiría tirando a la pantalla los
caracteres que encuentre en memoria después del string, usualmente el
mismo programa o datos sin valor que quedaron en la memoria luego
de encender la PC o que fueron dejados ahí por un programa anterior.
El signo $ es algo para no olvidar.

Hay otra manera de manejar strings en assembly, llamada string-cero,


consistente en que en lugar de terminar con "$" terminan con 00h. No
es mejor una que la otra, solo son diferentes ( en realidad, la string-
cero es manejada por el BIOS en lugar del DOS).

Uno podría hacer una rutina para impresión de cadenas de caracteres


terminadas en cualquier valor no imprimible, aunque no sería muy
útil teniendo gratis las dos ya mencionadas. Quizás más adelante usted
quiera desarrollarla para esconder alguna encriptación en la que se
incluya el caracter de terminación. También puede usar rutinas que
impriman un determinado número de caracteres, que no necesitan
contar con un caracter de terminación.

Volvamos a nuestro ejemplo:

No he mencionado aún que los datos deben separarse del código para
evitar ser ejecutados. Si pone atención en los modelos de programas
.COM y .EXE vistos un par de capítulos antes, verá que en ellos hay
una zona para datos y otra para código ejecutable. La primera
sentencia del programa hace un salto por sobre la zona de datos para
que el nunca se confundan con instrucciones de máquina.

Esto tampoco es muy diferente que en BASIC, en donde la gente


tiende a poner las sentencias DATA al final del programa. Considere
además que cualquier lenguaje de programación decente tiene que
haber sido escrito alguna vez en assembly, y por tanto no se sorprenda
en tener que usar sentencias de string similares.

COMO TRABAJAN LOS REGISTROS

En el ejemplo anterior, hemos visto que se puede utilizar el registro


DX para almacenar una variable de string (que se denominó D$ en
BASIC para hacer más fácil la comparación). En BASIC uno tiene la
cantidad de variables que quiera, pero en assembly sólo hay pocas
variables de registro para escoger, lo cual sigue estando bien porque no
se necesitan más, ya que las variables pueden almacenarse en
cualquier lugar de la memoria que uno elija, y no sólo en la zona de
variables como en BASIC. A continuación se resumen los registros de
propósitos generales de un procesador x86:

AX - Acumulador (donde usualmente quedan los resultados)


BX - Registro Base (usualmente indica el comienzo de una estructura
que reside en memoria)
CX - Contador (lara contar lo que sea, incluso la longitud de strings)
DX - Registro de datos - Usualmente apunta a strings o áreas de datos
en memoria.

Los anteriores registros de propósito general son exactamente eso: de


propósito general. En el programa uno puede en ocasiones
intercambiar las funciones de uno con otro, pero cuando nuestro
programa se tiene que comunicar con el DOS no, porque DOS espera
datos específicos en cada registro. El programa "hello" visto es un
buen ejemplo de esto.

El Acumulador (AX) tiene el mayor perfil que uno puede imaginar.


Tiende a "acumular" lo que sea. Cuando uno sale de un programa o
de una subrutina de cualquier clase, el resultado o los códigos de error
por lo general vuelven en AX. Y cuando se llama un procedimiento,
contiene el código de comando como el en ejemplo "hello" en donde
AH se carga con un 9h.

Cada uno de estos registros tiene 16 bits (dos bytes) aunque desde el
80386 en adelante estos registros pasan a ser de 32 bits y a llamarse
EAX, EBX, etc (aunque sigue siendo válido referirse a la parte baja
del registro como AX, o a los más pequeños AH y AL -por high y low).

AX está compuesto por AH (bits 15...7) y AL (bits 7...0)


BX está compuesto por BH y BL
etc.

Veamos ahora registros de uso mucho más especializado. Los dos


siguientes usualmente se unan en operaciones de copia o comparación
de cadenas de caracteres.

DI - Indice Destino (El lugar a dónde se mueven los datos)


SI - Indice Fuente (El lugar de origen de los datos)

y ahora mencionemos a otro:


BP - Puntero Base

Muy frecuentemente SI, DI y BP se usan para tener presente en qué


lugar del código uno se encuentra -realmente no importa cuál sea el
uso que se le da a cada registro hasta que uno tiene que comunicarse
con algún otro código que espera los datos ubicados en lugares
específicos. Esto sucede bastante a menudo. Examine por ejemplo los
virus y verá qué poco frecuentes son las referencias a SI y BP.

Hay un registro especial que parece como poner la mano de dios en el


programa. Es el puntero de instrucciones IP, usado por el procesador
para saber cuál es la próxima instrucción que ha de ejecutarse.

Por qué esto es importante?

Ahora mostraremos un truco de uso frecuente: digamos que por


ejemplo estamos en un depurador como SoftICE viendo un lazo del
programa que estamos examinando y queremos salir de ese lazo.
Cambiando el valor de IP podemos quedar en la parte exterior del
lazo. Tenga cuidado al hacer esto porque pasar de un lugar a otro del
programa puede tener consecuencias imprevisibles.

Los virus (otra vez usando estas bestias como ejemplo) tienden a usar
bastante instrucciones que cambian al IP de manera no convencional.
Los encabezados de archivos .EXE informan al DOS cuál es el
segmento de arranque de código (que debe cargarse en CS) y cuál es la
dirección de la primera instrucción a ejecutar (que debe cargarse en
IP).

Por lo común los virus de archivos EXE ponen su código al final del
programa y alteran el encabezado de tal forma que los registros CS e
IP apunten a sus instrucciones de inicio, con lo cual logran ejecutarse
antes que cualquier otra instrucción del programa. Luego al final de
su código hacen un salto al inicio del programa (cuya dirección saben
porque la leyeron del encabezamiento antes de cambiarla). Más que
creativo, podría decirse.

Sólo por diversión héchele un vistazo a mi programa SYMBIOTE.


Hace exactamente la misma cosa y es el modo que hay que usar para
agregar código a los programas. Los archivos .COM son un poco
diferentes, tal vez incluso más simples. Symbiote puede manejar
archivos EXE o COM y aunque le tome un rato, por favor no deje de
revisarlo porque puede aprender bastante de él, ya que está
comentado de forma que se pueda comprender lo que está haciendo en
cada momento.

SI USTED NO HA HECHO ESTO ANTES:

Vaya y modifique tanto los modelos .COM o .EXE agregando las


líneas de código para nuestro anterior ejemplo "hello". Considerando
que la mayoría de las llamadas DOS usan básicamente el mismo
método, no tendrá dificultades con otras llamadas.

En la próxima lección, entraremos en el tema de interactividad


leyendo entradas de usuario como parámetros en la línea de comando.
Sería muy bueno que antes usted practique algo con el DEBUG. Abra
una ventana DOS e ingrese los siguientes comandos:

cd \windows\command (o cualquiera sea el directorio de comandos)

debug mode.com master greythorne

-d 80

Se obtendrá esta imagen de la dirección DS:0080 y subsiguientes:

1788:0080 12 20 6D 61 73 74 65 72 - 20 67 72 65 79 74 68 6F

1788:0090 72 6E 65 0D ...............

Lo que estamos viendo es la parte del PSP que DOS crea para correr el
programa MODE.COM, en donde se almacenan los parámetros que el
usuario ingresa en la línea de comandos. Los valores son todos hexa y
el primer 12 indica que el largo de la línea de comandos es 18
caracteres (=12h), la que comienza con 20h (código ASCII del espacio
que separa el nombre del programa cargado MODE.COM del primer
parámetro). Notar además que finaliza con 0Dh, que es el ASCII para
el retorno de línea, pero que ese caracter no se cuenta entre los 12h de
largo. También sobre la derecha de la ventana DOS verá el texto que
ha escrito como parámetro. Los caracteres más allá del 0Dh no tienen
ninguna importancia. No olvide que para salir de debug se utiliza el
comando "q".

Todo esto será explicado próximamente, pero un poco de investigación


previa no puede herir a nadie ;-)

Nuestro primer programa real


En esta sección vamos a realizar un pequeño programa con una dosis de
interactividad con el usuario.

Ante todo, insistamos sobre los comentarios, todo aquello que sigue al
punto y coma en cada línea, que son muy útiles y que el compilador los
ignora. Note además que la convención del punto y coma iniciando un
comentario es para los assemblers, pero no intente comentar así una
porción en lenguaje assembly de un programa C: el compilador C/C++
interpreta el símbolo ";" de manera distinta al assembler.

El siguiente trozo de programa muestra texto en pantalla:

;**********************************************
;
; .COM Modelo de archivo de programa (COM.ASM)
;
; Compilar con:
;
; TASM COM.ASM
; TLINK /t COM.OBJ
;
; +gthorne'97
;
;**********************************************

.model small
.code
.386

org 100h

start: jmp MAIN_PROGRAM

CopyMsg db 'Copyright (c)1997 By Me!',0Dh,0Ah,'$'

MAIN_PROGRAM:

mov dx,offset CopyMsg ;apunta al string en zona de datos


mov ah, 09h ;función DOS 9 = print string
int 21h ;ejecutar función
mov al, 0h ;codigo de retorno (0 = sin error)

EXIT_PROGRAM:

mov ah,4ch ;salir al DOS


int 21h

end start

Nótese que el mensaje tiene un "rótulo" (CopyMsg). El programa hace


referencia al rótulo cuando solicita que se imprima el string porque en
realidad todo string es referido como la dirección de la memoria en
donde está almacenada su primer caracter, lo que se llama brevemente
"offset" (desplazamiento en castellano, aunque le seguiremos diciendo
offset para que coincida con el nombre de la directiva de compilador que
se usa en los programas), pero que en realidad significa "offset desde el
comienzo del segmento".

Notemos además que MAIN_PROGRAM es también un rótulo, pero en


el segmento de código, por lo que no sería adecuado utilizar la directiva
offset para referirse a él. En su lugar, se puede hacer que este rótulo sea
la dirección de destino de una instrucción de salto.

Aunque nuestro string a imprimir es "'Copyright (c)1997 By Me!", hay


otros tres caracteres "de cola": 0Dh (retorno de carro), 0Ah (nueva
línea) y $, que indica el final del string. La combinación 0Dh,0Ah es el
equivalente a apretar la tecla ENTER y hace que el cursor se ubique en
el comienzo de la línea siguiente. Otro caracter de interés es Bell, código
07h, que en lugar de mostrarse en pantalla, hace que la PC haga un
"beep" en el parlante, el mismo que se escucha durante el proceso de
booteo o al producirse algún tonto error de Windows (lo que sucede
bastante frecuentemente :-)

Ahora veamos cómo hacer para imprimir un sólo caracter. La versión


DOS se muestra en las líneas que siguen y aunque para esta clase no se
necesita, tenga en cuenta que las personas imprimen caracteres usando
métodos de lo más variados y la ingeniería inversa requiere conocer
todas estas posibilidades.

Primero veamos dos líneas que son muy útiles y que muestran cómo
obtener un caracter del teclado. En esta versión, el se examina el teclado
hasta que el usuario apriete una tecla.

mov ah,08h ; DOS función 08h, esperar que el usuario apriete una
int 21h ; tecla.

Al volver, la función DOS tiene el código de la tecla apretada en el


registro AL. A continuación veremos cómo hacer que ese caracter sea
enviado a la pantalla. La función DOS que lo hace espera que el caracter
a mostrar esté en el registro DL, de modo que la próxima instrucción
copiará el contenido de AL en DL (instrucción MOV) y a continuación se
llama a la función DOS para mostrar el caracter en la pantalla.

mov dl, al ; copiar el caracter que vino del teclado en AL al reg. DL


mov ah,06h ; función DOS 06h, imprimir un caracter en pantalla.
int 21h

Emplearemos lo aprendido en un programa que ya hemos usado como


ejemplo:

;**********************************************
;
; KEYPRESS.ASM
; Nuestro primer programa interactivo
;
; Compilar con:
;
; TASM KEYPRESS.ASM
; TLINK /t KEYPRESS.OBJ
;
; +gthorne'97
;
;**********************************************

.model small
.code
.386

org 100h

start: jmp MAIN_PROGRAM

;----------------------datos-------------------

CopyMsg db 'Copyright (c)1997 By Me!',0Dh,0Ah,'$'

PressEnter db 0Dh,0Ah,'$'

;----------------------código------------------

MAIN_PROGRAM:

; DISPLAY OUR COPYRIGHT MESSAGE

mov dx,offset CopyMsg ;dar a conocer el offset del string


mov ah, 09h ;función 9 = print string
int 21h ;llamada a función DOS

;adicionamos un nuevo ENTER

mov dx, offset PressEnter ;offset de string a DX


mov ah, 09h ;función 9 = print string
int 21h ;

; tomar una tecla apretada por el usuario (sin eco)


; El resultado queda en el registro AL

mov ah,08h ;función 8: leer una tecla


int 21h

; sacar a pantalla el eco (imprimir el caracter)

mov dl, al ; copiar el código del caracter al DL


mov ah,06h ;función 6: mostrar el contenido de DL
int 21h ;en pantalla

; Sólo por diversión, emitiremos un beep

mov dl, 07h ;poner en DL el código del beep


mov ah,06h ;igual que antes
int 21h

mov al, 0h ;código de retorno (0 = sin error)


mov ah,4ch ;salir al DOS
int 21h

end start

Tenemos ahora casi todo lo necesario para hacer un programa que


realice una tarea útil.

Toda vez que en un programa hay un cursor parpadeando esperando


que se apriete una tecla, no está inactivo, sino que se encuentra en un
loop que verifica constantemente si se apretó una tecla. No es la PC en si
misma la que lo hace sino el programa que esta corriendo.

El procesador está haciendo sus propias tareas dentro de la PC. Y de


repente, una persona o programa hace algo que interrumpe el flujo
normal de las cosas. No es necesario que el procesador gaste su tiempo en
lo que le interesa un programa en particular (escaneando
constantemente al teclado para ver si se apretó alguna tecla). El que
nuestro programa deba atender permanentemente al teclado en un
momento dado, no significa problema y es en realidad la forma en que
cualquier juego o programa de entrada de datos lo debe hacer, aún
cuando no sea evidente.

Es usual que se establezca un lazo infinito del que sólo se sale en un caso
especial o cuando se ingresa determinado código. En nuestro caso,
aceptaremos como teclas válidas una "Y" o una "N" tanto en mayúscula
como en minúscula.

Es importante aclarar este aspecto: el programa no será optimizado.


Dejaremos esto para más tarde y nos preocuparemos de hacer que
funcione. El pseudocódigo de un lazo infinito es:

START_OF_LOOP: ; el rótulo (observe los dos puntos ":")

; el código va aqui

JMP START_OF_LOOP ; saltar hacia el inicio del lazo

GO_ON_WITH_PROGRAM: ; un rótulo fuera del lazo

Lo que necesitamos ahora es saber cómo se sale del lazo (la lógica que
nos lleva al rótulo GO_ON_WITH_PROGRAM). Debemos poder decirle
que si se obtuvo una tecla válida que salga del lazo. Para esto,
disponemos de la función CMP (comparar), que evalúa dos variables y
prende o apaga flags según sean iguales o una mayor que la otra (pero no
modifica a ninguna de las variables).

Por si queremos usarlo para otra cosa, pondremos en BL el código de la


tecla que la Int 08 nos deja en AL. Por qué BL? sólo porque es un
registro que no hemos usado aún. No hay otra razón. La sintaxis es:

CMP var_x, var_y

Para nuestro caso, que queremos comparar BL con el caracter "Y":

CMP BL,'Y'

Notar las comillas en la "Y", que indican que la comparación se hace


entre BL y el código ASCII correspondiente a la letra "Y". Para los
números (decimales y hexa) se utilizan las notaciones:

CMP BL, 89

CMP BL, 059h

Las tres formas son equivalentes ya que el código ASCII para la Y es 89


decimal o 59h hexa.

Ahora veamos la instrucción de salto JZ (jump if zero), también llamada


JE (jump if equal) que salta a la dirección indicada si el resultado de la
comparación es cero, es decir var_x es igual a var_y. En caso de ser
distintos, continúa la ejecución de la instrucción que sigue. La
instrucción opuesta es JNZ, también llamada JNE.

El siguiente trozo de código hace lo que hemos descrito hasta ahora:

START_OF_LOOP:

mov ah,8 ;funccion 8, leer una tecla apretada


int 21h
mov bl, al ;guardamos la tecla en BL
cmp bl, 'Y' ;ver si la tecla es una 'Y'
je GO_ON_WITH_PROGRAM
cmp bl, 'N' ;ver si la tecla es una 'N'
je GO_ON_WITH_PROGRAM
JMP START_OF_LOOP ;volver a buscar una tecla

GO_ON_WITH_PROGRAM: ;lugar de salida, ya fuera del lazo

Ahora está en condiciones de seguir este programa con facilidad:

;**********************************************
;
; KEYPRESS.ASM
; Nuestro primer programa interactivo
;
; Compilar con:
;
; TASM KEYPRESS.ASM
; TLINK /t KEYPRESS.OBJ
;
; +gthorne'97
;
;**********************************************

.model small
.code
.386

org 100h

start: jmp MAIN_PROGRAM

;----------------------

CopyMsg db 'Copyright (c)1997 By Me!',0Dh,0Ah,'$'


PressEnter db 0Dh,0Ah,'$'

;----------------------

MAIN_PROGRAM:

; Mostramos el mensaje de Copyright

mov dx, offset CopyMsg ; offset del string en DX


mov ah, 09h ; función 9 = print string
int 21h

;ahora enviemos un retorno y nueva línea adicionales

mov dx, offset PressEnter ;offset en DX


mov ah, 09h ; función 9 = print string
int 21h

START_OF_ENDLESS_LOOP:

mov ah,8 ;funcion 8, buscar una tecla apretada


int 21h
mov bl, al ;guardamos el código de la tecla

cmp bl, 'Y' ; es la tecla una 'Y'?


je GO_ON_WITH_PROGRAM ;salir del lazo si es cierto

cmp bl, 'N' ; es la tecla una 'N'?


je GO_ON_WITH_PROGRAM ;salir del lazo si es cierto

cmp bl, 'y' ; es la tecla una 'y'?


je GO_ON_WITH_PROGRAM ;salir del lazo si es cierto

cmp bl, 'n' ; es la tecla una 'n'?


je GO_ON_WITH_PROGRAM ;salir del lazo si es cierto

; si no es lo que esperábamos, emitir un beep

mov dl, 07h ; poner código de beep en DL


mov ah,6 ; funcion 6, imprimir un character
int 21h

JMP START_OF_ENDLESS_LOOP

GO_ON_WITH_PROGRAM:

; mostrar la tecla apretada (eco)

mov dl, bl ;poner el código de la tecla en DL


mov ah,6 ;función 6, imprimir un character
int 21h

mov al, bl ; poner de nuevo el código de la tecla


; en AL para que sea el valor de retorno

mov ah,4ch ; salir al DOS


int 21h

end start

Como práctica, modifique el programa anterior para que si la tecla


apretada fue una "n" o "N", saque a pantalla el texto "ha dicho que
no!" y si la tecla fue una "y" o "Y", que imprima "Afirmativo!".

También sería de utilidad que en el inicio del programa oriente al futuro


usuario que las teclas que se esperan son Y/N. No hay nada más
frustrante que no conocer qué espera la PC como respuesta.

NOTA: el valor de salida de un programa se almacena en la variable


ERRORLEVEL del DOS, de manera que es posible usar el programa
dentro de un archivo batch que haga diferentes cosas basado en la tecla
que el programa le informa que fue apretada.

Desarrollo de Aplicaciones
Recientemente me preguntador cómo crear grandes programas. Hay
algunos trucos para hacerlo. En la segunda parte haremos una revisión
para hacer que nuestros programas sean modulares.

IMPORTANTE MAS ALLA DE TODA RAZON: COMENTE TANTO


COMO PUEDA LOS PROGRAMAS, es decir ponga un comentario en
la mayoría de las líneas sobre la acción que se está tomando en esa parte
del código.

Decir para un MOV CX,8 que se carga un 8 al CX no es comentario


brillante, pero en cambio si : "cargar CX con el número de loops " y es
invaluable a la hora de depurar el código.

Si nunca ha escrito programas grandes, NO CUESTIONE, HAGALO.


Los comentarios son importante cuando usted necesita que alguien le
ayude a depurar el código. Nadie ayudará si no hay comentarios que den
información, porque es verdaderamente difícil, cuando no imposible. Y si
esta pensando que usted lo puede lograr sin comentarios ni ayuda,
adelante, seguramente es mejor que yo. O un tonto, usted decide.

También escriba comentarios sobre lo que hacen las distintas secciones


del programa, por ejemplo:

;***********

;esta sección toma la línea de comandos y la copia en un buffer

;luego la interpreta y almacena switches y flags en memoria

;espera que se le pase en CX la longitud de la línea de comando

;***********

---------------

Modularidad:

Se puede escribir un programa linealmente desde el principio al fin sin


separarlo en módulos. Pero hay problemas: no es posible escribir código
más allá de los 64 kB. El código puede quedar tan rígido que un ligero
cambio en una de sus partes, posiblemente implique la re-escritura del
programa completo. Y además si nuestro programa de una sección es
más extenso que el buffer de memoria del compilador, no lo podremos
compilar.

Escribiendo el programa en módulos, si es necesario cambiar algo, sólo


se debe modificar el módulo en cuestión. Como estos módulos pueden
llamarse desde varios puntos del programa, se reduce el tipeado y por lo
tanto la posibilidad de error. Cuando los programas se escriben en
módulos, los compiladores toman cada parte por separado, no produce
desborde de memoria y la depuración se hace más fácil.

La forma de modularizar un programa es utilizando PROCs. Todos los


lenguajes decentes permiten la construcción de subrutinas que pueden
ser llamadas desde cualquier parte del programa. En assembly se las
llama "procs" (abreviatura de procedures, igual que en Pascal). En C se
las denomina "funciones", aunque cada lenguaje las maneja de manera
ligeramente diferente.
Para el TASM, un proc puede verse como sigue:

PrintLine proc near

mov ah, 9 ;función ah=9 (imprime en pantalla)


int 21h ;
ret ;código de retorno desde el proc

endp PrintLine

y para llamarla usamos el siguiente código:

mov dx, offset MyMessage


call PrintLine

Es verdaderamente práctico!!! Hay también algunas


desventajas, que se muestran cuando uno escribe muchos
procs.

CompareByte proc near

Loop:
inc ah
cmp ah, 092h
jne Loop
ret

endp CompareByte

;----------------------

CompareWord proc near

Loop:
inc ax
cmp ax, 02942h
jne Loop
ret

endp CompareWord

;----------------------

Cuando esto se compila, si bien CompareWord y CompareByte son dos


rutinas distintas, el rótulo "Loop" está duplicado y el compilador nos da
error porque no sabe a cual de los dos nos referimos en los saltos JNE.
La solución evidente es tratar de diferenciarlos:
CompareByte proc near

CmpByteLoop1:
inc ah
cmp ah, 092h
jne CmpByteLoop1
ret
endp CompareByte

;----------------------

CompareWord proc near

CompareWordLoop1:
inc ax
cmp ax, 02942h
jne CompareWordLoop1
ret

endp CompareWord

;----------------------

Si nuestro programa es suficientemente largo uno puede volverse tonto


tratando de encontrar maneras de diferenciar las cosas.

Hay una solución fácil: Usar el IDEAL MODE

Al principio del modelo EXE o COM, agregamos la palabra IDEAL para


que TASM sepa que debe utilizar el modo ideal. Veamos cómo alterar el
encabezado del modelo COM :

;----------------------

COM_PROG segment byte public

ideal

assume cs:COM_PROG

org 100h

start:

;----------------------

Esto simplifica la manera de escribir procedimientos también:

proc PrintLine

mov ah, 9 ;función ah=9 (imprimir en pantalla)


int 21h ;
ret ;volver del proc

endp PrintLine

La ventaja real viene a la hora de diferenciar rótulos comunes. Esto se


hace con u par de símbolos "at" (@@) antepuestos al rótulo:
proc CompareByte

@@Loop:
inc ah
cmp ah, 092h
jne @@Loop
ret

endp CompareByte

;----------------------

proc CompareWord

@@Loop:
inc ax
cmp ax, 02942h
jne @@Loop
ret

endp CompareWord

;----------------------

Los símbolos @@ indican que se trata de un símbolo local a la rutina y


que no debe ser visible para el resto del código. Cuando algo no es local,
se lo denomina GLOBAL, y está disponible para cualquier parte del
programa. Las partes globales son útiles, pero para algunas pocas cosas.
Por ejemplo en el programa de un juego es adecuado tener el score en
variable global para que sea visible en todas las secciones del juego. Las
variables y símbolos locales permiten hacer programas largos sin el
riesgo de tener efectos peligrosos en otras secciones del código. Veamos
como ejemplo nuestro programa para obtener una tecla Y o N. Lo
escribiremos como ejemplo de procedimientización (qué palabrita!) de
código. Lo exageraremos un poco a propósito. Nuestro objetivo es hacer
una sección "main" con la menor líneas de código posible, sólo hacer
unos pocos llamados y salir. Simplísticamente, una sección main sería:

; entrada

call input
call process
call output

; salida

Si bien este no es un requerimiento excluyente, tenga en cuenta que


cuanto más se acostumbre a programar en módulos, más fácil le será
hacerlo.
;**********************************************
;
; KEYPRESS.ASM
; Nuestro primer Programa Interactivo
; (con un poco de procedimientización)
;
; Compilar con:
;
; TASM KEYPRESS.ASM
; TLINK /t KEYPRESS.OBJ
;
; +gthorne'97
;
;**********************************************

.model small
.code
.386

ideal

org 100h

start: jmp MAIN_PROGRAM

;---------------------- datos --------------

CopyMsg db 'Copyright (c)1997 By Me!',0Dh,0Ah,'$'


PressEnter db 0Dh,0Ah,'$'

;---------------------- código --------------

proc PrintString ;imprime el string apuntado por DX

mov ah, 09h ;comando 9 = imprimir string


int 21h ;
ret ;

endp PrintString

;----------------------

proc PrintChar ; imprime caracter contenido en DL

mov ah,6 ;función 6 = imprimir un caracter


int 21h ;
ret

endp PrintChar

;----------------------

proc CopyRight ;muestra anuncio de Copyright

mov dx,offset CopyMsg ;poner en DX el puntero al string


call PrintString
mov dx,offset PressEnter ;agregar un ENTER final
call PrintString
ret

endp CopyRight

;----------------------

proc GetInput ;verifica teclas válidas

@@Loop:

; primero buscar una tecla leyendo el teclado

mov ah,8 ;función 8, leer tecla apretada


int 21h
mov bl, al ;guardamos el código de la tecla
;porque nos fascina hacerlo
cmp bl, 'Y' ;ver si la tecla es una 'Y'
je @@Done
cmp bl, 'N' ;ver si la tecla es una 'N'
je @@Done
cmp bl, 'y' ;ver si la tecla es una 'y'
je @@Done
cmp bl, 'n' ;ver si la tecla es una 'n'
je @@Done

; hacer un "beep" si la tecla es distinta a la esperada

mov dl,07h ;poner código para BEEP en DL


call PrintChar
jmp @@Loop ;y pedir tecla nuevamente

@@Done:

;ECO (mostrar en pantalla la tecla que el usuario apretó)

mov dl,bl ;copiar el código de tecla a DL


call PrintChar
ret

endp GetInput

;----------------------

MAIN_PROGRAM:

call CopyRight ;mostrar mensaje de Copyright


call GetInput ;requerir de entrada de usuario

;-----------------------

mov al, bl ;valor de salida = código de tecla


mov ah,4ch ;salir al DOS
int 21h

end start

------------------------------------------------------

Aqui damos por terminado este pequeño tutorial de lenguaje assembly.


No olvide que así como usted obtuvo estos conocimientos gratuitamente,
debe brindarlos a los demás y aportar los propios para que la comunidad
de entusiastas del assembly siga creciendo día tras día.

You might also like