2023-06-03 14:00 – 2023-06-04 14:00 (JST)に開催されたSECCON Beginners CTF 2023のWriteupです。
前々回・前回に引き続き、友人であるmaa氏との2人チームで出場し、1266pt獲得して全778チーム中48位でした。
Writeup
Welcome (welcome)
ctf4b{Welcome_to_SECCON_Beginners_CTF_2023!!!}
Forbidden (web)
// app/index.js (抜粋)
const block = (req, res, next) => {
if (req.path.includes('/flag')) {
return res.send(403, 'Forbidden :(');
}
next();
}
app.get("/flag", block, (req, res, next) => {
return res.send(FLAG);
})
パスに “/flag” という文字列が含まれていると弾かれてしまうが、Expressのルーティングはデフォルトで大文字小文字を区別しないことを知っていたので /FLAG にアクセスしてflagを取得した。開始直後に取り掛かったので正答順で4位とかだったらしい。
ctf4b{403_forbidden_403_forbidden_403}
CoughingFox2 (crypto)
# main.py
import random
import os
flag = b"ctf4b{xxx___censored___xxx}"
# Please remove here if you wanna test this code in your environment :)
flag = os.getenv("FLAG").encode()
cipher = []
for i in range(len(flag)-1):
c = ((flag[i] + flag[i+1]) ** 2 + i)
cipher.append(c)
random.shuffle(cipher)
print(f"cipher = {cipher}")
前回出題されたCoughingFoxの発展版のような感じ。前回同様にシャッフルを解除した後、さらに各文字の復元を行う必要がある。flagのフォーマットは ctf4b{[\x20-\x7e]+} であることがルールで明示されているため、先頭が c であることがわかりそれを用いて連鎖的に残りの文字も復元できた。
# solver.py
cipher = [4396, 22819, 47998, 47995, 40007, 9235, 21625, 25006, 4397, 51534, 46680, 44129, 38055, 18513, 24368, 38451, 46240, 20758, 37257, 40830, 25293, 38845, 22503, 44535, 22210, 39632, 38046, 43687, 48413, 47525, 23718, 51567, 23115, 42461, 26272, 28933, 23726, 48845, 21924, 46225, 20488, 27579, 21636]
ordered_cipher = {}
for i in range(len(cipher)):
for j in range(len(cipher)):
if ((cipher[i] - j) ** 0.5).is_integer():
ordered_cipher[j] = int((cipher[i] - j) ** 0.5)
ordered_cipher = sorted(ordered_cipher.items())
flag = "c"
for i in range(len(ordered_cipher)):
flag += chr(ordered_cipher[i][1] - ord(flag[i]))
print(flag)
ctf4b{hi_b3g1nner!g00d_1uck_4nd_h4ve_fun!!!}
poem (pwnable)
// src.c (抜粋)
char *flag = "ctf4b{***CENSORED***}";
char *poem[] = {
"In the depths of silence, the universe speaks.",
"Raindrops dance on windows, nature's lullaby.",
"Time weaves stories with the threads of existence.",
"Hearts entwined, two souls become one symphony.",
"A single candle's glow can conquer the darkest room.",
};
int main() {
int n;
printf("Number[0-4]: ");
scanf("%d", &n);
if (n < 5) {
printf("%s\n", poem[n]);
}
return 0;
}
負数のチェックをしていないのでout-of-boundsのメモリアクセスができる。
-4 を渡してあげるとグローバル変数flagの値が入手できる。
ctf4b{y0u_sh0uld_v3rify_the_int3g3r_v4lu3}
Half (reversing)
strings でバイナリ中の文字列を抜き出すと2分割されたflagが手に入る
$ strings half
(省略)
ctf4b{ge4_t0_kn0w_the
_bin4ry_fi1e_with_s4ring3}
(省略)
ctf4b{ge4_t0_kn0w_the_bin4ry_fi1e_with_s4ring3}
Three (reversing)
バイナリとひたすらにらめっこをした結果スキュタレー暗号で3分割されたflagが見つかったので、書き写しながら復号する以下のようなプログラムを書いてもらった。
// solver.js
const str1 = "c4c_ub__dt_r_1_4"; // }
const str2 = "tb4y_1tu04tesifg";
const str3 = "n0ae0n_e4ept13";
let output = "";
for (let i = 0; i < str1.length; i++) {
output += str1[i] + str2[i] + str3[i];
}
実際にはstr1だけ他に比べて1文字多かったのでその辺りの処理を行う必要があるが、このとき既に終了3分前だったので1文字削ってから提出時に手動で追加したほうが早いと判断してどうにか通した。
ctf4b{c4n_y0u_ab1e_t0_und0_t4e_t4ree_sp1it_f14g3}
所感
目標だった50位以内 (かつ過去最高順位) を取れたのでハッピ−