Memory and the Stack
Every overflow you will ever write starts with knowing where your bytes land in memory.
What you will learn
- -Map the segments of a running Linux process from stack to text
- -Explain how the stack grows and what a stack frame holds
- -Read rsp, rbp, and rip and say what each one controls
You cannot corrupt something you cannot picture. Before you overwrite a return address, you need a clear mental map of where a program keeps its code, its data, and the little scratchpad it uses for every function call. That scratchpad is the , and it is where most of the bugs in this module live.
A running process on Linux is just a chunk of virtual memory carved into . Low addresses hold the program's own and constants. High addresses hold the stack. The and the shared libraries sit in the middle. Look at a live process and it is right there:
$cat /proc/$(pgrep -n vuln)/mapsRead that output from top to bottom and you the whole layout. The .text segment is r-xp, readable and executable but not writable, because you should never write to your own instructions. The stack near the top is rw-p, writable but on a modern box not executable. That single x bit is the entire reason on the stack stopped working, which is a fight for a later lesson.
The stack grows down
Here is the part that trips up everyone at first. The stack grows toward lower addresses. When a function needs room for local variables, the CPU subtracts from the stack pointer. When the function returns, it adds back. Push moves the pointer down, pop moves it up.
Three drive all of this on x86-64. rsp is the stack pointer and always points at the top of the stack, which is the lowest address in use. rbp is the base pointer and marks the bottom of the current function's frame, so local variables get addressed relative to it. rip is the instruction pointer and holds the address of the next instruction to run. Own rip and you own the program.
What a stack frame holds
Every function call builds a frame. The call instruction pushes the return address, the spot execution should resume after the function finishes. Then the function saves the old rbp, sets up its own, and reserves space for locals by subtracting from rsp.
So a frame, from high address down to low, looks like this: the saved return address, the saved rbp, then the local buffers. Your input usually lands in one of those local buffers. Overflow it and you write upward through the saved rbp and straight into that saved return address. That is the classic bug, and now you can see exactly why it works.
Everything in this module assumes a binary you compiled yourself or a lab box you are cleared to attack: a local target, a HackTheBox machine, pwnable.kr. Debugging and overflowing someone else's process without written permission is unauthorized access. Keep it on your own hardware.
A local buffer sits at a lower address than the saved return address in the same frame. To overwrite that return address, which way do your bytes have to run as you write past the buffer's end?