はじめに
大学のゼミ的なやつでCTFを久しぶりに触り、先輩に紹介されたので、昔動いてたチームで参加しました。多分常設以外で参加したのが1年以上ぶりになります。
Cryptoが得意ですが、チーム参加時点で簡単な問題が既に片付いており、泣いちゃった……
fusion(Crypto)
RSA暗号におけるe,n,c,そして0101...でマスクされたpと1010...でマスクされたqが与えられた問題。
上の桁からpq>nにならないように埋めていけば何とかなるかなと思ったが、pもqもどんどん変わっていくのでダメだった……退散。
IndexedDB(Web)
とりあえず解かれていなかったWebに移行。
デベロッパツールでIndexedDBを覗くだけ。
Extract Service 1(Web)
.docx,.pptx,.xlsxのファイルをアップロードすると中の文章を取り出してくれるサービスのサーバーにある/flagからflagを取り出そうという問題。
HTMLを見るとFormで送っているファイル形式の指定部分の直接参照先ファイルを指定しており、
<select name="target">
<option value="word/document.xml">.docx</option>
<option value="xl/sharedStrings.xml">.xlsx</option>
<option value="ppt/slides/slide1.xml">.pptx</option>
</select>
main.goを見ると/tmp/{uuid}.zipとして保存、解凍し、tmp/{uuid}/{上記のvalueのパス}にアクセスしていることが分かるので
参照先のvalueを書き換えてやる
<select name="target">
<option value="../../flag">.docx</option>
<option value="xl/sharedStrings.xml">.xlsx</option>
<option value="ppt/slides/slide1.xml">.pptx</option>
</select>
と、出てきた。
意気揚々と提出しようとしたら他の人に解かれていた……
チームで出るときは報連相をちゃんとしよう。(n敗)
ちゃんと報連相しようということを投げ、相談したうえで空いていそうなpwnへ……
netcat(Pwnable)
netcatでサーバと接続し計算問題を通過するとシェルが起動するのでflagを探してねという入門問題。
とりあえず起動してみると
+-----------------------------------------+
| your score: 0, remaining 100 challenges |
+-----------------------------------------+
244 + 866 =
100問!?
ということでPythonでたまにお世話になっているnetcat.py使ってさくっと自動で回答送信を書いて動かすと、3問正解でシェルが起動。ええ……
ls
cat FLAG
で完了。
ソースコードを読もう。(n敗)
only once(Pwnable)
int main() {
init();
srand((unsigned int)time(NULL));
int x = rand_gen(), y = rand_gen();
int score = 0, chall = 1; //ここで問題数が1になっている
char buf[8];
while (1) {
printf("\n+---------------------------------------+\n");
printf("| your score: %d, remaining %d challenges |\n", score, chall);
printf("+---------------------------------------+\n\n");
if (chall == 0) { //残り問題数が0の場合終了
printf("Bye!\n");
break;
}
printf("%3d + %3d = ", x, y);//適当な足し算を出し
scanf("%8s", buf); //回答をscanf(8文字制限)で得る
if (atoi(buf) == x + y) { //正解なら得点加算
printf("Cool!\n");
score++;
} else { //不正解なら得点リセット
printf("Oops...\n");
score = 0;
}
if (score >= 3) { //3点以上ならシェル起動
printf("Congrats!\n");
win();
}
x = rand_gen();
y = rand_gen();
chall--;
}
return 0;
}
scanfで%8s
として入力フィールドを8バイトに制限しているものの、それ以上入れると標準入力のバッファにそれが残って悪さをすることは可能っぽいので、とりあえず過剰な入力をぶっこんでみる。(正直厳密な原理わからんです……)
+---------------------------------------+
| your score: 0, remaining 1 challenges |
+---------------------------------------+
644 + 151 = 99999999999999999999999
Oops...
+---------------------------------------+
| your score: 0, remaining -1 challenges |
+---------------------------------------+
165 + 218 = Oops...
+---------------------------------------+
| your score: 0, remaining -257 challenges |
+---------------------------------------+
940 + 940 = Oops...
+---------------------------------------+
| your score: 0, remaining -258 challenges |
+---------------------------------------+
304 + 267 =
breakの条件はchall==0
であるのでそれ以上にしてしまえばやりたい放題になる。
ということでここから3問正解してシェル起動、その後は前の問題と同様。
ret2win(Pwnable)
スタックの状態が表示されているので、うまくオーバーフローさせてリターンアドレスが格納されている場所をwin関数を呼び出すアドレスに書き換えようという問題。
そもそもwin関数のアドレスってなんだっけ、という段階で苦戦。しばらくして逆アセンブルの存在を思い出す。
アドレスが分かったもののどうやって入力するんだ、というところでまた苦戦。キーボードでCRをどう入力するんだみたいなことをずっと悩む。
苦慮の末netcat.pyを再び頼ってエスケープしたものを送ってクリア。
感想
ブランクがヤバい!!(ソースコードを読もう。(n敗))
そしてチーム戦が下手!!(チームで出るときは報連相をちゃんとしよう。(n敗))
結局気が乗りきらず1日目の数時間だけとはいえBeginnerとEasyしか解けなかったことが本当に悔しい。
本当にcrypto以外の知識がごっそり死んでそうだし、得意のcryptoですら何もできなかったので、ちょっとしばらく鍛えなおし&追加の勉強をせねばならないなあ、という感触。
あと正直順位とかどうでもいい上に得意問題解かれるの癪なので個人でやったほうがよさそう。チーム戦向いてない。