高専セキュリティコンテスト Binary 500 OreNoFS
高専セキュリティコンテストでCTFに触れてきたのwrite upの分割です。
OreNoFSとは
高専セキュリティコンテストで1日目終了前に追加された500点問題で、32MB程度のオレオレファイルシステムの中からzipファイルを取り出す問題です。
バイナリ解析ツールなんて高尚なものは初挑戦で持ち合わせているはずもなく、 tail
hexdump
とC言語の知識だけで挑みました。
プロセス
まず、ファイルを hexdump
で確認します。パーティションっぽい文字列があります。ここは問題から無視していいと思ったのでひとまず無視。
下まで見ていくと、「flag.zip」という文字列が見つかります。
* 00104000 0f 66 6c 61 67 2e 7a 69 70 00 00 00 1a e7 0f 00 |.flag.zip.......| 00104010 fd 05 00 00 00 00 00 00 00 00 00 00 00 00 ff 00 |................| 00104020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| *
これがディレクトリエントリっぽいので、約束に従って読んで仮定とします。
filename:flag.zip extension: 0*3 size: 1a e7 0f 00 = 1042202? offset_of_cluster: fd 05 = 1533? attribute: 0*12 reserved: ff 00 = 255
エンディアンはわからない・・・けれど、このファイルは32MB弱なので、sizeがそれを上回ると不自然だなあと思ってリトルエンディアンで読んでみました。 オフセットはきっとクラスタのことなので、クラスタサイズ4096をかけると0x005fd000くらいになりました。こんな番地にデータが置いてあれば便利なんだけどなー。
次に、zipファイルがどこかに置いてありそうなので「50 4b」で検索してみました。
* 006fd000 50 4b 03 04 14 00 08 00 08 00 69 b5 49 4b 00 00 |PK........i.IK..| 006fd010 00 00 00 00 00 00 00 00 00 00 08 00 10 00 66 6c |..............fl| 006fd020 61 67 2e 70 6e 67 55 58 0c 00 0a 7d db 59 f5 7c |ag.pngUX...}.Y.|| 006fd030 db 59 f5 01 14 00 ec bc 07 5c 53 dd b6 2f 4a 93 |.Y.......\S../J.|
「50 4b 03 04」まで一致するのは0x006fd000番地にあります。上で計算したアドレスに似ています。きっと差分(1MB分くらい?)はGPTが持っていったのでしょう!
いらない部分を tail -c+N
で切り取って解析を進めます。
データの入ったクラスタの次のクラスタが0埋めされている事に気づきます。
00710fe0 74 b1 87 81 9b 0f ed 73 2b 92 7e 40 ec 91 e1 47 |t......s+.~@...G| 00710ff0 5c fe 2b 4b 55 4d 9f 3f 9f 05 a0 eb a7 d4 c4 ba |\.+KUM.?........| 00711000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| * 00712000 df 59 4c 7c 5a f8 da 12 96 6f 27 e7 e7 98 72 b5 |.YL|Z....o'...r.| 00712010 61 6d c3 65 ec 97 a4 28 65 d3 f5 f4 3a 9e d2 e6 |am.e...(e...:...|
つまり、そのまま読んでもダメで、うまくファイルを繋げ直さないといけない。 直列につなげようかとも思ったけれど、zipっぽいパートは16MBくらいの位置にあってダメそうだな、と思って寝ました。
2日目に入ってから、よく考えるとATを無視していることに気づきました。 ATには各クラスタが使えるかどうかが書いてあって、FAT16ではff f7は不正クラスタ、適当な値が書いてある時はそのクラスタに続く、ということらしいです。 ひとまずC言語(C++だけど)でATを1個ずつ出力します。
#include<cstdio> int main(){ FILE* file = fopen("AT.dmg", "r"); if(!file){ puts("read error."); return 1; } short n; short* nptr = &n; for(int i=0; fread(nptr, 2, 1, file); i++){ if (n > 0){ printf("%4d %d\n", i, n); } } }
0や負の値が多いので、正の値だけ表示すると255行でした。
仮定で求めたsizeは4096で割ると255弱なので、きっと問題なしです。
適当に tsort
を使って並べ替えてみると最初は1533番目(最初の仮定でメモしたオフセット)、最後は6202番目のクラスタということで、クラスタ6202を見に行くとzipの末尾のようなものがありました。
0193a6e0 81 b3 e4 0f 00 5f 5f 4d 41 43 4f 53 58 2f 2e 5f |.....__MACOSX/._| 0193a6f0 66 6c 61 67 2e 70 6e 67 55 58 08 00 0a 7d db 59 |flag.pngUX...}.Y| 0193a700 f5 7c db 59 50 4b 05 06 00 00 00 00 03 00 03 00 |.|.YPK..........| 0193a710 d2 00 00 00 32 e6 0f 00 00 00 00 00 00 00 00 00 |....2...........| 0193a720 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| *
ちなみに、途中に余計なルートが混じっていても tsort
では気づけないので、ちゃんとCで書き直してます。
あとはこんなC言語風味を書いて、
#include<cstdio> #include<iostream> char buf[4096*8192 + 1]; short ns[8192]; int main(){ FILE* fs_file = fopen("no-gpt.dmg", "r"); if(!fs_file){ puts("FS read error."); return 1; } fread(buf, 4096, 8192, fs_file); fclose(fs_file); FILE* at_file = fopen("AT.dmg", "r"); if(!at_file){ puts("AT read error."); return 1; } fread(ns, 2, 8192, at_file); fclose(at_file); for(int i = 1533; i > 0; i = ns[i]) { std::cerr << i << std::endl; fwrite(buf+(i*4096), 1, 4096, stdout); } }
解凍すると画像に鍵が。 正直、画像に鍵が隠されているとお手上げだなあと思ったのですが、最後は素直に鍵が出てきたので嬉しかったです。 あと、書くべきプログラムもとてもシンプルだったので、いるやんでも解くことができました。
問題のファイルは32MBとサイズが大きいのでここでは公開しませんが、いるやん含め参加者が持っています。