Saltar a contenido

Análisis estático - M02_E04_S_Arrays_Matr.exe

Alcance

Análisis 100% estático sobre M02_E04_S_Arrays_Matr.exe (sin debugging), usando decompilado y disassembly de main en 0x140001060.

Qué hace el binario

El programa es un ejercicio didáctico de arrays/matriz en C sobre x64 MSVC:

  1. Inicializa dos constantes locales rows_count=3 y cols_count=4.
  2. Construye tres colecciones de strings de 1 carácter:
  3. array1_ptrs (global): apunta a 'A', 'B', 'C', 'D'.
  4. stack_array2 (en stack): apunta a 'E', 'F', 'G'.
  5. heap_array3 (en heap): apunta a 'I', 'J' (reservado con malloc(0x10), liberado con free).
  6. Imprime las 3 colecciones con printf("%s\t", ...).
  7. Inicializa una matriz int matrix3x4[3][4] con valores 1..12.
  8. Recorre la matriz con doble for e imprime cada elemento con printf("%d\t", ...).
  9. Llama a getchar() para pausar y retorna 0.

Hallazgos técnicos

  • ABI x64 Windows (fastcall implícito): en llamadas a printf, RCX lleva el formato y RDX el primer argumento variable.
  • El compilador dejó instrucciones algo verbosas para indexado (movsxd, imul) y loops con saltos condicionales (jnb, jge).
  • Los chars están en .rdata como byte + padding de 4 bytes ('A' 00 00 00, etc.), por eso aparecen como mini-strings válidas para %s.
  • Se corrigieron símbolos unk_* relevantes de datos a nombres semánticos: ch_A, ch_B, ch_C, ch_D, ch_E, ch_F, ch_G, ch_I, ch_J.
  • Se tipó el global de punteros como const char *[4] (array1_ptrs) y los chars individuales como char.

Mejoras aplicadas en IDA

1) Renombre de variables locales en main

  • iidx_array1
  • var_74idx_array2
  • var_70idx_array3
  • var_6Crow
  • jcol
  • array3heap_array3
  • matrixmatrix3x4
  • rowsrows_count
  • colscols_count
  • array2stack_array2

2) Renombre de funciones no útiles (thunks)

Las funciones tipo import thunk con sufijo _0 se renombraron a prefijo thunk_... para legibilidad, por ejemplo:

  • __C_specific_handler_0thunk___C_specific_handler
  • memset_0thunk_memset
  • exit_0thunk_exit

3) Comentarios de assembler en main

Se agregaron comentarios en cada instrucción de main (124 instrucciones), explicando rol de:

  • prólogo/epílogo,
  • inicializaciones,
  • armado de punteros,
  • llamadas a API/CRT,
  • control de loops y comparaciones,
  • acceso indexado a arrays/matriz.

Mapa rápido de datos relevantes

  • 0x140003250 = ch_A
  • 0x140003254 = ch_B
  • 0x140003258 = ch_C
  • 0x14000325C = ch_D
  • 0x140003260 = ch_E
  • 0x140003264 = ch_F
  • 0x140003268 = ch_G
  • 0x14000326C = ch_I
  • 0x140003270 = ch_J
  • 0x140005000 = array1_ptrs (const char *[4])

Pseudocodigo

int __fastcall main(int argc, const char **argv, const char **envp)
{
  int idx_array1; // [rsp+20h] [rbp-78h]
  int idx_array2; // [rsp+24h] [rbp-74h]
  int idx_array3; // [rsp+28h] [rbp-70h]
  int row; // [rsp+2Ch] [rbp-6Ch]
  int col; // [rsp+30h] [rbp-68h]
  char **heap_array3; // [rsp+38h] [rbp-60h]
  int matrix3x4[3][4]; // [rsp+40h] [rbp-58h] BYREF
  int rows_count; // [rsp+70h] [rbp-28h]
  int cols_count; // [rsp+74h] [rbp-24h]
  const char *stack_array2[3]; // [rsp+78h] [rbp-20h]

  rows_count = 3;
  cols_count = 4;
  stack_array2[0] = (const char *)&unk_140003260;
  stack_array2[1] = (const char *)&unk_140003264;
  stack_array2[2] = (const char *)&unk_140003268;
  heap_array3 = (char **)malloc(0x10u);
  *heap_array3 = (char *)&unk_14000326C;
  heap_array3[1] = (char *)&unk_140003270;
  printf("Array 1:\n");
  for ( idx_array1 = 0; (unsigned __int64)idx_array1 < 4; ++idx_array1 )
    printf("%s\t", array1[idx_array1]);
  printf("\nArray 2:\n");
  for ( idx_array2 = 0; (unsigned __int64)idx_array2 < 3; ++idx_array2 )
    printf("%s\t", stack_array2[idx_array2]);
  printf("\nArray 3:\n");
  for ( idx_array3 = 0; idx_array3 < 2; ++idx_array3 )
    printf("%s\t", heap_array3[idx_array3]);
  printf("\n");
  free(heap_array3);
  *(_QWORD *)&matrix3x4[0][0] = 0x200000001LL;
  *(_QWORD *)&matrix3x4[0][2] = 0x400000003LL;
  *(_QWORD *)&matrix3x4[1][0] = 0x600000005LL;
  *(_QWORD *)&matrix3x4[1][2] = 0x800000007LL;
  *(_QWORD *)&matrix3x4[2][0] = 0xA00000009LL;
  *(_QWORD *)&matrix3x4[2][2] = 0xC0000000BLL;
  printf("Matrix:\n");
  for ( row = 0; row < 3; ++row )
  {
    for ( col = 0; col < 4; ++col )
      printf("%d\t", matrix3x4[row][col]);
    printf("\n");
  }
  getchar();
  return 0;
}

Pseudocódigo simplificado (equivalente legible)

int main()
{
    // Configuración de dimensiones
    int rows_count = 3;
    int cols_count = 4;

    // Array 1: punteros globales en .data
    // array1_ptrs = { &ch_A, &ch_B, &ch_C, &ch_D }

    // Array 2: punteros locales en stack
    const char *stack_array2[3] = {
        &ch_E,  // 0x140003260
        &ch_F,  // 0x140003264
        &ch_G   // 0x140003268
    };

    // Array 3: punteros dinámicos en heap
    char **heap_array3 = (char **)malloc(16); // la idea de char puntero puntero, y hacer un malloc de 16 bytes (2 punteros) es para reservar espacio para 2 punteros a char (2 * 8 bytes en x64)
    heap_array3[0] = &ch_I;  // 0x14000326C
    heap_array3[1] = &ch_J;  // 0x140003270

    // Imprimir Array 1
    printf("Array 1:\n");
    for (int i = 0; i < 4; i++)
        printf("%s\t", array1_ptrs[i]);

    // Imprimir Array 2
    printf("\nArray 2:\n");
    for (int i = 0; i < 3; i++)
        printf("%s\t", stack_array2[i]);

    // Imprimir Array 3
    printf("\nArray 3:\n");
    for (int i = 0; i < 2; i++)
        printf("%s\t", heap_array3[i]);

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

    // Matriz 3x4 inicializada con 1..12
    int matrix3x4[3][4] = {
        { 1,  2,  3,  4},
        { 5,  6,  7,  8},
        { 9, 10, 11, 12}
    };

    // Imprimir matriz
    printf("Matrix:\n");
    for (int row = 0; row < 3; row++)
    {
        for (int col = 0; col < 4; col++)
            printf("%d\t", matrix3x4[row][col]);
        printf("\n");
    }

    getchar();
    return 0;
}

Codigo original

// (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;
}

Diferencias entre pseudocódigo y binario original (ASM)

  1. Inicialización de la matriz
  2. En pseudocódigo parece una inicialización "compacta" (a veces vista como asignación de QWORD o bloque).
  3. En ASM real son escrituras escalares consecutivas (mov) de 32 bits para cada elemento (1..12) en stack.

  4. Índices y tamaños en 64 bits

  5. El pseudo muestra for simples con int.
  6. El ASM extiende índices con movsxd para usar direccionamiento x64 seguro (int -> int64).

  7. Comparaciones de loops

  8. El pseudo usa condiciones legibles (i < 4, j < cols).
  9. El ASM implementa con cmp + saltos (jnb, jge) según flags.

  10. Acceso a arrays/punteros

  11. El pseudo oculta cálculo de offsets.
  12. El ASM muestra stride explícito:

    • punteros: base + idx*8 (punteros de 8 bytes),
    • matriz int: base + row*0x10 + col*4.
  13. Construcción de array3 dinámico

  14. El pseudo lo muestra directo (array3[0], array3[1]).
  15. El ASM pasa por preparación de offset (imul rax, 0/1) antes de escribir en mallocado.

  16. Llamadas a funciones y ABI

  17. El pseudo no expone registros.
  18. En ASM (x64 MSVC ABI), printf recibe formato en RCX y primer argumento variable en RDX.

  19. Constantes de texto/chars

  20. El pseudo las ve como strings/chars directos.
  21. En ASM se cargan direcciones con lea hacia .rdata; los chars (ch_A..ch_J) están con padding.

  22. Estructura de función

  23. El pseudo no enfatiza prólogo/epílogo.
  24. El ASM muestra sub rsp, 98h y add rsp, 98h, más retn y xor eax,eax para retorno 0.