x86-assembler-learning

10 minute read

  10 minute read

Compilado

Entonces, primero, ¿qué es el código ensamblador? El código ensamblador es el código que el procesador realmente ejecuta en su computadora. Por ejemplo, tome un código C:

#include <stdio.h>

void main(void)
{
    puts("Hello World!");
}

Ese código no se ejecutó. La cuestión es que el código se compila en código ensamblador, que se ve así:

hello_asm

También con el código ensamblador, hay muchas arquitecturas diferentes. Diferentes tipos de procesadores pueden ejecutar diferentes tipos de arquitecturas de código ensamblador. Los dos que más trataremos aquí serán ELF (formato ejecutable y enlazable) de 64 bits y de 32 bits. A menudo llamaré a estas dos cosas x64x86.

Registers

En una computadora, un registro es uno de un pequeño conjunto de datos que tienen lugares que forman parte de un procesador de computadora. Un registro puede mantener una instrucción de computadora, una dirección de almacenamiento o cualquier tipo de datos (como una secuencia de bits o caracteres individuales). Los procesadores modernos (I.E 386 y más allá) X86 tienen ocho registros de 32 bits de propósito general

Registers

rbp: Puntero base, apunta a la parte inferior del marco de pila actual
rsp: Puntero de pila, apunta a la parte superior del marco de pila actual
rip: Puntero de instrucciones, apunta a la instrucción a ejecutar.

Registros de propósito general
Estos se pueden utilizar para una variedad de cosas diferentes.
rax:
rbx:
rcx: cuarto argumento 
rdx: terser argumento 
rsi: segundo argumento 
rdi: primer argumento 
r8:
r9:
r10:
r11:
r12:
r13:
r14:
r15:

En x64Linux, los argumentos de una función se pasan a través de registros. Los primeros argumentos pasan por estos registros:

rdi:    First Argument
rsi:    Second Argument
rdx:    Third Argument
rcx:    Fourth Argument
r8:     Fifth Argument
r9:     Sixth Argument

Con la x86arquitectura elf, los argumentos se pasan a la pila. También una cosa, como sabrá, en la función C puede devolver un valor. En x64, este valor se pasa en el rax registro. En x86este valor se pasa en el eax registro.

También una cosa, hay diferentes tamaños para los registros. Estos tamaños típicos con los que trataremos son 8bytes, 4bytes, 2bytes y 1. La razón de estos diferentes tamaños se debe al avance de la tecnología, podemos almacenar más datos en un registro.

8 Bytes Registe4 Lower 4 Bytes Lower 2 Bytes Lower Bytes
rbp ebp bp bpl
rsp esp sp spl
rip eip    
rax eax ax al
rbx ebx bx bl
rcx ecx cx cl
rdx edx dx dl
rsi esi si sil
rdi edi di dil
r8 r8d r8w r8b
r9 r9d r9w r9b
r10 r10d r10w r10b
r11 r11d r11w r11b
r12 r12d r12w r12b
r13 r13d r13w r13b
r14 r14d r14w r14b
r15 r15d r15w r15b

En x64veremos los 8registros de bytes. Sin embargo, x86los registros de mayor tamaño que podemos usar son los 4registros de bytes como ebpespeipetc. Ahora también podemos usar registros más pequeños que los registros de tamaño máximo para la arquitectura.

Allí x64está el registro raxeaxaxy . alEl raxregistro apunta al pleno 8. El eaxregistro es solo los cuatro bytes inferiores del raxregistro. El axregistro son los últimos 2bytes del raxregistro. Por último, el alregistro es el último byte del raxregistro.

The Stack, Stack Pointer, and Base Pointer

Ahora, una de las regiones de memoria más comunes con las que trabajará es la pila. Es donde se almacenan las variables locales en el código.

Por ejemplo, en este código la variable xse almacena en la pila:

#include <stdio.h>

void main(void)
{
    int x = 5;
    puts("hi");
}

Específicamente, podemos ver que está almacenado en la pila en rbp-0x4.

stack

Ahora los valores en la pila se mueven empujándolos a la pila o sacándolos. Esa es la única forma de agregar o eliminar valores de la pila (es una estructura de datos LIFO). Sin embargo, podemos hacer referencia a valores en la pila.

Los límites exactos de la pila se registran mediante dos registros, rbprsp. El puntero base rbpapunta a la parte inferior de la pila. El puntero de la pila rspapunta a la parte superior de la pila.

El puntero de la pila suele ser un registro que contiene la parte superior de la pila. El puntero de la pila contiene la dirección más pequeña X de tal manera que cualquier dirección más pequeña que x se considera basura.

Siempre que se llama una función, se inicia un nuevo marco de pila. -Este se llama la función Prolog.

push rbp ; Preservar el puntero de marco actual

mov rbp, rsp ; Cree un nuevo puntero de marco que apunte a la parte superior de la pila actual

sub esp, 0x10 ; Asignar 10 bytes por valor de los locales en la pila.

La pila crece hacia las direcciones de memoria más bajas. Cuando se declara una variable local, se decrementa el puntero de la pila (rsp). El espacio entre el puntero base (rbp) y el puntero de la pila es donde se almacenan todas las variables locales.

Como se muestra en la imagen, la variable local 1 se encuentra en la dirección de la memoria ebp-0x4.

the_stack

64 Bit Assembly Instructions (Intel)

Puntero de instrucción (rip, eip)

Es un registro que indica cuál es la siguiente instrucción a ejecutar. Cada vez que el procesador ejecuta una instrucción, actualiza el valor del puntero de instrucción para que apunte a la siguiente instrucción que se ejecutará. Para ello, su valor se incrementa en función del tamaño de la instrucción.

<instrucion> <destination> <source>
mov rax ebx

MOV

La instrucción mov copia el elemento de datos a que se refiere su segundo operando en la ubicación mencionada por su primer operando. Mientras que los movimientos de registro para registrarse son posibles, los movimientos directos de memoria a la memoria no lo son. En los casos en que se deseen las transferencias de memoria, los contenidos de la memoria de origen primero deben cargarse en un registro, luego se pueden almacenar a la dirección de memoria de destino.

Los [] son como un dereferencer en C (* PTR). Así que esp+0x4 es la ubicación en la memoria y [esp+0x4] es lo que está contenido en esa ubicación

## Examoles | Syntac | Examples | | —————— | ————– | | mov <reg>,<reg> | mov rax, rbx |

Copia el valor en rbx en rax

Syntax Example
mov <reg>,<mem> mov rax, [rbp-0x0]

Copie el valor de la variable almacenada en la dirección de la memoria rbp-0x4 en el registro rax

Sintax Example
mov <mem>,<reg> mov [rbp-0x4],rbx

Mueva el contenido de rbx a los 4 bytes en la dirección de la memoria ebp-0x4

En algunos casos, el tamaño de una región referida a memoria es ambigua. Considere la instrucción mov [rbx], 2. ¿Debe esta instrucción mover el valor 2 en el byte único en la dirección rbx? Tal vez debería mover la representación de enteros de 32 bits de 2 en el inicio de 4 bytes en la dirección rbx. Dado que cualquiera es una interpretación posible válida, el ensamblador debe dirigirse explícitamente en cuanto a lo que es correcto. Las directivas de tamaño BYTE PTR, WORD PTR y DWORD PTR sirven a este propósito, lo que indica tamaños de 1, 2 y 4 bytes respectivamente.

mov BYTE PTR [ebx], 2 ; Mueva 2 en el único byte en la dirección almacenada en EBX.

mov WORD PTR [ebx], 2 ; Mueva la representación entera de 16 bits de 2 en los 2 bytes que comienzan en la dirección en EBX

mov DWORD PTR [ebx], 2 ; Mueva la representación entera de 32 bits de 2 en los 4 bytes que comienzan en la dirección en EBX.

sub

Este valor restará el segundo operando del primero y almacenará la diferencia en el primer argumento. Por ejemplo:

sub rsp, 0x10

Esto establecerá el rspregistro igual arsp - 0x10

CALL/RET

call, ret — Llamada de subrutina y devolución

Estas instrucciones implementan una llamada de subrutina y devuelve. La instrucción de llamadas primero empuja la ubicación del código actual en la pila admitida de hardware en la memoria, y luego realiza un salto incondicional a la ubicación del código indicada por el operando de la etiqueta. A diferencia de las instrucciones de salto simples, la instrucción de llamadas guarda la ubicación para volver a la hora de que se complete la subrutina.

Antes de llamar a una función, sus argumentos se colocan en la pila. Entonces, si alguna vez quiere saber qué se está pasando a una función, solo mire lo que se pone en la pila directamente antes de su call.

La instrucción res implementa un mecanismo de retorno de subrutina. Esta instrucción primero saca una ubicación de código del hardware compatible pila en memoria. Luego realiza un salto incondicional a la ubicación del código recuperado.

El valor de retorno de una función siempre se mueve a eax. Entonces, para ver qué se devolvió una función, solo mire a eax después de la llamada.

Syntax

call <function> ret

desreferencia

Si alguna vez ve corchetes como [], significan que no hacen referencia a los punteros. Un puntero es un valor que apunta a una dirección de memoria particular (es una dirección de memoria). Desreferenciar un puntero significa tratar un puntero como el valor al que apunta. Por ejemplo:

mov rax, [rdx]

Moverá el valor señalado por rdxen el raxregistro. Por otro lado:

mov [rax], rdx

Moverá el valor del rdxregistro a cualquier memoria a la que apunte el raxregistro. El valor real del raxregistro no cambia.

lea

La instrucción lea coloca la dirección especificada por su segundo operando en el registro especificado por su primer operando. Nota, el contenido de la ubicación de la memoria no se carga, solo se calcula la dirección efectiva y se coloca en el registro. Esto es útil para obtener un puntero en una región de memoria.

Syntax

lea <reg32>,<mem>

Ejemplo

lea edi, [ebx+4*esi] — La cantidad EBX+4*ESI se coloca en EDI. lea eax, [esp+0x8] — El valor en va se coloca en eax.

add

Esto simplemente suma los dos valores y almacena la suma en el primer argumento. Por ejemplo:

Syntax

add <destination>,<source>

Examples

add eax, 0x10; EAX ← EAX + 10 add eax, ebx; Añadir el contenidos de EBX al contenido de eax

xor

Esto realizará la operación binaria xor en los dos argumentos que se le dan, y almacenará el resultado en la primera operación:

xor rdx, rax

Eso establecerá el rdxregistro igual a rdx ^ rax.

Las operaciones andand orhacen esencialmente lo mismo, excepto con los operadores binarios and or or .

PUAH

La pushinstrucción hará crecer la pila en 8bytes (for x644for x86), luego empujará el contenido de un registro al nuevo espacio de la pila. Por ejemplo:

push rax

Esto aumentará la pila por 8bytes, y el contenido del raxregistro estará en la parte superior de la pila.

Syntax

push <reg> push <mem> push <con32>

Examples

push eax — Enpuja eax en la pila push [var] — Empuje los 4 bytes en la dirección var en la pila

pop

La popinstrucción sacará los 8bytes superiores (8 for x644for x86) de la pila y los colocará en el argumento. Entonces reducirá la pila. Por ejemplo:

pop rax

Los bytes superiores 8de la pila terminarán en el raxregistro.

jmp

La jmpinstrucción saltará a una dirección de instrucción. Se utiliza para redirigir la ejecución del código. Por ejemplo:

jmp 0x602010

Esa instrucción hará que la ejecución del código salte a 0x602010y ejecute cualquier instrucción que esté allí.

cmp

Compare los valores de los dos operandos especificados, configurando los códigos de condición en la palabra de estado de la máquina adecuadamente.

Los contenidos de la palabra estado de la máquina incluyen información sobre la última operación aritmética realizada. Por ejemplo, un bit de esta palabra indica si el último resultado fue cero. Otro indica si el último resultado fue negativo. Basado en estos códigos de condición, se pueden realizar varios saltos condicionales.

Esta instrucción es equivalente a la sub Instrucción, excepto que el resultado de la resta se descartó en lugar de reemplazar el primer operando.

Syntax

cmp <arg1>,<arg2>

JUMP

Se administran nombres de las ramas condicionales que se basan intuitivamente en la última instrucción de comparación, cmp. Por ejemplo, las ramas condicionales como jle y jne se basan en la primera vez que realizan una operación de cmp en los operandos deseados.

Syntax

je <label> (salta cuando es igual)

jne <label> (saltar cuando no es igual)

jz <label> (saltar cuando el último resultado fue cero)

jg <label> (salta cuando es mayor que)

jge <label> (salta cuando es mayor o igual que)

jl <label> (salta cuando menos de)

jle <label> (salta cuando es menor o igual que)