Ret2PLT
src: https://github.com/0xmanjoos/Exploit-Development/tree/main/ret2/ret2plt
ret2plt
what is ret2plt?
before we get into the details on this exploitation technique, mandatory concepts to understand if you want this writeup to benefit you is ASLR, stack overflows, x86_64 asm, Global Offset Table, Procedure Linkage Table, and return oriented programming.
You may find resources on these particular topics here:
I will not be elaborating on these particular topics in this post, i will only be detailing the technique known as ret2plt. Lets first start off with a refresher on ASLR, since that is what this technique will bypass. ASLR will randomize the address layout of our loaded executable, shared libraries, heap, stack, and etc.. Essentially, this mitigation was implemented in all modern systems in order to render unreliable exploits useless.
the premise behind Ret2PLT is exploiting a loophole of sorts within the linux dynamic addressing system. when have a dynamic executable make a call to, lets say puts(), it will call the plt entry for puts. When binaries are dynamically linked, this address to the plt will stay static since it remains within the binary. Lets take a look at this diagram to help us visualize this scenario
note: not to scale, memory is not layed out this way, nor are the sizes representative of themselves
Now as we can see, on our first run our binary is all loaded perfectly into memory and will execute lets take a look at the layout of memory on our next run.
as we can see, the addresses of everything has been randomized within memory. You may notice that nothing INSIDE of libc changes, the offsets between each other will all remain the same. The only thing that changes is WHERE the program is placed into memory. Lets take a look at this particular scenario
everything is randomized within memory, so how can we reach our ever so desired system() function within libc? Lets take this scenario for example, if we are able to get the base address of libc, how would we be able to reach system?
as we can see from the chart, the base address of libc is 0x20, and lets just say the address of system is 0x22. We know, that normally libc would start at 0x00 right?, so if we statically look inside our libc, we will be able to find the offset to system at 0x2. So within the static libc
but when our program is loaded into memory, aslr is enabled and randomizing our space. we know the base address of libc, which is 0x20
that is how we are able to find the address of whichever function we want by calculating the base address of libc. Now the lingering question remains, how exactly do we do that?
with our technique ret2plt i say!
we know what the plt and got works, and we know how they interact with each other to dynamically resolve addresses within memory. So if our PLT entry within the binary is static, then we will always know the address of that entry, no matter if we are trying this exploit locally or remotely.
Which means that if were to find the address of the PLT entry, in which it points to the reloc GOT entry, we are able to dynamically leak to wherever the GOT is pointing to.
That may have sounded a bit confusing at first, but i will try to elaborate on what i just said with examples, lets first reverse this binary. pull it up in radare2 or any disassembler/decompiler of your choice and lets play around
when we see the function, we notice sym.imp.puts in radare2, it is named sym.imp due to the fact that it is an imported symbol. lets seek to the address of this import and see what lies inside.
hmhm, now this is interesting indeed, what we are looking at is our PLT entry. We know this since it will make a call to our GOT table, reloc.puts from what we can see from the binary, the static address of puts@plt is "0x00401030" now that we know the address of puts, and how to call it, lets get the address of the static pointer to the GOT.
as we can see, this is our pointer to the GOT this is extremely important, we also need the address of main, since we want to re-call it after program execution is completely within our control.
now that we know the address of puts@got and puts@plt, we can begin to explain the comcept of how we will be using these addresses
first, we obviously will want to hijack program execution, so we overflow a buffer and overwrite the return address to get control over the IP/PC registers.
The next value to write to the stack will be a rop gadget, in this case we are using the x86_64 cdecl calling convention. The convention in which parameters are passed is rdi, rsi, rdx, etc.. So since we are attempting to pass parameters to puts() and system(), we will need this gadget:
we can find this easily with any rop gadget searching tool, and i am assuming you have prior knowledge in ROP before you read this so i will continue
now that we have the addresses of puts@got, puts@plt, main(), and pop_rdi_ret, how will we use this to leak the address of puts within libc? Another question that may arise is, why do we want to leak the address of puts() within libc?
Leaking any function within libc will result in us being able to find the base address, which is why we want to leak the address of puts. Any GOT entry will do, as long as it exists within memory. The formula for calculating the base address of libc is:
like how we had spoken before about the offset, if the libc were to start at 0x0, and the address of puts() were 0x3, then the offset between 0x0 and 0x3 would be 0x3. But when we leak the address of libc within memory, it will look something like 0x23, since our libc base address will exist on 0x20 if we were to do 0x23 - 0x3, we would get the base address of libc, 0x20.
ok, so lets begin to leak it. The general premise behind this technique is, again, a loophole of sorts we first write over the return address with our pop_rdi_ret ROP gadget. Then, we will write the address of puts@got onto the stack, as so the address pointing to the GOT entry for puts will be pop'd into rdi. next, we write puts@plt, which calls puts. This essentially means that we passed the address of puts@got as a parameter to puts(), so what we are essentially doing is puts(plt@got). Which would, of course, leak the address of puts within memory.
So again, we leak puts, calculate the base address of libc, then we do the fun part. We call main() again, so we write the address of main onto the stack, as so when puts() ret's, it will ret2main(), and allow us to input whatever we want again, except this time we have the base address of libc.
Now its just a simple ret2libc from here, and since this writeup assumes prior knowledge i will not elaborate further.
Some things should be noted about this technique though, on modern binaries we will have another protection called PIE, short for Position Independent Executable. This means that our PLT entry within the program will not be static, nor will each of our functions be either. This greatly reduced the viability of this technique, as you would still need an arbitrary read in main before you can use it as an offset to puts.
Here is the binary source code, and exploit script, the libc will be included within this directory.
exploit.py
lab.c
Last updated
Was this helpful?