AndrewNohawk
Coding Security

HackFu 2016 Writeup

hackfulogo

First off let me just say a big thank you to the MWR guys who put this CTF together, usually I don’t partake in CTFs because the skillset required is usually out of my grasp (IANAP).

To have developed this CTF in a manner that allows people who do not work with crypto/hackz0r wizardry to still have a chance of solving the problems is awesome! I didn’t solve all of the problems, but I did spend far too much of my free time and apologise to the many bars I had to let down during that time. After this writeup I shall resume my social responsibilities ;)

Each of the various problems took my many hours of frustrating, wallpunching, facepalming and omgnoobing to complete, however I will just go over the solutions to each of them without the hours of frustration — the tl;dr of each one if you will.

Challenges:

Challenge 1 ( GUASS RIFLE ) — A book cipher requiring you to parse various words from lines in books
Challenge 2 ( RADIATION POISONING ) — An LSB Stego QR code that needed to be decoded and then parsed
Challenge 3 — Not completed
Challenge 4 ( FACSIMILE ) — A audio fax that needed to be decoded
Challenge 5 ( GREEN SKIN ) — A literal jigsaw puzzle representing 4 sides of a puzzle piece with 3 characters
Challenge 6 ( WHIRLPOOL ) — A multiple times rotated image that needed to be ‘unrotated’
Challenge 7 ( SCORCHED EARTH ) — An Office document with a weak password
Challenge 8 ( SMOG AND SMOKE ) — A Modified playfair cipher that needed to be recronstructed based on solar systems
Challenge 9 — Not completed
Challenge 10 — Not completed

If you have the writeup to challenges 3/9/10 please let me know so I can link to them!

GitHub

All the challenges/instruction text and solutions are available on the following github: https://github.com/AndrewMohawk/HackFu2016


Challenge 1

[Instruction Text] [GitHub Page]

For challenge one we were giving a number of books in plain text format that we needed to work with as well as a readme that contained the following text:

85,8
124,11
1984,8
3,5
901,1
3,13
8546,12
5,2
3,4
85,10
3437,7

The formula above (after a few quick attempts) described {line,word}. From this I could parse each of the books and do the following:

1. Does the book contain enough lines (it needs at least 3437 lines)
2. Populate a string/array with each word for each co-ordinate pair found
3. If we have found words for each co-ordinate pair then display the sentence.

Writing a quick script to do this you get the following:

# php solution.php
 Sense and Sensibility -- sentence: providingwastheirthewholeandsoonitforthosethe
 The Count of Monte Cristo -- sentence: wemustembracethepainandburnitforourjourney

The first sentence ‘providingwastheirthewholeandsoonitforthosethe‘ seems to be cut off, but the second one makes more sense. Simply decoding the AES-CBC-256 file with that will give you the output.

openssl aes-256-cbc -d -base64 -in solution.txt.enc -out solution.dec.txt \
 -pass pass:wemustembracethepainandburnitforourjourney

Challenge 2

[Instruction Text] [GitHub Page]

We were provided an image and asked to derive a key.

Using Stegsolv.jar we could see that there was a QR code hidden within the 0 layer of each individual colour (R,G,B):

stegosolve

To solve this I wrote a simple python script to parse each RGB value and identify if the least significant bit (LSB) was either a 0 or a 1 and then set the RGB value of a new image to either 255 or 0 depending on the LSB.

import Image
 
def LSB(b):
 
        b = str(bin(b))[-1:]
        if(b == "1"):
                return 0
        else:
                return 255
 
image = Image.open("image")
out = image.copy()
out = image.convert('RGB')
out.putdata( [(LSB(r), LSB(g), LSB(b))
                for r, g, b in out.getdata()] )
 
out.save("LSB.bmp")

Initially I had the values reversed, but the QR code would not decode, however after looking at the specification it appeared that I needed to reverse the colours to create the white/negative space needed to properly decode it:

LSB

The QR code decoded to the text ‘theleastsignificantisthemostsignificant’

Challenge 4

[Instruction Text] [GitHub Page]

For Challenge four we were given an audio file as well as a zip file (exif.zip) with a password. Audio is purely digital and the zip file is password protected.

Looking at the exif for the audio file we get the following:

# exiftool audio1.wav
ExifTool Version Number : 10.13
File Name : audio1.wav
Directory : .
File Size : 375 kB
File Modification Date/Time : 2016:03:03 08:17:22+01:00
File Access Date/Time : 2016:05:15 11:45:34+02:00
File Inode Change Date/Time : 2016:05:15 11:41:57+02:00
File Permissions : rw-r--r--
File Type : WAV
File Type Extension : wav
MIME Type : audio/x-wav
Encoding : Microsoft PCM
Num Channels : 2
Sample Rate : 8000
Avg Bytes Per Sec : 32000
Bits Per Sample : 16
Title : Fldigi
Artist : Rudolf Light Writer
Comment : feld
Duration : 11.99 s

Having a look at the Title actually gives us an application ‘fldigi’ that can be used to encode and decode various digital audio formats. The Artist field is ‘Rudolf Light Writer’ and using this and a few minutes on google you will also see that this text describes a type of Audio Fax called hellschreiber or feld hell.

Playing this audio file into a decoder you receive a small segment of text in the form of a fax that contains the words “bringlifetothewasteland”. The easiest way I found to do this was to run fldigi on a one machine listening to incoming audio via the microphone and then play back the audio file from another machine

fldigi fldigi2

After you have decoded that you can use the password (‘bringlifetothewasteland’) to decrypt the zip file.

exiftool exif.zip
ExifTool Version Number : 10.13
File Name : exif.zip
Directory : .
File Size : 505 kB
File Modification Date/Time : 2016:03:03 09:26:46+02:00
File Access Date/Time : 2016:05:20 10:58:30+02:00
File Inode Change Date/Time : 2016:05:15 11:41:57+02:00
File Permissions : rw-r--r--
File Type : ZIP
File Type Extension : zip
MIME Type : application/zip
Zip Required Version : 819
Zip Bit Flag : 0x0001
Zip Compression : Unknown (99)
Zip Modify Date : 2016:03:03 10:26:02
Zip CRC : 0x00000000
Zip Compressed Size : 516693
Zip Uncompressed Size : 705838
Zip File Name : audio2.wav

To unzip this zip file you can simply use 7z with the following:

7z -pbringlifetothewasteland x exif.zip

This zip file contains a single file [audio2.wav].

Looking at the exif for the file audio2.wav you will notice the title field is populated with “backmasking” and the Artist field is populated with “speed”. These two give you the hints that tell you how to “decrypt” the audio.

When you first listen to the audio you can clearly hear that it is reversed, simply going to effect->reverse within Audacity will have the audio the correct way round. Next you will hear that it is painfully slow so you will need to move the speed slider on the right up to about 1.8x/2x to hear the audio:

“You have solved the fourth challenge, proceed to eden to face the fifth challenge”
Audacity

You can then use the entire string as the password:

openssl aes-256-cbc -d -base64 -in solution.txt.enc -out solution.dec.txt -pass pass:youhavesolvedthefourthchallengeproceedtoedentofacethefifthchallenge

Challenge 5

[Instruction Text] [GitHub Page]

Challenge 5 was by far the most difficult challenge for me and the one that I spent the most time on. I tried so many different possible ideas to solve this, and had got to the stage where I was asking strangers how they saw the puzzle in the hopes it shone some new light on it. However there was a clue within the instructions, it mentioned the word ‘Puzzle’ in the final sentence as well “Challenge: Retrieve a phrase by assembling the string puzzle.”

Looking at what was given to you, you had the following ‘pieces’:

OIOOb
IIOIe
IIIId
IOIO9
IOOI8
IIIO8
OOII8
IOOO6
OIOI9
OOOOa
OIIIe
OOIOb
_IOI0
OI_If
_OOO8
I_II9
_IOO1
_III8
IOI_f
OO_Of
_OIO0
_IIO4
IO_Oe
OIO_d
O_OO4
IO_Ic
IO_I0
OO_I5
__IOc
II__3
O__I2
_IO_1

Each piece was made up of 3 characters (O, I and _) as well as a hex character at the end. There was a single duplicate row in the form of IO_Ic and IO_I0, but these were interchangeable later.

First if you split the pieces on _’s you would see there are 4 pieces with two underscores, 16 pieces with one underscore and 12 pieces that have neither. After many hours of frustratingly staring at it I realised this was a co-ordinate system for a puzzle.

Pieces were describing physical jigsaw puzzle pieces that are square with 4 sides being either a Male piece, Female piece or Side/border piece.

To visualise it, here is an actual possibility for the puzzle:
puzzle

Looking at the possibilities of how the pieces could fit together we can see that the formats for each piece could be one of the following for (T)op, (L)eft, (B)ottom, (R)ight and could be in either an 8×4 or 4×8 configuration :

8x4:
#### #### #### #### #### #### #### ####
#### #### #### #### #### #### #### ####
#### #### #### #### #### #### #### ####
#### #### #### #### #### #### #### ####

T,R,B,L
T,L,B,R
B,R,T,L
B,L,T,R

4x8:
#### #### #### ####
#### #### #### ####
#### #### #### ####
#### #### #### ####
#### #### #### ####
#### #### #### ####
#### #### #### ####
#### #### #### ####

L,T,R,B
L,B,R,T
R,T,L,B
R,B,L,T

The reason it can only be these is that there are 6 pieces that contain an underscore in the first col and 6 that contain an underscore in the third col. These will need to be on opposite sides to each other (ie, the top and bottom or left and right). This is of course presuming this puzzle will be rectangular (because otherwise – cryingemoticonhere).

Once I have the pieces I needed to figure out how they fit together as there were a number of possibilities, using the co-ordinate system above I used the following “formula”:

1. Iterate through each of the schemas above

2. Place the corner pieces

3. Calculate all the permutations for possible borders (top row, bottom row, left row, right row).

4. Validate each of these based on the rules for jigsaw puzzles  – ie for pieces to fit together one side of the piece needs to be ‘male’ and the other needs to be ‘female’ of the piece it is attaching to. This will greatly reduce the number of possibilities that will work for the borders of our puzzle.

5. Build the top row and the sides for each 8×4 (in this case) and send it to a recursive function that can look at the remaining pieces and justify where they should be. Because we don’t have all the information available we still have bits of it. For example even though we cant know the piece below we can still calculate possible pieces that will fit into each spot from the piece above. We usually have 2 sides of each piece. Once we have calculated that piece we can branch off to calculate the next piece and recurse through the function.

6. Validate and prune every second row we now have from 5.

7. Do the same as (5) but now building the ‘top’ row as row 3

8. Validate and prune every third row

Once this was done we had all the possibilities for rows 2 and 3 as well as all the permutations for the top and bottom borders and sides. From here I simple iterated through every combination of the puzzle and validated each combination. Once I had these combo’s I saved the output hex to a ‘dict’ file, something like:

# wc dict
   30160   30160  995280 dict
# head dict
1801084cfe9dbae4d8b968893ffec052
1801084cf8e8a8e4d9db69b93ffec052
1801084cf8d68eb4d8bea9993ffec052
1801084cf8d8bae4d9896eb93ffec052
1801084cfd6ea8e4db8989b93effc052
1801084cfd689be4db89ea893effc052
1801084cf8d8a894d6ebeb993effc052
1801084cf8d8b9b4d68eae993effc052
1801084cfd688eb4dbe8a9993fcfe052
1801084cfd9ebae4db9868893fcfe052

This gave mme about 30K odd results to try, so I simply bruteforced them with a bash script:

#!/bin/bash
echo "################################"
k=1
total=`wc dict | cut -d " " -f 4`
echo "total is: $total"
lastPercent=0
while read -r line;do
                chomped_line=${line%$'\r'}
                openssl enc -aes-256-cbc -base64 -d -in solution.txt.enc -out out/$chomped_line -pass pass:$chomped_line 2> /dev/null
                if [ $? -eq 0 ]; then
                        yes=no #wut.
                else
                        rm out/$chomped_line
                fi
 
        percent=$((100*$k/$total))
        #echo $percent
        if [ $percent != $lastPercent ]; then
            echo "($percent) $k of $total"
            lastPercent=$percent
        fi
 
        ((k++))
done < dict
echo "Total number of lines in file: $k"

From here I simply ran the script to get the output as follows:

 ./dictbash.sh
################################
total is: 30160
(1) 302 of 30160
(2) 604 of 30160
(3) 905 of 30160
(4) 1207 of 30160
....snip...
(94) 28351 of 30160
(95) 28652 of 30160
(96) 28954 of 30160
(97) 29256 of 30160
(98) 29557 of 30160
(99) 29859 of 30160
(100) 30160 of 30160
Total number of lines in file: 30161

This then create a lot of files within the out/ directory and it was merely the task to find the right one:

# file out/* | grep -i unicode
out/1148008cfeb9dae4d68898b93e05cff2: UTF-8 Unicode English text, with very long lines
# head out/1148008cfeb9dae4d68898b93e05cff2
“CORRECT. YOU MAY ENTER THE GARDEN OF EDEN.”
 
And with that, the giant metal door begins to slowly creak open. You stand before it, wide-eyed and relieved that you didn’t waste your last attempt on “4pp134”.

Taduh :)

Challenge 6

[Instruction Text] [GitHub Page]

Challenge 6 was a relatively straight forward one, we were given the following image and asked to decode:

image

If you zoom into the image you can see that there is a pattern to it where every piece is rotated, then there is a ‘zoom’ and then it is rotated again:

image_zoomed

From here it was simply creating a python script with the help of the PIL library to solve this and was as follows:

#!/usr/bin/python
from PIL import Image
import _imaging
import sys
 
img = Image.open("image.jpg")
 
for i in range (0,100):
    x1 = 790 - (i * 10)
    x2 = 810 + (i * 10)
    y1 = 790 - (i * 10)
    y2 = 810 + (i * 10)
    inlay = img.crop((x1,y1,x2,y2)).rotate(90)
    img.paste(inlay, (x1,y1,x2,y2))
img.save("imagedecoded.jpg")

I used a photo editing application to get the initial co-ordinates of the center square and ran it to produce the following which included the decoded message :)
imagedecoded

Challenge 7

[Instruction Text] [GitHub Page]

This challenge was fairly straight forward, and also not that straight forward. So first we got an office document “OPENME.doc”, its password protected and we need to get in. To crack the password was relatively simple and the easiest way to get in.

First we needed the hash of the password (this we get with office2john.py)

python office2john.py OPENME.doc
OPENME.doc:$oldoffice$1*62aac83b6f5ce35078ed4c9bf6a0946f*4c8858d41869a01cf3a5af53a8f50005*0f099570e1e4a850d6bc0a3195b80b7c:::1252 1 1 220 786432 1252 mwri Normal mwri 8 1440 13101308220 13101313620 1 32 189 Microsoft Office Word 1::OPENME.doc

Next we can use the fantastic cudaHashCat to attack the password with the default rockyou password list:
cudahashcat

cudaHashcat64.exe -a 0 -m 9700 --username OPENME.doc:$oldoffice$1*62aac83b6f5ce35078ed4c9bf6a0946f*4c8858d41869a01cf3a5af53a8f50005*0f099570e1e4a850d6bc0a3195b80b7c "rockyou.txt"
cudaHashcat v2.01 starting...
 
Device #1: GeForce GTX 960, 2048MB, 1304Mhz, 8MCU
Device #1: WARNING! Kernel exec timeout is not disabled, it might cause you errors of code 702
           You can disable it with a regpatch, see here: http://hashcat.net/wiki/doku.php?id=timeout_patch
 
Hashes: 1 hashes; 1 unique digests, 1 unique salts
Bitmaps: 16 bits, 65536 entries, 0x0000ffff mask, 262144 bytes, 5/13 rotates
Rules: 1
Applicable Optimizers:
* Zero-Byte
* Precompute-Init
* Not-Iterated
* Single-Hash
* Single-Salt
Watchdog: Temperature abort trigger set to 90c
Watchdog: Temperature retain trigger set to 80c
Device #1: Kernel ./kernels/4318/m09700_a0.sm_52.64.cubin
 
Cache-hit dictionary stats rockyou.txt: 139921507 bytes, 14343297 words, 14343297 keyspace
 
$oldoffice$1*62aac83b6f5ce35078ed4c9bf6a0946f*4c8858d41869a01cf3a5af53a8f50005*0f099570e1e4a850d6bc0a3195b80b7c:salinas
 
Session.Name...: cudaHashcat
Status.........: Cracked
Input.Mode.....: File (rockyou.txt)
Hash.Target....: $oldoffice$1*62aac83b6f5ce35078ed4c9bf6a0...
Hash.Type......: MS Office <= 2003 MD5 + RC4, oldoffice$0, oldoffice$1
Time.Started...: 0 secs
Speed.GPU.#1...:  1822.7 kH/s
Recovered......: 1/1 (100.00%) Digests, 1/1 (100.00%) Salts
Progress.......: 12288/14343297 (0.09%)
Rejected.......: 0/12288 (0.00%)
Restore.Point..: 0/14343297 (0.00%)
HWMon.GPU.#1...:  0% Util, 45c Temp,  0rpm Fan
 
Started: Sat Jun 04 21:17:43 2016
Stopped: Sat Jun 04 21:17:45 2016

Opening the file with this password will give you the key.

However you can also view the macros within the document and you find this interest snippet:

# ./oledump.py OPENME.doc
  1:       121 '\x01CompObj'
  2:      4096 '\x05DocumentSummaryInformation'
  3:      4096 '\x05SummaryInformation'
  4:      6580 '1Table'
  5:       491 'Macros/PROJECT'
  6:        71 'Macros/PROJECTwm'
  7: M    1319 'Macros/VBA/NewMacros'
  8: M    2036 'Macros/VBA/ThisDocument'
  9:      2549 'Macros/VBA/_VBA_PROJECT'
 10:      1423 'Macros/VBA/__SRP_0'
 11:       110 'Macros/VBA/__SRP_1'
 12:       384 'Macros/VBA/__SRP_2'
 13:       103 'Macros/VBA/__SRP_3'
 14:        84 'Macros/VBA/__SRP_4'
 15:       103 'Macros/VBA/__SRP_5'
 16:       574 'Macros/VBA/dir'
 17:      4096 'WordDocument'
 
#./oledump.py -s 7 -v OPENME.doc
Attribute VB_Name = "NewMacros"
Sub Hackfu()
'
' Hackfu Macro
'Sec
'ond Fl
'ag is:
'youcannotspellscorchedearthwithoutdeath'
'
 
End Sub

I am still not sure where that came into play — perhaps later?

Challenge 8

[Instruction Text] [GitHub Page]

The final challenge I did was Challenge 8, it came with 12 image files and a very strange story about sextants and the night sky. After a lot of learning about old navigation methods and then trying tons of different things with the images (exif, stego, etc) I ended up with a listing of the various places and GPS co-ordinates for them as well as the date which they were taken:

DC046_2015:12:01.jpg / Tokyo Tower / 35.6585805,139.7454329
DC097_2015:03:01.jpg / abuja National Mosque nigeria / 9.0603227, 7.4891962
DC097_2015:06:01.jpg / texas state capitol / 30.2746652, -97.7403505
DC099_2015:07:01.jpg / cliffs of moher ireland / 52.9715368, -9.4308825
DC100_2015:09:01.jpg / branden burg gate ( Berlin ) / 52.5162746, 13.377704
DC105_2015:02:01.jpg / Djinguereber Mosque / 16.7715422, -3.0101439
DC105_2015:08:01.jpg / Old Trafford Manchester, England / 53.4630589, -2.2913401
DC108_2015:05:01.jpg / palacio de bellas artes mexico ( Mexico city)  / 19.4352, -99.1412
DC109_2015:01:01.jpg / Hassan II Mosque casablanca  / 33.6073889, -7.6323399
DC112_2015:04:01.jpg / Christ the redeemer, Rio de Janeiro, Brazil  / -22.951916, -43.2104872
DC115_2015:11:01.jpg / Great wall of China / 40.4319077, 116.5703749
DC117_2015:10:01.jpg / suomenlinna finland / 60.1454, 24.9881401

From here I started plotting them in different orders on maps to try and see if there was any pattern I could find:

googleMaps

Looking at it, I was convinced it was a reference to the big dipper, but simply couldnt get it to match exactly.

Next I started looking at the playfair cipher, it’s a relatively simple cipher and being lazy as I am, I managed to grab some code from here and here to get something I wanted to work. They had modified the cipher slightly to have some repetition as per the instructions but the modification wasn’t too difficult to make.

Now I had the cipher down and the output I just needed to find the keyword used to try and break it. Still convinced the map was trying to show me a constellation, I tried another method, bruteforce. I looked at various pages include this to build up a constellation list of 106 different constellations.

Additionally looking at the output it looked like we were getting a base64 encoded string out so we needed to decode that as well, so I added that to the script as well. From here I changed the playfair cipher to read these in and try them as the key and return the output, and got the following:

# python solution.py
ANTLIA :
dGhcY1JhdGVhkYTmZSKmaz0bazgygqzZtsFxsbWMwb254Nb9VzaL5mZXLfb9H3b9HsaT==x
-------------------------------
APUS :
dGikdlIidGrYbsSYfHCcfn2vfnhxinymvqO6tvjVwc254Pv4CzqQ7laWJnv4Rxv4JrZP==x
-------------------------------
AQUARIUS :
dGhckxJhdGvZbjSklRLZmz2vmzfzguzlpZ5xtkWKws254Qv59poP8kcAGuv5Pyv5GiqR==x
....snip...
CARINA :
dGhld3JhdGhvZnRoZWNvc21vc2hhc2ZhbGxlbnVwb255b3VyaW5mZXJpb3J3b3JsZA==x
thewrathofthecosmoshasfallenuponyourinferiorworld

The key ‘CARINA’ unlocked the cipher and decoded the B64 string to ‘thewrathofthecosmoshasfallenuponyourinferiorworld’.

The Carina constellation did look somewhat like the original Google map, but you definitely would have needed some intricate knowledge of the solar system to see it!

Constellation Carina

Constellation Carina

Cheers!

I had a ton of fun solving these problems and look forward to next years hackfu!

Comments

  1. A follow up on challenge 8. If you sort out the images in chronological order and look up the ASCII letters corresponding to the name of the images you will get the word Maiplacidus. Google search that and you have your key.

Leave a Reply

Your email address will not be published. Required fields are marked *