目次
はじめに
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}
おわりに
学びがたくさん得られる期間になって良かった(小並感)。