Cuando nosotros escribimos nuestro código fuente en un lenguaje de alto nivel este al ser compilado es traducido a código maquina el cual es ejecutado por el procesador. Cuando realizamos ingeniería inversa tomamos un ejecutable ya compilado y realizamos el desensamblado del mismo para proceder con el análisis.
¿Qué es desensamblar?
Desensamblar es el proceso de traducir el código de máquina de un programa en lenguaje ensamblador. Es decir, el proceso de convertir el código de máquina en una representación legible por humanos, que es más fácil de entender y analizar que el código de máquina.
Un desensamblador toma el archivo ejecutable del programa y lo analiza para identificar las instrucciones de código de máquina y su correspondiente representación en lenguaje ensamblador. El resultado es un archivo de texto legible por humanos que muestra el código ensamblador del programa.
Es importante destacar que el proceso de desensamblar no siempre produce un código ensamblador idéntico al código fuente original, ya que el código de máquina puede ser optimizado y reordenado por el compilador durante la compilación. Por lo tanto, el código ensamblador producido por el desensamblador puede requerir cierta interpretación y análisis para comprender completamente su funcionamiento.
¿Qué es el lenguaje ensamblador?
El ensamblador (también conocido como «Assembly» en inglés) es un lenguaje de programación de bajo nivel utilizado para escribir programas que operan directamente en la arquitectura de una computadora. El ensamblador utiliza una sintaxis que está muy cerca del lenguaje de máquina, lo que permite al programador escribir instrucciones que se traducen directamente en códigos de máquina ejecutables por el procesador.
Antes de ver las instrucciones en ensamblador debemos conocer lo que son los registros, Los registros del procesador son pequeñas áreas de almacenamiento en la CPU que se utilizan para realizar operaciones aritméticas y lógicas, y para almacenar temporalmente datos y direcciones de memoria.
Existen varios registros entre los cuales destacamos: registros de propósito general, registros de coma flotante y registros de propósito especifico.
Registros de propósito General (GPR)
Los registros de propósito general son utilizados para almacenar temporalmente datos y direcciones de memoria. Algunos de estos cumplen propósitos específicos en determinadas instrucciones.
Entre los registros de propósito general para la arquitectura de 64 bits tenemos los registros RAX, RBX, RCX, RDX, RSI, RDI, RBP, RSP y los registros desde el R8 al R15
Registros de punto Flotante
Los registros de punto flotante son utilizados para realizar operaciones matemáticas con números de punto flotante en la arquitectura de 64 bits tenemos los registros de la serie XMM, YMM y ZMM
Registros de Propósito Especifico
Entre los registros de propósito especifico tenemos los registros RFLAGS y RIP. El registro RIP es utilizado para contener la dirección de la próxima instrucción que se va a ejecutar y el registro RFLAGS es utilizado para contener el estado de la CPU y contiene diferentes banderas que indican el resultado de las operaciones entre otras cosas
Si por ejemplo el resultado de una resta nos da cero el registro RFLAGS colocara la bandera que indica que el resultado es cero (Zero Flag) a 1 indicando que el resultado de la operación anterior fue cero. O si por ejemplo el resultado de la resta da un numero negativo la bandera del registro RFLAGS que indica que el resultado es un numero negativo (Sign Flag) será colocada a 1 en caso contrario es decir el resultado dio un numero positivo la bandera que indica que el resultado es negativo será colocada a 0
Instrucciones en ensamblador
Las instrucciones del lenguaje ensamblador son el conjunto de instrucciones que son interpretadas directamente por el procesador estas las podemos clasificar en, instrucciones de movimiento de datos, instrucciones de comparación, instrucciones aritméticas, instrucciones de control de flujo, instrucciones de llamada y retorno, instrucciones de manejo de pila, instrucciones lógicas y de desplazamiento de bits
instrucciones de movimiento de datos
Las instrucciones de movimiento de datos nos permiten mover desde la fuente al destino estas pueden ser de movimiento incondicional o movimiento condicional
Instrucciones de movimiento incondicional
Las instrucciones de movimiento incondicional son instrucciones que se encargan de mover datos desde la fuente al destino incondicionalmente es decir que no tienen en cuenta los FLAGS o banderas antes de mover entre estas instrucciones tenemos LEA, MOV y XCHG
MOV
La instrucción MOV nos permite mover datos desde fuente a destino en caso de que la fuente se encuentre entre corchetes (por ejemplo, modo de direccionamiento indirecto) se calculará la dirección y se moverá el contenido de la dirección a la fuente
sintaxis
mov destino, origen
ejemplo
mov eax, 1 ; mueve el valor 1 al registro EAX
mov ebx, eax ; mueve el valor de EAX a EBX
mov [memoria], eax ; mueve el valor de EAX a la dirección de memoria indicada
mov al, [ebx] ; mueve el byte almacenado en la dirección apuntada por EBX al registro AL
mov dword ptr [eax], 0 ; mueve el valor 0 a la dirección de memoria indicada por el registro EAX
LEA
La instrucción Lea (Load effective Address) es una instrucción utilizada para calcular la dirección efectiva, esta instrucción va a calcular la dirección del segundo operando (el operando fuente) y lo va a almacenar en el primer operando (destino) es decir calculará la dirección contenida entre corchetes y moverá la dirección no el contenido de la dirección
sintaxis
lea registro, dirección_de_memoria
ejemplo
lea ebx, [memoria] ; carga la dirección de memoria en EBX
lea eax, [ebx+4] ; carga la dirección de memoria desplazada por 4 bytes de la dirección almacenada en EBX en EAX
lea edx, [ebx+eax*2] ; carga la dirección de memoria desplazada por el doble del valor almacenado en EAX desde la dirección almacenada en EBX en EDX
XCHG
Exchange Esta instrucción intercambia los contenidos de registros/memoria con registros
sintaxis
xchg destino, origen
ejemplo
xchg eax, ebx ; intercambia el contenido de los registros EAX y EBX
MOVSX
La instrucción MOVSX (move sign extended) es una instrucción que mueve desde una fuente más pequeña a un destino más grande y el espacio sobrante lo rellena con el bit de signo de la fuente
sintaxis
movsx registro_destino, origen
ejemplo
movsx eax, byte ptr [memoria] ; mueve el byte almacenado en la dirección de memoria indicada a EAX y lo extiende a 32 bits
movsx ebx, word ptr [memoria] ; mueve la palabra almacenada en la dirección de memoria indicada a EBX y lo extiende a 32 bits
movsx edx, al ; extiende el valor del registro AL (8 bits) a 32 bits y lo mueve a EDX
MOVZX
La instrucción MOVZX (move zero extended) es una instrucción que mueve desde una fuente más pequeña (la cual puede ser un Word o byte) a un destino más grande y el espacio sobrante lo rellena con ceros
sintaxis
movzx registro_destino, origen
ejemplo
movzx eax, byte ptr [memoria] ; mueve el byte almacenado en la dirección de memoria indicada a EAX y lo extiende a 32 bits con ceros
movzx ebx, word ptr [memoria] ; mueve la palabra almacenada en la dirección de memoria indicada a EBX y lo extiende a 32 bits con ceros
movzx edx, al ; extiende el valor del registro AL (8 bits) a 32 bits con ceros y lo mueve a EDX
Instrucciones de movimiento condicional
Las instrucciones de movimiento condicional son instrucciones que verifican las banderas de estado o flags antes de mover en caso de que la condición necesaria para el movimiento no se cumpla es decir las banderas que esta verifica no están seteadas el movimiento no se realizará
instrucciones de comparación
las instrucciones de comparación realizan la comparación de dos operandos sin modificar ninguno de ellos, pero actualizando las banderas de estado dependiendo del resultado de la comparación
entre las instrucciones de comparación tenemos TEST y CMP
TEST
La instrucción test realiza la operación AND entre el primer y segundo operando sin alterar ninguno, pero modificando las banderas de estado dependiendo del resultado de la operación AND
sintaxis
test destino, origen
ejemplo
test eax, ebx ; realiza la operación AND lógica entre EAX y EBX y establece las banderas según el resultado
test al, 0xFF ; realiza la operación AND lógica entre el registro AL y el valor hexadecimal 0xFF, estableciendo las banderas según el resultado
test [memoria], 0x10 ; realiza la operación AND lógica entre el valor almacenado en la dirección de memoria indicada y el valor hexadecimal 0x10, estableciendo las banderas según el resultado
CMP
La instrucción CMP (Compare) realiza una resta entre el primer y segundo operando (operando1-operando2) actualiza las banderas de estado y no modifica ninguno de los operandos
sintaxis
cmp operando1, operando2
ejemplo
cmp eax, ebx ; compara los valores de los registros EAX y EBX y establece las banderas según el resultado
cmp al, 0xFF ; compara el valor del registro AL con el valor hexadecimal 0xFF y establece las banderas según el resultado
cmp [memoria], ebx ; compara el valor almacenado en la dirección de memoria indicada con el valor del registro EBX y establece las banderas según el resultado
instrucciones aritméticas
las instrucciones aritméticas como su nombre lo indica nos permiten realizar operaciones aritméticas tales como la suma, resta, multiplicación o división
ADD
La instrucción ADD suma el operando fuente más el operando de destino y el resultado de la suma queda almacenado en el operando de destino
sintaxis
add destino, origen
ejemplo
add eax, ebx ; suma el valor del registro EBX al valor del registro EAX y almacena el resultado en EAX
add [memoria], eax ; suma el valor del registro EAX al valor almacenado en la dirección de memoria indicada y almacena el resultado en esa dirección de memoria
add al, 0x10 ; suma el valor hexadecimal 0x10 al valor del registro AL y almacena el resultado en AL
SUB
La instrucción SUB le resta al operando de destino la fuente (destino-fuente) y el resultado de la resta es almacenado en el operando de destino
sintaxis
sub destino, origen
ejemplo
sub eax, ebx ; resta el valor del registro EBX del valor del registro EAX y almacena el resultado en EAX
sub [memoria], eax ; resta el valor del registro EAX del valor almacenado en la dirección de memoria indicada y almacena el resultado en esa dirección de memoria
sub al, 0x10 ; resta el valor hexadecimal 0x10 del valor del registro AL y almacena el resultado en AL
MUL
Esta instrucción realiza la multiplicación sin signo del operando con el registro RAX y el resultado de la multiplicación es almacenado en RDX:RAX (la parte alta en RDX y la parte baja en RAX)
sintaxis
mul operando
ejemplo
mul eax ; multiplica el valor del registro EAX por el valor del registro EAX y almacena los 32 bits más significativos del resultado en EDX y los 32 bits menos significativos en EAX
mul ebx ; multiplica el valor del registro EBX por el valor del registro EAX y almacena los 32 bits más significativos del resultado en EDX y los 32 bits menos significativos en EAX
mul dword [memoria] ; multiplica el valor almacenado en la dirección de memoria indicada por el valor del registro EAX y almacena los 32 bits más significativos del resultado en EDX y los 32 bits menos significativos en EAX
IMUL
La instrucción IMUL realiza la multiplicación de números con signo y esta puede tener uno dos o tres operandos cuando esta tiene un operando se comporta similar a MUL pero en este caso multiplicando con signo el registro RAX por el operando y el resultado se almacena en el registro RDX:RAX. Con dos operandos multiplica la fuente por el destino y el resultado se almacena en la fuente. Con tres operandos multiplica el segundo operando con el tercer operando el cual debe ser una constante y el resultado lo almacena en el primer operando
sintaxis
imul operando
imul destino, operando
imul destino, operando, const
ejemplo
imul eax, ebx ; multiplica el valor del registro EBX por el valor del registro EAX y almacena el resultado en EAX
imul ecx, dword [memoria] ; multiplica el valor almacenado en la dirección de memoria indicada por el valor del registro ECX y almacena el resultado en ECX
imul rax, rbx ; multiplica el valor del registro RBX por el valor del registro RAX y almacena el resultado en RAX
DIV
Esta instrucción realiza la división de números sin signo de los registros RDX:RAX (dividendo) entre el operando (divisor), el resultado es almacenado en el registro RAX y el residuo en el registro RDX
sintaxis
div operando
ejemplo
div ebx ; divide el valor del registro EDX:EAX (valor de 64 bits) por el valor del registro EBX y almacena el cociente en EAX y el resto en EDX
div dword [memoria] ; divide el valor almacenado en la dirección de memoria indicada por el valor del registro EAX y almacena el cociente en EAX y el resto en EDX
IDIV
Esta instrucción realiza la división con signo de los registros RDX:RAX (dividendo) entre el operando (divisor), el resultado es almacenado en el registro RAX y el residuo en el registro RDX
sintaxis
idiv operando
ejemplo
idiv ebx ; divide el valor del registro EDX:EAX (valor de 64 bits) por el valor del registro EBX y almacena el cociente en EAX y el resto en EDX
idiv dword [memoria] ; divide el valor almacenado en la dirección de memoria indicada por el valor del registro EAX y almacena el cociente en EAX y el resto en EDX
instrucciones de control de flujo
estas instrucciones nos permiten alterar el flujo del programa entre estas instrucciones tenemos las instrucciones de salto incondicional y salto condicional
instrucciones de salto incondicional
JMP
La instrucción JMP salta a la dirección de memoria especificada por el operando incondicionalmente es decir salta sin tener en cuenta las banderas o FLAGS
sintaxis
jmp destino
ejemplo
etiqueta:
; instrucciones
jmp etiqueta ; salta de vuelta a la etiqueta
; más instrucciones
Instrucciones de salto condicional
Las instrucciones de salto condicional verifican determinadas banderas y si la condición se satisface es decir las banderas que chequea están seteadas salta a la dirección indicada por el operando, en caso contrario (la condición no se satisface) la instrucción no salta
instrucciones de llamada y retorno
las instrucciones de llamada y retorno nos permiten entrar y retornar de las funciones
CALL
La instrucción CALL salta a la dirección apuntada por el operando, pero antes coloca en el stack la dirección de retorno es decir la dirección siguiente a donde fue ejecutada
sintaxis
call subrutina
ejemplo
subrutina:
; instrucciones
ret ; retorna de la subrutina
call subrutina ; llama a la subrutina indicada y salta a la dirección de memoria donde comienza la subrutina
RET
La instrucción RET saltará a la dirección de retorno es decir la dirección colocada en el stack por la instrucción CALL
sintaxis
ret
ejemplo
subrutina:
; instrucciones
ret ; retorna de la subrutina
call subrutina ; llama a la subrutina indicada y salta a la dirección de memoria donde comienza la subrutina
instrucciones de manejo de pila
las instrucciones de manejo de pila nos permiten colocar y retirar valores de la pila o stack
PUSH
La instrucción PUSH coloca valores en el stack
sintaxis
push operando
ejemplo
push eax ; coloca el valor del registro eax en la pila
push 10 ; coloca el valor 10 en la pila
push dword [memoria] ; coloca el valor almacenado en la dirección de memoria "memoria" en la pila
POP
La instrucción POP retira valores del stack
sintaxis
pop destino
ejemplo
pop eax ; saca el valor de la cima de la pila y lo coloca en el registro eax
pop dword [memoria] ; saca el valor de la cima de la pila y lo coloca en la dirección de memoria "memoria"
instrucciones lógicas
Las instrucciones Lógicas nos permiten realizar operaciones lógicas entre las cuales tenemos AND, OR, NOT, XOR
AND
La instrucción AND nos permite realizar la operación lógica AND entre el operando destino y fuente y el resultado de esta operación es almacenado en el operando de destino
sintaxis
and destino, origen
ejemplo
and eax, ebx ; realiza la operación lógica "AND" entre los valores de los registros eax y ebx, y almacena el resultado en eax
and dword [memoria], 0xFF ; realiza la operación lógica "AND" entre el valor almacenado en la dirección de memoria "memoria" y la constante 0xFF, y almacena el resultado en la misma dirección de memoria
OR
La instrucción OR nos permite realizar la operación lógica OR entre el operando destino y fuente y el resultado de esta operación es almacenado en el operando de destino
sintaxis
or destino, origen
ejemplo
or eax, ebx ; realiza la operación lógica "OR" entre los valores de los registros eax y ebx, y almacena el resultado en eax
or dword [memoria], 0x7F ; realiza la operación lógica "OR" entre el valor almacenado en la dirección de memoria "memoria" y la constante 0x7F, y almacena el resultado en la misma dirección de memoria
NOT
Esta instrucción realiza la negación del operando
sintaxis
not destino
ejemplo
not eax ; realiza la operación lógica "NOT" en el valor del registro eax, y almacena el resultado en eax
not byte [memoria] ; realiza la operación lógica "NOT" en el valor almacenado en la dirección de memoria "memoria", y almacena el resultado en la misma dirección de memoria
XOR
La instrucción XOR nos permite realizar la operación lógica XOR entre el operando destino y fuente y el resultado de esta operación es almacenado en el operando de destino. Un uso frecuente de esta operación es colocar un registro a cero.
sintaxis
xor destino, origen
ejemplo
xor eax, eax ; coloca el registro eax a cero
xor eax, ebx ; realiza la operación lógica "XOR" entre los valores de los registros eax y ebx, y almacena el resultado en eax
xor byte [memoria], 0xff ; realiza la operación lógica "XOR" entre el valor almacenado en la dirección de memoria "memoria" y el valor 0xff, y almacena el resultado en la misma dirección de memoria
instrucciones de desplazamiento de bits
Las instrucciones de desplazamiento de bits como su nombre lo indica son utilizadas para desplazar bits, un uso muy frecuente es para manejar valores de campos de bits o para multiplicar o dividir entre potencias de 2
SHL y SAL
Las instrucciones SHL (Shift logical Left) y SAL (Shift artitmenical Left) desplazan os bits hacia la izquierda el número de veces indicado por la fuente, rellenando los nuevos lugares con ceros
sintaxis
shl destino, fuente
sal destino, fuente
ejemplo
mov eax, 0x00000001 ; mueve el valor hexadecimal 0x00000001 al registro eax
shl eax, 1 ; desplaza los bits en eax un bit a la izquierda
mov ebx, 0xffff0000 ; mueve el valor hexadecimal 0xffff0000 al registro ebx
sal ebx, 4 ; desplaza los bits en ebx cuatro bits a la izquierda, realizando un desplazamiento aritmético
desplazar hacia la izquierda equivale a multiplicar por una potencia de dos
SHR
La instrucción SHR (Shift logical Right) desplaza los bits hacia la derecha el número de veces indicado por la fuente, rellenando los nuevos lugares con ceros
sintaxis
shr destino, fuente
ejemplo
mov eax, 0x12345678 ; coloca el valor 0x12345678 en eax
shr eax, 4 ; desplaza 4 bits a la derecha
SAR
La instrucción SAR (Shift Aritmetical Right) desplaza los bits hacia la derecha el número de veces indicado por la fuente y rellena los nuevos lugares con el bit de signo que tenía el destino
sintaxis
sar destino, fuente
ejemplo
mov eax, 0xFFFF0000 ; coloca el valor 0xFFFF0000 en eax
sar eax, 16 ; desplaza 16 bits a la derecha
desplazar hacia la derecha equivale a dividir entre una potencia de dos
ROR y ROL
Las instrucciones ROR (Rotate right) y ROL (Rotate left) realizan una rotación de bits a la derecha e izquierda respectivamente
sintaxis
ror destino, fuente
rol destino, fuente
ejemplo
mov eax, 0x0F0F0F0F ; coloca el valor 0x0F0F0F0F en eax
ror eax, 8 ; rota los bits de eax 8 posiciones a la derecha
mov eax, 0x0F0F0F0F ; coloca el valor 0x0F0F0F0F en eax
rol eax, 8 ; rota los bits de eax 8 posiciones a la izquierda