はじめに
少し遅くなってしまいましたが,2024/06/15から16にかけて行われたSECCON BEGINNERS CTF 2024のWriteupを書いていきたいと思います.
当初の目標としては難易度beginnrs
を全部解けたらいいなとか思ってたんですが現実はそんなに甘くなく結局2問しか解けず,結果は513位と散々な結果でした.
しかし去年は1問しか解けてない(去年はチームで参加して解いたのは僕ではないので実質0問)のに対して今年は2問解けたので多少は成長してるかなと思います.
simpleoverflow
以下のようなソースコードが配布されます.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
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;
}
__attribute__((constructor)) void init() {
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
alarm(120);
}
このコードのおかしいところは配列buf
が要素数10で定義されているので,10バイト分確保されていますが,read
関数では0x10(16バイト)
分読み込むようになっています.
定義の順番からしても配列buf
の後にis_admin
が定義されているので入力する際にオーバーフローを狙っていけそうですね.
目標としてはis_admin
の中身を1にすればいいので\x1
を書き込めればいいと思います.またis_admin
はint
型で定義されているので4バイト確保されています.
なので入力する文字列としては,buf
に書き込む10バイト分はなんでもいいのでとりあえずAAAAAAAAAA
とかにしときます.問題のis_admin
に書き込む内容ですがリトルエンディアンで書き込む必要があるため\x1\x0\x0\x0
とする必要があります.
なので最終的にはAAAAAAAAAA\x1\x0\x0\x0
を入力すればflagがgetできます.
getRank
配布されるmain.ts
の中身の一部です.
const RANKING = [10 ** 255, 1000, 100, 10, 1, 0];
type Res = {
rank: number;
message: string;
};
function ranking(score: number): Res {
const getRank = (score: number) => {
const rank = RANKING.findIndex((r) => score > r);
return rank === -1 ? RANKING.length + 1 : rank + 1;
};
const rank = getRank(score);
if (rank === 1) {
return {
rank,
message: process.env.FLAG || "fake{fake_flag}",
};
} else {
return {
rank,
message: `You got rank ${rank}!`,
};
}
}
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);
}
変数rank
が1の時にFlagがGETできるみたいですね.
rankの求め方はconst RANKING = [10 ** 255, 1000, 100, 10, 1, 0];
のどの値よりも大きいかで決まるみたいですね.
*例えばScoreが1001だったら10 ** 255
よりも小さく1000よりも大きいので
rankは2になります.なので10 ** 255
よりも大きい値を入力すればいいのですが以下のような条件があるため,これではうまくいきません.
- score>10**255よりも大きい
- この場合入力したスコアよりも減点されてしまいます
- 入力文字数が300文字より大きい
- この場合-1になってしまいます.
ここでinput
がparseInt
を適用してscoreに渡されていることがわかります.
リファレンスによると入力された文字列に0x
が含まれている場合,16進数と解釈されるようです.
なのでBurpsuite使って,0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
みたいな感じで300文字超えない程度に送信すればOKです.
最後に
多少は考え方がわかるようになってきた気がしますが,まだまだ実力不足を感じました.
特にcryptoやreverseなどはほとんど何も知らないので少しずつ勉強していきたいです.
次はWani CTFですね.どのくらい参加できるかわかりませんが参加される皆さん頑張りましょう!