HackyEaster 2017 write-up

Easter 2017 – means new HackyEaster challenges are online. The challenges were easier than the ones on Hackvent 2016. For HackyEaster all challenges are released at once and it does not matter in what time-frame the challenges are solved, this makes the CTF much less stressful than Hackvent. I solved my last challenge on April 16th at 01:24 AM and completed the CTF after eleven others did before me. Here is a screenshot of the ranking at the time I finished the last challenge.

After the competition ended in total 53 hackers solved all challenges and got the full points.

01 – Puzzle this!

Author: PS
Level: easy
Solutions: 882
An easy one to start with.


You could actually play the game by clicking on the fields. If you were able to solve it the QR code was revealed. I started to play the game but in the end finalized the QR code with gimp, as I was faster this way. 🙂

02 – Lots of Dots

Author: PS
Level: easy
Solutions: 647
The dots in the following image contain a secret message. Can you find it?
(click to enlarge)


While examining the picture in Gimp I recognized that the orange color has slight different specifications. The bigger dots are RGB 232/178/97 and the small ones RGB 232/178/98! To solve this challenge I selected one of the colors with the pipette and filled the background with it. Then I used the “fuzzy select tool” to select a region based on the color. I just clicked somewhere inside the picture and the code was revealed: 70 57 49 36 13 22 8 42

03 – Favorite Letters

Author: Goo9ping
Level: easy
Solutions: 802
Francesca’s favourite letter is s
Riley’s favourite letter is o
Ellie’s favourite letter is a
Vince’s favourite letter is p
Quintain’s favourite letter is r
Otto’s favourite letter is i
David’s favourite letter is p
Tom’s favourite letter is l
Paul’s favourite letter is e
Ulrich’s favourite letter is y
Henry’s favourite letter is w
Norman’s favourite letter is h
Louis’ favourite letter is i
Zane’s favourite letter is s
York’s favourite letter is c
Bob’s favourite letter is h
Meave’s favourite letter is s
Ian’s favourite letter is o
Sidney’s favourite letter is g
George’s favourite letter is s
Kitty’s favourite letter is d
Wilbert’s favourite letter is h
Adam’s favourite letter is t
Xander’s favourite letter is i
Callum’s favourite letter is e
Jack’s favourite letter is r


This challenge can be solved with bash in 1 line:

$ sort file.txt | grep -o '.$' | tr -d '\012\015'

04 – Cool Car (mobile)

Author: PS
Level: easy
Solutions: 481


I downloaded and decompiled the APK. The sensors are somehow used to change the graph in the mobile app. While browsing through the source code files I found interesting code-parts in two different files:

if (l >= 1000.0d) {
    k = sha1("file:///android_asset/www/index.html");
this.appView.loadUrl("javascript:sensorFeedback('{\"k\": \"" + k + "\", \"l\": \"" + l + "\"}')");


if (jsonResp.k) {

This means ‘k’ is the key to decrypt the scrambled egg, and ‘k’ is nothing else than the sha1 sum of the string “file:///android_asset/www/index.html”. I rebuilt the java-script function which is used to decrypt the scrambled egg and got the QR code.

<script type="text/javascript" src="js/crypto-js/aes.js"></script>
<script type="text/javascript" src="js/crypto-js/sha1.js"></script>
<script type="text/javascript" src="js/crypto-js/core-min.js"></script>
<script type="text/javascript" src="js/crypto-js/enc-base64-min.js"></script>
<img class="eggImage" id="scrambledEggImage" /><br />
scrambledEggCipher = 'U2FsdGVkX1+KZ3l0MlF......<truncated>......BCA5nYgqRIl7iA==';
function decryptScrambledEggWithKey(key) {
	var decrypted = CryptoJS.AES.decrypt(scrambledEggCipher, key);
	var fin = 'data:image/png;base64,' + CryptoJS.enc.Latin1.stringify(decrypted);
							+ CryptoJS.enc.Latin1.stringify(decrypted));

This html page then reveals the QR code:

05 – Key Strokes

Author: PS
Level: easy
Solutions: 532

esc i c e l a n d esc a y a n k e e space f o x
space esc o f l o w e r up esc $ esc i y esc e esc a
y esc / l a return esc r w esc right right right
right esc x i f r esc e esc X x x : s / c e / a g i
c / return esc down d d esc i m esc Z Z


This one took me some time until I found out what to do with it. I first thought it’s a log from a keylogger. After thinking about where this could make sense, I finally figured out it is from the editor VI! After typing it exactly the way described I got the password: magicwandfrankfoxy.

06 – Message to Ken

Author: PS
Level: easy
Solutions: 460
Barbie has written a secret message for her sweetheart Ken. Can you decrypt it?

Fabrgal JaeM Hsa faonah uiff;rnl tf btuxbrffuinhzoroyhitbM Fincta dd



I had no clue what to do here. After googling for “barby encryption” I came across this interesting link: Barbie typewriter encoding! It even has an encoder on the website which works. The decoded string is:
Beloved Ken. The secret password is lipglosspartycocktail. Barbie xx

07 – Crypto for Rookies

Author: PS
Level: easy
Solutions: 458
This crypto is not hard to crack.


– B O N T B B O K –> http://www.dcode.fr/dancing-men-cipher
– B O N T E A O K –> Base64 encoded
– B O N T E B R K –> Index of the alphabet
– B A N T E B O K –> Rot13
– C O N T E B O K –> http://www.pruzkumnik.cz/praxe/sifry/tabulky.html
– B O N T E B O A –> Reverse string
– B O P T E B O K –> Caesar (rot 3)
– B O N Y E B O K –> Character codes
After having decrypted all words we need to get the final password. If we keep the format exactly as in the picture and look at the columns and rows we see that in each column one character is different than the others. If we take all different characters from left to right we get the final password: CAPYBARA

08 – Snd Mny (mobile)

Author: PS
Level: easy
Solutions: 330


Not much information here. I again worked with the decompiled APK and went through the code. In one java class I found what was needed to solve this challenge.

if ("android.intent.action.SEND".equals(action) && type != null && HTTP.PLAIN_TEXT_TYPE.equals(type)) {
    String text = intent.getStringExtra("android.intent.extra.TEXT");
    if (text != null && "c95259de1fd719814daef8f1dc4bd64f9d885ff0".equals(sha1(text.toLowerCase()))) {
        ((TextView) findViewById(C0085R.id.sndTextView)).setText("Thank you!!");

We need to send an android action.SEND intent as PLAIN_TEXT_TYPE containing a text which matches the sha1 hash “c95259de1fd719814daef8f1dc4bd64f9d885ff0”. Cracking the sha1 hash was easy as no salt was used. The needed text is “money”.
We can send Android intents from the command line with ADB:

am [start|instrument]
am start [-a <action>] [-d ]
[-t <mime_type>] [-c <category> [-c <category>] ...]
[-e <extra_key> <extra_value>
[-e <extra_key> <extra_value> ...]
[-n <component>] [-D] [<uri>]
am instrument [-e <arg_name> <arg_value>] [-p <prof_file>] [-w] <component>

I solved this challenge with the following two commands:

$ adb shell
$ am start -a android.intent.action.SEND -t "text/plain" -e android.intent.extra.TEXT "money"

I over-engineered the solution for this challenge a bit, as it would have been possible to just share the text “money” with the app in Android to solve it. 🙂

09 – Microscope (mobile)

Author: PS
Level: easy
Solutions: 414


Another mobile challenge, again I worked with the decompiled APK.

webview.loadUrl("https://hackyeaster.hacking-lab.com/hackyeaster/challenge09_su6z47IoTT7.html".replace('6', '5'));

The URL gets changed because there is “.replace(‘6’, ‘5’)” in the code. The QR Code is embedded in the website https://hackyeaster.hacking-lab.com/hackyeaster/challenge09_su5z47IoTT7.html and if we look at the HTML source code we can see the image is loaded from https://hackyeaster.hacking-lab.com/hackyeaster/images/challenge/egg09_fs0sYle2SN.png.

10 – An egg or not …

Author: inik
Level: medium
Solutions: 233
… an egg, that’s the question!
Are you able to answer this question and find the (real) egg?


Analyzing the SVG shows that there are duplicate coordinate declarations! In SVGs the last of the duplicated coordinates will be used, as it just overwrites existing locations with the new declarations. To solve this challenge, I wrote a python script which ignores duplicates if there is already a declaration for the coordinates.

from collections import Counter
FILE = "./aneggornot.svg"
SOLUTION = "./solution.svg"
content = []
    in the original svg file duplicate coordinate declarations are found!
    in SVG files the last coordinates defined in the file will be used,
    therefore in the original file duplicated lines at the end of the file will be used.
    this challenge is solved by removing duplicate lines which come later, this will reveal the new QR code
with open(FILE) as f:
    for i, line in enumerate(f):
        item = line.split(" ")
        fnd = False
        for c, x in enumerate(content):
            if len(x) == 4 and len(item) == 4\
                    and item[1] == x[1] and item[2] == x[2]:
                print("[+] Duplicate found - Don't use it in solution")
                fnd = True
        if not fnd:
solution = open(SOLUTION, 'w')
for line in content:
    for x in line:
        solution.write("%s " % x)

Finally the solution looks like this:

11 – Tweaked Tweet (mobile)

Author: PS
Level: medium
Solutions: 108


This was the last mobile challenge. The only useful part I extracted from the APK was:

startActivity(new Intent("android.intent.action.VIEW", Uri.parse("https://twitter.com/intent/tweet?text=%23%EF%BC%A8a%EF%BD%83%EF%BD%8By%CE%95%EF%BD%81ste%EF%BD%92%E2%80%A9201%EF%BC%97%E2%80%A9%E2%85%B0%EF%BD%93%E2%80%80a%E2%80%84l%EF%BD%8F%EF%BD%94%E2%80%80%CE%BFf%E2%80%89%EF%BD%86un%EF%BC%81%E2%80%A8%23%D1%81tf%E2%80%88%23%EF%BD%88%EF%BD%81%CF%B2king-lab")));

This one almost drove my crazy!
I tried to find any suspicious pattern, like to identify standard ASCII characters and extended ASCII characters, mapping them to 0s and 1s, exclude all extended ones, exclude all standard ones, etc. In the end I was identifying characters which were used as some Unicode combinations instead of the standard, most simple way. But I did not come to any solution.. After wasting hours on this one I just googled for “Twitter steganography”. First link http://holloway.co.nz/steg/ hosts a converter which can be used to decode our message!!!
If we enter our decoded string “#HackyΕaster
ⅰs a lot οf fun!
#сtf #haϲking-lab” into the decoder on the website, we get the result: st3g4isfunyo.
Stego is no fun yo 🙁

12 – Once Upon a File

Author: inik
Level: medium
Solutions: 252
Once upon a file there was a hidden egg. It’s still waiting to be saved by a noble prince or princess.


‘binwalk’ is a tool to identify header information within a file. If multiple files are hidden in one single file, ‘binwalk’ can identify and automatically extract these files. With ‘binwalk’ this challenge is pretty easy to solve.

$ unzip 12_onceupon.zip
Archive:  12_onceupon.zip
  inflating: file
$ binwalk -e file
36447         0x8E5F          Unix path: /0/1/2/3/4/5/6/7/8/9/:/;/</=/>/?/@/A/B/C/D/E/F/G/H/I/J/K/L/M/N/O/P/Q/R/S/T/U/V/W/X/Y/Z/[/\/]/^/_/`/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o
184320        0x2D000         Zip archive data, at least v2.0 to extract, compressed size: 439156, uncompressed size: 5242880, name: file
623596        0x983EC         End of Zip archive
$ cd _file.extracted/
$ ls
2D000.zip  file
$ binwalk -e file
36447         0x8E5F          Unix path: /0/1/2/3/4/5/6/7/8/9/:/;/</=/>/?/@/A/B/C/D/E/F/G/H/I/J/K/L/M/N/O/P/Q/R/S/T/U/V/W/X/Y/Z/[/\/]/^/_/`/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o
1093632       0x10B000        Microsoft Cabinet archive data, 17834 bytes, 1 file
2832320       0x2B37C0        Microsoft Cabinet archive data, 17834 bytes, 1 file
3116030       0x2F8BFE        Microsoft executable, MS-DOS
3788479       0x39CEBF        mcrypt 2.2 encrypted data, algorithm: blowfish-448, mode: CBC, keymode: 8bit
3793983       0x39E43F        mcrypt 2.2 encrypted data, algorithm: blowfish-448, mode: CBC, keymode: 8bit
4477995       0x44542B        mcrypt 2.2 encrypted data, algorithm: blowfish-448, mode: CBC, keymode: SHA-1 hash
5073287       0x4D6987        mcrypt 2.2 encrypted data, algorithm: blowfish-448, mode: CBC, keymode: 8bit
5075359       0x4D719F        mcrypt 2.2 encrypted data, algorithm: blowfish-448, mode: CBC, keymode: 8bit
5173248       0x4EF000        PNG image, 480 x 480, 8-bit colormap, non-interlaced
5173767       0x4EF207        Zlib compressed data, best compression
$ cd _file.extracted/
$ ls
 10B000.cab   2B37C0.cab   4EF207   4EF207.zlib   egg12.png  'eg'$'\t''Z2.png'

The QR code is in the file ‘egg12.png’.

13 – Lost the Thread

Author: CoderKiwi
Level: medium
Solutions: 126
Searching for eggs is fun! But sometimes they come in weird shapes and sizes. Download the image and wind up the strand!


If we open the png with Gimp we can identify a pattern with two different shapes.

On first sight it looks like Morse-Code, but as there are only two different shapes it must be bits.
I wrote a python script which reads the pixels of the image. If a line is complete without interruption it is a 1. If the line is interrupted at the beginning by a transparent pixel it is a 0.

from PIL import Image
im = Image.open('thread.png', 'r')
pixel_values = list(im.getdata())
result = ""
for c,x in enumerate(pixel_values):
    if c > 2 and pixel_values[c-2][3] == 255 or pixel_values[c-1][3] == 255:
    if pixel_values[c][3] == 255 and pixel_values[c+1][3] == 0:
        result += "0"
    if pixel_values[c][3] == 255 and pixel_values[c+1][3] == 255:
        result += "1"
print result
$ python2 solution.py

The result string looks like it contains ASCII art. I replace all the ‘0’ with ‘.’ and all the ‘1’ with Unicode blocks ‘█’. If we play a bit with the editor, resize the window, we get the QR.

We scan the QR code we get the password ‘kiwisarekewl‘.

14 – Shards

Author: PS
Level: medium
Solutions: 252
Oh no! What a mess!


We unzip the file and get 1600 files.

$ ls | wc -l
$ ls -u | head -10

After trying out different ways of sorting I found the right way to do it:

|0| |1||2|    |3|    |4|
Sort by 2 (a..zA..Z), 4, 3, 1

I wrote a script in Python which sorts all the images the right way, creates a new image and combines all the shards in the right way to get the final image.

import sys
from PIL import Image
import glob
files = glob.glob('./*.png')
splitted = []
for x in files:
    x = x.replace(".png", "_png")
    |0| |1||2|    |3|    |4|
    Sort by 2 (a..zA..Z), 4, 3, 1
sorted_files = sorted(splitted, key=lambda x: (x[2].swapcase(), int(x[4]), int(x[3]), int(x[1])))
new_im = Image.new('RGB', (40*12,40*12))
counter = 0
while counter < 40:
    sublist = []
    x = 0
    while x < 40:
        c = (counter * 40)+x
        fname = sorted_files[c][0]+"_"+sorted_files[c][1]+"_"+sorted_files[c][2]+"_"+sorted_files[c][3]+"_"+sorted_files[c][4]+"."+sorted_files[c][5]
        x += 1
    for x in sublist:
        print x
    print "------"
    images = map(Image.open, sublist)
    x_offset = 0
    for im in images:
        y_offset = counter * im.size[0]
        new_im.paste(im, (x_offset, y_offset))
        x_offset += im.size[0]


15 – P Cap

Author: PS
Level: medium
Solutions: 181
What about a little P cap?


I started analyzing the file with Wireshark. I found various interesting things inside the PCAPNG file. I landed on a suspicious polish forum but the traffic was encrypted and I didn’t find a key to decrypt the traffic. Then I analyzed the DNS queries and found a radio streaming service, but this was a dead end as well.
Finally I focussed on the SMB traffic. I used the function “Follow TCP” stream in Wireshark, selected only the incoming traffic and raw format. This way I could save the incoming SMB traffic to a file. From now on I could work with my favorite forensic tool ‘binwalk’. 🙂

$ binwalk -e smb.bin
8157          0x1FDD          JPEG image data, JFIF standard 1.01
8187          0x1FFB          TIFF image data, big-endian, offset of first image directory: 8
13244         0x33BC          JPEG image data, JFIF standard 1.01
13274         0x33DA          TIFF image data, big-endian, offset of first image directory: 8
113880        0x1BCD8         Zip archive data, at least v2.0 to extract, compressed size: 1169, uncompressed size: 1379, name: imnothere.txt
115187        0x1C1F3         End of Zip archive
$ cd _smb.bin.extracted/
$ ls
1BCD8.zip  imnothere.txt

A file called “imnothere.txt” is really suspicious! Again, using ‘binwalk’ on it shows the txt file is a JPEG image and not a text file.

$ binwalk imnothere.txt
0             0x0             JPEG image data, JFIF standard 1.01
30            0x1E            TIFF image data, big-endian, offset of first image directory: 8

I simply rename the file to imnothere.jpg and this is the result:

Now comes the confusing part! I tried to find anything related to this php file inside the PCAP file but didn’t succeed. It was a dead end and I tried to find other things inside the PCAP, but didn’t manage to find any… I stopped working on this challenge for some days and then tried again. I came to the conclusion that the really suspicious file imnothere.txt must be something. And finally tried to use this php path on the hackyeaster site:
This page reveals the needed QR code!

I didn’t like this challenge at all, because the end of the challenge was really irritating. Having a PCAP file and finding a path to a PHP file somehow leads to the conclusion that there must be more inside the PCAP file. It was really far fetched to try the path on the main HackyEaster website. I wasted way too much time on this one.

16 – Pathfinder

Author: MaMe82
Level: medium
Solutions: 181
Can you find the right path?



This challenge was a really nice one! Somehow the description of the challenge leads to the conclusion to connect to the server with netcat or telnet. But this does not return any response. Using nmap on the port 9999 reveals that it’s running an ‘abyss’ webserver. We can use ‘curl’ to connect to it.

$ nmap -p 9999 hackyeaster.hacking-lab.com
Starting Nmap 7.40 ( https://nmap.org ) at 2017-04-23 14:41 CEST
Nmap scan report for hackyeaster.hacking-lab.com (
Host is up (0.010s latency).
rDNS record for urb80-74-140-117.ch-meta.net
9999/tcp open  abyss
Nmap done: 1 IP address (1 host up) scanned in 0.08 seconds
$ curl hackyeaster.hacking-lab.com:9999
{"Answer":"I only talk to PathFinder!"}

No matter what I tried, the webserver always returned the same response. After trying to send different things to webserver I tried to change the UserAgent to ‘PathFinder’ and it worked!

$ curl -A "PathFinder" hackyeaster.hacking-lab.com:9999
{"Answer":"Follow one of the possible paths","paths":[1,3,5,8]}
$ curl -A "PathFinder" hackyeaster.hacking-lab.com:9999/1
{"Answer":"Go on! Follow one of the possible paths","paths":[5]}
$ curl -A "PathFinder" hackyeaster.hacking-lab.com:9999/1/5
{"Answer":"You've left the path!"}

It looks like we have to call all the possible path combinations. This calls for a recursive solution! My python script to solve this challenge looks as follows:

import urllib2
import json
import time
host = "http://hackyeaster.hacking-lab.com"
port = "9999"
opener = urllib2.build_opener()
opener.addheaders = [('User-Agent', 'PathFinder')]
finish = False
def solve(path):
    global finish
    if finish:
    u = host+":"+port+"/"+path
    print("[+] Call URL: " + u)
    resp = opener.open(u).read()
    j = json.loads(resp)
    if j['Answer'].startswith("This"):
        print("[!] Dead End...")
    elif j['Answer'].startswith("Thanks"):
        print("[!!!] FOUND [!!!]")
        print resp
        finish = True
    if "paths" in j:
        for x in j['paths']:

It turns out we have to bruteforce a Sudoku game. Nice! 🙂

[+] Call URL: http://hackyeaster.hacking-lab.com:9999/157294683269358174843716529496583712528971346731642895972135468685427931314869257
[!!!] FOUND [!!!]
{"Answer":"Thanks PathFinder you saved my life by giving me the solution to this sudoku!","sudoku":[[0,0,0,2,0,4,6,0,0],[2,0,9,0,0,0,0,0,0],[0,0,0,0,0,6,5,0,0],[0,0,6,5,0,0,7,1,0],[0,0,0,9,0,0,0,4,0],[7,3,1,0,0,0,0,0,0],[0,7,0,0,3,0,0,0,8],[0,8,0,0,2,7,0,3,1],[0,1,4,0,6,0,0,0,0]],"your_solution":[[1,5,7,2,9,4,6,8,3],[2,6,9,3,5,8,1,7,4],[8,4,3,7,1,6,5,2,9],[4,9,6,5,8,3,7,1,2],[5,2,8,9,7,1,3,4,6],[7,3,1,6,4,2,8,9,5],[9,7,2,1,3,5,4,6,8],[6,8,5,4,2,7,9,3,1],[3,1,4,8,6,9,2,5,7]],"Secret":"https://hackyeaster.hacking-lab.com/hackyeaster/images/challenge/egg16_UYgXzJqpfc.png"}

The link https://hackyeaster.hacking-lab.com/hackyeaster/images/challenge/egg16_UYgXzJqpfc.png reveals the QR code.

17 – Monster Party

Author: otaku
Level: medium
Solutions: 75
The monsters do have a big party, jumping around like fools.
Each of them has its own jump-pattern. When two or more meet on a field, they are happy to see each other, but continue hopping. Passing the border on either side makes them appear again on the opposite side.

Make the monsters jump, and they will reveal you a secret!


This challenge looked like there would be a lot of coding effort to solve it. But fortunately looking at the source code reveals, that the board was created by using Javascript. Many parts can be reused.
I implemented a jump button to simulate the jumps of the monsters. I also added a second table where I colorize all fields with at least one monster black. But somehow it never revealed a QR code. Apparently it wasn’t as straight forward as I first thought. After reading the challenge description again I noticed that there is no information about the starting conditions, it’s not mentioned if the monsters already did a jump or not. What means the first jump the monsters do could be another one as I implemented. I added an offset of 1 and then the QR code was revealed after 72 jumps!
My implementation can be found here.

18 – Nitwit’s Doormat Key

Author: CoderKiwi
Level: medium
Solutions: 267
Being sure that no one can read the obfuscated code, bunny Nitwit has hidden the egg behind his login-page.

Find out the username and password to show that he lives up to his own name!


We can actually debug this. I first saved the page locally and added the ‘debugger’ keyword to the end of the script. Then I opened the page with ‘firedebug’ and set a breakpoint at the end of the script where the ‘debugger’ keyword is.
The Javascript code is heavily obfuscated and encrypted, but as Javascript runs on the client side, the final source which is executed must be revealed before execution. My breakpoint in ‘firedebug’ revealed this:

window.addEventListener("load", init, false);
function init() {
  document.getElementById("sub").addEventListener("click", logMeInScotty, false);
function sendRequest(url, cb) {
  var ll = new XMLHttpRequest();
  ll.onreadystatechange = function() {
    if (ll.readyState == 4 && ll.status == 200) {
      var l1 = ll.responseText;
  ll.open("GET", url, true);
function logMeInScotty() {
  var lI = document.getElementById("uzr").value;
  var l1l = document.getElementById("puzzwerd").value;
  if (lI.length == 12 && (lI[0] == "b") && (lI.charCodeAt(0) == lI.charCodeAt(1) - 19) && (String.fromCharCode(lI.charCodeAt(3) & 0x7F) == "n") && (lI[3] == lI[2]) && (lI.charCodeAt(4) == lI.charCodeAt(1) + lI[7] * 1) && (lI[5] == "X!&)=" [0]) && (lI[6] == String.fromCharCode(109)) && (lI[7] == (1 << 2)) && (lI[8] == "s") && (lI.charCodeAt(8) == lI.charCodeAt(9) - 1) && (lI[10] == lI[7] - 1) && (lI[11] == String.fromCharCode(114))) {
    if (l1l == magic(lI)) {
      dataUrl = 'https:' + String.fromCharCode(47, 47) + 'hackyeaster.hacking-lab.com/hackyeaster/files/' + lI + l1l + '.txt';
      sendRequest(dataUrl, function(lIl) {
      document.getElementById("egg").src = "data:image/png;base64," + lIl;
  } else {
      alert("Haha wrong password!");
  } else {
    alert("Haha wrong username!");
function magic(str) {
  var l11 = "";
  for (var l1I = str.length - 1; l1I >= 0; l1I--) {
    if (l1I > 5) {
      l11 += moreMagic(str[l1I]);
    } else {
      l11 = moreMagic(str[l1I]) + l11;
  return l11;
function moreMagic(c) {
  return String.fromCharCode(c.charCodeAt(0) + 1);

Finding out the username was much harder than the password. But with some manual work I eventually got it:

01. 98      = b
02. 98 + 19 = 117 = u
03. n
04. n = 110d = x & 0x7f
   1101110b = x & 1111111b
   1101110b = n
05. 117 + 4 * 1 = 121 = y
06. "X!&)="[0] = X
07. 109 -> m
08. 1 << 2 == 4
09. s
10. t
11. 4-1 = 3
12. r
--> bunnyXm4st3r

Getting the password was much easier, as we could just run the magic function with the username! The password is cvoozYs4ut5n. The egg with the QR code is then downloaded from this URL https://hackyeaster.hacking-lab.com/hackyeaster/files/bunnyXm4st3rcvoozYs4ut5n.txt.

19 – Disco Time

Author: DeathsPirate
Level: hard
Solutions: 139
Disco time!


This is the first challenge rated as hard.
First step with gifs is always to look at the single frames. The gifs with the cats didn’t reveal anything interesting. But disco2.gif did.

$ convert *.gif frames/out.png
$ ls -u frames/ | head -10

If we look at the frames folder, we can see a lot of red and white images. Looks like a pattern. We have 31 pictures before the color changes, this seems important.
I tried to combine the frames to a new picture with ‘montage’. ‘montage’ takes the pictures by name and orders them from top left corner to the top right corner, then it goes to the next line and so on.
I used montage this way:

montage frames/*.png -geometry 48x48+1+1 -tile 31x200 result.png

This takes all the pictures from the frames folder and combines it into result.png. Result.png is 48×48 pixels and the boarders between the frames are 1 pixel. It takes 31 pictures per row and then goes to the next row. I had to do some trial and error to find the right values.

After rotating the picture and flipping it vertically I got this picture: 
It’s a bit hard to read, but the codeword is “PixelPixiesArePractical“.
Helpful Links:

20 – Spaghetti Hash

Author: PS
Level: hard
Solutions: 162
Lazy Larry needs to improve the security of his password hashing implementation. He decides to use SHA-512 as a new hashing algorithm in order to be super secure. Unfortunately, the database column for the hash can only hold 128 bit. As Bob is too lazy to extend the column and all the code related to it, he decides to shrink the output of the SHA-512 operation, to 128 bit. For this purpose he picks certain characters from the SHA-512 output for producing the new value.
You got hold of four password hashes, calculated with Bob’s new implementation. Can you find the corresponding passwords?

 hash 1: 87017a3ffc7bdd5dc5d5c9c348ca21c5
 hash 2: ff17891414f7d15aa4719689c44ea039
 hash 3: 5b9ea4569ad68b85c7230321ecda3780
 hash 4: 6ad211c3f933df6e5569adf21d261637

Lucky you, you know that the following web service is calculating Bob’s algorithm. However, the web service only accepts strings of length 4 or less – brute-forcing a password list thus is no option, since the passwords you are looking for are all longer.



The hardest part of this was to find out how the hash was shortened. I wrote a python script to solve this challenge.
First I use the webservice on hackyeaster.hacking-lab.com to find out which elements of the sha512 hash were taken into the shortened hash. To accomplish this i generate random 4-letter strings, calculate the sha512 hash and get the shortened hash from the webservice. I take the first element from the shortened hash and get the indices of all the matching elements in the sha512 hash. I repeat this with the next hash and only keep the indices which were there before – until I only have 1 left. Afterwards I move to the next element and repeat. This way I get get all exact positions which are used from the sha512 hash.
The second step was to bruteforce the passwords. Luckily no salt was used to hash the passwords, so I downloaded a wordlist with the top passwords and used it to bruteforce. I calculate the sha512 hashes of the passwords and whenever all elements of the shortened hash match with the positions in the sha512 hash I have a password.
Here is the full python script:

import hashlib
import urllib2
import random, string
url = "https://hackyeaster.hacking-lab.com/hackyeaster/hash?string="
def call_page(string):
    resp = urllib2.urlopen(url+string).read()
    return resp+"\n"
def random_str():
   return ''.join(random.choice(string.lowercase) for i in range(4))
def sha_512(st):
    hash_object = hashlib.sha512(st)
    hex_dig = hash_object.hexdigest()
    return hex_dig
def dict_attack(hsh, positions):
        words = open("../22/10_million_password_list_top_1000000.txt", 'r')
        print '[-] Error: Check your wordlist path.\n'
    words = words.readlines()
    for word in words:
        wrd = word.replace('\n', '').replace('\r', '')
        hash = hashlib.sha512(wrd).hexdigest()
        bob_hash = "";
        for x in positions:
            bob_hash += str(hash[x])
        if hsh == bob_hash:
            print("[+] Found! :)\n" + bob_hash + " == " + wrd)
            return wrd
print("[+] Find position pattern...")
positions = []
for i in range(32):
    indeces = []
    while len(indeces) != 1:
        st = random_str()
        sha = sha_512(st)
        hackysha = call_page(st)
        ind = [n for n, x in enumerate(sha) if x == hackysha[i]]
        if len(indeces) == 0:
            indeces = ind
            for n,x in enumerate(indeces):
                if x not in ind:
                    del indeces[n]
        #print indeces
    print("[+] Found position " + str(i+1) + ": " + str(indeces[0]))
print("[+] Found all positions: " + str(positions))
print("[+] Cracking passwords")
pw1 = dict_attack("87017a3ffc7bdd5dc5d5c9c348ca21c5", positions)
pw2 = dict_attack("ff17891414f7d15aa4719689c44ea039", positions)
pw3 = dict_attack("5b9ea4569ad68b85c7230321ecda3780", positions)
pw4 = dict_attack("6ad211c3f933df6e5569adf21d261637", positions)
print("[+] Done, got all passwords")

And this is how it looks like when executing the script:

$ python solution.py
[+] Find position pattern...
[+] Found position 1: 65
[+] Found position 2: 17
[+] Found position 3: 115
[+] Found position 4: 31
[+] Found position 5: 45
[+] Found position 6: 11
[+] Found position 7: 67
[+] Found position 8: 92
[+] Found position 9: 0
[+] Found position 10: 7
[+] Found position 11: 123
[+] Found position 12: 37
[+] Found position 13: 5
[+] Found position 14: 22
[+] Found position 15: 87
[+] Found position 16: 124
[+] Found position 17: 25
[+] Found position 18: 89
[+] Found position 19: 38
[+] Found position 20: 61
[+] Found position 21: 90
[+] Found position 22: 109
[+] Found position 23: 63
[+] Found position 24: 28
[+] Found position 25: 102
[+] Found position 26: 12
[+] Found position 27: 47
[+] Found position 28: 59
[+] Found position 29: 110
[+] Found position 30: 86
[+] Found position 31: 24
[+] Found position 32: 18
[+] Found all positions: [65, 17, 115, 31, 45, 11, 67, 92, 0, 7, 123, 37, 5, 22, 87, 124, 25, 89, 38, 61, 90, 109, 63, 28, 102, 12, 47, 59, 110, 86, 24, 18]
[+] Cracking passwords
[+] Found! :)
87017a3ffc7bdd5dc5d5c9c348ca21c5 == Prodigy
[+] Found! :)
ff17891414f7d15aa4719689c44ea039 == Cleveland
[+] Found! :)
5b9ea4569ad68b85c7230321ecda3780 == benchmark
[+] Found! :)
6ad211c3f933df6e5569adf21d261637 == 12345678
[+] Done, got all passwords

The passwords are ‘Prodigy‘, ‘Cleveland‘, ‘benchmark‘ and ‘12345678‘.

21 – MonKey

Author: PS
Level: hard
Solutions: 74
The monkey is laughing at you. Get the hidden egg from his binary.


This challenge was the hardest one for me.
First step was to rename the ipa file to zip and extract the content. There are a lot of interesting files but no QR code in sight. Running ‘strings’ on the Monkey binary reveals some information. We can see that the library CCCrypt is used, there are function named aesDecrypt and aesEncrypt, we can see an encrypted string which probably is our encrypted QR code. And then there are these two strings “thisIStheKEYyoyo” and “monkeyluv$Banana”. I was a bit disappointed at the first moment, I thought this was too easy! But I was wrong, the 2 strings didn’t work as keys no matter how I tried. It is time to use a disassembler. I used hopper to solve this task.
Most interesting function is onBtnPressed. I worked mostly with the generated pseudo code of hopper, which worked pretty well:

We can see in the else-part at the end of the function, the two strings “thisIStheKEYyoyo” and “monkeyluv$Banana” are used in a log output to display the nopeCat! Nice play PS, nice play! 😉
Proceeding with the reverse engineering I found this method call: r4 = [[NSString stringWithFormat:@”%@omo%@”, @”makybk”, @”oaenklo”] retain]; This method results in the string “makybkomooaenklo”. But that string does not work as key either.
So, I stepped backwards from where the key actually was used:

  • aesDecrypt, the key used is r5
  • r5 is r6 UTF8 decoded
  • r5 is equal r4, r4 is “makybkomooaenklo”
  • As r5 was assigned from r6 before, we have to follow r6
  • r6 was last changed in the function sub_a75c()
  • input to sub_a75c is the key entered in the app, output is r6

We have to look closer what exactly happens in the function sub_a75c()

If we simplify this method to something more readable and only take the needed values from the array ’27fbc’, we get this function:

void sub_a75c(int original_password, int result) {
    27fbc = [0x07, 0x09, 0x00, 0x0f, 0x08, 0x05, 0x01, 0x0a, 0x02, 0x04, 0x06, 0x0e, 0x0c, 0x0d, 0x03, 0x0b, 0x00]
    x = 0;
    while (x < 0x10) {
        result[x] = original_password + 27fbc[x];

We have a password with the length of 16 characters. In this method the order of the characters of our password is chosen and a new string resulting in ‘makybkomooaenklo’ is created. ‘int original_password’ is a pointer to the address of the first element/character of the password used in the app and to it an offset from the array ’27fbc’ is added. All values inside ’27fbc’ are smaller than 0x10. Means that the ordering of the entered password is changed inside this function. In the app someone enters a password, this function changes the order of the characters and the result of it is the known string ‘makybkomooaenklo’. But the first typed password is used to decrypt the egg.

  • End ‘result’ is ‘makybkomooaenklo’
  • result[0] is address_of_first_character_of_our_password + 27fbc[0] (Which is 0x07)
  • result[0] is the character which is at position 0x07 of our password

We don’t know the entered key, but we know what it becomes after the function ‘sub_a75c’. Now we can reverse the function and get the key to decrypt the QR code!

sub_a75c = [0x07, 0x09, 0x00, 0x0f, 0x08, 0x05, 0x01, 0x0a, 0x02, 0x04, 0x06, 0x0e, 0x0c, 0x0d, 0x03, 0x0b, 0x00]
string = "makybkomooaenklo"
result = ""
for n in range(len(string)):
    for c,x in enumerate(sub_a75c):
        if x == n:
            result += string[c]
print("[+] Password found: '" + result + "'")

To reverse the string I loop over the lenght of ‘makybkomooaenklo’ and for each position (0..15) we check at what index in ‘sub_a75c’ this number is located. When we found the position, we know the position of the character in the ‘makybkomooaenklo’ string.
Running the function reveals the password ‘koolokambamonkey‘. With this key we can decrypt the encrypted image of the QR code.

22 – Game, Set and Hash

Author: PS
Level: hard
Solutions: 226
Can you beat the tennis master?



This one was pretty straight forward. You connect to the server, the server gives you a sha1 hash and you have to decrypt it. If you manage to do so in the given time it’s a point for you otherwise for the server. The points are counted the same way as a Tennis match is counted. The game is started by entering “y”.

$ nc hackyeaster.hacking-lab.com 8888
Ready for the game?
Let's go!
Wrong! Point for me.
Player > 0 0
Master   0 15

I first tried to solve it the same way as I solved PathFinder, with dictionary bruteforce. But apparently there is some logic behind, whenever the server is losing he uses stronger,combined passwords which are not in my wordlists and wins the game. Using this method the server always won 7-6,7-6,7-6. 😀

$ python solution.py
Player > 6 6 6 5
Master   7 7 6 6
Wrong! Point for me.
Player > 6 6 6
Master   7 7 7
You lose!

I solved it by using an API to crack the password hashes. I registered a free account on http://md5decrypt.net. This service was able to crack all password hashes and so I won the game. I think there is a more elegant solution to somehow trick the servers logic and win the game. But as I already had a working solution I was too lazy to look for another one. 😉
My python script:

import socket
import sys
import re
import hashlib
import urllib2
host = "hackyeaster.hacking-lab.com"
port = 8888
api_host = "http://md5decrypt.net/Api/api.php"
api_email = "[email protected]"
api_key = "YOUR_API_KEY_XXX"
def dict_attack(hsh):
        words = open("./10_million_password_list_top_100000.txt", 'r')
        print '[-] Error: Check your wordlist path.\n'
    words = words.readlines()
    for word in words:
        wrd = word.replace('\n', '').replace('\r', '')
        hash = hashlib.sha256(wrd).hexdigest()
        if hsh == hash:
            print "[+] Password is: " + wrd, "\n"
            return wrd+"\n"
    return "\n"
def api_call(hsh):
    url = api_host+"?hash="+hsh+"&hash_type=sha256"+"&email="+api_email+"&code="+api_key
    resp = urllib2.urlopen(url).read()
    print("[+] Password is: " + resp)
    return resp+"\n"
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
# Start the game
tm = s.recv(1024)
print("%s" % tm.decode('ascii'))
tm = s.recv(1024)
print("%s" % tm.decode('ascii'))
while True:
    tm = s.recv(1024)
    if not tm:
    print("%s" % tm.decode('ascii'))
    last_line = str(tm.decode('ascii').splitlines()[-1])
    # Check if the response is hash or text
        int(last_line, 16)
        # Not hex value
    print("-->" + last_line)
    #solution = dict_attack(last_line)
    solution = api_call(last_line)

Here is the output of running the script:

$ python solution.py
[+] Password is: rajat962
Correct! Point for you.
Player   6 6 5 40
Master > 0 0 0 0
[+] Password is: emilio3403
Correct! Point for you.
Player > 6 6 6
Master   0 0 0
You win! Solution is: !stan-the_marth0n$m4n

The password needed to get the points is “!stan-the_marth0n$m4n“.

23 – Lovely Vase

Author: PS
Level: hard
Solutions: 83
What a nice vase! Beautiful, don’t you think?



The image of the vase gave some hints how to get the passwords from the 3 strings. To solve this one you must know about Transposition Ciphers and Rail-Fence Ciphers. More information can be found on https://en.wikipedia.org/wiki/Transposition_cipher.
The top part of the vase reveals how to arrange the string of the first text and how to read it. It looks like a square. So I aligned the first string as a square 5×5.

We read the text the same way as the pattern shows on the vase and we start in the top left corner. It’s a bit tricky in the middle, but in the end the solution for the first string is: “the first part is adriane rick“.
The second pattern on the vase clearly shows a Rail-Fence cipher. I arranged the string this way:

Now again we start reading in the top left corner but follow the zig-zag stream. We get the sentence “the second part is susanna bob“.
For the last part the vase doesn’t help much. I could not read a pattern from the image. So I just aligned it like the first one 5×5. And because of the first two solutions I knew the sentence would start wiht “the third part is”. This made it a lot easier.

This time we start reading on the bottom left corner upwards. As soon as we reach the top, we continue on the second column upwards again. Solution of this one is: “the third part isclaire frank“.
Combine all the three names and we get the final password:

24 – Your Passport, please

Author: inik
Level: hard
Solutions: 93
After another exhausting Easter, Thumper decides to travel abroad for recreation. As a real h4x0r, he of course is using his own, homemade e-passport:

Write a client which connects to the virtual terminal, and fetch the portrait photo stored on Thumper’s passport! The virtual terminal is running on:


As a starting point for your client, the following eclipse project is provided:


On first sight this challenge looks pretty hard. I never heard about the jmrtd library nor anything about how to read out ePassports. But I know Java and with a bit of googling I was able to solve this challenge pretty fast.
Most information needed to get the image with JMRTD can be found in the documentation:

It was necessary to change localhost to the hackyeaster server in HE17Terminal.java. And the rest was all done in the Java class JMRTDMain.java.
Fist thing to do is to call the doBAC() function. This is used to do the basic access control. Normally you would have to do some authentication and provide a key for it. But in our implementation this is not necessary – we create a BACKeySpec object with the details of the passport to readout.
The passport image is saved in the DG2File. I’ve found this information in the documentations mentioned before:


public static final short EF_DG2
File identifier for data group 2. Data group 2 contains face image data.
See Also:
Constant Field Values

From the DG2File we can read out the face information with the function getFaceInfos() and from there we get the image with the function getImageInputStream().
Here is my implementation of the JMRTDMain class:

package ch.he17.epassclient;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import javax.smartcardio.CardException;
import javax.smartcardio.CardTerminal;
import org.jmrtd.BACKeySpec;
import org.jmrtd.PassportService;
import org.jmrtd.lds.LDSFileUtil;
import org.jmrtd.lds.icao.DG2File;
import org.jmrtd.lds.iso19794.FaceImageInfo;
import org.jmrtd.lds.iso19794.FaceInfo;
import ch.he17.epassclient.terminal.HE17Terminal;
import net.sf.scuba.smartcards.CardService;
import net.sf.scuba.smartcards.CardServiceException;
public class JMRTDMain {
	public static void main(String[] args) throws CardServiceException, CardException, IOException {
		CardTerminal cardTerminal = new HE17Terminal();
		CardService cService = CardService.getInstance(cardTerminal);
		PassportService passService = new PassportService(cService);
		 * P<HLAEASTERWOOD<<THUMPER<<<<<<<<<<<<<<<<<<<<
		 * P012345673HLA7707076M21010150000007<<<<<<<96
		BACKeySpec bacKey = new BACKeySpec() {
			private static final long serialVersionUID = 1L;
            public String getDocumentNumber() {
                return "P01234567";
            public String getDateOfBirth() {
                return "770707";
            public String getDateOfExpiry() {
                return "210101";
        InputStream is = passService.getInputStream(PassportService.EF_DG2);
        DG2File dg2File = (DG2File) LDSFileUtil.getLDSFile(PassportService.EF_DG2, is);
        List<FaceImageInfo> allFaceImageInfos = new ArrayList<>();
        List<FaceInfo> faceInfos = dg2File.getFaceInfos();
        for (FaceInfo faceInfo : faceInfos) {
        if (!allFaceImageInfos.isEmpty()) {
            FaceImageInfo faceImageInfo = allFaceImageInfos.iterator().next();
            int imageLength = faceImageInfo.getImageLength();
            DataInputStream dataInputStream = new DataInputStream(faceImageInfo.getImageInputStream());
            byte[] buffer = new byte[imageLength];
            dataInputStream.readFully(buffer, 0, imageLength);
            InputStream inputStream = new ByteArrayInputStream(buffer, 0, imageLength);
            Path destination = Paths.get("./result.png");
            Files.copy(inputStream, destination);

Running this Java program saves the image as result.png in the running folder.

Leave a Reply

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