Saltar a contenido

Estructuras y Constructores en C++: Guía de Reversing

Introducción

Este documento cubre la teoría y práctica de reversing de estructuras (structs) y constructores en C++. A diferencia de los tipos primitivos (int, char), las estructuras agrupan múltiples campos de diferentes tipos, lo que añade complejidad al análisis en ensamblador.

Objetivo: Entender cómo aparecen las estructuras en IDA Pro, cómo leer sus campos en el stack, y cómo reconocer patrones de inicialización (constructores).


Parte 1: Teoría de Estructuras en C++

¿Qué es una Estructura?

Una estructura (struct) es un tipo de dato que agrupa múltiples campos (fields) de diferentes tipos:

struct Persona {
    char nombre[65];        // Array de caracteres
    char direccion[65];     // Array de caracteres
    int anio_nacimiento;    // Entero (4 bytes)
};

Características principales:

Característica Descripción
Campos heterogéneos A diferencia de arrays, los campos pueden tener diferentes tipos
Tamaño en memoria Suma de todos los campos + padding por alineamiento
Acceso a campos Mediante el operador punto . (si es un objeto) o flecha -> (si es puntero)
Inicialización Puede tener constructor para inicializar automáticamente

Diferencia: Arrays vs Estructuras

Arrays:

int numeros[10];  // 10 enteros, todos del mismo tipo
char texto[100];  // 100 caracteres, todos del mismo tipo
- Todos los elementos del mismo tipo - Acceso por índice: numeros[0], numeros[1] - Tamaño homogéneo

Estructuras:

struct Persona {
    char nombre[65];        // Campo 1: array
    char direccion[65];     // Campo 2: array
    int anio_nacimiento;    // Campo 3: entero
};
- Campos de diferentes tipos - Acceso por nombre: persona.nombre, persona.direccion - Tamaño heterogéneo (suma de todos los campos)

Declaración de Estructuras

Forma 1: Declaración global con variables

struct Persona {
    char nombre[65];
    char direccion[65];
    int anio_nacimiento;
} fulanito, menganito;  // Variables declaradas directamente

Forma 2: Declaración de tipo, variables separadas

struct Persona {
    char nombre[65];
    char direccion[65];
    int anio_nacimiento;
};

// Después, en otra parte del código:
Persona variable1;
Persona variable2;

Forma 3: Typedef para simplificar

typedef struct {
    char nombre[65];
    char direccion[65];
    int anio_nacimiento;
} Persona;

Persona p1;  // Acceso directo sin escribir "struct"

Acceso a Campos

Mediante objeto directo:

Persona p;
p.nombre = "Juan";           // Asignar a campo
p.anio_nacimiento = 1990;
int edad = p.anio_nacimiento;

Mediante puntero:

Persona* p_ptr = &p;
p_ptr->nombre = "Juan";      // Operador flecha
p_ptr->anio_nacimiento = 1990;

En ensamblador (sobre RSP en x64):

mov byte ptr [rsp+10], 'A'   ; Primer campo (array de chars)
mov dword ptr [rsp+40], 1990 ; Tercer campo (int, offset 40)

Parte 2: Estructuras en Memoria

Cálculo del Tamaño (sizeof)

El tamaño de una estructura es la suma de todos sus campos más padding para alineamiento.

Ejemplo simple:

struct A {
    int x;      // 4 bytes
    char c;     // 1 byte
    int y;      // 4 bytes
};
// sizeof(A) = ?

Cálculo manual:

  • int x: 4 bytes (offset 0-3)
  • char c: 1 byte (offset 4)
  • Padding: 3 bytes (offset 5-7) para alinear int y a múltiplo de 4
  • int y: 4 bytes (offset 8-11)

Total: 12 bytes

En IDA Pro:

Local Types > A > Properties
Size: 12 (0xC)
Fields:
  x @ offset 0x0 (4 bytes)
  c @ offset 0x4 (1 byte)
  [padding] @ offset 0x5 (3 bytes)
  y @ offset 0x8 (4 bytes)

Ejemplo del Curso: Estructura Persona

struct Persona {
    char nombre[65];        // Offset 0x00, tamaño 65
    char direccion[65];     // Offset 0x41 (65), tamaño 65
    int anio_nacimiento;    // Offset 0x82 (130), tamaño 4
};
// sizeof(Persona) = 139 bytes (0x8B)
// + 5 bytes de padding para alineamiento = 144 bytes (0x90)
// Pero en el ejemplo del curso: 88 bytes (variable optimizada)

Nota: En el ejemplo específico del video, la estructura ocupa 88 bytes (probablemente con diferentes tamaños de arrays o alineamiento específico del compilador).

Stack Layout en la Función

Pseudocódigo:

void main() {
    Persona menganito;  // Variable local
    // ... código ...
}

En el stack (windows x64):

RSP+0x00:  [Shadow Space (32 bytes)] RSP+0x20
RSP+0x20:  [Variable suma (4 bytes)]
RSP+0x24:  [Padding (4 bytes)]
RSP+0x28:  [menganito (88 bytes)]
           ├─ nombre[65]   (offset 0x00-0x40)
           ├─ direccion[65](offset 0x41-0x81)
           └─ anio_nacimiento (offset 0x82-0x85)
RSP+0x80:  [Security Cookie (8 bytes)]
RSP+0x88:  [Previous RBP (8 bytes)]
RSP+0x90:  [Return Address (8 bytes)]

En dirección absoluta (ejemplo con RSP=0x1000):

0x1028:  menganito.nombre
0x1049:  menganito.direccion
0x106A:  menganito.anio_nacimiento

Parte 3: Estructuras Anidadas

Definición

Una estructura puede contener otra estructura dentro:

struct Nombre {
    char nombre[30];
    char apellido[30];
};

struct Persona {
    Nombre nombre_completo;    // Estructura anidada
    int anio_nacimiento;
    char direccion[65];
};

Acceso a Campos Anidados

En C++:

Persona p;
p.nombre_completo.nombre = "Juan";      // Campo dentro de estructura
p.nombre_completo.apellido = "Pérez";
p.anio_nacimiento = 1990;

En ensamblador:

; Acceso a p.nombre_completo.nombre
lea rcx, [rsp+20]           ; RSP+20 = dirección de nombre_completo
mov byte ptr [rcx], 'J'     ; Primer byte del campo nombre

En Local Types de IDA

Después de importar estructuras:

Persona
  ├─ nombre_completo (Nombre)
  │  ├─ nombre[30]
  │  └─ apellido[30]
  ├─ anio_nacimiento (int)
  └─ direccion[65]

Parte 4: Constructores

¿Qué es un Constructor?

Un constructor es una función especial que se ejecuta automáticamente cuando se crea un objeto. Su propósito principal es inicializar los campos.

Sintaxis:

struct Punto {
    int x;
    int y;

    // Constructor (mismo nombre que la estructura)
    Punto() {
        x = 0;
        y = 0;
    }
};

// Uso:
Punto p;  // Automáticamente llama al constructor, p.x=0, p.y=0

Constructor Inline vs Separado

Inline (dentro de la definición):

struct Punto {
    int x;
    int y;

    Punto() {
        x = 0;
        y = 0;
    }  // Se define aquí mismo
};

Separado (fuera de la definición):

struct Punto {
    int x;
    int y;

    Punto();  // Solo declaración
};

// Definición fuera:
Punto::Punto() {
    x = 0;
    y = 0;
}

En ensamblador, ambos generan el mismo código ejecutable.

En Ensamblador (x64)

Constructor simple:

struct Persona {
    char nombre[65];
    char direccion[65];
    int anio_nacimiento;

    Persona() {
        // Constructor vacío (no hace nada)
    }
};

En ensamblador:

; Llamada al constructor en main()
lea rcx, [rsp+68]           ; RCX = dirección de menganito
call Persona::Persona()     ; Ejecutar constructor

; Dentro del constructor:
Persona::Persona proc near:
    push    rbp
    mov     rbp, rsp
    mov     qword ptr [rbp+8], rcx  ; "this" pointer
    pop     rbp
    ret

Nota: El constructor recibe el puntero this en RCX (primer parámetro implícito).

Constructor que Inicializa Campos

struct Persona {
    char nombre[65];
    char direccion[65];
    int anio_nacimiento;

    Persona() {
        anio_nacimiento = 0;  // Inicializar
    }
};

En ensamblador:

lea rcx, [rsp+68]           ; RCX = &menganito (this)
call Persona::Persona()

; Dentro del constructor:
Persona::Persona proc near:
    push    rbp
    mov     rbp, rsp
    mov     qword ptr [rbp+8], rcx

    ; Inicializar anio_nacimiento (offset 0x82 dentro de la estructura)
    lea rax, [rcx+82h]      ; RAX = dirección del campo
    mov dword ptr [rax], 0  ; Asignar 0

    pop     rbp
    ret

Constructor con Parámetros

struct Persona {
    int anio_nacimiento;

    Persona(int anio) {
        anio_nacimiento = anio;
    }
};

// Uso:
Persona p(1990);  // Pasar parámetro

En ensamblador (x64 FastCall):

; Llamada:
mov edx, 1990           ; EDX = segundo parámetro (anio)
lea rcx, [rsp+68]       ; RCX = this (primer parámetro implícito)
call Persona::Persona(int)

; Dentro del constructor:
Persona::Persona(int) proc near:
    push    rbp
    mov     rbp, rsp
    mov     qword ptr [rbp+8], rcx      ; this
    mov     dword ptr [rbp+10], edx     ; anio (parámetro)

    ; Asignar: this->anio_nacimiento = anio
    lea rax, [rcx+82h]
    mov eax, [rbp+10]                  ; Cargar anio
    mov dword ptr [rax], eax           ; Guardar en anio_nacimiento

    pop     rbp
    ret

Parte 5: Ejemplo Práctico del Curso

Código Fuente

struct Persona {
    char nombre[65];
    char direccion[65];
    int anio_nacimiento;
};

// Variables globales
Persona fulanito;
Persona menganito;

int main() {
    int suma;
    Persona menganito_local;

    // Asignar valores
    menganito.anio_nacimiento = 1991;
    fulanito.anio_nacimiento = 1990;

    // Ingresar datos
    cout << "Ingresar dirección de fulanito: ";
    cin.getline(fulanito.direccion, 65);

    cout << "Ingresar dirección de menganito: ";
    cin.getline(menganito_local.direccion, 65);

    // Imprimir resultados
    cout << "Dirección de fulanito: " << fulanito.direccion << endl;
    cout << "Dirección de menganito: " << menganito_local.direccion << endl;
    cout << "Año de nacimiento fulanito: " << fulanito.anio_nacimiento << endl;

    // Sumar y imprimir
    suma = menganito_local.anio_nacimiento + fulanito.anio_nacimiento;
    cout << "Suma de años: " << suma << endl;

    return 0;
}

Stack Frame en IDA Pro

Prólogo de main():

00401000: push    rbp
00401001: mov     rbp, rsp
00401004: sub     rsp, 0xA0       ; Reservar 160 bytes
00401008: mov     rax, fs:[28h]   ; Security Cookie
0040100F: mov     [rsp+0x98], rax ; Guardar cookie

Stack layout:

RSP+0x00:  Shadow Space (32 bytes)
RSP+0x20:  suma (DWORD, 4 bytes)
RSP+0x24:  [Padding, 4 bytes]
RSP+0x28:  menganito_local (Persona, 88 bytes)
           ├─ nombre[65]
           ├─ direccion[65]
           └─ anio_nacimiento
RSP+0x80:  [Padding]
RSP+0x98:  Security Cookie (8 bytes)
RSP+0xA0:  Prólogo (RBP guardado)

Inicialización de Campos

En ensamblador:

; Asignar fulanito.anio_nacimiento = 1990
mov dword ptr [rip+offset_fulanito], 1990
; (fulanito es global, en sección .data)

; Asignar menganito_local.anio_nacimiento = 1991
mov dword ptr [rsp+0xA8], 1991
; (offset 0x28 + 0x80 = 0xA8 para el campo anio_nacimiento)

Lectura de Estructuras (cin.getline)

Pseudocódigo:

cin.getline(fulanito.direccion, 65);
// Equivalente a leer 65 caracteres del stdin
// y guardarlos en fulanito.direccion

En ensamblador:

; Obtener dirección de fulanito.direccion
lea     rax, [rip+offset_fulanito]   ; RAX = &fulanito (global)
add     rax, 0x41                    ; RAX += 65 (offset del campo direccion)

; Parámetros para cin.getline(buffer, maxlen)
mov     rdx, 65                      ; RDX = maxlen
lea     rcx, std::cin                ; RCX = this (&cin)

; Llamada (signature: void getline(char*, size_t))
call    std::istream::getline

Parte 6: Identificación de Estructuras en IDA Pro (Sin Símbolos)

Reconocimiento de Patrones

Patrón 1: Múltiples campos secuenciales en el stack

lea     rcx, [rsp+20]       ; Campo 1
call    some_function
lea     rcx, [rsp+30]       ; Campo 2 (offset diferente)
call    some_function
lea     rcx, [rsp+88]       ; Campo 3 (offset mayor)

Indicador: Diferentes offsets sobre RSP → Probablemente campos de una estructura.

Patrón 2: Tamaño de inicialización consistente

sub     rsp, 0x60           ; Reserva 96 bytes
mov     [rsp+0x20], rax     ; Inicializa algo
mov     [rsp+0x28], rbx
mov     [rsp+0x80], rcx     ; Offset grande

Indicador: Offsets grandes (>80) sugieren estructura con campos grandes.

Patrón 3: Constructor call

lea     rcx, [rsp+28]       ; RCX = this
call    sub_401234          ; Posible constructor

Verificar: Si sub_401234 solo inicializa algunos campos sin parámetros adicionales → Probablemente constructor.

Importación de Estructuras en IDA

Si tienes el archivo .pdb:

File > Load file > PDB file...
Seleccionar el .pdb correspondiente

Si no tienes símbolos pero tienes el código fuente:

File > Produce file > Dump typeinfo to IDC file...
(En otro IDA con símbolos)

File > Script file... (en el IDA sin símbolos)
Cargar el .idc exportado

Manual (crear estructura desde cero):

View > Open subviews > Local Types (Shift+F1)
Botón derecho > New struct...
Agregar campos con offsets correctos

Aplicar Estructura al Stack

Una vez que tienes la estructura definida:

Posicionarse en una variable del stack
Presionar Y (Change type)
Escribir: Persona
Presionar Enter

Resultado:

; Antes:
[rsp+28] = db 60h dup(?)

; Después:
[rsp+28] = Persona

Pseudocódigo actualizado:

// Antes:
char var_28[96];
char var_28_41[65];
int var_28_82;

// Después:
Persona menganito;
menganito.nombre[65];
menganito.direccion[65];
menganito.anio_nacimiento;

Parte 7: Alineamiento (Alignment)

Concepto

El compilador alinea los campos de una estructura para acceso eficiente en memoria.

Regla típica (x64):

  • char (1 byte): alineado a 1 byte (sin restricción)
  • short (2 bytes): alineado a 2 bytes
  • int (4 bytes): alineado a 4 bytes
  • long long (8 bytes): alineado a 8 bytes
  • puntero (8 bytes): alineado a 8 bytes

Padding por Alineamiento

struct Ejemplo {
    char a;         // Offset 0, tamaño 1
    // [3 bytes de padding]
    int b;          // Offset 4, tamaño 4
    char c;         // Offset 8, tamaño 1
    // [7 bytes de padding]
};
// sizeof = 16 bytes (para alinear la estructura a 8 bytes)

En IDA Pro Local Types:

Ejemplo @ 0x0:
  a @ 0x0 (char, 1 byte)
  [padding] @ 0x1 (3 bytes)
  b @ 0x4 (int, 4 bytes)
  c @ 0x8 (char, 1 byte)
  [padding] @ 0x9 (7 bytes)
Total size: 0x10 (16 bytes)

Impacto en Reversing

Cuando veas offsets raros en ensamblador:

mov [rcx+0], eax        ; Offset 0
mov [rcx+4], ebx        ; Offset 4 (después de padding)
mov [rcx+8], ecx        ; Offset 8
mov [rcx+10], edx       ; Offset 16 (después de más padding)

Probablemente hay padding por alineamiento de la estructura.

Solución: Verificar el tamaño de cada campo declarado y contar bytes reales vs offsets.


Parte 8: Campos de Bits (Bit Fields)

Concepto

Los bit fields permiten empaquetar múltiples booleanos o valores pequeños en un entero:

struct Flags {
    unsigned int bit0 : 1;   // 1 bit
    unsigned int bit1 : 1;   // 1 bit
    unsigned int bit2 : 3;   // 3 bits (valores 0-7)
    unsigned int bit3 : 5;   // 5 bits (valores 0-31)
};
// Total: 10 bits = 2 bytes

En Ensamblador

; Asignar bit0 = 1
mov al, [rcx]           ; Cargar byte
or al, 1                ; Setear bit 0
mov [rcx], al

; Asignar bit2 = 5 (3 bits)
mov al, [rcx]
and al, 0xE3            ; Limpiar bits 2-4
or al, (5 << 2)         ; Setear a 5
mov [rcx], al

Nota: Los bit fields son raros en código moderno, pero puedes encontrarlos en:

  • Estructuras de flags de Windows
  • Código de bajo nivel (drivers)
  • Binarios muy optimizados

Parte 9: Patrones Comunes en Reversing

Patrón 1: Variable Global vs Local

Global:

lea rax, [rip+offset_global]   ; Dirección relativa al código
mov [rax+10], ebx

Local (en stack):

lea rax, [rsp+28]              ; Dirección relativa al stack
mov [rax+10], ebx

Indicador: [rip+offset] → Global | [rsp+offset] → Local

Patrón 2: Constructor Llamado

lea rcx, [rsp+28]              ; RCX = this
call sub_401234                ; Posible constructor

Verificar: Dentro de sub_401234, busca:

  • Solo inicialización de campos (sin lectura de parámetros)
  • Parámetro implícito RCX (this)
  • Sin parámetros explícitos (o con pocos)

Si todo esto se cumple → Probablemente es un constructor.

Patrón 3: Campo Accedido Múltiples Veces

mov rax, [rsp+28]              ; Cargar dirección
mov [rax+10], 1990             ; Asignar anio_nacimiento
mov eax, [rax+10]              ; Leer anio_nacimiento
add eax, 1                      ; Incrementar
mov [rax+10], eax              ; Guardar de nuevo

Indicador: Mismo offset accedido múltiples veces → Probablemente el mismo campo.

Patrón 4: Getline y Estructuras

lea rdx, [rsp+41]              ; RDX = buffer (offset 41 = 65+1 bytes)
mov r8d, 65                    ; R8 = maxlen
lea rcx, std::cin              ; RCX = this (&cin)
call std::istream::getline

lea rdx, [rsp+82]              ; RDX = otro buffer (offset 82)
mov r8d, 65
lea rcx, std::cin
call std::istream::getline

Indicador: Dos campos de tamaño similar (65 bytes) accedidos secuencialmente → Probablemente dos arrays en una estructura.


¿Qué es?

Un valor aleatorio guardado en el stack al inicio de una función para detectar buffer overflows.

void funcion() {
    int x;
    Persona p;
    // ... código ...
    // Si algo sobrescribe el stack, lo detectará
}

En ensamblador:

mov rax, fs:[28h]              ; Leer security cookie de TLS
mov [rsp+0x98], rax            ; Guardar en el stack

; ... resto de la función ...

mov rcx, [rsp+0x98]            ; Leer cookie
xor rcx, fs:[28h]              ; Comparar con valor original
call __security_check_cookie   ; Si no coincide, crash

En IDA Pro

Reconocerlo:

Buscar: mov rax, fs:[28h]

Si lo encuentras:

  • Hay overflow protection activada
  • El offset donde se guarda la cookie → Conoces el tamaño exacto del stack

No es parte de la lógica del programa, solo seguridad.


Resumen: Checklist para Reversing de Estructuras

Cuando Veas una Función Desconocida:

  • ¿Hay reserva de stack? (sub rsp, 0x60)
  • ¿Hay múltiples offsets sobre RSP? ([rsp+20], [rsp+28], [rsp+88])
  • ¿Hay un constructor llamado? (call sub_xxxxx inmediatamente después de lea rcx)
  • ¿Hay inicialización de campos? (movs en offsets específicos)
  • ¿Hay funciones de I/O? (cin.getline, cin >>, cout <<)
  • ¿Hay security cookie? (mov rax, fs:[28h])

Si Respondiste SÍ a 3+ Preguntas:

Probablemente hay estructuras involucradas

Pasos para Análisis:

  1. Estimar el tamaño de la estructura (último offset accedido + tamaño del campo)
  2. Identificar tipos de campos (array, int, char, etc.)
  3. Importar o crear estructuras en IDA Pro Local Types
  4. Aplicar estructura al stack
  5. Re-analizar pseudocódigo con nombres claros
  6. Buscar constructores y funciones miembro

Ejemplo Completo: Reversing del Ejemplo del Curso

Binario Compilado con Símbolos

Ensamblador crudo:

00401000: push    rbp
00401001: mov     rbp, rsp
00401004: sub     rsp, 0xA0
00401008: mov     rax, fs:[28h]
0040100F: mov     [rsp+0x98], rax

; Asignar menganito_local.anio_nacimiento = 1991
00401018: mov     dword ptr [rsp+0xA8], 1991

; Asignar fulanito.anio_nacimiento = 1990
00401020: lea     rax, [rip+0x2FD0]    ; &fulanito (global)
00401027: mov     dword ptr [rax], 1990

; Imprimir y leer datos
00401030: lea     rcx, [rip+0x2FD5]    ; &std::cout
00401037: lea     rdx, aIngresar       ; "Ingresar dirección..."
0040103E: call    std::operator<<

; getline para fulanito.direccion
00401050: lea     rdx, [rip+0x2FD0]    ; &fulanito
00401057: add     rdx, 0x41            ; Offset de direccion
0040105B: mov     r8d, 65
00401062: lea     rcx, std::cin
00401069: call    std::istream::getline

; getline para menganito_local.direccion
0040107F: lea     rdx, [rsp+0x28]      ; &menganito_local
00401083: add     rdx, 0x41            ; Offset de direccion
00401087: mov     r8d, 65
0040108E: lea     rcx, std::cin
00401095: call    std::istream::getline

; Suma de años
000401AA: mov     eax, [rsp+0xA8]      ; menganito_local.anio_nacimiento
000401B1: mov     ecx, [rip+0x2FB0]    ; fulanito.anio_nacimiento
000401B8: add     eax, ecx
000401BA: mov     [rsp+0x20], eax      ; suma

; Epilogo y verificación de cookie
000401BC: mov     rcx, [rsp+0x98]
000401C3: xor     rcx, fs:[28h]
000401CB: call    __security_check_cookie
000401D2: add     rsp, 0xA0
000401D9: pop     rbp
000401DA: ret

Análisis Paso a Paso

1. Identificar el tamaño de la estructura:

  • Último offset accedido: [rsp+0xA8] (168)
  • Campo accedido: 4 bytes (DWORD)
  • Offset = 0xA8 - 0x28 (base de menganito) = 0x80 (128 bytes)
  • Tamaño estimado: 128 + 4 = 132 bytes

2. Identificar campos:

  • [rsp+0x28]: Inicio de estructura
  • [rsp+0x28] + 0x41 = [rsp+0x69]: Offset 0x41 (65) → Segundo array de 65 chars
  • [rsp+0xA8]: Offset 0x80 (128) → Última parte del campo anio_nacimiento

Estructura deducida:

struct Persona {
    char nombre[65];           // Offset 0x00
    char direccion[65];        // Offset 0x41
    int anio_nacimiento;       // Offset 0x82
    // 1 byte padding
    // 6 bytes padding para alineamiento
};
// sizeof = 136 bytes, pero el compilador lo alineó a 0x90 (144 bytes)

3. Aplicar en IDA Pro:

View > Local Types > New Struct
Nombre: Persona
Campos:
  nombre @ 0x00 (char[65])
  direccion @ 0x41 (char[65])
  anio_nacimiento @ 0x82 (int)

Aplicar al offset [rsp+0x28]:
  Posicionarse > Presionar Y > Escribir "Persona"

4. Pseudocódigo resultante:

void __cdecl main() {
    Persona menganito_local;
    int suma;

    menganito_local.anio_nacimiento = 1991;
    fulanito.anio_nacimiento = 1990;

    cout << "Ingresar dirección de fulanito: ";
    cin.getline(fulanito.direccion, 65);

    cout << "Ingresar dirección de menganito: ";
    cin.getline(menganito_local.direccion, 65);

    // ... resto del código ...

    suma = menganito_local.anio_nacimiento + fulanito.anio_nacimiento;

    // ... imprimir suma ...
}

Lecciones Finales

Sin Símbolos

Cuando no tienes símbolos (como en binarios ofuscados o compilados sin debug info):

  • El análisis se complica significativamente
  • Necesitas automatización (scripts IDAPython)
  • Necesitas usar Bindiff para comparar con versión compilada con símbolos
  • Importar estructuras manualmente es tedioso

Con Símbolos (Recomendado para Aprendizaje)

  • Los nombres de funciones y campos son claros
  • IDA Pro muestra la estructura automáticamente
  • Puedes enfocarte en la lógica en vez de la mecánica

Estrategia de Reversing Progresivo

  1. Primero: Aprender con código compilado con símbolos (como el del curso)
  2. Luego: Practicar sin símbolos pero con código fuente disponible
  3. Finalmente: Binarios reales sin información adicional

Recursos y Referencia Rápida

Comando de IDA Rápido

Comando Efecto
Shift+F1 Abrir Local Types
Y Cambiar tipo de variable
; Agregar comentario
N Renombrar
G Saltar a dirección
X Ver referencias cruzadas

Patrones a Buscar

lea rcx, [rsp+XX]
call sub_XXXXX              // Posible constructor

[rsp+OFFSET1]               // Campo 1
[rsp+OFFSET2]               // Campo 2
[rsp+OFFSET3]               // Campo 3

mov rax, fs:[28h]           // Security cookie

Cálculo Mental

Offset final - Offset inicial = Tamaño de campo
Tamaño total ≈ Offset final + Tamaño del último campo
Alineamiento típico: 8 bytes en x64