Saltar a contenido

Binary Exploitation - Gecko Academy - Modulo 2

C / C++ Reversing (fundamentos)

Objetivo del modulo

Pasar de "leer codigo fuente" a "reconocer patrones en binario". Este modulo cubre piezas base de C/C++ que despues aparecen en reversing:

  • entrada/salida y flujo en main
  • funciones y paso de parametros
  • tipos de datos y comparaciones signed/unsigned
  • arrays, matrices y punteros
  • struct, union y layout de memoria
  • clases basicas en C++

Preparacion del laboratorio

Compilar cada ejemplo en dos modos:

  1. Debug (/Od /Zi) para ver logica casi 1:1.
  2. Release (/O2) para ver optimizaciones y codigo plegado.

Herramientas:

  • IDA Pro para analisis estatico
  • x64dbg / WinDbg para analisis dinamico

Slide 2 - Primer programa C/C++

I/O basico y stack buffer

#include <iostream>
#include <windows.h>

int main()
{
    char name[20];
    memset(name, 0, sizeof(name));
    printf("Enter your name: ");
    gets_s(name, sizeof(name));
    printf("Hello %s !\n", name);
    getchar();
    return 0;
}

Que mirar en reversing

  • reserva de stack (sub rsp, ...)
  • direccion de buffer local (lea rcx, [rbp-...])
  • llamadas a CRT (printf, gets_s)
  • limpieza y retorno de main

Riesgo tecnico

gets_s es mas seguro que gets, pero igual es entrada de usuario. Siempre revisar limites y como se usa el buffer luego.


Slide 3 - Funciones

Concepto: bloque reutilizable con parametros y retorno.

#include <iostream>

void to_print(char* name, char* age) {
    printf("Hello %s, you are %s years old", name, age);
}

int main(int argc, char* argv[])
{
    if (argc != 3) {
        printf("[+] Usage: name, age.  \nExample: funciones.exe John 25");
        exit(1);
    }

    to_print(argv[1], argv[2]);
    getchar();
    return 0;
}

Que mirar en asm (Win64)

  • RCX, RDX, R8, R9 para los 4 primeros parametros
  • prologo/epilogo de funcion
  • valor de retorno en RAX
  • llamadas con call y manejo de argumentos argc/argv

Slide 4 - Tipos de datos

#include <iostream>
#include <windows.h>

int main()
{
    unsigned int un_num = 0x81828384;
    unsigned short un_two_bytes = 0x8182;
    int s_num = 0x81828384;
    short s_two_bytes = 0x8182;
    char character = 0x41;
    bool condition = false;
    bool condition2 = true;

    printf("[+] Data type: UNSIGNED INTEGER, len: 4 bytes: 0x%x\n", un_num);
    printf("[+] Data type: UNSIGNED SHORT, len: 2 bytes: 0x%x\n", un_two_bytes);
    printf("[+] Data type: INTEGER, len: 4 bytes: 0x%x\n", s_num);
    printf("[+] Data type: INTEGER, len: 2 bytes: 0x%x\n", s_two_bytes);
    printf("[+] Data type: INTEGER, len: 1 bytes: 0x%x\n", character);
    printf("[+] Data type: INTEGER, len: 1 bytes: 0x%x\n", condition);
    printf("[+] Data type: INTEGER, len: 1 bytes: 0x%x\n\n", condition2);

    printf("[+] Checking unsigned data type\n");
    if (un_num > 0) {
        printf("[!] Positive value\n\n");
    }
    else {
        printf("[!] Negative value or zero\n");
    }

    printf("[+] Checking signed data type\n");
    if (s_num > 0) {
        printf("[!] Positive value\n");
    }
    else {
        printf("[!] Negative value or zero\n");
    }

    getchar();
    return 0;
}

Punto clave para reversing

No alcanza con ver cmp:

  • signed suele usar saltos jl/jg/jle/jge
  • unsigned suele usar jb/ja/jbe/jae

Esto impacta directamente en la logica de validacion.


Slide 5 - Arrays y matrices

// (Global Scope)
const char* array1[] = { "A", "B", "C", "D" };

int main() {
    const int rows = 3;
    const int cols = 4;
    const char* array2[] = { "E", "F", "G" };
    char** array3 = (char**)malloc(2 * sizeof(char*));
    array3[0] = (char*)"I";
    array3[1] = (char*)"J";

    printf("Array 1:\n");
    for (int i = 0; i < sizeof(array1) / sizeof(array1[0]); ++i) {
        printf("%s\t", array1[i]);
    }

    printf("\nArray 2:\n");
    for (int i = 0; i < sizeof(array2) / sizeof(array2[0]); ++i) {
        printf("%s\t", array2[i]);
    }

    printf("\nArray 3:\n");
    for (int i = 0; i < 2; ++i) {
        printf("%s\t", array3[i]);
    }

    printf("\n");
    free(array3);

    int matrix[rows][cols] = {
        { 1,  2,  3,  4 },
        { 5,  6,  7,  8 },
        { 9, 10, 11, 12 }
    };

    printf("Matrix:\n");
    for (int i = 0; i < rows; ++i) {
        for (int j = 0; j < cols; ++j) {
            printf("%d\t", matrix[i][j]);
        }
        printf("\n");
    }

    getchar();
    return 0;
}

Que mirar en asm

  • calculo de indice: base + i*size
  • doble indice en matriz: base + (i*cols + j)*size
  • loops anidados (cmp/jcc, inc, jmp)

Error comun

Confundir un puntero doble (char**) con un array 2D real. En memoria no son equivalentes.


Slide 6 - struct

analisis del binario de struct

#include <stdio.h>
#include <stdlib.h>
#define _CRT_SECURE_NO_WARNINGS

struct Employee {
    int id;
    char name[50];
    char gender;
    unsigned int age;
    int monthlySalary;
};

void printEmployee(struct Employee* emp) {
    printf("\n--- Employee Information ---\n");
    printf("ID: %d\n", emp->id);
    printf("Name: %s\n", emp->name);
    printf("Gender: %c\n", emp->gender);
    printf("Age: %u\n", emp->age);
    printf("Monthly Salary: %d\n", emp->monthlySalary);
    printf("Annual Salary: %d\n", emp->monthlySalary * 12);
}

int main() {
    struct Employee* emp = (struct Employee*)malloc(sizeof(struct Employee));
    if (emp == NULL) {
        printf("Memory allocation failed!\n");
        return 1;
    }

    printf("Enter employee ID: ");
    scanf_s("%d", &emp->id);

    printf("Enter name: ");
    scanf_s("%s", emp->name, (unsigned)_countof(emp->name));

    printf("Enter gender (M/F): ");
    scanf_s(" %c", &emp->gender, 1);

    printf("Enter age: ");
    scanf_s("%u", &emp->age);

    printf("Enter monthly salary: ");
    scanf_s("%d", &emp->monthlySalary);

    printEmployee(emp);
    free(emp);
    return 0;
}

Reversing checklist

  • identificar offsets reales de campos
  • detectar padding/alineacion
  • verificar si vive en stack o heap (malloc/new)
  • seguir funciones que reciben struct*

Slide 7 - union

// Basic union example
union Data {
    int i;
    float f;
    char c;
};
// Advanced example: Status Register
union StatusRegister {
    struct {
        unsigned int carry    : 1;
        unsigned int zero     : 1;
        unsigned int sign     : 1;
        unsigned int overflow : 1;
        unsigned int reserved : 4;
    } flags;

    unsigned char value;
};

Implicacion en reversing

El mismo offset puede representar tipos distintos segun contexto. No fuerces una sola interpretacion temprano.


Slide 8 - Punteros

#include <stdio.h>

int main() {
    int number = 42;
    int* p_number = &number;

    printf("--- Basic Concepts ---\n");
    printf("Value of 'number': %d\n", number);
    printf("Address of 'number': %p\n", (void*)&number);
    printf("Value via pointer 'p_number': %d\n", *p_number);
    printf("Address stored in 'p_number': %p\n", (void*)p_number);

    printf("\n--- Pointer Modification ---\n");
    *p_number = 100;
    printf("New value of 'number': %d\n", number);

    printf("\n--- Pointer Arithmetic ---\n");
    int numbers[] = { 10, 20, 30 };
    int* p = numbers;
    printf("First element: %d (address: %p)\n", *p, (void*)p);
    p++;
    printf("Second element: %d (address: %p)\n", *p, (void*)p);

    printf("\n--- Null Pointers ---\n");
    int* p_null = NULL;
    if (p_null == NULL) {
        printf("p_null is a null pointer!\n");
    }

    return 0;
}

En asm

  • lea suele construir direcciones
  • mov reg, [reg+offset] suele dereferenciar
  • test reg, reg / cmp reg, 0 para null-check

Slide 9 - Clases C++ (base)

#include <cstdio>
#include <cstring>
#define _CRT_SECURE_NO_WARNINGS

class Rectangle {
private:
    int width;
    int height;
    char name[50];

public:
    Rectangle(int w, int h, const char* n) : width(w), height(h) {
        strncpy_s(name, n, sizeof(name) - 1);
        name[sizeof(name) - 1] = '\0';
    }

    ~Rectangle() {
        printf("Destroyed: %s\n", name);
    }

    int area() const {
        return width * height;
    }

    virtual void print() const {
        printf("%s: %dx%d\n", name, width, height);
    }

    static const char* className() {
        return "Rectangle";
    }
};

class ColoredRectangle : public Rectangle {
    char color[20];
public:
    ColoredRectangle(int w, int h, const char* n, const char* c)
        : Rectangle(w, h, n) {
        strncpy_s(color, c, sizeof(color) - 1);
        color[sizeof(color) - 1] = '\0';
    }

    void print() const override {
        printf("Colored %s: %s\n", className(), color);
    }
};

int main() {
    Rectangle rect1(3, 4, "StackRect");
    rect1.print();
    printf("Area: %d\n", rect1.area());

    Rectangle* rect2 = new Rectangle(5, 6, "HeapRect");
    rect2->print();
    delete rect2;

    ColoredRectangle coloredRect(10, 20, "PolymorphRect", "Red");
    coloredRect.print();

    printf("Class Name: %s\n", Rectangle::className());
    return 0;
}

Que mirar en reversing

  • this en RCX
  • constructor inicializando campos por offset
  • destructor limpiando recursos
  • llamadas virtuales indirectas via vtable
  • metodos static sin this

Explicación sobre vtable

  • En C++, una vtable (tabla de funciones virtuales) es un mecanismo generado por el compilador que facilita el polimorfismo en tiempo de ejecución (despacho dinámico). Es básicamente un arreglo estático de punteros a funciones, creado para cualquier clase que contiene o hereda funciones virtuales.

Slide 10 - Preguntas tecnicas para autoevaluacion

  1. Como distingues signed vs unsigned solo viendo saltos?
  2. Que cambia en el asm entre /Od y /O2?
  3. Como identificas un campo de struct sin tipos?
  4. Que pista fuerte te confirma llamada virtual?
  5. Donde esperas ver this en Win64?

Slide 11 - Practica y evaluacion

Valores del slide:

  • 0x434F5245 (int, big endian interpretado por bytes)
  • 0x45524F43 (int, little endian segun representacion en memoria x86/x64)

Ejercicio recomendado

  • Cargar ambos en memoria y verificar orden de bytes en debugger.
  • Confirmar con db/dd la diferencia entre valor logico y representacion.

Metodologia recomendada para este modulo

Para cada ejemplo:

  1. Abrir IDA, pararse en main.
  2. Pasar todos los flirts/sig-files que tengas en IDA.
  3. Pasar el class informer y el hrt.
  4. Decompilar las funciones principales y empezar a armar clases/uniones/structs e ir seteando en el codigo en donde se utilizan estos tipos de datos.
  5. Analizar comportamiento e ir tomando notas.

Hotkeys

Cheatsheet: Hotkeys para structs y offsets en IDA

  • Alt+Q: Aplicar estructura al puntero seleccionado (Set structure type).
  • T: Cambiar tipo de dato (Type) en la posición actual.
  • Shift+T: Cambiar tipo de dato para un rango seleccionado.
  • Ctrl+S: Abrir el editor de estructuras (Structures window).
  • Ctrl+Shift+S: Crear nueva estructura.
  • Ctrl+Alt+S: Buscar estructura por nombre.
  • Ctrl+Shift+M: Aplicar estructura a memoria (Make structure).
  • Ctrl+Shift+U: Deshacer estructura aplicada.

Tips

  • Para setear offsets, selecciona el campo y usa T para asignar el tipo correcto.
  • En el Structures window, puedes arrastrar campos para ajustar offsets manualmente.
  • Si tienes un puntero a struct, usa Alt+Q para aplicar la estructura directamente.

Tips Structs C++

  • Apretar la D sobre el primer elemento:
struct my_block {

    int field;
    ...
}

- Apretar asterisco para definir arreglo.
- Click derecho -> convert to struct
- Pasar los structs definidos al código donde se usan (main, printEmployeeInfo, etc).
- Local Types -> ins add type
- hrt -> recast item
    - hrt -> create dummy struct -> copiar y pegar definicion del struct en C-like -> aplicar tipo a puntero en main y printEmployeeInfo
- Decompiler -> click derecho -> convert to struct -> seleccionar struct creado -> aplicar tipo a puntero en main y printEmployeeInfo

#### Tips Unions C++

- options -> setup data types -> y lo modificamos.
- **Si hay un diferente tipo de dato en la misma dirección es un candidato a union**.
- Local types -> create union

#### Tips clases C++

- Para clases utilizar class informer y hrt.
- Armar structs en el local type con las clases del class informer
- Detectar: En donde se utilizan las clases, su constructor y destructor, y si hay funciones virtuales (vtable).

Referencia: IDA Structure Hotkeys

T hotkey structure offset

t-hotkey-structure-offset