Control de Flujo
loop — Bucle con contador rcx
¿Qué hace?
Combina dec rcx + jnz en una sola instrucción. Salta al label si rcx != 0 después de decrementarlo.
Cuándo se usa
- Repetir N veces exactas
- Procesar arrays/buffers de tamaño conocido
- Decodificadores de shellcode
Ejemplos explicados
nasm
; Loop básico — repetir 10 veces
mov rcx, 10
miLoop:
; instrucciones aquí
loop miLoop ; dec rcx; si rcx!=0 → salta a miLoop
; Procesar cada byte de un buffer
mov rdi, buffer
mov rcx, 100 ; 100 bytes
procesarBuffer:
mov al, [rdi] ; leer byte actual
; procesar al...
inc rdi ; avanzar al siguiente byte
loop procesarBuffer
; Fibonacci con loop
xor rax, rax ; F0 = 0
xor rbx, rbx
inc rbx ; F1 = 1
mov rcx, 10 ; 10 iteraciones
loopFib:
add rax, rbx
xchg rax, rbx
loop loopFib
; resultado: rbx = Fibonacci(11) = 89Contexto ofensivo
nasm
; Decodificador XOR de shellcode polimórfico
; El shellcode está XOR-eado para evadir AV
; Este loop lo desencripta en memoria antes de ejecutarlo:
mov rdi, shellcode ; puntero al shellcode encriptado
mov rcx, 27 ; tamaño del shellcode
mov al, 0x41 ; clave XOR
decode_loop:
xor byte [rdi], al ; desencriptar byte actual
inc rdi ; siguiente byte
loop decode_loop ; repetir hasta rcx=0
; después de este loop, el shellcode está desencriptado en memoria
; NOP sled con rep stosb (versión más eficiente que loop)
xor rax, rax
mov al, 0x90 ; NOP opcode
mov rdi, buffer
mov rcx, 200 ; 200 NOPs
rep stosb ; almacena al en [rdi], inc rdi, dec rcx — repite
; rep stosb es básicamente un loop optimizado en hardwarejmp — Salto Incondicional
¿Qué hace?
Salta SIEMPRE al destino especificado. No evalúa ninguna condición. El CPU simplemente mueve rip al nuevo destino.
Cuándo se usa
- Saltar a una etiqueta sin condición
- Implementar switch/case
- ROP gadgets (raros pero existen)
- Saltar al shellcode desencriptado
Ejemplos explicados
nasm
; Salto simple
jmp miFuncion ; rip = dirección de miFuncion
; Mientras jmp vs loop:
mov rcx, 10
loopConJmp:
add rax, rbx
xchg rax, rbx
dec rcx ; jmp NO decrementa rcx solo
jnz loopConJmp ; necesitas decrementar manualmente
; Jump a registro (saltar a dirección en registro)
jmp rax ; rip = rax (útil para shellcodes dinámicos)
call rax ; llamar a función cuya dirección está en rax
; jmp para skip de código
mov rax, 1
jmp skip_malicioso
; este código nunca se ejecuta:
mov rax, 60
syscall
skip_malicioso:
; continuar aquíContexto ofensivo
nasm
; jmp rax / call rax — ejecutar shellcode en memoria
; después de mmap() con permisos rwx:
mov rax, 9 ; mmap syscall
xor rdi, rdi ; addr = NULL
mov rsi, 4096 ; tamaño
mov rdx, 7 ; PROT_READ|PROT_WRITE|PROT_EXEC
mov r10, 0x22 ; MAP_PRIVATE|MAP_ANONYMOUS
mov r8, -1 ; fd = -1
xor r9, r9
syscall
; rax = dirección de la memoria ejecutable asignada
; copiar shellcode allí y:
jmp rax ; ¡ejecutar! rip salta al shellcode
; En shellcodes: jmp al shellcode desencriptado
decode_loop:
xor byte [rdi], 0x41
inc rdi
loop decode_loop
jmp shellcode_start ; ejecutar shellcode ya limpio
; PLT/GOT hijacking — jmp a función de libc
; si consigues sobreescribir una entrada en GOT:
; jmp [got_entry] → redirige a tu códigojnz / jz — Salto si No Zero / si Zero
¿Qué hacen?
Saltan basándose en el Zero Flag (ZF):
jnz→ salta si ZF = 0 (resultado NO fue cero)jz→ salta si ZF = 1 (resultado FUE cero)
Cuándo se usan
- Verificar si un registro llegó a 0
- Comparar igualdad con
cmp - Reemplazar
loopcon más control
Ejemplos explicados
nasm
; Loop manual con jnz (equivale a loop)
mov rcx, 10
miLoop:
; instrucciones
dec rcx
jnz miLoop ; salta si rcx != 0 (ZF=0)
; aquí llegamos cuando rcx = 0
; Verificar si syscall falló
mov rax, 2 ; open syscall
; ... args ...
syscall
test rax, rax ; ZF=1 si rax=0, ZF=0 si rax!=0
jz syscall_fallo ; si rax = 0, algo salió mal
js syscall_error ; si rax < 0 (negativo), hubo error
; Comparar con cmp + jz
mov rax, 42
cmp rax, 42
jz son_iguales ; ZF=1 porque 42-42=0
jnz son_distintos ; no salta porque ZF=1
; test vs cmp
test rax, rax ; AND rax,rax → ZF=1 si rax=0 (más eficiente que cmp rax,0)
jz es_ceroContexto ofensivo
nasm
; Verificar si open() tuvo éxito antes de leer
mov rax, 2
mov rdi, filename
xor rsi, rsi ; O_RDONLY
syscall
test rax, rax
js open_fallo ; si rax < 0 → error
mov rbx, rax ; guardar fd
; Anti-debugging: verificar si estamos siendo debuggeados
; ptrace(PTRACE_TRACEME, 0, 0, 0) retorna -1 si ya hay un debugger
mov rax, 101 ; ptrace syscall
mov rdi, 0 ; PTRACE_TRACEME
xor rsi, rsi
xor rdx, rdx
xor r10, r10
syscall
test rax, rax
jns no_hay_debugger ; si rax >= 0 → no hay debugger → continuar
; si llegamos aquí, hay un debugger — hacer algo:
mov rax, 60 ; exit
xor rdi, rdi
syscall ; terminar limpiamente para no revelar shellcode
; Decodificador con jnz
mov rcx, shellcode_len
decode:
xor byte [rdi], 0x55
inc rdi
dec rcx
jnz decode ; continuar mientras rcx != 0js / jns — Salto si Negativo / si No Negativo
¿Qué hacen?
Basados en el Sign Flag (SF):
js→ salta si SF = 1 (resultado fue negativo)jns→ salta si SF = 0 (resultado fue 0 o positivo)
Cuándo se usan
- Verificar si un valor está por debajo de un umbral (con
cmp) - Detectar errores de syscalls (retornan negativo)
- Loops que continúan mientras un valor es menor que otro
Ejemplos explicados
nasm
; Loop Fibonacci — continuar mientras rbx < 10
loopFib:
add rax, rbx
xchg rax, rbx
cmp rbx, 10 ; rbx - 10
js loopFib ; si resultado negativo (rbx < 10) → salta
; Verificar error de syscall
syscall
js error_handler ; si rax < 0 → error (linux retorna negativo en error)
; Comparar si un número es negativo
cmp rax, 0
js es_negativo
jns es_positivo_o_ceroContexto ofensivo
nasm
; Verificar errores de syscalls — patrón estándar
mov rax, 2 ; open
; args...
syscall
js open_fallo ; cualquier errno retornado como negativo
; Verificar si mmap falló
; mmap retorna -1 (0xFFFFFFFFFFFFFFFF) si falla
mov rax, 9
; args mmap...
syscall
test rax, rax
js mmap_fallo ; rax negativo = error
; Loop de escritura mientras queden bytes
mov rcx, file_size
write_loop:
; write(fd, buf, count)
mov rax, 1
syscall
sub rcx, rax ; rcx -= bytes escritos
js write_error ; si resultado negativo → error
jnz write_loop ; si quedan bytes → seguirjg / jge / jl / jle — Comparaciones con signo
¿Qué hacen?
Saltos condicionales comparando Destino y Fuente:
jg→ D > S (Greater)jge→ D >= S (Greater or Equal)jl→ D < S (Less)jle→ D <= S (Less or Equal)
Ejemplos explicados
nasm
; Verificar límite de un índice
mov rax, indice
cmp rax, 100 ; rax - 100
jge fuera_de_rango ; si rax >= 100 → error (bound check)
jl dentro_de_rango ; si rax < 100 → OK
; Ordenar dos valores (swap si están al revés)
cmp rax, rbx
jle ya_ordenado ; si rax <= rbx → ya está bien
xchg rax, rbx ; si no → swap
ya_ordenado:
; Buscar el mayor de dos números
cmp rax, rbx
jg rax_es_mayor
mov rax, rbx ; rbx era mayor, moverlo a rax
rax_es_mayor:
; rax tiene el mayorContexto ofensivo
nasm
; Verificar si shellcode cabe en el buffer
; (antes de copiar para evitar detectar overflow propio)
mov rax, shellcode_size
cmp rax, buffer_size
jg no_cabe ; si shellcode > buffer → buscar otro método
; Iterar sobre una lista de payloads hasta encontrar uno válido
mov rbx, payloads_list
mov rcx, num_payloads
check_payload:
mov rax, [rbx] ; cargar tamaño del payload
cmp rax, max_size
jle payload_valido ; si payload <= max → usarlo
add rbx, 16 ; siguiente payload en la lista
dec rcx
jnz check_payloadcmp — Comparar sin modificar
¿Qué hace?
Resta internamente (D - S) y actualiza RFLAGS, pero NO guarda el resultado. Los registros quedan intactos — solo cambian los flags.
Cuándo se usa
- Comparar dos valores antes de un salto condicional
- Verificar límites
- Implementar if/else/switch en ASM
Ejemplos explicados
nasm
; cmp vs sub — la diferencia clave:
cmp rax, 10 ; rax - 10, actualiza flags, rax NO cambia
sub rax, 10 ; rax - 10, actualiza flags, rax SÍ cambia (rax = rax-10)
; if (rax == 5)
mov rax, 5
cmp rax, 5 ; 5 - 5 = 0 → ZF = 1
jz es_cinco
; if (rbx > 100)
cmp rbx, 100
jg mayor_que_100
; switch/case simulado
cmp rax, 1
jz caso_1
cmp rax, 2
jz caso_2
cmp rax, 3
jz caso_3
jmp caso_default
; Verificar null pointer (puntero es 0)
test rdi, rdi ; AND rdi, rdi (más eficiente que cmp rdi, 0)
jz puntero_nuloContexto ofensivo
nasm
; Verificar magic bytes de un archivo (file format detection)
; ELF magic: 0x7F 'E' 'L' 'F'
mov eax, dword [rdi] ; leer primeros 4 bytes
cmp eax, 0x464C457F ; "ELF\x7F" en little-endian
jne no_es_elf
; Verificar si estamos en root (uid == 0)
mov rax, 102 ; getuid syscall
syscall
test rax, rax ; ZF=1 si uid=0
jnz no_somos_root
; si llegamos aquí, somos root → escalar privilegios
; Comparar versión del kernel para elegir exploit
; (diferente syscall table en versiones distintas)
cmp r8, 0x0415 ; kernel >= 4.21?
jge usar_exploit_nuevo
jmp usar_exploit_viejo
; Verificar si un proceso existe buscando su PID
; kill(pid, 0) retorna 0 si el proceso existe
mov rax, 62 ; kill syscall
; rdi = pid objetivo
xor rsi, rsi ; señal 0 = solo verificar
syscall
test rax, rax
jz proceso_existetest — AND bit a bit sin guardar resultado
¿Qué hace?
Como and pero solo actualiza flags, no guarda el resultado. M�s eficiente que cmp reg, 0 para verificar si algo es cero.
Ejemplos explicados
nasm
; test vs cmp para verificar cero:
test rax, rax ; AND rax,rax → ZF=1 si rax=0 (2-3 bytes)
cmp rax, 0 ; rax-0 → ZF=1 si rax=0 (7 bytes con rax)
; Verificar bit específico
test rax, 0x01 ; ¿el bit 0 está activo?
jnz bit_activo
; Verificar si syscall retornó error
syscall
test rax, rax
js hubo_error ; rax negativo = errno
; Verificar puntero null
test rdi, rdi
jz puntero_es_nullContexto ofensivo
nasm
; Patrón estándar de verificación de syscall en shellcodes:
mov rax, 2 ; open
syscall
test rax, rax
js error ; si negativo → falló
; Verificar permisos rwx de una región de memoria
; (para saber si podemos ejecutar shellcode ahí)
; /proc/self/maps parsing...
test al, 0x04 ; bit de execute permission
jz no_ejecutable
; Anti-debug con ptrace check
mov rax, 101
; args ptrace TRACEME...
syscall
test rax, rax
jns no_debugger ; si >= 0 → no hay debugger adjuntoResumen — Flujo de control en contexto ofensivo
loop label → decodificadores XOR, procesar buffers N veces
jmp rax/label → ejecutar shellcode en memoria, skip de código
jnz / jz → verificar errores de syscalls, loops de decode
js / jns → detectar errno negativo, loop "mientras < umbral"
jl / jg → bound checks, comparar tamaños
cmp D, S → comparar sin destruir valores (base de todo if/else)
test reg, reg → verificar null / cero de forma eficiente
Patrones comunes en shellcodes:
syscall → test rax, rax → js error
dec rcx → jnz loop
cmp rbx, límite → js continuar