How do I even pwn anything? Part 9— Getting RCE by Returning to LIBC
Explore how to perform Linux Binary Exploitation from Capture-the-Flag (CTF) competitions.
Course Link
You can find all the files required for the exercises on GitHub.
Leaking Contents From GOT
Now that we understand how to bypass ASLR, and what the PLT and GOT is used for, we will now learn how to leak the contents of the global offset table.
Do you remember what puts
does? It takes in a single parameter, an address and writes the contents of that location in memory as ASCII to STDOUT (until it hits a null-byte). This means that if the address we send to puts
is the location of the function at the global offset table, we will be able to get the address of where the function is in LIBC! This can be done by generating a ROP chain to call puts
and set the GOT as the parameter!
However, we will likely only have one gets
function to abuse. What is the point of leaking an address if we cannot use it to spawn ourselves a shell? Is there a way to overflow the buffer another time?
That’s right! We can chain the vulnerable function into the ROP chain. This means that it will first call puts
to leak the function address in LIBC, then jump back to the start of the function and run gets
again!
Since puts
will write the address to STDOUT in ASCII, we can use the u32
function from Pwntools to convert it back into a number. We can then calculate the base address of LIBC.
>>> PUTS_LIBC = u32(b"\x44\x43\x42\x41")
>>> hex(PUTS_LIBC)
'0x41424344'
>>> libc = ELF("/usr/lib32/libc.so.6")
>>> libc.address = PUTS_LIBC - libc.sym["puts"]
Spawning A Shell
Since we know the version of LIBC, and the location of a function in LIBC, we can calculate the location of system
. But how can we pass /bin/sh
to the system
function?
Well, LIBC contains the string /bin/sh
in the binary itself. This is rather convenient as it allows us find and pass the address as the first parameter.
libc = ELF("./libc.so")
next(libc.search(b"/bin/sh\x00"))
Building the ROP chain again allows us to pop a shell.
Exercise
Return To LIBC — Bypassing ASLR and executing system
You can find the binary and source code in at ret2libc32. If you deployed the services locally, this exercise can be accessed using nc localhost 30006
#include <stdio.h>
#include <stdlib.h>
void vuln() {
char buffer[64];
gets(buffer);
}
int main() {
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stdin, NULL, _IONBF, 0);
puts("Guess my name");
vuln();
puts("Wrong!");
return 0;
}
Solution
If you didn’t manage to pwn it, do not worry, look at the solution and see if you can replicate it for yourself.
You should be able to build the ROP Chain and leak the contents in the GOT address of 2 functions to retrieve the version of LIBC.
When leaking the address, you might have notice that there are way more than 4 bytes.
Since puts
keeps printing until it hits the a null-byte, we can just ignore any bytes after the first 4 bytes.
>>> u32(p.recvline().strip()[:4])
We can then use the LIBC Database to get the LIBC version. After some trial an error, the bottom 3 LIBCs can be used.
Calculate the base address, and create a second ROP Chain that calls system
with the address of /bin/sh
to pop a shell!