- Published on
Pwn Learning Path #1: Reading Stack Frames in GDB
- Authors

- Name
- Muhammad Huzaifa
Before overwriting a return address, I needed to know where that address lives and how it gets there. My first lab was therefore not an exploit. It was a GDB fluency drill.
The program
The important function accepts three integers, creates local values, and returns their sum:
static long inspect_me(long a, long b, long c) {
volatile long local_a = a + 1;
volatile long local_b = b + 2;
volatile long local_c = c + 3;
volatile long total = local_a + local_b + local_c;
printf("local_a=%ld local_b=%ld local_c=%ld total=%ld\n",
local_a, local_b, local_c, total);
return total;
}
The volatile locals make the values easier to observe in a simple debug build.
What I inspected
I built the program, opened it in GDB, and stopped at both main and inspect_me:
cd labs/phase1-stack-basics
make
gdb ./stack_frames
set disassembly-flavor intel
break main
run
disassemble main
break inspect_me
continue
info registers
x/40gx $rsp
disassemble inspect_me
On Linux x86_64, the first integer or pointer arguments arrive in these registers:
1: rdi
2: rsi
3: rdx
4: rcx
5: r8
6: r9
That meant the values 10, 20, and 30 could be checked in rdi, rsi, and rdx as the function began.
The frame model that clicked
For this simple unoptimized build, I could reason about the function frame like this:
higher addresses
[rbp + 8] saved return address
[rbp] saved caller RBP
[rbp - ...] local variables
lower addresses
Each register had a different job:
ripidentifies the current or next instruction;rsppoints at the current top of the stack;rbpacts as a stable frame reference in this build;raxcarries the function's return value.
The function epilogue made more sense once I stopped treating it as magic:
leave -> restore this function's stack frame
ret -> take an address from [rsp], advance rsp by 8, and jump there
The saved return address is therefore data until ret consumes it. If a memory bug lets me change that data, I can change the next value loaded into rip.
My main takeaway
The useful skill was not memorizing [rbp+8]. Compiler options can change the exact layout, and optimized code may not use rbp as a frame pointer at all.
The real skill was learning how to verify the layout:
- stop before and after
call; - inspect
rsp,rbp, and the stack; - step through the prologue and epilogue;
- connect each instruction to a concrete memory change.
That model became the foundation for the first control-flow exploit.