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.
Solution
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)
Solution
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
Solution
This challenge can be solved with bash in 1 line:
$ sort file.txt | grep -o '.$' | tr -d '\012\015' thepasswordishieroglyphics
04 – Cool Car (mobile)
Author: PS
Level: easy
Solutions: 481
Solution
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:
ps/hacking/hackyeaster/android/Activity.java:
... if (l >= 1000.0d) { k = sha1("file:///android_asset/www/index.html"); } this.appView.loadUrl("javascript:sensorFeedback('{\"k\": \"" + k + "\", \"l\": \"" + l + "\"}')"); ...
assets/www/challenge04.html:
... if (jsonResp.k) { decryptScrambledEggWithKey(jsonResp.k); clearInterval(intervalId); } ...
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.
<html> <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 /> <script> scrambledEggCipher = 'U2FsdGVkX1+KZ3l0MlF......<truncated>......BCA5nYgqRIl7iA=='; decryptScrambledEggWithKey("d2d109036a07c1080a6e77e8063cebdc155f888b"); function decryptScrambledEggWithKey(key) { var decrypted = CryptoJS.AES.decrypt(scrambledEggCipher, key); var fin = 'data:image/png;base64,' + CryptoJS.enc.Latin1.stringify(decrypted); console.log(fin); document.getElementById('scrambledEggImage') .setAttribute( 'src', 'data:image/png;base64,' + CryptoJS.enc.Latin1.stringify(decrypted)); } </script> </html>
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
Solution
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
Hint:
Solution
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.
Solution
– 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
Solution
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.
ps/hacking/hackyeaster/android/SndActivity.java
... 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
Solution
Another mobile challenge, again I worked with the decompiled APK.
ps/hacking/hackyeaster/android/MicroscopeActivity.java
... 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?
Solution
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 break if not fnd: content.append(item) 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
Solution
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
2017
ⅰ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.
Solution
‘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 DECIMAL HEXADECIMAL DESCRIPTION -------------------------------------------------------------------------------- 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 DECIMAL HEXADECIMAL DESCRIPTION -------------------------------------------------------------------------------- 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!
Solution
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: #ignore continue 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 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001111111011111011111110000000010000010110110100000100000000101110100111001011101000000001011101001011010111010000000010111010100010101110100000000100000101010001000001000000001111111010101011111110000000000000000111100000000000000000111001101111111110011000000001110000101100100101010000000000111011110000101010100000000111000011110110011011000000000110101110000101100100000000000000000101000111100100000000111111100001100010001000000001000001010000101010000000000010111010010001011100000000000101110100010110010100000000001011101011101010101110000000010000010111111011100000000000111111101100100110001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
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!
Solution
We unzip the file and get 1600 files.
$ ls | wc -l 1601 $ ls -u | head -10 img_4094_G_1385030273_12.png img_4089_k_1402213399_12.png img_4077_n_1752533258_19.png img_4072_J_2017475834_21.png img_4050_N_1946535358_21.png img_4049_n_2125616175_23.png img_4095_I_1080239564_26.png img_4035_w_1070882995_27.png img_4033_E_2047396671_19.png img_3996_s_1197910755_29.png
After trying out different ways of sorting I found the right way to do it:
|0| |1||2| |3| |4| img_230_N_1501859652_31.png 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") splitted.append(tuple(x.split('_'))) ''' |0| |1||2| |3| |4| img_230_N_1501859652_31.png -- Sort by 2 (a..zA..Z), 4, 3, 1 http://stackoverflow.com/questions/28136374/python-sort-strings-alphabetically-lowercase-first ''' 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] sublist.append(fname) 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] counter+=1 new_im.show() #new_im.save('result.jpg')
15 – P Cap
Author: PS
Level: medium
Solutions: 181
What about a little P cap?
Solution
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 DECIMAL HEXADECIMAL DESCRIPTION -------------------------------------------------------------------------------- 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 DECIMAL HEXADECIMAL DESCRIPTION -------------------------------------------------------------------------------- 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:
https://hackyeaster.hacking-lab.com/hackyeaster/7061n.php
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?
hackyeaster.hacking-lab.com:9999
Solution
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 (80.74.140.117) Host is up (0.010s latency). rDNS record for 80.74.140.117: urb80-74-140-117.ch-meta.net PORT STATE SERVICE 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: return 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...") return elif j['Answer'].startswith("Thanks"): print("[!!!] FOUND [!!!]") print resp finish = True return if "paths" in j: for x in j['paths']: solve(path+str(x)) solve("")
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!
Solution
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!
Solution
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; cb(l1); } }; ll.open("GET", url, true); ll.send(); } 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!
Solution
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 out-4184.png out-4187.png out-4186.png out-4185.png out-4183.png out-4182.png out-4181.png out-4180.png out-4179.png out-4178.png
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:
http://helpdeskgeek.com/how-to/create-a-photo-montage-with-imagemagick/
http://www.imagemagick.org/Usage/montage/
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.
https://hackyeaster.hacking-lab.com/hackyeaster/hash?string=abcd
Solution
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): try: words = open("../22/10_million_password_list_top_1000000.txt", 'r') except(IOError): print '[-] Error: Check your wordlist path.\n' sys.exit(1) 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 else: 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])) positions.append(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.
Solution
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]; 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] break 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?
hackyeaster.hacking-lab.com:8888
Solution
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? y Let's go! 99bde068af2d49ed7fc8b8fa79abe13a6059e0db320bb73459fd96624bb4b33f 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 ---------------------- 9f27e1246ab067a1aeb1e71e773a0e0ceb3bcd6844593977b2d8e94ea74e233e -->9f27e1246ab067a1aeb1e71e773a0e0ceb3bcd6844593977b2d8e94ea74e233e Wrong! Point for me. ---------------------- Player > 6 6 6 Master 7 7 7 ---------------------- You lose! ...FINISH...
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): try: words = open("./10_million_password_list_top_100000.txt", 'r') except(IOError): print '[-] Error: Check your wordlist path.\n' sys.exit(1) 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')) s.sendall(b'y\n') tm = s.recv(1024) print("%s" % tm.decode('ascii')) while True: tm = s.recv(1024) if not tm: print("...FINISH...") break print("%s" % tm.decode('ascii')) last_line = str(tm.decode('ascii').splitlines()[-1]) # Check if the response is hash or text try: int(last_line, 16) except: # Not hex value continue print("-->" + last_line) #solution = dict_attack(last_line) solution = api_call(last_line) s.sendall(solution) s.close()
Here is the output of running the script:
$ python solution.py ... -->8f0634444a04df777ef0837f13627769ef65ca3bca3f3732ee13a7bb2d936045 [+] Password is: rajat962 Correct! Point for you. ---------------------- Player 6 6 5 40 Master > 0 0 0 0 ---------------------- 3d597cf4ffb063c2f8c9f09a8efed76f70c6428752771959a5233fc5bc3c564a -->3d597cf4ffb063c2f8c9f09a8efed76f70c6428752771959a5233fc5bc3c564a [+] Password is: emilio3403 Correct! Point for you. ---------------------- Player > 6 6 6 Master 0 0 0 ---------------------- You win! Solution is: !stan-the_marth0n$m4n ...FINISH...
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?
trickhesitenadrfairairstp
tedtunbhscnprissnaoeoasab
hacektpsrnediiahrtartirlf
Solution
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.
1.
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“.
2.
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“.
3.
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:
adrianericksusannabobclairefrank
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:
hackyeaster.hacking-lab.com:7777
As a starting point for your client, the following eclipse project is provided:
Solution
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:
- http://static.javadoc.io/org.jmrtd/jmrtd/0.5.9/org/jmrtd/PassportService.html
- http://static.javadoc.io/org.jmrtd/jmrtd/0.5.9/org/jmrtd/lds/icao/DG2File.html
- http://static.javadoc.io/org.jmrtd/jmrtd/0.5.9/org/jmrtd/lds/iso19794/FaceImageInfo.html
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:
EF_DG2
public static final short EF_DG2
- 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); cardTerminal.waitForCardPresent(1000); passService.open(); passService.sendSelectApplet(false); /* * P<HLAEASTERWOOD<<THUMPER<<<<<<<<<<<<<<<<<<<< * P012345673HLA7707076M21010150000007<<<<<<<96 */ BACKeySpec bacKey = new BACKeySpec() { private static final long serialVersionUID = 1L; @Override public String getDocumentNumber() { return "P01234567"; } @Override public String getDateOfBirth() { return "770707"; } @Override public String getDateOfExpiry() { return "210101"; } }; passService.doBAC(bacKey); 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) { allFaceImageInfos.addAll(faceInfo.getFaceImageInfos()); } 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.
…