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:
- Inicializa dos constantes locales
rows_count=3ycols_count=4. - Construye tres colecciones de strings de 1 carácter:
array1_ptrs(global): apunta a'A','B','C','D'.stack_array2(en stack): apunta a'E','F','G'.heap_array3(en heap): apunta a'I','J'(reservado conmalloc(0x10), liberado confree).- Imprime las 3 colecciones con
printf("%s\t", ...). - Inicializa una matriz
int matrix3x4[3][4]con valores1..12. - Recorre la matriz con doble
fore imprime cada elemento conprintf("%d\t", ...). - Llama a
getchar()para pausar y retorna0.
Hallazgos técnicos¶
- ABI x64 Windows (fastcall implícito): en llamadas a
printf,RCXlleva el formato yRDXel 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
.rdatacomo 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 comochar.
Mejoras aplicadas en IDA¶
1) Renombre de variables locales en main¶
i→idx_array1var_74→idx_array2var_70→idx_array3var_6C→rowj→colarray3→heap_array3matrix→matrix3x4rows→rows_countcols→cols_countarray2→stack_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_0→thunk___C_specific_handlermemset_0→thunk_memsetexit_0→thunk_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_A0x140003254=ch_B0x140003258=ch_C0x14000325C=ch_D0x140003260=ch_E0x140003264=ch_F0x140003268=ch_G0x14000326C=ch_I0x140003270=ch_J0x140005000=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)¶
- Inicialización de la matriz
- En pseudocódigo parece una inicialización "compacta" (a veces vista como asignación de
QWORDo bloque). -
En ASM real son escrituras escalares consecutivas (
mov) de 32 bits para cada elemento (1..12) en stack. -
Índices y tamaños en 64 bits
- El pseudo muestra
forsimples conint. -
El ASM extiende índices con
movsxdpara usar direccionamiento x64 seguro (int->int64). -
Comparaciones de loops
- El pseudo usa condiciones legibles (
i < 4,j < cols). -
El ASM implementa con
cmp+ saltos (jnb,jge) según flags. -
Acceso a arrays/punteros
- El pseudo oculta cálculo de offsets.
-
El ASM muestra stride explícito:
- punteros:
base + idx*8(punteros de 8 bytes), - matriz
int:base + row*0x10 + col*4.
- punteros:
-
Construcción de
array3dinámico - El pseudo lo muestra directo (
array3[0],array3[1]). -
El ASM pasa por preparación de offset (
imul rax, 0/1) antes de escribir enmallocado. -
Llamadas a funciones y ABI
- El pseudo no expone registros.
-
En ASM (x64 MSVC ABI),
printfrecibe formato enRCXy primer argumento variable enRDX. -
Constantes de texto/chars
- El pseudo las ve como strings/chars directos.
-
En ASM se cargan direcciones con
leahacia.rdata; los chars (ch_A..ch_J) están con padding. -
Estructura de función
- El pseudo no enfatiza prólogo/epílogo.
- El ASM muestra
sub rsp, 98hyadd rsp, 98h, másretnyxor eax,eaxpara retorno0.