WaniCTF 2024に参加しました!

はじめに

2024年6月21日~23日に開催されたWaniCTF 2024に参加しました。

結果はぼっちチーム「だんびらむーちょ」で1558ポイント、全1022チーム中178位でした。

「俺も阪大生やし余裕やろ」くらいの気持ちで挑みましたが結構難しくて苦戦しました…。

Crypto

beginners_rsa

解法

nをfactordbに通すと5つに分けられるので、これを使ってdを求めれば良い。

from Crypto.Util.number import long_to_bytes
import gmpy2

n = 317903423385943473062528814030345176720578295695512495346444822768171649361480819163749494400347
e = 65537
enc = 127075137729897107295787718796341877071536678034322988535029776806418266591167534816788125330265

p = 9953162929836910171
q = 11771834931016130837
r = 12109985960354612149
s = 13079524394617385153
a = 17129880600534041513

phi = (p-1) * (q-1) * (r-1) * (s-1) * (a-1)

d = gmpy2.invert(e, phi)

print(long_to_bytes(pow(enc, d, n)))

Flag

FLAG{S0_3a5y_1254!!}

beginners_aes

解法

鍵とIVは先頭15バイトが分かっているがラス1が乱数で足されていて分からない。

ラス1のパターンは256 * 256通りしかないので総当たりで試せば良い。

from Crypto.Util.Padding import unpad
from Crypto.Cipher import AES
import hashlib

key = b'the_enc_key_is_'
iv = b'my_great_iv_is_'

enc = b'\x16\x97,\xa7\xfb_\xf3\x15.\x87jKRaF&"\xb6\xc4x\xf4.K\xd77j\xe5MLI_y\xd96\xf1$\xc5\xa3\x03\x990Q^\xc0\x17M2\x18'
flag_hash_origin = "6a96111d69e015a07e96dcd141d31e7fc81c4420dbbef75aef5201809093210e"

for i in range(256):
    for j in range(256):
        cipher = AES.new(key+(i.to_bytes(1, "big")), AES.MODE_CBC, iv+(j.to_bytes(1, "big")))

        dec = cipher.decrypt(enc)
        try:
            flag = unpad(dec, 16)
        except ValueError:
            continue

        flag_hash = hashlib.sha256(flag).hexdigest()

        if flag_hash == flag_hash_origin:
            print(flag)
            exit(0)

Flag

FLAG{7h3_f1r57_5t3p_t0_Crypt0!!}

replacement

解法

文字からハッシュを計算する方法が分かるので使われてそうな文字候補を全部通して置き換えれば良い。

diary = [...]  # 長いので省略

letters = string.digits + string.ascii_letters + "{}_-.$@!?" + ",\n "

ldict = {}
for l in letters:
    x = ord(l)
    x = hashlib.md5(str(x).encode()).hexdigest()
    x = int(x, 16)
    ldict[str(x)] = l

for d in diary:
    try:
        print(ldict[str(d)], end="")
    except KeyError:
        pass

print()

Flag

FLAG{13epl4cem3nt}

Forensics

tiny_usb

解法

Windowsのマウント機能を使ってisoをマウントすると中にFlagが書かれたFLAG.PNGがある。

Flag

FLAG{hey_i_just_bought_a_usb}

Surveillance_of_sus

解法

ファイルのバイナリを見ると先頭が52 44 50 38 62 6d 70 RDP8bmpとなっていてRDP Bitmap Cacheというものらしい。

ググると欲しかった情報全部書いてるところがあったので参考にした(RDPビットマップキャッシュについて: NECセキュリティブログ | NEC)。

まずはbmc-toolsを使ってタイルをパースする。

$ python ./bmc-tools/bmc-tools.py -s ./Cache_chal.bin -d ./output/ -b
[+++] Processing a single file: './Cache_chal.bin'.
[===] 650 tiles successfully extracted in the end.
[===] Successfully exported 650 files.
[===] Successfully exported collage file.

次にRdpCacheStitcherでパズルをする。小文字のyがvにしか見えなくて一敗。

Flag

FLAG{RDP_is_useful_yipeee}

tiny_10px

解法

渡される画像のサイズは10px*10pxだが、明らかにファイルサイズがデカすぎる。

バイナリエディタで画像を見ると0xD8AからFF C0 00 11 08 00 0A 00 0Aとなっている部分の2つの00 0A(=10)が画像サイズを表しているのでこれをいい感じに調整する。00 A0(=160)にするとピッタリハマる。

いけた

Flag

FLAG{b1g_en0ugh}

mem_search

解法

volatility3を使って気合で解いた。今回触った中だと一番面白かったです。

まずはwindows.infoで詳細を見てみた。

$ python vol.py -f ../chal_mem_search.DUMP windows.info
Volatility 3 Framework 2.7.1
Progress:  100.00               PDB scanning finished                                                                                              
Variable        Value

Kernel Base     0xf8030e400000
DTB     0x1ad000
Symbols file:///mnt/d/waniCTF2024/for-mem-search/volatility3/volatility3/symbols/windows/ntkrnlmp.pdb/D9424FC4861E47C10FAD1B35DEC6DCC8-1.json.xz
Is64Bit True
IsPAE   False
layer_name      0 WindowsIntel32e
memory_layer    1 WindowsCrashDump64Layer
base_layer      2 FileLayer
KdDebuggerDataBlock     0xf8030f000b20
NTBuildLab      19041.1.amd64fre.vb_release.1912
CSDVersion      0
KdVersionBlock  0xf8030f00f400
Major/Minor     15.19041
MachineType     34404
KeNumberProcessors      1
SystemTime      2024-05-11 09:33:57
NtSystemRoot    C:\Windows
NtProductType   NtProductWinNt
NtMajorVersion  10
NtMinorVersion  0
PE MajorOperatingSystemVersion  10
PE MinorOperatingSystemVersion  0
PE Machine      34404
PE TimeDateStamp        Mon Dec  9 11:07:51 2019

次にwindows.pslistでプロセス一覧を見てみたが何が悪さをしているのか分からず

$ python vol.py -f ../chal_mem_search.DUMP windows.pslist

windows.filescanでメモリ内のファイルを探してみる。

$ python vol.py -f ../chal_mem_search.DUMP windows.filescan

テキストファイルが怪しそうとか探してみるとecho.txtというファイルを見つける。

0xcd88ceb9f2b0  \Users\Mikka\Desktop\echo.txt   216

windows.dumpfilesでdumpして中身を見てみる。

$ python vol.py -o ../ -f ../chal_mem_search.DUMP windows.dumpfiles --virtaddr 0xcd88ceb9f2b0
...
$ cat ../file.0xcd88ceb9f2b0.0xcd88cd76fcf0.DataSectionObject.echo.txt.dat
FAKEFAKEFAEKFAKEKFAKEDAKIFIOEAGVOAENGVO 

FAKEでした。チクショー

着眼点は悪くないのではと思いユーザーフォルダ以下を探して見ると以下のような明らかに怪しいものを発見。

0xcd88cebae1c0  \Users\Mikka\Downloads\read_this_as_admin.download      216
0xcd88cebc26c0  \Users\Mikka\Desktop\read_this_as_admin.lnknload        216

同様にdumpして中身を見るとなんか通信してそうなのが分かる。

$ cat ../file.0xcd88cebae1c0.0xcd88ced4eaf0.DataSectionObject.read_this_as_admin.download.dat
[ZoneTransfer]
ZoneId=3
ReferrerUrl=http://192.168.0.16:8282/
HostUrl=http://192.168.0.16:8282/read_this_as_admin.lnk

windows.netscanで見るとPIDが2704のpowershellが192.168.0.16:8282宛に通信していることが分かる。

$ python vol.py -f ../chal_mem_search.DUMP windows.netscan
...
0xcd88ccc2b010  TCPv4   192.168.0.19    49837   192.168.0.16    8282    CLOSED  2704    powershell.exe  2024-05-11 09:33:55.000000 
...

windows.memmapでPIDが2704のプロセスのメモリをダンプし、stringsでファイル内の文字列を抜き出す。

$ python vol.py -o ../ -f ../chal_mem_search.DUMP windows.memmap --pid 2704 --dump
$ strings ../pid.2704.dmp > ../string2704.txt

適当に「FLAG{」で検索かけても何も出てこなくて半ば諦めながら目で追っかけていたら気になる部分を発見。

...
GET /B64_decode_RkxBR3tEYXl1bV90aGlzX2lzX3NlY3JldF9maWxlfQ%3D%3D/chall_mem_search.exe HTTP/1.1
User-Agent: Mozilla/5.0 (Windows NT; Windows NT 10.0; ja-JP) WindowsPowerShell/5.1.19041.3803
Host: 192.168.0.16:8282
Connection: Keep-Alive
...

base64でエンコードされている部分をデコードしたらFlagが出てきた(CyberChef)。

Flag

FLAG{Dayum_this_is_secret_file}

pwn

nc

解法

netcatやるだけ。do_not_rewrite以降もやりたかったけど気力が持たなかった…。

$ nc chal-lz56g6.wanictf.org 9003
15+1=0x10
FLAG{th3_b3ginning_0f_th3_r0ad_to_th3_pwn_p1ay3r}

Flag

FLAG{th3_b3ginning_0f_th3_r0ad_to_th3_pwn_p1ay3r}

Reversing

lambda

解法

長い部分を眺めてみるとchr(123 ^ ord(c))chr(ord(c) - 3)chr(ord(c) + 12)の3つの操作を行ってから比較していることが分かる。

プログラム内の文字列に対して逆の操作を行う。実行順はよく分からなかったのでどうせ6パターンしかないし総当たりでやった。

import itertools

s = '16_10_13_x_6t_4_1o_9_1j_7_9_1j_1o_3_6_c_1o_6r'

# chr(123 ^ ord(c))
# chr(ord(c) - 3)
# chr(ord(c) + 12)

funcs = [
    lambda c: chr(123 ^ ord(c)),
    lambda c: chr(ord(c) + 3),
    lambda c: chr(ord(c) - 12)
]
func_sets = list(itertools.permutations(funcs))

for i in range(len(func_sets)):
    print(i)

    for c in s.split('_'):
        try:
            cc = chr(int(c, 36) + 10)
            cc = func_sets[i][0](cc)
            cc = func_sets[i][1](cc)
            cc = func_sets[i][2](cc)
            print(cc, end="")
        except:
            pass
    
    print("\n")

Flag

FLAG{l4_1a_14mbd4}

home

解法

Ghidraでザッと見ると、

  • getcwdで”Service”という名前が含まれているディレクトリにいるか
  • デバッガで動いていたら弾く

という処理をしていることが分かる。

1つ目はやるだけ。2つ目はいい感じの回避方法が分からなかったので強引に分岐部分を以下のように書き換えた。

19f7:	75 13                	jne    1a0c <main+0x95>
→ 74 13にしてJNEをJEにして条件逆転
(逆にデバッガで動かさないと弾かれるようになる)

後はgdbで動かしてFlagを見る。

// ブレークポイントを張る
> b *constructFlag+1847
> r

// 適当にスタックを眺めて文字列を探す
> x/200s 0x00007fffffffd520
...
0x7fffffffd63f: ""
0x7fffffffd640: "FLAG{How_did_you_get_here_4VKzTLibQmPaBZY4}"
0x7fffffffd66c: "W8C)\260\267\374\367\377\177"
...

Flag

FLAG{How_did_you_get_here_4VKzTLibQmPaBZY4}

Thread

解法

頑張って逆の処理を実装した。

最初計算結果が格納されている配列のi*4バイト目しか読んでなくて逆算結果が一部合わなくてんなぁ~ってなっていた。4バイト分にリトルエンディアンで数値が入っているのでちゃんとi*4+1以降も読まないといけない。

DAT_00104020 = [ 0xa8, 0x00, 0x00, 0x00, 0x8a, 0x00, 0x00, 0x00, 0xbf, 0x00, 0x00, 0x00, 0xa5, 0x00, 0x00, 0x00, 0xfd, 0x02, 0x00, 0x00, 0x59, 0x00, 0x00, 0x00, 0xde, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x00, 0x0f, 0x01, 0x00, 0x00, 0xde, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x5d, 0x01, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0xde, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x00, 0xde, 0x00, 0x00, 0x00, 0x51, 0x00, 0x00, 0x00, 0xef, 0x00, 0x00, 0x00, 0x3f, 0x01, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, 0x5d, 0x01, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, 0xde, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, 0x4b, 0x01, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x00, 0xde, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, 0x5d, 0x01, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x24, 0x01, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x5f, 0x00, 0x00, 0x00, 0x4e, 0x01, 0x00, 0x00, 0xd5, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00 ]
DAT_00104020_4 = []
for i in range(0, len(DAT_00104020), 4):
    DAT_00104020_4.append(
        (DAT_00104020[i + 3] << 24)
        + (DAT_00104020[i + 2] << 16)
        + (DAT_00104020[i + 1] << 8)
        + DAT_00104020[i + 0]
    )

FLAG_LEN = len(DAT_00104020_4)


def rev_FUN_00101289(i: int, c: int) -> str:
    if i % 3 == 0:
        return chr(((c ^ 0x7f) - 5) // 3)
    elif i % 3 == 1:
        return chr(((c // 3) ^ 0x7f) - 5)
    elif i % 3 == 2:
        return chr(((c - 5) // 3) ^ 0x7f)

for i in range(FLAG_LEN):
    print(rev_FUN_00101289(i, DAT_00104020_4[i]), end="")

print()

Flag

FLAG{c4n_y0u_dr4w_4_1ine_be4ween_4he_thread3}

おわりに

先々週先週と続いた週末CTFラッシュもこれにて一段落。

学びがたくさん得られる期間になって良かった(小並感)。