Assembly Language - x86 Overview

¿Qué es Assembly?

  • Low-level language: versión legible por humanos del instruction set de la arquitectura
  • Diferente para cada arquitectura de computadora
  • Basado en mnemonics
  • Complejo de manejar

¿Por qué Assembly?

  • Assembly es el lenguaje de bajo nivel más popular
  • Es en realidad una clase de lenguajes
  • Los más comunes actualmente: 8086, ARM, MIPS, x86, x64
  • x86:** la mayoría de computadoras de escritorio, laptops y consolas de juegos
  • ARM:** Apple M1, dispositivos móviles

Compilación y Desassembly

El proceso es:

  1. El autor del programa escribe código en un lenguaje de alto nivel ( ej. C)
  2. El código C se compila a machine code ( series de bytes que la CPU entiende)
  3. El investigador generalmente no tiene acceso al código fuente C, solo a los bytes
  4. Un disassembler traduce esos bytes a una representación textual más legible

Arquitectura x86

Cuando un programa se ejecuta, ocurre el siguiente loop:

  1. Una instrucción de CPU se lee de la memoria principal por la Control Unit
  2. La instrucción se procesa y ejecuta por la Arithmetic-Logic Unit, junto con input del usuario o de los registros
  3. El output de la operación se almacena en los registros de la CPU o se envía a un dispositivo de salida

x86-32

  • Arquitectura CISC de 32-bit, Little-Endian
  • Opera en dos modos:
  • Modo Real: según arranca, soporta instrucciones de 16 bits
  • Modo Protegido: modo que soporta paginación y memoria virtual

Little-Endian vs Big-Endian

Endianness es el orden o secuencia de bytes de una palabra de datos digital en la memoria de computadora.

x86 - Rings ( Anillos de Privilegio)

x86 soporta el concepto de separación de privilegios mediante 4 anillos:

  • Ring 0** ( nivel de privilegio máximo)
  • Ring 1 y 2** ( no se usan normalmente)
  • Ring 3** ( nivel de privilegio mínimo) Los sistemas operativos típicamente implementan el nivel de usuario en Ring 3 y el kernel en Ring 0.

Sintaxis de Assembly Language x86

Sintaxis INTEL ( más común):

mov eax, offset var
mov ebx, 0f02h

Sintaxis AT&T:

movl $var, %eax
movl $0xf02, %ebx

Instrucciones

Una instrucción de assembly = mnemonic + optional operand ( s) Ejemplo: mov eax, 0xFF equivale al opcode B8 FF 00 00 00 Un operand puede ser:

  • Immediate:** un valor constante ( ej. 0x3)
  • Registro:** ej. eax
  • Dirección de memoria:** ej. [0x400100 + 4] El opcode son los bytes que corresponden a la instrucción y sus operands.

Instrucciones Populares

Datos

  • mov
  • lea

Lógica

  • or
  • and
  • xor
  • shr
  • shl

Aritmética

  • add
  • sub
  • inc
  • dec
  • mul
  • div

Control de Flujo

  • test
  • cmp
  • jmp
  • jcc

Stack

  • push
  • pop
  • call
  • ret

Instrucciones Básicas

MOV ( Mover)

mov destination source — mueve el valor de source a destination Ejemplos:

  • mov es, ax — es = ax
  • mov bl, 255 — bl = 255
  • mov ebx, [eax] — memory access por registro
  • mov ebx, [eax + 8] — acceso por desplazamiento
  • mov ebx, [eax + esi * 4] — acceso por index y desplazamiento

LEA ( Load Effective Address)

lea operand1, mem — lee una dirección de memoria y la almacena en operand1 ( sin acceder al contenido) Ejemplos:

  • lea eax, [ebx+8] — pone en eax el valor ebx+8
  • lea edx, [eax * 5] — pone en edx el valor eax*5

ADD, SUB, INC, DEC

  • add operand1, operand2 — suma los dos operands y almacena el resultado en el primero
  • sub operand1, operand2 — resta operand2 de operand1
  • inc operand — incrementa el registro en 1
  • dec operand — decrementa el registro en 1

SHR/SHL ( Shift Right/Left)

Corre bits a izquierda o derecha

  • shr eax, 0x10
  • shl eax, 0x2

MUL ( Multiplicación sin signo)

Multiplica el registro AX por el operand:

  • Byte ⇒ resultado se almacena en AX
  • Word ⇒ resultado se almacena en DX y AX

DIV ( División)

Divide entre el operand:

  • Byte ⇒ divide AX, almacena resultado en AL
  • Word ⇒ divide DX y AX, almacena en AX y DX ( remainder)

Registros

Un registro es la unidad básica de almacenamiento de datos de la CPU, con tiempo de acceso más rápido que RAM.

Registros Generales ( 32-bit con variantes 16 y 8-bit)

  • EAX** ( AX, AH, AL) — usado frecuentemente en operaciones
  • EBX** ( BX, BH, BL)
  • ECX** ( CX, CH, CL) — contador en loops
  • EDX** ( DX, DH, DL)
  • EBP** ( BP) — base pointer
  • ESP** ( SP) — stack pointer
  • ESI** ( SI) — source index
  • EDI** ( DI) — destination index

Registros de Segmento

  • CS** — code section
  • SS** — stack section
  • DS** — data section
  • ES, FS, GS** — general purpose

Registros de Punteros

  • SP** — stack pointer
  • BP** — base pointer
  • SI** — source index
  • DI** — destination index
  • IP** — instruction pointer

Registros Especiales

  • EFLAGS:** registro de 32 bits con flags que proporcionan información sobre operaciones previas
  • EIP:** siempre contiene la dirección de la siguiente instrucción a ejecutar

Register Breakdown

EAX, EBX, ECX y EDX pueden desglosarse así:

  • EAX** — todos los 32 bits
  • AX** — 16 bits menos significativos de EAX
  • AH** — 8 bits más significativos de AX
  • AL** — 8 bits menos significativos de AX Los 16 bits más significativos de EAX pueden accederse usando SHR EAX, 0x10 y luego AX.

Convenciones de Registros

  • EAX** — valor de retorno de una función
  • EAX:EDX** — cociente y remainder en multiplicación y división
  • ECX** — contadores ( variables de loop)
  • ESI** — source en operaciones de chain/memory ( strcpy, memcpy)
  • EDI** — destination en operaciones de chain/memory
  • EBP** — base del stack frame
  • ESP** — stack pointer

EFLAGS Register

Registro de 32 bits con flags que dan información sobre el resultado de cálculos previos. Los flags más comunes son:

  • Zero Flag:** se activa si el resultado de una operación es 0; de lo contrario se limpia
  • Carry Flag:** se activa si el resultado es demasiado grande o pequeño para el operand destino; de lo contrario se limpia
  • Sign Flag:** se activa si el MSB se establece ( el resultado es negativo); de lo contrario se limpia

Ejemplo EFLAGS

Dado:

mov eax, 0x1
mov ebx, 0x0
sub ebx, eax ; ebx - eax = 0x0 - 0x1 = -1

Estados de flags:

  • Zero flag:** 0 ( el resultado no es cero)
  • Carry flag:** 1 ( hay underflow)
  • Sign flag:** 1 ( el resultado es negativo)

Branching ( Control de Flujo)

Hay dos tipos de jumps en x86:

  • Unconditional:** solo salta donde se le dice
  • jmp 0x401072
  • Conditional:** chequea el resultado de algún cálculo, salta según corresponda
  • cmp eax, 0x10 — compara el valor de eax con 0x10
  • jge 0x401072 — salta a 0x401072 si eax es mayor/igual que 0x10

Jumps Condicionales

Hay muchos tipos de jumps condicionales, frecuentemente referidos como jcc ( donde j = jump y cc = condition). Cada instrucción de jump realiza diferentes chequeos en el registro EFLAGS para determinar si el jump debe realizarse o no.

Comparaciones

CMP: compara dos operands

  • cmp operand1, operand2
  • La operación cambia el estado de los flags dependiendo del resultado
  • Ejemplo: cmp ax, cx TEST: realiza un AND lógico
  • Comúnmente usado para chequear si un registro es cero
  • Ejemplo: test eax, eax; jz 0x400100 — salta si eax == 0

Cómo decir en x86…

if ( a == b) goto 0x1000; cmp a, b; jz 0x1000;
if ( a < b) goto 0x1000; cmp a, b; jl 0x1000;
if ( a) goto 0x1000; test a, a; jnz 0x1000;

Loops

Los loops en assembly se establecen con la instrucción LOOP:

mov ECX, 10
l1:
 dec CL
 loop l1

O alternativamente:

mov ECX, 10
L1:
 DEC CL
 JNZ L1

Cuando se usa LOOP, el contador del loop se establece en ECX y cada vez que se ejecuta se resta 1.

Main Memory ( Memoria Principal)

Cuando un programa se ejecuta, se cargan varias secciones en RAM:

  • Data:** variables estáticas/globales, puestas en su lugar cuando el programa se carga
  • Code:** instrucciones de CPU del programa
  • Heap:** memoria dinámica, asignada y liberada durante la ejecución
  • Stack:** variables y argumentos locales a las funciones del programa

The Stack

El stack es una estructura de datos LIFO ( Last In First Out) con operaciones push & pop.

Stack-relevant registers:

  • ESP** ( Stack Pointer) — siempre apunta al tope del stack ( dinámico)
  • EBP** ( Base Pointer) — se mantiene consistente dentro de una función y se usa para referenciar variables locales y parámetros

Stack-relevant instructions:

  • pop, push, call, ret ( y también enter, leave)

PUSH y POP

PUSH

push origin — decrementa stack pointer ( ESP) e inserta el valor source en el stack Equivalente a:

sub esp, 4
mov [esp], origin

POP

pop destination — envía el primer valor del stack al operand destino e incrementa el stack pointer Equivalente a:

mov destination, [esp]
add esp, 4

Function Calls

Una llamada a función en x86 ocurre así:

  1. Los parámetros se pushean en el stack en orden inverso
  2. La instrucción call pushea la dirección de retorno en el stack
  3. En la función:
  • Se pushea el EBP anterior
  • Se establece el nuevo EBP para el stack frame actual
  • Se asigna espacio para variables locales decrementando ESP
  1. Al retornar, ret extrae la dirección de retorno del stack y salta a ella

C to x86

Un programa C con argumentos en main:

int main ( int argc, char** argv)
program.exe -o output.txt

Tendrá:

  • argc = 3 ( programa, “-o”, “output.txt”)
  • argv = [dirección de "program.exe", dirección de "-o", dirección de "output.txt"]

Remember #1

mov [ebp+myvar], somevalue — myvar es un offset (-4, -8, -C…) desde EBP a la dirección del stack donde se almacena myvar. Esto asigna el valor somevalue a myvar.

Remember #2

function ( a,b,c) : push c; push b; push a; — Los parámetros de función se pushean en orden inverso.

Remember #3

mov eax, [ebp+argv] — argv es un offset desde EBP donde se almacena la dirección de argv mov ecx, [eax+4*i] — esto significa “establece ecx igual a argv[i]“

Interrupt Handler

En lenguaje assembly a menudo veremos estructuras de código así:

mov eax, 4 ;system call number ( syswrite)
int 0x80 ;interrupt handler

O:

mov eax, 1 ;system call number ( sysexit)
int 0x80 ;interrupt handler

Estas llamadas hacen que el programa ceda el control de ejecución al kernel.

Bibliografía

  • The Art of Assembly Language — Randall Hyde
  • Fundamentos de ensamblador — Tecnología de computadores UAH
  • Practical Malware Analysis — Michael Sikorski and Andrew Honig
  • https://www.begin.re/x86-overview — Ophir Harpaz
  • Eduardo Blázquez — Reversing, técnicas de ingeniería inversa [RootedCON 2018 - ESP]