Controlling rbp
A method of arbitrary writing
Once again, let's go back to the demo program.
// gcc demo.c -o demo -no-pie -fno-stack-protector
#include <stdio.h>
int main() {
char buf[0x20];
puts("ROP me if you can!");
gets(buf);
}
The main idea we'd have when overflowing this to just control the return address to get RCE. And in most cases that's sufficient, however with the lack of pop rdi ; ret
, we're forced to look for alternatives.
There's another value that's overwritten when we overflow, and that is the saved base pointer. While not as exciting as the return address, it can also be very useful.
What is the saved base pointer?
In our case, the stack is managed using 2 registers: rsp
and rbp
.
rsp
is the stack pointer, and is used when popping and pushing values.rbp
is the base pointer, and is used to determine where the variables on the stack are located.
rsp
will typically point to the bottom of the stack (address-wise), and rbp
to the top of the current stack frame.

main
When main
is called:
call main
will pushrip
(the address of the instruction aftercall
), which is the return address. Then it jumps to the function.push rbp
will push the previous value ofrbp
to the stack. This becomes the saved base pointer.mov rbp, rsp
makesrbp
now point to the recently pushed "saved base pointer". This is important to recognise, thatrbp
points to the saved base pointer of the previous function.sub rsp, 0x20
allocates the space for the variables (the space used for variables is betweenrsp
andrbp
)The code of the function
leave
will restore the previous values ofrbp
andrsp
beforepush rbp
(i.e. at the very start of the function). It does this by effectively doingmov rsp, rbp pop rbp
ret
now jumps back to the previous function, withrsp
andrbp
back to their original values.
And we also see that when gets
is called, the argument is loaded using lea rax, [rbp-0x20]
, showing that the buf
buffer is relative to rbp
.
Arbitrary write
However, in the process above, when we overflow, things start going wrong. The obvious one is changing the return address to jump somewhere else, but right before the return address, is the saved base pointer. When we overwrite that, we end up controlling the value of rbp
when we return.
Combining this with the fact that we have a gadget that does gets(rbp-0x20)
, we can return back to this with a controlled value of rbp
, and get an arbitrary write!
Overwriting GOT
GOT
The scope of this write is limited when we don't have any leaks, but one example of a useful target is GOT
. In fact, I wrote a challenge for HTB Business CTF 2024 which used this exact technique, and if you want to see an example of how you could use this, check out my writeup.
Other targets
Overwriting GOT
can be useful in some cases, but what about if there are no good targets in GOT
, or FULL RELRO
is enabled?
Aside from that, your targets for a write could be global variables used by the program for example.
Another idea is to create fake objects in a known location, which can be useful for certain things, such as ret2dlresolve
(covered here).
Another idea is to forge a fake stack frame, and to return to a certain section of a function. Since all variables are relative to rbp
, by pointing rbp
to a controlled buffer, you could "set" certain variables to controlled values, such as pointers to buffers, size variables, and so on.
Unfortunately both of these are dependent on what the binary does, so there aren't many universal approaches for when FULL RELRO
is enabled, but this can still remain to be a useful trick which may aid exploitation in some cases.
Last updated
Was this helpful?