Controlling registers when gadgets are lacking
When an application is dynamically compiled (compiled with libc linked to it), there is a selection of functions it contains to allow the linking. These functions contain within them a selection of gadgets that we can use to populate registers we lack gadgets for, most importantly
__libc_csu_init, which contains the following two gadgets:
0x004011a2 5b pop rbx
0x004011a3 5d pop rbp
0x004011a4 415c pop r12
0x004011a6 415d pop r13
0x004011a8 415e pop r14
0x004011aa 415f pop r15
0x004011ac c3 ret
0x00401188 4c89f2 mov rdx, r14 ; char **ubp_av
0x0040118b 4c89ee mov rsi, r13 ; int argc
0x0040118e 4489e7 mov edi, r12d ; func main
0x00401191 41ff14df call qword [r15 + rbx*8]
The second might not look like a gadget, but if you look it calls
r15 + rbx*8. The first gadget chain allows us to control both
rbxin that series of huge
popoperations, meaning whe can control where the second gadget calls afterwards.
call qword [r15 + rbx*8], not
call qword r15 + rbx*8. This means it'll calculate
r15 + rbx*8then go to that memory address, read it, and call that value. This mean we have to find a memory address that contains where we want to jump.
These gadget chains allow us, despite an apparent lack of gadgets, to populate the RDX and RSI registers (which are important for parameters) via the second gadget, then jump wherever we wish by simply controlling
rbxto workable values.
This means we can potentially pull off syscalls for
execve, or populate parameters for functions such as
You may wonder why we would do something like this if we're linked to libc - why not just read the GOT? Well, some functions - such as
write()- require three parameters (and at least 2), so we would require ret2csu to populate them if there was a lack of gadgets.