#include <stdio.h>
struct persona {
char nombre[20];
int edad;
char telefono[60];
};
int main(void){
struct persona persona1;
printf("Tamaño de la estructura: %d\n",sizeof(persona1));
// Esto no se puede hacer.
printf("Ingrese su nombre: ");
scanf("%s",persona1.nombre);
printf("Ingrese su edad: ");
scanf("%d",&persona1.edad);
printf("Ingrese su telefono: ");
scanf("%s",persona1.telefono);
printf("Nombre: %s, Edad: %d, Teléfono: %s\n", persona1.nombre, persona1.edad, persona1.telefono);
return 0;
}
│ 0x00001159 55 push rbp
│ 0x0000115a 4889e5 mov rbp, rsp
│ 0x0000115d 4883ec60 sub rsp, 0x60
│ 0x00001161 64488b0425.. mov rax, qword fs:[0x28]
│ 0x0000116a 488945f8 mov qword [canary], rax
│ 0x0000116e 31c0 xor eax, eax
│ 0x00001170 be54000000 mov esi, 0x54 ; 'T'
│ 0x00001175 488d058c0e.. lea rax, str.Tamao_de_la_estructura:__d_n ; 0x2008 ; "Tama\u00f1o de la estructura: %d\n"
│ 0x0000117c 4889c7 mov rdi, rax ; const char *format
│ 0x0000117f b800000000 mov eax, 0
│ 0x00001184 e8b7feffff call sym.imp.printf ; int printf(const char *format)
│ 0x00001189 488d05960e.. lea rax, str.Ingrese_su_nombre: ; 0x2026 ; "Ingrese su nombre: "
│ 0x00001190 4889c7 mov rdi, rax ; const char *format
│ 0x00001193 b800000000 mov eax, 0
│ 0x00001198 e8a3feffff call sym.imp.printf ; int printf(const char *format)
│ 0x0000119d 488d45a0 lea rax, [var_60h]
│ 0x000011a1 4889c6 mov rsi, rax
│ 0x000011a4 488d058f0e.. lea rax, [0x0000203a] ; "%s"
│ 0x000011ab 4889c7 mov rdi, rax ; const char *format
│ 0x000011ae b800000000 mov eax, 0
│ 0x000011b3 e898feffff call sym.imp.__isoc99_scanf ; int scanf(const char *format)
│ 0x000011b8 488d057e0e.. lea rax, str.Ingrese_su_edad: ; 0x203d ; "Ingrese su edad: "
│ 0x000011bf 4889c7 mov rdi, rax ; const char *format
│ 0x000011c2 b800000000 mov eax, 0
│ 0x000011c7 e874feffff call sym.imp.printf ; int printf(const char *format)
│ 0x000011cc 488d45a0 lea rax, [var_60h]
│ 0x000011d0 4883c014 add rax, 0x14
│ 0x000011d4 4889c6 mov rsi, rax
│ 0x000011d7 488d05710e.. lea rax, [0x0000204f] ; "%d"
│ 0x000011de 4889c7 mov rdi, rax ; const char *format
│ 0x000011e1 b800000000 mov eax, 0
│ 0x000011e6 e865feffff call sym.imp.__isoc99_scanf ; int scanf(const char *format)
│ 0x000011eb 488d05600e.. lea rax, str.Ingrese_su_telefono: ; 0x2052 ; "Ingrese su telefono: "
│ 0x000011f2 4889c7 mov rdi, rax ; const char *format
│ 0x000011f5 b800000000 mov eax, 0
│ 0x000011fa e841feffff call sym.imp.printf ; int printf(const char *format)
│ 0x000011ff 488d45a0 lea rax, [var_60h]
│ 0x00001203 4883c018 add rax, 0x18 ; "`\x10"
│ 0x00001207 4889c6 mov rsi, rax
│ 0x0000120a 488d05290e.. lea rax, [0x0000203a] ; "%s"
│ 0x00001211 4889c7 mov rdi, rax ; const char *format
│ 0x00001214 b800000000 mov eax, 0
│ 0x00001219 e832feffff call sym.imp.__isoc99_scanf ; int scanf(const char *format)
│ 0x0000121e 8b55b4 mov edx, dword [var_4ch]
│ 0x00001221 488d45a0 lea rax, [var_60h]
│ 0x00001225 488d4818 lea rcx, [rax + 0x18]
│ 0x00001229 488d45a0 lea rax, [var_60h]
│ 0x0000122d 4889c6 mov rsi, rax
│ 0x00001230 488d05310e.. lea rax, str.Nombre:__s__Edad:__d__Telfono:__s_n ; 0x2068 ; "Nombre: %s, Edad: %d, Tel\u00e9fono: %s\n"
│ 0x00001237 4889c7 mov rdi, rax ; const char *format
│ 0x0000123a b800000000 mov eax, 0
│ 0x0000123f e8fcfdffff call sym.imp.printf ; int printf(const char *format)
│ 0x00001244 b800000000 mov eax, 0
│ 0x00001249 488b55f8 mov rdx, qword [canary]
│ 0x0000124d 64482b1425.. sub rdx, qword fs:[0x28]
│ ┌─< 0x00001256 7405 je 0x125d
│ │ 0x00001258 e8d3fdffff call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
│ │ ; CODE XREF from sub.main_1159 @ 0x1256(x)
│ └─> 0x0000125d c9 leave
└ 0x0000125e c3 ret
Todos los registros que empiezan con r toman los primeros 64 bits, los que empiezan con e los primeros 32 bits, y si no tienen ninguno hacen referencia a los primeros 16 bits. Ejemplo: rax(64 bits), eax(32 bits), AX(16 bits)
.
En este caso:
RDI: Es el primer argumento de una llamada a la función.
RSI: Es el segundo argumento de una llamada a la función.
RCX: Es el tercero argumento de una llamada a la función.
RDX: Es el cuarto argumento de una llamada a la función.
Luego tenemos r8 que es el 3er argumento y r9 que es el 4to argumento en muchos casos.
Por ejemplo acá tenemos un caso en donde no es así: en este caso rcx es el 1er argumento, … y r9 es el 4to.
┌ 116: sub.fcn.1400012d0_1400012d0 (int64_t arg1, int64_t arg2, int64_t arg3, int64_t arg4);
│ `- args(rcx, rdx, r8, r9) vars(7:sp[0x8..0x50])
│ 0x1400012d0 48894c2408 mov qword [var_8h], rcx ; arg1
│ 0x1400012d5 4889542410 mov qword [var_10h], rdx ; arg2
│ 0x1400012da 4c89442418 mov qword [var_18h], r8 ; arg3
│ 0x1400012df 4c894c2420 mov qword [var_20h], r9 ; arg4
Es decir: printf(“Nombre: %s, Edad: %d, Teléfono: %s\n”, persona1.nombre, persona1.edad, persona1.telefono);
RDI = “Nombre: %s, Edad: %d, Teléfono: %s\n” RSI = persona1.nombre RCX = persona1.edad RDX = persona1.telefono
Los demás argumentos se pasan por el stack a la pila.
Estas dos líneas están presentes en todos los programas, push “empuja” a la pila la dirección de la base de la pila.
mov
siempre copia del 2do argumento al 1ero.
push rbp
mov rbp, rsp
sub rsp, 0x60
Resta al puntero de la pila (stack pointer) 0x60 que son 96 bytes (la estructura tiene 20 bytes de nombre + 4 bytes de int + 60 bytes de teléfono = 84 bytes). Es como que le pide espacio para las variables.
mov rax, qword fs:[0x28]
mov qword [canary], rax
Esto es equivalente a:
mov qword [canary], qword fs:[0x28]
Pero en ASM siempre un argumento debe ser un registro no podés tener 2 variables.
mov rax, qword fs:[0x28]
mov
: This is the instruction to move data.rax
: This is the destination register, a 64-bit general-purpose register in the x86-64 architecture.qword
: This indicates that the operation is dealing with a 64-bit (8-byte) value.- fs:[0x28]
: This indicates that we are accessing memory at an offset of 0x28
from the base address stored in the fs
segment register.In the x86-64 architecture, segment registers like fs
(and gs
) are typically used for special purposes. The fs
register often points to a specific segment of memory, and in many modern operating systems, it is used to access thread-local storage (TLS) or other system-specific data.
In the case of fs:[0x28]
, it’s commonly used in the Windows kernel (or similar systems) to refer to a specific location in the thread information block (TIB). On Windows, for example, fs:[0x28]
often holds the address of the thread’s environment block (TEB).
xor eax, eax
mov esi, 0x54
ESI es el 2do argumento pero los primeros 32 bits. En este caso carga 0x54 que son 84 bytes (el tamaño de la estructura).
lea rax, str.Tamao_de_la_estructura:__d_n ; 0x2008 ; “Tama\u00f1o de la estructura: %d\n”
lea
es como el mov
. Carga en el rax
que es un registro acumulador la cadena: “Tamaño de la estructura: %d\n”
Luego del rax lo mueve al rdi: mov rdi, rax
como dije anteriormente hace esto porque no se puede trabajar con 2 variables, siempre hay que un registro en cada mov.
rdi es el primer argumento del printf luego carga 0 al eax y llama al printf:
mov eax, 0
call sym.imp.printf
Desde el siguiente código podemos asumir que var_60h
es el puntero (es un puntero cuando tiene los corchetes [var_60h]
) a la variable nombre. Lo carga al RSI es decir al segundo argumento de la función scanf
y en el primer argumento carga: "%s"
. Es deicr: scanf("%s", &var_60h);
│ 0x0000119d 488d45a0 lea rax, [var_60h]
│ 0x000011a1 4889c6 mov rsi, rax
│ 0x000011a4 488d058f0e.. lea rax, [0x0000203a] ; "%s"
│ 0x000011ab 4889c7 mov rdi, rax ; const char *format
│ 0x000011ae b800000000 mov eax, 0
│ 0x000011b3 e898feffff call sym.imp.__isoc99_scanf ; int scanf(const char *format)
Acá hace algo similar: carga la dirección de memoria del comienzo de la cadena de caracteres al rax, y luego lo carga al rdi (el 1er argumento de la función) y llama a printf con ese argumento:
│ 0x000011b8 488d057e0e.. lea rax, str.Ingrese_su_edad: ; 0x203d ; "Ingrese su edad: "
│ 0x000011bf 4889c7 mov rdi, rax ; const char *format
│ 0x000011c2 b800000000 mov eax, 0
│ 0x000011c7 e874feffff call sym.imp.printf ; int printf(const char *format)
scanf: Aca vemos algo importante, le suma 0x14 (20 bytes) al puntero al primer byte de nombre para llegar a edad que es el 2do elemento de la estructura: add rax, 0x14
, y lo carga al rsi (2do argumento de la función). Luego carga “%d” que está ubicado en la dirección de memoria 0x0000204f
al rax, carga 0 al eax y llama a scanf.
│ 0x000011cc 488d45a0 lea rax, [var_60h]
│ 0x000011d0 4883c014 add rax, 0x14
│ 0x000011d4 4889c6 mov rsi, rax
│ 0x000011d7 488d05710e.. lea rax, [0x0000204f] ; "%d"
│ 0x000011de 4889c7 mov rdi, rax ; const char *format
│ 0x000011e1 b800000000 mov eax, 0
│ 0x000011e6 e865feffff call sym.imp.__isoc99_scanf ; int scanf(const char *format)
│ 0x0000121e 8b55b4 mov edx, dword [var_4ch]
│ 0x00001221 488d45a0 lea rax, [var_60h]
│ 0x00001225 488d4818 lea rcx, [rax + 0x18]
│ 0x00001229 488d45a0 lea rax, [var_60h]
│ 0x0000122d 4889c6 mov rsi, rax
│ 0x00001230 488d05310e.. lea rax, str.Nombre:__s__Edad:__d__Telfono:__s_n ; 0x2068 ; "Nombre: %s, Edad: %d, Tel\u00e9fono: %s\n"
│ 0x00001237 4889c7 mov rdi, rax ; const char *format
│ 0x0000123a b800000000 mov eax, 0
│ 0x0000123f e8fcfdffff call sym.imp.printf ; int printf(const char *format)
│ 0x00001244 b800000000 mov eax, 0
Bool - 1 bit
Byte - 1 Byte
Word - 2 Byte - 16 bits
Dword - 2 Word - 4 Bytes - 32 bits
var_4ch = telefono
, lo carga al edx que es el 4 argumento.
var_60h = nombre persona (primer elemento de la estructura y es el 2do argumento porque lo carga al rsi)
rax + 0x18 = edad (2do elemento de la estructura y es el 3er argumento porque lo carga al rcx)
str.Nombre:__s__Edad:__d__Telfono:__s_n ; 0x2068 ; "Nombre: %s, Edad: %d, Tel\u00e9fono: %s\n"
es el 1er argumento de la función printf
porque lo carga al rdi
.
Finalmente:
│ 0x00001244 b800000000 mov eax, 0
│ 0x00001249 488b55f8 mov rdx, qword [canary]
│ 0x0000124d 64482b1425.. sub rdx, qword fs:[0x28]
│ ┌─< 0x00001256 7405 je 0x125d
│ │ 0x00001258 e8d3fdffff call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
│ │ ; CODE XREF from sub.main_1159 @ 0x1256(x)
│ └─> 0x0000125d c9 leave
└ 0x0000125e c3 ret