Days fifteen through twentyone were the hard challenges. There were some really great coding challenges. I loved day sixteen, where I’ll have to check tons of QRcodes to find the flag. And day twenty, where I’ll abuse a unicode bug to brute force padding on an AES encryption. There were couple signals analysis challenges, including a radio wave and serial line decode. There was also a neat trick abusing how zip archives handle long passwords, and a nice relateively beginner-friendly heap exploitation.
HV22.15
Challenge
HV22.15 Message from Space
Categories:
WIRELESS FORENSIC
Level:
hard
Author:
cbrunsch
One of Santa’s elves is a bit of a drunkard and he is incredibly annoyed by the banning of beer from soccer stadiums. He is therefore involved in the “No Return to ZIro beer” community that pledges for unrestricted access to hop brew during soccer games. The topic is sensitive and thus communication needs to be top secret, so the community members use a special quantum military-grade encryption radio system.
Santa’s wish intel team is not only dedicated to analyzing terrestrial hand-written wishes but aims to continuously picking up signals and wishes from outer space too. By chance the team got notice of some secret radio communication. They notice that the protocol starts with a preamble. However, the intel team is keen to learn if the message is some sort of wish they should follow-up. Can you lend a hand?
The download is a RDI Acoustic Doppler Current Profiler:
$file message_1msps.cu8
message_1msps.cu8: RDI Acoustic Doppler Current Profiler (ADCP)
Solution
Raw Analysis
The file is clearly made up of binary non-ASCII data. I’ll look at it with xxd running cat message_1msps.cu8 | xxd | less to get a feel for it. Looking at the top, it’s all 0x7f, 0x80, and an occasional 0x7e. I can look at this a bit more completely using Python:
oxdf@hacky$pythonPython 3.8.10 (default, Nov 14 2022, 12:59:47)
[GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>withopen('message_1msps.cu8','rb')asf:... bytes = f.read()
...
>>>fromcollectionsimportCounter>>>Counter(bytes)Counter({127: 9321343, 128: 6066621, 126: 559355, 129: 207626, 125: 51129, 121: 43856, 134: 41072, 133: 35989, 130: 35258, 122: 32513, 132: 26601, 123: 25113, 124: 22279, 131: 22227, 120: 15795, 135: 7836, 119: 386, 136: 73})
The vast majority of the bytes are in the 126 (0x7e) to 129 (0x81) range, but there are other values as well.
There’s not much of a pattern to make out looking at the bytes this way.
urh
There are lots of audio processing tools out there, but the one that makes this challenge easiest is Universal Radio Hacker. I’ll install it with python -m pip install urh, and then open it:
Opening the file returns this:
Zooming in on the chunk in the middle, it looks like a wave, where the amplitude changes between two values:
To Binary Data
It looks like there’s 6 cycles per block, and if I assume that, I can try to interpret them as bit values:
Clicking into the Analysis tab, it does this conversion into bits for me:
Instead of bits, it can show the result as ASCII, though it’s not right:
The current “Decoding” is “Non-Return To Zero”, or NRZ. That’s using the scheme I described above, with high amplitude being a 1, and low being 0.
The Wikipedia page on NRZ shows several variations. Given that the theme of this challenge is space, Non-Return To Zero Space (NRZ(S)) seems interesting. In that scheme, a change in level is a one, and the same level is a 0.
urh has some other encodings, but not that:
Clicking on the “…”, I can define my own. The “Differential Encoding” option under “Additional Functions” seems to be a good match, so I’ll add it by double-clicking it:
Clicking “Save as…”, I’ll give it a name:
Using that encoding, the file starts with all 1s:
Changing to ASCII, after the first two bytes of 1s (the preamble from the description?), it is ASCII:
It’s a QRcode made up of smaller QRcodes (zoomed version here):
Solution
I’ll need to write a program to parse 853,126 sub-images as QRCodes and find one that has a flag. I’ll show my process in this video:
The final script is:
#!/usr/bin/env python3
fromPILimportImageimportpyzbar.pyzbaraspyzbarimportsysImage.MAX_IMAGE_PIXELS=615050000defget_qrs(im,div_by_arr):globalcount_img,count_codeswidth,height=im.sizediv_by=div_by_arr[0]sw,sh=width//div_by,height//div_byforyinrange(0,height,sh):forxinrange(0,width,sw):square=im.crop((x,y,x+sw,y+sh))count_img+=1iflen(div_by_arr)>1:get_qrs(square,div_by_arr[1:])codes=pyzbar.decode(square,symbols=[pyzbar.ZBarSymbol.QRCODE])forcodeincodes:count_counts+=1ifcode.data!=b"Sorry, no flag here!":print(code.data)print(f'{count_img=}\n{count_codes=}')im.save('flag.png')#sys.exit()
im=Image.open('haystack.png')im=im.crop((2400,2400,22400,22400))count_img,count_codes=0,0get_qrs(im,[25,2,2,2,2,2])print(f'{count_img=}\n{count_codes=}')
The script runs in a bit over a minute, finding the flag in around 40 seconds:
$time python solve.py
b"HV22{1'm_y0ur_need13.}"
count_img=437543
count_codes=6979
Final:
count_img=853125
count_codes=14443
real 1m15.869s
user 1m15.487s
sys 0m0.369s
Flag: HV22{1'm_y0ur_need13.}
HV22.17
Challenge
HV22.17 Santa's Sleigh
Categories:
FORENSIC REVERSE_ENGINEERING
Level:
hard
Author:
dr_nick
As everyone seems to modernize, Santa has bought a new E-Sleigh. Unfortunately, its speed is limited. Without the sleight’s full capabilities, Santa can’t manage to visit all kids… so he asked Rudolf to hack the sleigh for him.
I wonder if it worked.
Unfortunately, Rudolph is already on holiday. He seems to be in a strop because no one needs him to pull the sledge now. We only got this raw data file he sent us.
The file is a single long ASCII line, with 6211 characters:
$file SantasSleigh.raw
SantasSleigh.raw: ASCII text, with very long lines, with no line terminators
$wc SantasSleigh.raw
0 1 6211 SantasSleigh.raw
A couple of hints were released on the Hackvent discord when no one solved it:
Rodolph is heavy on duty during his holiday trip, but he managed to send und at least a photo of his first step.
</picture>
And then:
Rudolf finally wants some peace and quiet on vacation. But send us one last message: “I thought they speak 8 or 7 N1” together with that picture.
</picture>
Solution
These are serial line readings, and the tool pictured in the second hint is PulseView, part of the sigrok project. These devices are UART devices.
I’ll open PulseView and select “Import Raw binary logic data”:
I don’t know either of these for sure. I’ll leave number of channels at 8, and change the sample rate to basically anything (I don’t think it matters). I’ll use 1200.
It opens like this:
There are only two channels in use, so I can clear the other 6.
Zooming in, I’ll notice that data is transmitted in chunks of four samples:
That shows that the baud rate needs to be 1/4 of the sample rate (so 300).
The button at the top right of the menu bar is to add a decoder:
I’ll click and select UART. It adds, unconfigured:
I’ll click on it to configure it:
This applies the decoder to channel 0 with the correct baud rate, 8 databits and no parity (8N1). By selecting the data format as ASCII, I can see the messages from 0:
I could try to add channel 1 as the TX line, but it won’t work. It’s actually using 7N1. I’ll add a second UART decoder, and configure it:
Now the replies are there:
There are some odd bytes mixed in, but the script goes:
0: Hello!
1: Hi.
0: Tell me the secret!
1: Never.
0: So, give me the flag instead.
1: No way.
0: Please!
1: Ok, here it is: HV22{H4ck1ng_S4nta’s_3-Sleigh}
0: Thx!
Flag: HV22{H4ck1ng_S4nta's_3-Sleigh}
HV22.18
Challenge
HV22.18 Santa's Nice List
Categories:
CRYPTO
Level:
hard
Author:
keep3r
Santa stored this years “Nice List” in an encrypted zip archive. His mind occupied with christmas madness made him forget the password. Luckily one of the elves wrote down the SHA-1 hash of the password Santa used.
xxxxxx69792b677e3e4c7a6d78545c205c4e5e26
Can you help Santa access the list and make those kids happy?
In this year’s HACKvent, you can be assured that all bruteforcing hurdles do not take longer than 5 minutes on an Intel(R) UHD Graphics 620, if done smartly and correctly.
The download is a zip:
$file nice-list.zip
nice-list.zip: Zip archive data, at least v?[0x333] to extract
Trying to extract asks for a password:
$7z x nice-list.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,4 CPUs AMD Ryzen 9 5900X 12-Core Processor (A20F10),ASM,AES-NI)
Scanning the drive for archives:
1 file, 554 bytes (1 KiB)
Extracting archive: nice-list.zip
--
Path = nice-list.zip
Type = zip
Physical Size = 554
Enter password (will not be echoed):
Solution
Strategy
This challenge is centered around the vulnerability laid out in this article on bleepingcomputer. It turns out that if a zip password is longer than 64 characters, that password is SHA1 hashed, and the resulting bytes are used as the password.
This is cool in a scenario where the password that has a SHA1 hash that’s also ASCII text, as now there are two different passwords that can be typed in that will unzip the archive.
I’m given all but the first six bytes of the SHA1 of the password. I’ll note that all the hex bytes in the remaining hash are in the ascii range:
This means there are only three unknown bytes in a potential password.
Crack
Three unknown bytes is still 2^24 possible unknowns, so I’ll need to use a hash cracking tool like john or hashcat to try. To get a hash that these can break, I’ll use zip2john and save the results into a file:
$ zip2john nice-list.zip | tee zip_hashes
nice-list.zip/flag.txt:$zip2$*0*3*0*e07f14de6a21906d6353fd5f65bcb339*5664*41*e6f2437b18cd6bf346bab9beaa3051feba189a66c8d12b33e6d643c52d7362c9bb674d8626c119cb73146299db399b2f64e3edcfdaab8bc290fcfb9bcaccef695d*40663473539204e3cefd*$/zip2$:flag.txt:nice-list.zip:nice-list.zip
nice-list.zip/nice-list-2022.txt:$zip2$*0*3*0*a53ba8a665f2c94e798835ab626994dd*96cc*5b*72b0a11e9ef17568256695cf580c54400f41cfe0055f1b0800ff91374216313ff9b6dc2c9b1309f9765e3873122d8e422e2d9ecd2c7aa6cbf66105ce837a0fe46c18dc6ccc0cb25f59233c9223d699f43bc2e69c5117b307f813fc*6308b50240b2b882b61e*$/zip2$:nice-list-2022.txt:nice-list.zip:nice-list.zip
There’s two there, likely the same. Now I can use john to crack. The --mask option allows me to give a password with different wildcards in it. This cheat sheet has a nice layout of the different masks:
So I can use three ?A to represent the unknown bytes (they don’t have to be ASCII):
$ john --mask='?A?A?Aiy+g~>LzmxT\\ \\N^&' zip_hashes
Using default input encoding: UTF-8
Loaded 2 password hashes with 2 different salts (ZIP, WinZip [PBKDF2-SHA1 256/256 AVX2 8x])
Loaded hashes with cost 1 (HMAC size) varying from 65 to 91
Will run 4 OpenMP threads
Press 'q' or Ctrl-C to abort, 'h' for help, almost any other key for status
0g 0:00:00:04 2.44% (ETA: 22:01:26) 0g/s 66914p/s 135857c/s 135857C/s *niy+g~>LzmxT\ \N^&..aniy+g~>LzmxT\ \N^&
4Ltiy+g~>LzmxT\ \N^& (nice-list.zip/nice-list-2022.txt)
4Ltiy+g~>LzmxT\ \N^& (nice-list.zip/flag.txt)
2g 0:00:00:08 DONE (2022-12-18 21:58) 0.2490g/s 69371p/s 138743c/s 138743C/s stiy+g~>LzmxT\ \N^&..eqtiy+g~>LzmxT\ \N^&
Use the "--show" option to display all of the cracked passwords reliably
Session completed.
It finds the password in a few seconds.
I could do the same thing with hashcat using the -a 3 to set up a brute-force or mask attack. I’ll trim the hashes down to match the format in the example hashes page for id 13600. Then instead of a wordlist, I give the pattern with a similar set of rules to the ones from john:
Santa has written an application to encrypt some secrets he wants to hide from the outside world. Only he and his elves, who have access too all the keys used, can decrypt the messages 🔐.
Santa’s friends Alice and Bob have suggested that the application has a padding vulnerability❗, so Santa fixed it 🎅. This means it’s not vulnerable anymore, right❗❓
Santa has also written a concept sheet of the encryption process:
</picture>
Start the service in the Resources section, connect to it with nc <docker> 1337 and get the flag!
You can download the service’s source code from Resources section. We don’t want to make challenges guessy, do we? 😉
Solution
Source Analysis
The given source for the server is a Python script:
#!/usr/bin/env python3
fromCrypto.CipherimportAESfromosimporturandom# pad block size to 16, zfill() fills on left. Invert the string to fill on right, then invert back.
defpad(msg):iflen(msg)%16!=0:msg=msg[::-1].zfill(len(msg)-len(msg)%16+16)[::-1]returnmsgflag=open('flag.txt').read().strip()whileTrue:aes=AES.new(urandom(16),AES.MODE_ECB)msg=input("Enter access code:\n")enc=pad(msg)+pad(flag)enc=aes.encrypt(pad(enc.encode()))print(enc.hex())retry=input("Do you want to try again [y/n]:\n")ifretry!="y":break
Something like socat must be exposing this to the internet using port 1337.
Strategy
The two bits of data (msg and flag) are padded as characters before they are combined, and then the entire thing is converted to bytes (.encode()) and then it’s padded again and encrypted.
I suspect the script author would have done this to prevent me from sending a message such that there’s only one unknown byte in the last block. If that were the case, I could generate all possible blocks of one character and then 15 pad bytes, and send them to see which matched the last block of flag.
Because my input is padded, I can’t easily adjust the spacing of flag.
The trick is to look for characters that will take up one space as characters, but multiple bytes once encoded. The emoji littered through the challenge description are a nice hint. For example, trying some of the symbols in the challenge text:
$ python solve.py 152.96.7.9
[+] Opening connection to 152.96.7.9 on port 1337: Done
HV22{len()!=len()}
[*] Closed connection to 152.96.7.9 port 1337
Flag: HV22{len()!=len()}
HV22.21
Challenge
HV22.21 Santa's Workshop
Categories:
EXPLOITATION
Level:
hard
Author:
0xi
Santa decided to invite you to his workshop. Can you find a way to pwn it and find the flag?
There’s an instance and a binary to download. The binary is a 64-bit ELF:
$ file santasworkshop.elf
santasworkshop.elf: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=14aac965f690be29c5d8a90a8e44e4316e703ce2, not stripped
Connecting to the instance or running the binary offer the same menu:
$ nc 152.96.7.19 1337
Welcome to Santa's Workshop
Enjoy your stay but don't steal anything!
1 View naughty list
2 Check the workshop for items
3 Steal presents
4 Tell a good deed
>
Solution
This challenge involves identifying a few key parts:
There’s a function that’s not called named present that just returns a shell.
There’s an information leak in menu item 1 that will give the necessary information to get the address of present despite PIE being enabled.
There’s a use after free vulnerability on the workshop construct which can be exploited with a combination of menu items 3, then 4, then 2, using the address of present.
This video shows all the steps to getting shell on the server: