Sunday, 31 May 2015

Hackyeaster CTF Writeup

Here's my full write-up for another Hacking-Lab's CTF: Hacky Easter 2015. This CTF happened between March 21st and May, 31st 2015.

EGG #1 - Puzzword

The following image was shown:

This one is really simple. The missing pieces form the word "hackerz"

EGG #2 - It's in the Media

A partially correct QR code was given:

However, if we tried to download the image, we would see only the background of the egg and not the QR code itself.

That means that the QR code itself is not an image.
Inspecting it with Firebug, we can see that it's actually made using a HTML table.

This table is composed of tiny cells with class names like l1,o2,i3... A class name stands out with the name "x5".
If we inspect the CSS applied we see that the background is white:
background: none repeat scroll 0 0 #fff;

So if we change it to another color, let's say red:
The image turns into this:
Very cool, it seems that we followed the right lead.
So now if we change it to black:
background: none repeat scroll 0 0 #000;

We got our QR code fixed and we can now scan it.

EGG #3 - Lego Stego

This was probably the most creative challenge of all.
The file provided has the extension "lxf". Searching it on Google shows that you can open this with Lego Digital Designer.
After downloading and installing the software we open the file and see the lego construction.

There are some lego pieces at the top with some strange configuration but they are actually there just to confuse us. Assuming that we were looking for a QR code, it must be hidden somewhere in the construction. Since the construction has a square format, I wondered "Is it covered by lego pieces?".

So i start to remove the pieces. Using the shape selection tool, i removed all the pieces but the white cubes.
After that i used the color selection tool and removed all white. This was the result:
So we got our QR code in Lego pieces. Very cool.

EGG# 4 - Twisted Num63rs

This challenge was just basically search for all the numbers using Wolfram Alpha or Google and order them ascendantly:

sqrt(1296) = 36
pi^pi = 36.462
ZmlmdHk=fifty= 50
middle C = 261.626 Hz 261.626
10101111000 - 1400
303240 (base8) = 100000
2^20 = 1048576
13 MiB = 1.363×10^7 bytes = 13630000
speed of light - 2.998 x 10^8 m/s = 299800000 as integer - 2130706433
java.lang.integer.MAX_VALUE = 2147483647
8 YiB - 9.671×10^24 bytes 9671000000000000000000000

EGG #5 - Phone Fumbling

This was another mobile challenge. After sending some time analyzing the behavior of the sensors, I've concluded that the bars were displaying the following:

1. time seconds (mod something)
2. oscilloscope
3. compass
4. battery

So to win this challenge, we just needed to have the full battery, have the right direction on the compass, the right orientation on the oscilloscope and wait some seconds to fill the time bar. The QR code then appears:

EGG #6 - Hack to the Future

In this challenge they gave us this encoded message:
dah-dah-dit dit dah-dah-dah di-dah-dit dah-dah-dit dit dah-dah dah-di-dah-dit di-di-dah-dit di-dah-di-dit dah-di-dah-dah

Searching on google for these kind of words, reveals that is the international morse code  language.

So now we just need to translate the message using the dictionary shown. The decoded message is:
Which is our flag (in lowercase)

However submitting this flag, showed the following error:
 So I did some investigating on how the flag submission was done. It seems to be a parameter called "m" that is sent with the current month.

 Easy, we changed it to three months earlier:
And that makes our submission accepted :)

EGG #7 - Vista de la Calle

Another mobile challenge. This one was kind of QR code hunting using an app similar to Google Street View. After spending some time searching, I've found it in the sky.

The image was kind of hard to make it readable with the QR code scanner, but after some time I've managed to read it using this enhanced image:
And that's it!

EGG #8 - Spread the Sheet

In this challenge the following string was shown:

Spreadsheet ID:

They also showed this image:

Everyone knows that this is a spreadsheet from Google Docs. So we can easily reconstruct our URL:

Opening this sheet, we see a scrambled QR Code.
 It's also possible to see that the first line and the first column have numbers that should be the correct order. So we just need to move the lines and columns so that the align correctly with the numbers order. This was the result:
It's our solution QR code.

EGG #9 - Fisheye

This was another mobile challenge. It said "Egg number nine is hidden here in the app. You've already seen it, haven't you?"

Indeed this QR code appears every time we start the application. We just need to capture a screenshot when it appears and then adjust it because it has a pitch effect.

So using Paint.NET, I used the Bulge tool to revert the pitch effect. Then it was a matter of adjusting the effect amount until the scanner managed to read the QR code.

EGG #10 - Thumper's Den

In this one the only thing that was written was "In order to get this egg, you need to search on the web site. Rumors say that Thumper himself has bagged it."

It means that the site should have some info about Thumper. There weren't many options. Thumper must be an user.
In the score page ( we can check the details of eggs found by other users by clicking them. So we check which eggs user Thumper has with the following link:

This immediately show us the solution QR code:

EGG #11 - You've got Mail

In this challenge we needed to analyze an inbox mail account and search for the egg in an email.
Looking at the Inbox file we see that the file "" was transfered at some point. We also know that the file is encoded in Base64:

We can then recreate the original file. After decoding the Base64, we can easily check that the file is a ZIP file and inside there is the egg with the QR code:

EGG #12 - This is just a Test

This level was a trolling level :) The answers didn't match the questions so besides having to search for the answers on Google, we also needed to put them in the right places.

For the answers this was the solution:

Q: What is the name of the popular port scanner, implemented by Fyodor?
A: nmap

Q: In the context of PKI systems, the shorthand "CRL" stands for "certificate __________ list".
A: revocation

Q: A group of 100 people plans to use symmetric encryption for secure communication. How many keys are needed to let everybody communicate with each other?
A: 4950 (this answer was taken from:

Q: Which hash sizes are supported by the SHA2 family? Choose two!
A: 384 and 512 (taken from Wikipedia: "The SHA-2 family consists of six hash functions with digests (hash values) that are 224, 256, 384 or 512 bits")

Q: Which port number is used by Kerberos:
A: 88

The problem was that when we POSTed this form, the following data was sent instead:
So I've intercepted the request using Burp Suite, and changed the values to the right ones:

And after submitting these results, we got our QR code:

EGG #13 - Leet TV

In this challenge there was a video provided ( This video was 15 min long and showed a random QR codes every second.
We needed the find the correct one.

Any n00b hacker knows that the leet time is 13:37 :)

Browsing the video to that specific time and scanning the QR code shows an URL link to:
Which thens redirects to:

After playing this WAV, it's easy to understand that the sound is reversed. So we reverse it again and now the sound is saying "842". So that's time in the video that we need to check (8:42).

Scanning the QR code at that time in the video, grant us the winning egg :)

EGG #14 - Wise Rabbit's Return

This challenge goes:

Wise Rabbit says:
An egg I give you for free,
it's below, as you can see.
But something got lost
add a dimension you must! 

And the following image was shown:

So we need to add a dimension... That means that we need to convert this to a QR code.
I extracted the barcode from the image and scanned it using zbar:

It returns the string "yckgKB2iV1rvNEfCoNiR". So now we just need to create a new QR code with this content. I used an online QR code generator for this:

We scan it with our scanning app and we got our solution.

EGG #15 - Photo Shooting

This level was fun and frustrating at the same time :)

The level says: "Your gallery needs some nice easter snapshots! What about a nice grasslands panorama, or a still life of a tomato?"
And then we could start a photo sub-app and take pictures of stuff.

The first part was easy to get. I just searched for "grasslands" on Google and took a picture to one of the results.
This saved the following photo on my mobile:
Ok so I got the first part of the QR code, so the tomato photo should have the other part. However this proven to be a challenge. Taking photos of a tomato didn't show any QR code, a trolling image was shown instead :P
So I went back to the grasslands image and start to take picture on different angles. Some of them worked and some of them didn't. So I assumed that i needed to hold the phone on a specific angle to take a picture of a tomato and get the other part of the QR code. So I started to take pics with my phone and after each one I rotated the cellphone a bit till I got a full 360ยบ turn. And after some tries, I've finally got it.
After that was just a matter of joining them together and scan the QR code:

EGG #16 - Ghost Room

The time I've sent on this level.... :P

This was a different level from any other.
The text in the challenge says "Ghosts only come out when it's dark..."

An image was then shown, but it didn't help us in anyway.

After spending hours searching for possible solutions, I've searched on my Burp's session for the word "dark" on the whole site and found an interesting match:

So there's a javascript function called toggleDark! I traced the code to where it was used and finally I saw that there was a light bulb on the bottom part of the challenges page which allowed us to... well... toggle dark on or off :)

When I clicked the bulb, the page reloaded with a darker look and the challenge 16 now appears different and link to a different URL:

Inside the text is also different. Now it says:

Dark is beautiful. A GOST with chaining appears and has a message for you:

There's also an image of a ghost with the word "spooky".

GOST is capitalized for a reason :) That's an encryption algorithm.
I've found an online tool to decrypt GOST and I used the key "spooky" to decrypt the message:

The result message is the link to our QR code flag:

EGG #17 - Spot the Difference

In this level the following two images were given:

So we need to pick the two images and apply some filter.
I normally use StegSolver for these kind of challenges. It usually works very well.
With the filter SUB, the result image was this:

Pretty cool image :) On the left lens we see a QR code. However if we zoom in, we see that something is wrong:
The QR code seems to be modified. We can clearly see a circle inside the QR code that looks like is inversing the colors. The circle has the same size as the circle on the right lens.
So what I did was copy the right lens image and paste it on top of the QR code and then apply the Difference filter. That corrected the QR code perfectly:

So now we just need to scan our code and get the egg.

Egg #18 - Sharks on Wire

In this challenge they give us an URL ( which has HTTP basic authentication and a PCAP file which should have the required credentials inside.

We open the PCAP on Wireshark and start the HTTP analysis.
By following the TCP stream we can see all the traffic sent, including the user and password submitted.

First thing we need are the HTTP basic auth credentials. These can be found on the stream on the Authorization header:
Decoding this in Base64, give us the login credentials: sharkman:sharks_have_j4ws
This login takes us to this page:
So there's another login that we need to find.
We filter out the current stream and continue the analysis on other HTTP requests.
Checking other possible streams we find the necessary info:
Besides the user and pass, we also need the hash to match.
We can use Burp's repeater tool to replicate the exact same request.

That gives us the following result:

The page accepted our login and is now redirecting us to page which has our solution egg:

Egg #19 - Cut n' Place

This was the last challenge I solved. I left for the end because I didn't like it.
The concept is super simple, we have to rearrange the strips of paper in a way that it composes a readable passphrase. Easier said than done :)
I tried to solve this using an image editor for hours and couldn't solve it. That's because on an image editor it's not simple to rearrange the layers in a way that you can actually simulate interlaces...
After some hours/days of frustration, I asked for a hint on #hacking-lab channel in IRC. I was told to actually print the damn paper, cut the strips and do it manually :P After I doing that, I solved it in a couple of hours. This is the final solution:

The passphrase is then: "paperstripsmadebyshredder"
Submitting it on the Egg-o-Matic revealed our solution QR code.

EGG #20 - Lots of Bots

The challenge says: "Robots have placed an egg on this web server. If you wanna find it, you need to think and act like a bot."

So I start by taking a look at the robots.txt file.

There's an interesting entry:

Loading the exact location doesn't seem to return anything, so it's safe to assume that the extension is missing. So I tried the basic ones 'htm' and 'html'. The latter worked and but it just redirect us to the C3PO page on Wikipedia. So I loaded the page again but this time through Burp and saw that there was content on bots.html.

The Javascript code stands out because it's encoded:

String.fromCharCode(105, 102, 32, 40, 33, 40, 110, 97, 118, 105, 103, 97, 116, 111, 114, 46, 117, 115, 101, 114, 65, 103, 101, 110, 116, 32, 61, 61, 61, 32, 39, 69, 97, 115, 116, 101, 114, 66, 111, 116, 39, 41, 41, 32, 123, 32, 108, 111, 99, 97, 116, 105, 111, 110, 46, 114, 101, 112, 108, 97, 99, 101, 40, 39, 104, 116, 116, 112, 58, 47, 47, 101, 110, 46, 119, 105, 107, 105, 112, 101, 100, 105, 97, 46, 111, 114, 103, 47, 119, 105, 107, 105, 47, 67, 45, 51, 80, 79, 39, 41, 59, 125)

This translates to:

"if (!(navigator.userAgent === 'EasterBot')) { location.replace('');}"

Hmm.. So if our User Agent is not 'EasterBot' we are redirected. Since we can read the HTML source, we don't really care about that condition.

The only other interesting part is a CSS that loads the image robotbg.jpg on the background.

This is a lead. There's a strange text on the image. It says "
bama waboki pisal fatatu fomu wosebi seju sowu seju - bamas mufe wafub fomu mowewe".

After spending some time trying to understand what this could be, I've found some of the words on a page that mentioned that this was a language called ROILA.

Here's the definition I've found:
ROILA is a spoken language for robots. It is constructed to make it easy for humans to learn, but also easy for the robots to understand. ROILA is optimized for the robots’ automatic speech recognition and understanding.

So I went to the vocabulary section on site ( and decoded the message word by word. The translation is then:
you must make word of addition two and two - this be name of page

The name of the page is the addition of two and two. That makes four. So our page should be called four.html =>
The pages a very similar page then before but I've noticed two differences: the background has changed to robotbg2.jpg and we have a strange phrase on the description meta tag.
This is the new image:

The description meta tag reads "Robots talk in ROILA language: eman egap eht esrever tsum"

At first I thought that this was again ROILA language but the words didn't match now.
Looking at them more closely we can see that it's just plain english but reversed :)

Reversing the phrase, it reads:
must reverse the page name

Cool, so instead of four.html our page should be "four" reversed: ruof.html

I loaded the page

This is our HTML:

We finally it the jackpot because now the background URL points to our egg:

EGG #21 - Cony Code

The challenge reads:
"Tired of boring QR codes, Dr. Bunny C. Easter developed an alternative. He's proudly introducing the "Cony Code" now! Crack the code in order to get another easter egg!
Hint: 110 is blue, the rest's up to you..."

Then it is shown a colored chess like image: 
I loaded the image on an image editor and started by taking note of each color's RGB value.
I noticed that the colors displayed all have values 00 or FF for each of the RGB components. This made me think that this probably means that we are dealing with binary encoding.

This is also consistent with the challenge hint: "110 is blue"
So if we take a look at the blue color's RGB we see that's #0000FF. This translates to 001 in binary.
It means that the hint is telling us to reverse all the bits to get our solution. Here's my analysis on a schematic:

So I've made a python script to read each RGB value of the image, convert it to binary and reverse the bits:

from PIL import Image
import sys
import re

print >> sys.stderr, "Loading image..."
im =

pix = im.load()
rgbimg = im.convert('RGB')
cmykimg = im.convert('CMYK')
print >> sys.stderr, "Image OK! Size: " + `im.size[0]` + "x"+`im.size[1]`+"\nProcessing...\n"
output = ""
for height in range(9,im.size[0],17): #im.size[0],17):
        for width in range(9,im.size[1],17):
                pixeloutput = ""
                r, g, b = rgbimg.getpixel((width,height))
                c,m,y,k = cmykimg.getpixel((width,height))
                if(r==255): pixeloutput+="0"
                else: pixeloutput+="1"
                if(g==255): pixeloutput+="0"
                else: pixeloutput+="1"
                if(b==255): pixeloutput+="0"
                else: pixeloutput+="1"
                print "["+pixeloutput+"] ",
        print ""

print "\nDecoded: ",output

This generates the following output:
However I couldn't find any meaning for it.

I started to wonder about the black squares at the end. If this is some kind of binary message it is highly unlikely that all that squares appear together.

So I loaded the binary string on the awesome Keygener Assistant tool and checked the output:

Then, assuming that we have some trailing bits in the end, I started to eliminate bits from the end one by one until I got a readable text:
The link to our QR code shows up:

EGG #22 - Hashes to Ashes

The challenge reads:
In this challenge, you need to prove your skills in hash cracking!
  • Standard algorithms (MD5, various SHA)
  • One iteration only, and no salting
  • Click the hint for each hash!
  • For hashes 3 and 4, use the following word list
Hash 1: Numeric PIN (16 digits)

Hash 2: Single word (lowercase only)

Hash 3: Complex word (1 upper, 1 substitution, ending with punctuation + digit)

Hash 4: Multi-Word (lowercase only)

Lets brake some hashes!

The BASE64 string translates to ASCII hex string 'ada2eeebe7809857a57f6fee4b2ffaee24eae7b1' which is MD5.
For this hash I used hash the tool ighashgpu for windows.
From the hint image we know that the pin is composed of only 4 digits: 1, 7, 9 and 0. We also know that the password length is 16 characters. Using CPU to crack this is painfully slow, but I used my 4 year old nVidia card to crack it and it got it in just 35 seconds :) Not bad.

The BASE64 string translates to ASCII hex string '6bbf7528d9dd2959a7afb37898425f67555f67f677987cae7e86210a2c8a0dbdfc248ec2d7b24010f440badc2223b4b5' which is SHA-384.

I used john the ripper for this one. Since the password is simple word, we can use CPU to crunch a big dictionary against this hash.
John rips the password in 17 seconds.

The BASE64 string translates to ASCII hex string 'b80814c5e0f386b0637163fd8afea929' which is MD5.

This was probably the hardest one. My first step was to take the wordlist provided and generate all combinations of common substitutions. I've extracted the common substitutions rules from leetspeak.rule file included in hashcat. This rule only replaces the first instance of the letter that matched the rule. So I used a tool called Rulify ( to add rules to replace it one more time and to do the first letter case substitution. Then I used this tool to generate a new dictionary with all the processed words.
This is what I've put on my rule1.txt file:

For the second part of the password, we only know that it has a number and a special character but we don't know the order. So I used the mask processor tool to generate both combinations and then joined them.

Then I ran hashcat using my rule file and my generated wordlist:
Hashcat destroyed the hash in less than 1 second :)

The BASE64 string translates to ASCII hex string '9791cbe0ae919a0330994a2d6ba26b8f0c3a1da15c73bce5fca39495881a6c90' which is SHA-256.

This is an "uncommon" combination attack. Hashcat allows us to execute a combination attack in which every entry in the wordlist is appended to all the words. But Hashcat doesn't allow us to do this 2 or more times. So what I did was to generate a new wordlist in which each word would be appended with the list of all the words on the wordlist once and then used the combinator attack. This way I could cover every combination of 4 words within the wordlist.
To generate the doubled wordlist I used a very simple python script:

Then I called hashcat with this wordlist using the Combinator attack:
The password was retrieved after 1 minute.

So resuming all the passwords:

Give us the QR code:

EGG #23 - Beat the Nerd Master

The challenge is:
"Did you beat the Swordmaster in Monkey Island? Even if, it ain't gonna help you this time. Get to know the mighty Nerd Master!
Connect to port 1400 of, and start the battle.
Here's an insult to start with: Go to your mummy."

This challenge was really fun. A very nice reference to the epic's Monkey Island Insult Swordfight :)

Once we connect to the specified port we are asked if we wanted to start and then the insult fight begins. At the beginning we don't know any insults to send, so we start by loosing against the computer, but we get to see which are the insults that it's sending us.

The next time we connect, we can use the insults that it sent us on the last game to check how it responds and learn how are we supposed to answer to each insult.

So my idea was to develop a script that would automatically learn insults and connect and disconnect multiple times until we know every answer to all insults.

This was the python script that I developed for this:

import socket               # Import socket module
import signal
import re
import argparse
import sys
from time import sleep

#we will learn more with our enemy =)
insultbook = {
        'Pna lbh ernq guvf?':'EBG13 vf sbe ynzref.',
        'I have more friends than you.':'Yeah, but only until you update your Facebook profile with a real picture of you!',
        '1f u c4n r34d th1s u r s70p1d.':"You better check your spelling. Stoopid has two 'o's.",
        'This fight is like a hash function - it works in one direction only.':'Too bad you picked LM hashing.',
        "You'll be 0xdeadbeef soon.":'Not as long as I have my 0xcafebabe.',
        'You should leave your cave and socialize a bit.':"",

unknowninsultbook = {
        'You should leave your cave and socialize a bit.':"",

def signal_handler(signal, frame):
        print('\nClient exited\n')

def parse_opts():
        parser = argparse.ArgumentParser(description='Hacky Easter 2015 - Henshin\'s Level 23 Beater')
        #parser.add_argument('-w', help='wordlist')
        args = parser.parse_args()
        return args

def recvall(s):
        output = ""
        while True:
                buf = s.recv(1024)
                if not buf or'TURN ----',buf):
        return output

def recvline(s):
        buffer = ''
        while True:
                c = s.recv(1)
                if c == '\n' and buffer != "":
                buffer += c
        return buffer.strip()

def send_insult(s, insult):
        print "YOU:",insult
        #response = recvall(s)
        response = s.recv(1024)
        response = response.rstrip()
        #print "RECV\n",response
        return response

def main():
        args = parse_opts()
        #wordlist = args.w
        signal.signal(signal.SIGINT, signal_handler)

        while True:
                s = socket.socket()
                host = ""
                port = 1400               
                s.connect((host, port))

                print "Connected to server"
                buffer = ""
                print s.recv(1024)
                sendstr = "y\r\n";
                print "Sending YES"
                s.send(sendstr); #initial yes
                print recvall(s)
                failcount = 0
                wincount = 0
                for insult, answer in unknowninsultbook.iteritems():
                        nerdanswer = send_insult(s, insult)
                        print "NERD:",nerdanswer
                        #add response to insultbook
                        if( not re.match("We've had this one before.",nerdanswer)):
                                insultbook[insult] = nerdanswer

                        output = recvline(s) # receive ---- MY TURN ----
                        if(not re.match("---- MY TURN ----",output)):
                        print "---- NERD's Turn ----"
                        question = recvline(s)
                        print "QUESTION: " + question
                        if(question in insultbook):
                                send_insult(s, insultbook[question])
                                #save question to book
                                unknowninsultbook[question] = ""
                                send_insult(s, "I dont know... yet!")

                        output = recvall(s)
                        print output
                        if(not"Point for",output)):

                                #terminate this session and start a new one


if __name__ == '__main__':

After some fine-tunning to the script I've ran it and it beat the game :)

 We get the link to the egg:

EGG #24:

This was a very nice challenge too.
The challenge says:
"Crypto Chiefs Ltd. developed a new hash function, which takes a 'divide and conquer' approach and combines several well-known hash functions ("Split, Hash, And Merge"). The inventors claim that with this approach, their function becomes more secure. Can you prove they are wrong?
Create a string which produces the following hash: 757c479895d6845b2b0530cd9a2b11"

The specification file is the following:

So if the text has 30 characters, it will be split in chunks of 6 characters and each chunk will be hashed with a different algorithm. The result is the concatenation of the 6 characters of each hash in the same position as the original string.

We have the target hash 757c479895d6845b2b0530cd9a2b11.
Our goal is then find:
  • Which MD2 hash starts with 757c47
  • Which MD5 hash has the chars  9895d6 on position 6 of the hash
  • Which SHA1 hash has the chars 845b2b on position 12 of the hash
  • Which SHA256 hash has the chars  0530cd on position 18 of the hash
  • Which SHA512 hash has the chars  9a2b11 on position 24 of the hash
I've made a python script that implements this logic by bruteforcing all alphanumeric charset with a recursive function until it finds a hash that partially collides with our target characters.
The function checks a,b,c,d,e,... and then cycles the next set of chars aa,ab,ac,ad,ae,... and so on until a match is found.

Here's the code:

from binascii import *
from Crypto.Hash import *
import base64
import re
import random
import string
from colorama import Fore, Back, Style, init



def hasPartial(word, hash,partial,start,stop):
                print "Found word '"+Style.BRIGHT+Fore.GREEN+word+Style.RESET_ALL+"' which hash collides: "+hash[0:start]+Style.BRIGHT+Fore.RED+partial+Style.RESET_ALL+hash[stop:]+" ..."
                return True
        return False

def recur(text, algorithm):

        for char in string.ascii_lowercase:
                t = text+char

                if(algorithm == "MD2"):
                        hash =
                        if(hasPartial(t, hash,"757c47",0,6)):
                                return True
                elif(algorithm == "MD5"):
                        hash =
                        if(hasPartial(t, hash,"9895d6",6,12)):
                                return True
                elif(algorithm == "SHA1"):
                        hash =
                        if(hasPartial(t, hash,"845b2b",12,18)):
                                return True
                elif(algorithm == "SHA256"):
                        hash =
                        if(hasPartial(t, hash,"0530cd",18,24)):
                                return True
                elif(algorithm == "SHA512"):
                        hash =
                        if(hasPartial(t, hash,"9a2b11",24,30)):
                                return True

                #char = random.choice(string.letters)
        for char in string.ascii_lowercase:
                t = text + char
                        if(recur(t, algorithm)==True):
                                return True
        return False

print "Searching MD2..."
recur("", "MD2")
print "Searching MD5..."
recur("", "MD5")
print "Searching SHA1..."
recur("", "SHA1")
print "Searching SHA256..."
recur("", "SHA256")
print "Searching SHA512..."
recur("", "SHA512")

And here's the output (nice and colored :P)

Since it uses just the CPU, it takes some time to find all the hashes. But it gets the job done.
The final string is then: afpmqtaaqidtdkkiozangecadjdgdb

Which give us the egg QR code:

EGG #25 - Jad & Ida

This was probably the most challenging egg of all.

The only thing that they give you is a ZIP file with some Java files. I started by running the program:

Our objective is then find the key that should give us our egg.

Since we don't have access to the .java files, we need to decompile the .class files and try to understand the code.

The objective here is then to check out the functions fizzle, rizzle, shizzle and bizzle and try to reverse the process, meaning that we need to find the initial 'h' given the final 'h' which is the key written in the code.

We don't really need to understand the details of what each function does as long as we manage to run them in reverse order. This would be easy if all the functions were in Java, but to increase difficulty hacking-lab guys choose to compile a DLL with 2 of  those functions inside.

I tried to load the DLL on many different languages but I couldn't import the functions inside no matter what.

So I opened the DLL in IDA and disassembled the code into pseudo-code.
These are the functions as I extracted them from IDA:

The next step was to interpret this code and try to understand what it does so that we can recode it in Java. 

The function Shizzle seems to be a string reverse. But function Fizzle has a lot of operations that are not easy at all to understand. So how can we test each one of these functions?

To test each of these functions individually I used Immunity Debugger with a very nice feature that I've never used before called 'Call DLL export' on the Debug menu.

This allows you to interact directly with the functions.
In order to do this, we need to call these functions with 2 parameters which are basically pointers to the input string and the output string.
I picked an empty zone on the stack and use that to perform my tests.
I loaded the hex-string 'A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF' to the memory dump and then passed the pointer as argument one. As argument two I passed a pointer to a place in memory below my initial pointer.
I called the Shizzle function and observed the output.

Nice, so we confirm that the function is just reversing our string!

Now about the Fizzle function.The part that I was confused about was the one below the LABEL_7 marker. In order for our string to reach this part of the code unchanged, we need to make sure that our first byte is big enough so that it's value minus 32 is bigger than 90 (which is the character 'Z'). So as long as our first byte is bigger than 122 (0x7a), it should it the 'break' statement and leave the first loop unchanged.
In fact, our test hex string works just fine since the first char is 'A0'. So let's test it:
Cool, we got our result.
If we take a look at it, we see that the function reversed each half of the string!

The rest of the function is easy to replicate. Having all the ingredients, I started to build my code using the awesome Coding Ground's online platform.

So here are my reversed Fizzle and Shizzle functions:

   private static String shizzle(String s){
      return new StringBuilder(s).reverse().toString();
  private static String fizzle(String s){
      int v3=0;
      char[] sa = s.toCharArray();
      String outstr = "";

          int v4 = sa[v3];
          sa[v3] = (char)((v4 + v3 * v3 - 27) % 91 + 32);
      outstr = new String(sa);
      String first = outstr.substring(0,8);
      String last = outstr.substring(8,16);
      outstr = last+first;
      return outstr;

Pretty simple, right? :)

So now I need to create the rest of the code to crack the key.
There's another important thing to notice: Since none of the functions actually adds or removes anything, it means we can brute-force character by character.

Here's the full code:

public class HelloWorld{
    public static void main(String []args){

        String h = "v3O] pmWm<Y(0=21";
        char[] targetkey = h.toCharArray();
        System.out.println("\n------------- REVERSING ---------------\n");
        char[] key = h.toCharArray();
        Boolean found = false;
        for(int k=0; k<16;k++){
            char[] trykey = key.clone();
            System.out.println("Bruteforcing char at position " + (k+1) + " " + new String(trykey));
            for(int l=32; l<=126; l++){
                trykey[k] = (char)l;
                h = new String(trykey);
                for(int i=0; i<10; i++){
                    h = fizzle(rizzle(shizzle(bizzle(h))));
                char[] outputkey = h.toCharArray();
                if(outputkey[k] == targetkey[k]){
                    System.out.println("Found key char! " + (char)l);
                    key[k] = (char)l;
        System.out.println("Input must be: " + new String(key));
        System.out.println("Full test: " + h);

        h = new String(key);
        for(int i=0; i<10; i++){
            h = fizzle(rizzle(shizzle(bizzle(h))));
        System.out.println("Output: " + h);

    private static String shizzle(String s){
        return new StringBuilder(s).reverse().toString();

    private static String fizzle(String s){
        int v3=0;
        char[] sa = s.toCharArray();
        String outstr = "";

            int v4 = sa[v3];
            sa[v3] = (char)((v4 + v3 * v3 - 27) % 91 + 32);
        outstr = new String(sa);

        String first = outstr.substring(0,8);
        String last = outstr.substring(8,16);
        outstr = last+first;
        return outstr;
    private static String rizzle(String s)
        char[] chars = s.toCharArray();
        for (int i = 0; i < chars.length; i++)
            char c = chars[i];
            if (Character.isUpperCase(c)) {
                chars[i] = Character.toLowerCase(c);
            } else if (Character.isLowerCase(c)) {
                chars[i] = Character.toUpperCase(c);
        return new String(chars);

    private static String bizzle(String s)
        char[] chars = s.toCharArray();
        for (int i = 0; i < chars.length; i++)
            char c = chars[i];
            if ((c >= 'a') && (c < 'z')) {
                c = (char)(c + '\001');
            } else if (c == 'z') {
                c = 'a';
            } else if ((c >= 'A') && (c < 'Z')) {
                c = (char)(c + '\001');
            } else if (c == 'Z') {
                c = 'A';
            chars[i] = c;
        return new String(chars);

And this is the output:

sh-4.3# java -Xmx128M -Xms16M HelloWorld

------------- REVERSING ---------------

Bruteforcing char at position 1 v3O] pmWm<Y(0=21
Found key char! j
Bruteforcing char at position 2 j3O] pmWm<Y(0=21
Found key char! a
Bruteforcing char at position 3 jaO] pmWm<Y(0=21
Found key char! d
Bruteforcing char at position 4 jad] pmWm<Y(0=21
Found key char! n
Bruteforcing char at position 5 jadn pmWm<Y(0=21
Found key char! I
Bruteforcing char at position 6 jadnIpmWm<Y(0=21
Found key char! d
Bruteforcing char at position 7 jadnIdmWm<Y(0=21
Found key char! a
Bruteforcing char at position 8 jadnIdaWm<Y(0=21
Found key char! l
Bruteforcing char at position 9 jadnIdalm<Y(0=21
Found key char! 0
Bruteforcing char at position 10 jadnIdal0<Y(0=21
Found key char! v
Bruteforcing char at position 11 jadnIdal0vY(0=21
Found key char! e
Bruteforcing char at position 12 jadnIdal0ve(0=21
Found key char! c
Bruteforcing char at position 13 jadnIdal0vec0=21
Found key char! o
Bruteforcing char at position 14 jadnIdal0veco=21
Found key char! d
Bruteforcing char at position 15 jadnIdal0vecod21
Found key char! 3
Bruteforcing char at position 16 jadnIdal0vecod31
Found key char! n
Input must be: jadnIdal0vecod3n
Full test: v3O] pmWm<Y(0=21
Output: v3O] pmWm<Y(0=21

Alright! So the string 'jadnIdal0vecod3n' generates the key 'v3O] pmWm<Y(0=21'!

Now we can run the original program again:

It worked, so we now have the egg decrypted on disk with the name eggizzle_25.png. Here it is:

EGG #26 - Clumsy Cloud

This was a mobile challenge. Opening the challenge on the mobile showed this info:

Welcome to the Clumsy Cloud!
If your files are the eggs, then we are the hen.

We encrypt all your files with a strong passphrase. The passphrase is kept securely in this app, protected by a PIN.

Download Files
Enter your PIN to download the secret files.

A keypad was then displayed to enter a 4 digit pin number.

Below there was a link to download a backup of our passphrase. The file contained this info:
"name" : "Clumsy Cloud Backup",
"comment" : "Backup of your passphrase, protected with your secret PIN.",
"params" : {
"s" : "ovaederecumsale",
"h" : "",
"i" : 10000,
"k" : 128,
"e" : "2.16.840.",
"p" : "8QeNdEdkspV6+1I77SEEEF4aWs5dl/auahJ46MMufkg="

Not very helpful. For this one I downloaded the APK of the Hackyeaster app from my phone to my computer and opened it on Bytecode Viewer.
Inside I search around a bit and found the following piece of code on the file ps/hacking/hackyeaster/android/Activity.class:

So now we can get a better idea of what the app is doing.

We see that the SecretKeySpec is being called this way:
final SecretKeySpec secretKeySpec = new SecretKeySpec(a(s, "ovaederecumsale", 10000), "AES");

Since there are more than 1 function called 'a' (overriding), we need to look for the one that has arguments that match our call, in this case: String, String, Int

We find it in the code a bit lower:

    public static byte[] a(final String s, final String s2, final int n) {
        final byte[] array2 = new byte[16];
        final MessageDigest instance = MessageDigest.getInstance("SHA1");
        byte[] array = (String.valueOf(s2) + s).getBytes();
        for (int i = 0; i < n; ++i) {
            array = instance.digest(array);
        System.arraycopy(array, 0, array2, 0, 15);
        }catch(Exception e){
        return array2;

So this means that our 's' variable which is the PIN goes in this function together with the passphrase 'ovaederecumsale' and it is digested 10000 times using SHA1 algorithm.

The result from this function is then used to decode our encrypted string ''8QeNdEdkspV6+1I77SEEEF4aWs5dl/auahJ46MMufkg="

The result of the decryption should be a string that is then passed on as the GET variable 'p' on the URL<password>

And that should give us the egg.

My first attempt was to brute force the PIN using a python script that would try each combination.But since we have some custom encryption methods here, I couldn't exactly replicate the same functionality. So I decided to develop a Java script instead since I already had all the code I needed to perform the brute force.

The idea is to try every combination of 4 digits PIN and if the decryption is something readable, output it.

Here's my final solution:
import javax.crypto.spec.*;
import javax.crypto.*;
import java.lang.*;
import java.util.*;
import java.util.Base64;

public class HelloWorld{
     public static void main(String []args){
        for(int i=0;i<=9999;i++){
            String s = String.format("%4d", i);
                //System.out.println("Trying key " + s);
                final SecretKeySpec secretKeySpec = new SecretKeySpec(a(s, "ovaederecumsale", 10000), "AES");
                final Cipher instance = Cipher.getInstance("AES");
                instance.init(2, secretKeySpec);
                Base64.Decoder b64dec = Base64.getDecoder();
                final String s2 = new String(instance.doFinal(b64dec.decode("8QeNdEdkspV6+1I77SEEEF4aWs5dl/auahJ46MMufkg=")));
                System.out.println("Key: " + s + " - Found possible key: " + s2);
            }catch(Exception e){
                // Errors regarding encryption should be caught here...
                //System.out.println("Error: " + e.getMessage());
    public static byte[] a(final String s, final String s2, final int n) {
        final byte[] array2 = new byte[16];
        final MessageDigest instance = MessageDigest.getInstance("SHA1");
        byte[] array = (String.valueOf(s2) + s).getBytes();
        for (int i = 0; i < n; ++i) {
            array = instance.digest(array);
        System.arraycopy(array, 0, array2, 0, 15);
        }catch(Exception e){
        return array2;

I used Coding Ground's platform again to run my code. Kudos for TutorialsPoint for putting together this awesome platform.

I ran my code and after some seconds I caught this on my output:

Most of the matches are just gibberish, but... If you check key 7113 we got a very nice readable english passphrase: wirestarter54321

That's it! We got everything we need. We can now head on to the URL and get our egg:

EGG #27 - Too Many Time Pad

Final challenge! The challenge says:
You intercepted messages exchanged by evil Dr. Hopper and his agents. They used a One Time Pad for achieving perfect secrecy. Lucky for you, they have miserably failed, since the same key was used multiple times.
Check out the ciphertexts, and try to decrypt them. Hint: The plain texts consist of lowercase letters and spaces only.


Many time pad... This wasn't the first time that I was confronted with this problem. For those of you who checked out the Coursera's Cryptography course by Dan Boneh would find this challenge very familiar.

I wasn't able to crack the exercises on the course before but I understood what needed to be done.
There are two ways to solve this kind of problem: One of them is called "crib dragging" technique. It consists of trying to XOR a common string like the english word 'the' with the result of the XOR between two of the ciphers. If the result is readable, it means that you probably found a piece of the key. You do this process by "dragging" your crib (the common word) along the cipher until you find a readable output. The other way to solve it is to, based on some assumptions, brute force the key.

The crib-dragging process is quite hard to implement with code in a way to be fully automatic because then you would need to pick the readable text which could be a part of another word and guess which words match it and then repeat that process over and over...
I tried to go with this approached but abandoned it once I realized how difficult it was.

So I used another process... I tried to crack the ciphers by brute forcing the key, byte by byte using all the full ASCII range.

It works something like this:
We know that Cipher = Message xor Key => C = M xor K

Since we know that the M's are consisted of lowercase plain text and spaces only, we can try guessing K and M by trying every possible combination of XORs between them
Once we found a M byte that when XORed with a K byte gives our known C byte, we try the same K byte on all the other ciphers. If it decodes all the ciphers than it's a candidate for the key.
For each byte of K our keyspace is 255. For each byte of M, we only have 27 possible characters (lowercase alphabet + space). So the whole keyspace is not that big from a computational point of view.

I've spent a lot of hours to develop the automation for this process, but i managed to pull off a very nice script which I'm proud of. The challenge for myself was to automate as much as possible the whole decoding process.

The first phase of the script it will try to brute force the key with the logic described above. The second phase is to try to fill the gaps automatically using an english dictionary and try all the words that are possible candidates for a specific position. The third phase is asking the user for words that it couldn't uniquely decipher.  If the user doesn't have a good guess, he can skip that word and proceed to the next one.

Here's the output of my script:

The only interactions by the user in my script is when it asks the user for the word 'bonbon':
Decoded cipher so far: enemy has the bo____
Found matching words:
bodach, bodega, bodger, bogans, bogard, bogart, bogeys, bogged, boggle, bohawn, bohunk, boites, bokard, bokark, bolded, bolden, bolder, boldly, bolero, bolete, boleti, bolted, boltel, bolter, bombed, bomber, bombes, bombyx, bonaci, bonagh, bonang, bonbon, bonded, bonder, bonduc, boners, bonged, bongos, boxers, boxtop, boyang, boyard, boyars, boydom, Enter word:  

Since the word isn't clear, the user can press enter to get the next word.
There are 6 valid words. Choose the one that makes more sense:
Decoded cipher so far: i wear a black h__
Found matching words:
has, hat, hau, haw, hub, hut, Enter word: 

At this point we can make an educated guess that the last word is "hat" to form the phrase "i wear a black hat".
When the user inputs "hat", the script automatically solves the rest of the ciphers.

Just two simple interactions from the user to break the Many Time Pad :)

The final decoded plaintexts are then:
enemy has the bonbon
five oh oh seven
mr bunny is the spy
i wear a black hat
the hq is in london

The last plain looks particulary interesting because it looks like a passphrase. Indeed when attempting to use it on the Egg-O-Matic gives us the final QR code:

Thanks Hacking-Lab team for pulling off another aweomse and creative CTF. It's always fun to solve these kind of challenges ;)

No comments:

Post a Comment