Skip to content

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) = 89

Contexto 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 hardware

jmp — 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ódigo

jnz / 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 loop con 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_cero

Contexto 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 != 0

js / 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_cero

Contexto 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 → seguir

jg / 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 mayor

Contexto 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_payload

cmp — 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_nulo

Contexto 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_existe

test — 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_null

Contexto 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 adjunto

Resumen — 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