17歳からのプログラミング入門。

情報系の門の先で起きたことをたまにメモ。

高専セキュリティコンテスト Binary 500 OreNoFS

高専セキュリティコンテストでCTFに触れてきたのwrite upの分割です。

OreNoFSとは

f:id:Iruyan_Zak:20171022163611p:plain

高専セキュリティコンテストで1日目終了前に追加された500点問題で、32MB程度のオレオレファイルシステムの中からzipファイルを取り出す問題です。 バイナリ解析ツールなんて高尚なものは初挑戦で持ち合わせているはずもなく、 tail hexdumpC言語の知識だけで挑みました。

プロセス

まず、ファイルを 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とサイズが大きいのでここでは公開しませんが、いるやん含め参加者が持っています。