Saltar a contenido

Binary Exploitation - Gecko Academy - Módulo 1

Cheatsheet Rápida de IDA Pro para Challenges

  • Navegación rápida: g ir a dirección/símbolo, SPACE alterna Graph/Text, ESC vuelve atrás.
  • Strings y xrefs: SHIFT + F12 abre strings, Enter salta a la referencia, x muestra referencias cruzadas.
  • Definir/limpiar código: U pasa a bytes sin desensamblar, C vuelve a definir código, o define offset en operandos.
  • Renombrar/documentar: n renombra, y cambia tipo, ; agrega comentario en línea, ALT + M / CTRL + M para bookmarks.
  • Navegar flujo: p crea función, [ / ] saltan a predecesor/sucesor, Ctrl + Enter sigue salto en Graph View.
  • Hex <-> decimal: h alterna bases en operandos; # fuerza valor numérico.
  • Depuración rápida: F2 breakpoint, F7/F8 step into/over, Ctrl + F7 re-run con mismo input.
  • IDA dummy names
  • ocultar casts en expresiones: Options -> General -> Disassembly -> Hide redundant casts in expressions esto sirve para que las expresiones sean más legibles.

Metodología de Análisis y Reversing de Binarios

Introducción

Este documento contiene la metodología aprendida en el primer modulo Gecko Academy para el análisis estático y dinámico de binarios ejecutables. El objetivo es comprender el funcionamiento interno de programas compilados para identificar vulnerabilidades, entender su lógica, y eventualmente poder explotarlos.


Proceso de Análisis (18/11/2025)

1. Análisis Dinámico Inicial

Objetivo: Observar el comportamiento del programa sin modificarlo.

  • Ejecutar el binario en un entorno controlado
  • Observar entradas esperadas y salidas producidas
  • Identificar mensajes de error, validaciones, o comportamientos anómalos
  • Documentar el flujo normal de ejecución

Ejemplo:

C:\> programa_vulnerable.exe
Enter password: test123
Access Denied!

2. Análisis Estático en Desensamblador

Objetivo: Entender la lógica interna del programa mediante ingeniería inversa.

2.1 Buscar Strings Relevantes

  • Presionar SHIFT + F12 para ver todas las strings del binario
  • Buscar palabras clave como:
  • "checking", "password", "access"
  • "success", "denied", "error"
  • "admin", "login", "verify"

2.2 Localizar Lógica Comparativa

  • Identificar funciones de comparación (cmp, test, strcmp, etc.)
  • Analizar saltos condicionales (je, jne, jz, jnz, etc.)
  • Seguir el flujo del programa desde el punto de entrada (main)

2.3 Organización Visual

Bookmarks:

  • ALT + M: Crear bookmark en el bloque actual
  • CTRL + M: Ver lista de todos los bookmarks

Código de colores:

  • Rojo: Caminos que NO queremos seguir (errores, acceso denegado)
  • Azul: Salidas exitosas del ciclo o cierre del programa
  • Verde: Lógica de validación exitosa
  • Amarillo: Áreas de interés o dudosas

3. Renombrado y Documentación

Objetivo: Hacer el código legible y comprensible.

Atajos Útiles

Atajo Función
n Renombrar variable/función en el cursor
x Ver referencias cruzadas (dónde se usa)
h Alternar entre hexadecimal y decimal
; Agregar comentario en la línea actual
SHIFT + F12 Ver todas las strings
ALT + M Crear bookmark
CTRL + M Ver bookmarks

Estrategia de Renombrado

  1. Variables de entrada: user_input, password_buffer, stdin_data
  2. Contadores: loop_counter, index, char_pos
  3. Flags de validación: is_valid, access_granted, check_result
  4. Funciones: Nombres descriptivos como validate_password, check_access

Ejemplo:

; Antes
var_10 = dword ptr -10h
var_C = dword ptr -0Ch

; Después (con renombrado)
user_input = dword ptr -10h
loop_counter = dword ptr -0Ch

4. Análisis de Funciones Clave

Funciones de Entrada

  • gets: Lee desde stdin (vulnerable a buffer overflow)
  • fgets: Lee con límite de tamaño
  • scanf: Lee con formato específico
  • ReadFile / ReadConsole: APIs de Windows para lectura

Funciones de Conversión

  • atoi: ASCII to Integer (string → int)
  • atol: ASCII to Long
  • strtol: String to Long con validación

Funciones de Comparación

  • strcmp: Comparación de strings
  • strncmp: Comparación con límite de longitud
  • memcmp: Comparación de memoria byte a byte

5. Análisis Dinámico con Debugger

Objetivo: Validar hipótesis del análisis estático observando la ejecución real.

Flujo de Depuración

  1. Establecer breakpoints en puntos clave
  2. Ejecutar el programa paso a paso
  3. Inspeccionar registros y memoria
  4. Comparar con el análisis estático

Comandos x64dbg / WinDbg Útiles

# x64dbg - Establecer breakpoints
bp 0x40060a                  # En dirección específica
bp main                      # En función
bp strcmp                    # En función de librería

# Ejecución
run / F9                     # Ejecutar programa
F8                           # Step over (sin entrar en funciones)
F7                           # Step into (entrando en funciones)
F9                           # Continue hasta breakpoint

# Inspección de memoria
dump [rcx]                   # Ver memoria en dirección
ds:[rcx]                     # Ver string en dirección
disasm rip                   # Ver desensamblado en RIP

# Inspección de registros
registers / r                # Ver todos los registros

WinDbg:

bp main                      # Breakpoint
g                            # Go / Continue
p                            # Step over
t                            # Step into
da [rcx]                     # Dump ASCII string
du [rcx]                     # Dump Unicode string
db [address]                 # Dump bytes
dd [address]                 # Dump dwords
r                            # Ver registros

Orden Recomendado de Análisis

Metodología Completa

  1. ANÁLISIS ESTÁTICO

    • Abrir en IDA Pro / Ghidra / Binary Ninja
    • Identificar strings relevantes
    • Mapear el flujo del programa
    • Renombrar variables y funciones
    • Colorear bloques según su función
    • Documentar con comentarios
  2. ANÁLISIS DINÁMICO

    • Ejecutar en x64dbg / WinDbg / IDA Debugger
    • Establecer breakpoints en zonas críticas
    • Observar valores de registros
    • Inspeccionar memoria y pila
    • Verificar hipótesis del análisis estático
  3. COMPARACIÓN Y SÍNTESIS

    • Comparar comportamiento esperado vs real
    • Leer y entender registros y pila
    • Identificar vulnerabilidades
    • Documentar hallazgos

Ejemplo Práctico: Ejercicio 2 y 3

Contexto

Análisis de un binario que valida entrada del usuario mediante un ciclo comparativo.

Pasos Realizados

  1. Ejecución inicial: Observamos que el programa solicita entrada y valida contra algo
  2. Búsqueda de strings: Encontramos "checking" con SHIFT + F12
  3. Análisis del flujo: Identificamos un ciclo for que compara caracteres
  4. Renombrado: Cambiamos var_10 a user_input, var_C a loop_index
  5. Código de colores:
    • Rojo: Bloque de "Access Denied"
    • Azul: Salida exitosa / "Access Granted"
    • Verde: Lógica de comparación correcta
  6. Análisis dinámico: Breakpoint en la comparación, observamos valores en registros

Resultado Visual

Ejercicio 3 coloreado

Análisis con bloques coloreados mostrando el flujo del programa


Consideraciones de Seguridad

Funciones Peligrosas

Función Riesgo Alternativa Segura
gets() Buffer overflow fgets()
strcpy() Buffer overflow strncpy() / strlcpy()
sprintf() Buffer overflow snprintf()
scanf("%s") Buffer overflow scanf("%Ns") con límite
strcat() Buffer overflow strncat() / strlcat()

Puntos de Interés en Reversing

  • Comparaciones de contraseñas en texto plano
  • Validaciones que se pueden saltar
  • Condiciones invertidas (cambiar je por jne)
  • Buffers sin validación de tamaño
  • Uso de funciones inseguras

Atajos Útiles en IDA Pro

Atajo Función
SHIFT + F12 Ver todas las strings del binario
ALT + M Crear bookmark en bloque actual
CTRL + M Ver lista de bookmarks
n Renombrar variable/función
x Ver referencias cruzadas (dónde se usa)
h Cambiar entre hexadecimal y decimal
; Agregar comentario
U Volver a bytes sin desensamblar (undefine)
C Volver a ensamblar bytes a código

Notas de las Diapositivas

Objetivo del Ejercicio

Analizar un binario de Windows que valida una contraseña mediante comparaciones byte a byte.

Conceptos Clave

Stack Frame (Marco de Pila)

  • El stack frame es la porción de la pila asignada a una función
  • Contiene:
  • Variables locales
  • Parámetros de función
  • Dirección de retorno
  • Base pointer guardado (rbp / ebp)

Registros Importantes x86/x64

Registros de propósito general (x64):

  • rax / eax: Valor de retorno, acumulador
  • rbx / ebx: Base register
  • rcx / ecx: Counter, primer parámetro (Windows x64 calling convention)
  • rdx / edx: Data, segundo parámetro
  • rsi / esi: Source index
  • rdi / edi: Destination index
  • rbp / ebp: Base pointer (marco de pila)
  • rsp / esp: Stack pointer (tope de pila)
  • r8-r15: Registros adicionales en x64

Registros de control:

  • rip / eip: Instruction pointer (próxima instrucción a ejecutar)
  • rflags / eflags: Flags (ZF, CF, SF, OF, etc.)

Calling Convention (Windows x64 - FastCall)

Parámetros:

  1. rcx - Primer parámetro
  2. rdx - Segundo parámetro
  3. r8 - Tercer parámetro
  4. r9 - Cuarto parámetro
  5. Stack - Parámetros adicionales

Valor de retorno: rax

Shadow space: Windows x64 requiere 32 bytes (0x20) de "shadow space" en la pila para los primeros 4 parámetros

RBP vs RSP en Win64

1. ¿Qué es RSP?

  • El puntero de pila real utilizado por el hardware.
  • Siempre apunta al tope de la pila.
  • Se modifica automáticamente con push, pop, call, ret.
  • El ABI de Win64 requiere alineación de 16 bytes antes de cada call.
  • Define:
  • la ubicación de la dirección de retorno,
  • el diseño de las variables locales,
  • el shadow space obligatorio de 32 bytes.

2. ¿Qué es RBP?

  • Un registro de propósito general, misma categoría que RAX/RBX/RCX.
  • Tradicionalmente usado como base pointer o puntero de marco (EBP en 32 bits).
  • Estructura clásica de marco:
push rbp
mov rbp, rsp
sub rsp, X
  • Proporciona un ancla estable para depuradores y humanos (offsets fijos).

3. Por qué Win64 raramente usa RBP como frame/base pointer

(1) Optimización: Frame Pointer Omission (FPO)

  • El compilador omite el frame pointer para liberar RBP para uso general.
  • Opción de MSVC /Oy (habilitada en compilaciones release).
  • Más registros libres = mejor optimización.

(2) Win64 no necesita RBP para el unwinding

  • A diferencia de x86, Win64 usa metadatos de unwinding .pdata / .xdata.
  • Permite recorrer la pila, SEH y manejo de excepciones sin RBP.
  • El compilador es libre de reutilizar RBP.

(3) Las funciones simples no necesitan un marco fijo

  • Si no hay tamaño de pila variable, ni alloca(), ni locales complejos:
  • El compilador usa directamente direccionamiento [rsp+offset].

4. Cuándo se usa RBP

  • Compilaciones debug (/Od, /Oy-).
  • Funciones con VLAs, alloca, o marcos de pila dinámicos.
  • Ensamblador inline que requiere frame pointer.
  • Funciones explícitamente marcadas para evitar optimizaciones.

5. Implicaciones para reversing

  • En binarios Win64 optimizados:
  • No hay push rbp / mov rbp, rsp.
  • Variables locales accedidas vía [rsp+offset].
  • RBP se usa libremente como registro general.
  • En compilaciones debug:
  • Aparece el frame pointer clásico.
  • Más fácil para análisis manual de la pila.

Resumen

  • RSP = puntero de pila real, siempre se usa.
  • RBP = frame pointer opcional, frecuentemente omitido.
  • Win64 omite RBP porque:
  • Usa metadatos de unwinding en lugar de frame pointers.
  • Liberar RBP mejora la optimización.

Instrucciones Comunes

Movimiento de datos:

  • mov dst, src: Copia src a dst
  • lea dst, [src]: Load Effective Address (carga dirección)
  • push value: Apila valor
  • pop dst: Desapila a dst

Aritmética:

  • add dst, src: dst = dst + src
  • sub dst, src: dst = dst - src
  • inc dst: dst++
  • dec dst: dst--
  • xor dst, src: XOR bit a bit (xor reg, reg = poner en 0)

Comparación y saltos:

  • cmp op1, op2: Compara (resta sin guardar resultado, solo actualiza flags)
  • test op1, op2: AND bit a bit sin guardar (actualiza flags)
  • je/jz: Jump if Equal / Zero (ZF=1)
  • jne/jnz: Jump if Not Equal / Not Zero (ZF=0)
  • jl/jg: Jump if Less / Greater (signed)
  • ja/jb: Jump if Above / Below (unsigned)
  • jmp: Salto incondicional

Patrones Comunes en Reversing

Ciclo FOR:

; for(int i = 0; i < 10; i++)
mov ecx, 0          ; i = 0
loop_start:
cmp ecx, 10         ; i < 10?
jge loop_end        ; si i >= 10, salir
; ... cuerpo del ciclo ...
inc ecx             ; i++
jmp loop_start
loop_end:

Comparación de strings:

; Comparación byte a byte
mov al, [rsi]       ; Cargar byte de string 1
mov dl, [rdi]       ; Cargar byte de string 2
cmp al, dl          ; Comparar
jne not_equal       ; Si no son iguales, saltar
inc rsi             ; Siguiente byte
inc rdi
test al, al         ; ¿Llegamos al null terminator?
jnz compare_loop    ; Si no, continuar

Metodología del Ejercicio 1

  1. Ejecutar el binario: Ver qué hace y qué input espera
  2. Abrir en IDA Pro: Análisis estático
  3. Buscar strings (SHIFT + F12): Encontrar mensajes de error/éxito
  4. Seguir referencias: Ver dónde se usan esas strings
  5. Identificar la lógica de validación: Ciclos, comparaciones
  6. Renombrar variables: password_input, correct_password, loop_index
  7. Colorear bloques: Rojo (error), Verde (éxito), Azul (salida)
  8. Análisis dinámico: x64dbg para ver valores en runtime
  9. Extraer la contraseña: Observar las comparaciones

Tips para el Análisis

  • Presta atención a cmp seguido de jne: Si algo no es igual, salta a error
  • Identifica el "happy path": El camino que NO salta a error
  • Las comparaciones byte a byte suelen indicar validación de password
  • El test al, al / test eax, eax verifica si un valor es 0
  • Stack strings: Strings construidas en la pila (puede ser ofuscación)

Desofuscación Básica

Vemos que hay un jump que dice algo como: jnz short near ptr locret_14000102c+1

Esto significa que si la condición no se cumple, salta a la dirección locret_14000102c+1, lo que puede indicar un intento de ofuscación para evitar el análisis directo del flujo del programa. Entonces:

  • Nos paramos sobre esa línea + 1 y tocamos la u para ver los bytes en crudo
  • Luego vamos a la +1 siguiente y tocamos la c para arreglarlo.

Desofuscación automatizada

  • Tenemos que sacar la secuencia de bytes que se repitan
  • Edit > Export Data > hex string (spaced)
  • Search > Sequence of bytes > pegar la secuencia
  • Entonces ahí agarras las direcciones.
dir = [0x14000101A, 0x14000102C, 0x14000103E, etc...] # las direcciones encontradas

for i in dir: #  recorremos las direcciones
    ida_bytes.del_items(i+4,0,1) # tomamos cada dirección, le sumamos 4 (el offset del jnz) y borramos 1 byte (el byte del jnz)
    for a in range(i+5, ida_ida.inf_get_max_ea()): create_insn(a) # para cada dirección, desde el byte siguiente al borrado hasta el final del binario, reensamblamos las instrucciones
    plan_and_wait(ida_ida.inf_get_max_ea(), ida_ida.inf_get_max_ea()) # planificamos el análisis nuevamente
  • Este código lo que hace es borrar el byte de más y reensamblar las instrucciones.

FLIRT

En reversing, FLIRT significa Fast Library Identification and Recognition Technology. Es el sistema de IDA Pro para reconocer funciones de librerías comparando sus patrones de bytes contra una base de firmas.

  • Un FLIRT suele ser un archivo .sig que contiene huellas binarias de funciones estandar como la glibc o las APIs de Windows. IDA lo usa para identificar automáticamente funciones conocidas dentro de un binario, incluso si están embebidas (static linking) y no aparecen en la IAT. Cuando IDA reconoce una función mediante FLIRT, reemplaza el nombre genérico (sub_1400A123) por el nombre real (memcpy, printf, std::string::_M_assign, etc.).

Esto sirve para facilitar el análisis, ya que las funciones reconocidas tienen nombres descriptivos y a menudo vienen con prototipos y comentarios que ayudan a entender su propósito.

Recursos Adicionales