CSAW CTF Quals 2020 Write-up
CTFはバイナリやセキュリティの知識を持たずに遊びで参加しているので、正攻法では無さそうな解法をしてしまっている。しかも最低点の問題を解くのがやっと。問題を始めてから勉強しているので注意。
rev, baby_mult
progam.txt として以下のテキストが与えられる
85, 72, 137, 229, 72, 131, 236, 24, 72, 199, 69, 248, 79, 0, 0, 0, 72, 184, 21, 79, 231, 75, 1, 0, 0, 0, 72, 137, 69, 240, 72, 199, 69, 232, 4, 0, 0, 0, 72, 199, 69, 224, 3, 0, 0, 0, 72, 199, 69, 216, 19, 0, 0, 0, 72, 199, 69, 208, 21, 1, 0, 0, 72, 184, 97, 91, 100, 75, 207, 119, 0, 0, 72, 137, 69, 200, 72, 199, 69, 192, 2, 0, 0, 0, 72, 199, 69, 184, 17, 0, 0, 0, 72, 199, 69, 176, 193, 33, 0, 0, 72, 199, 69, 168, 233, 101, 34, 24, 72, 199, 69, 160, 51, 8, 0, 0, 72, 199, 69, 152, 171, 10, 0, 0, 72, 199, 69, 144, 173, 170, 141, 0, 72, 139, 69, 248, 72, 15, 175, 69, 240, 72, 137, 69, 136, 72, 139, 69, 232, 72, 15, 175, 69, 224, 72, 15, 175, 69, 216, 72, 15, 175, 69, 208, 72, 15, 175, 69, 200, 72, 137, 69, 128, 72, 139, 69, 192, 72, 15, 175, 69, 184, 72, 15, 175, 69, 176, 72, 15, 175, 69, 168, 72, 137, 133, 120, 255, 255, 255, 72, 139, 69, 160, 72, 15, 175, 69, 152, 72, 15, 175, 69, 144, 72, 137, 133, 112, 255, 255, 255, 184, 0, 0, 0, 0, 201
127 以上の値があるから、単純な ASCII として読めるものではないのは確かだ。
const src = [85, 72, 137, 229, 72, 131, 236, 24, 72, 199, 69, 248, 79, 0, 0, 0, 72, 184, 21, 79, 231, 75, 1, 0, 0, 0, 72, 137, 69, 240, 72, 199, 69, 232, 4, 0, 0, 0, 72, 199, 69, 224, 3, 0, 0, 0, 72, 199, 69, 216, 19, 0, 0, 0, 72, 199, 69, 208, 21, 1, 0, 0, 72, 184, 97, 91, 100, 75, 207, 119, 0, 0, 72, 137, 69, 200, 72, 199, 69, 192, 2, 0, 0, 0, 72, 199, 69, 184, 17, 0, 0, 0, 72, 199, 69, 176, 193, 33, 0, 0, 72, 199, 69, 168, 233, 101, 34, 24, 72, 199, 69, 160, 51, 8, 0, 0, 72, 199, 69, 152, 171, 10, 0, 0, 72, 199, 69, 144, 173, 170, 141, 0, 72, 139, 69, 248, 72, 15, 175, 69, 240, 72, 137, 69, 136, 72, 139, 69, 232, 72, 15, 175, 69, 224, 72, 15, 175, 69, 216, 72, 15, 175, 69, 208, 72, 15, 175, 69, 200, 72, 137, 69, 128, 72, 139, 69, 192, 72, 15, 175, 69, 184, 72, 15, 175, 69, 176, 72, 15, 175, 69, 168, 72, 137, 133, 120, 255, 255, 255, 72, 139, 69, 160, 72, 15, 175, 69, 152, 72, 15, 175, 69, 144, 72, 137, 133, 112, 255, 255, 255, 184, 0, 0, 0, 0, 201]; const hex = src.map(e => '\\x' + e.toString(16).padStart(2, '0')).join(''); console.log(hex);
hex 表記にしてみる。
\x55\x48\x89\xe5\x48\x83\xec\x18\x48\xc7\x45\xf8\x4f\x00\x00\x00\x48\xb8\x15\x4f\xe7\x4b\x01\x00\x00\x00\x48\x89\x45\xf0\x48\xc7\x45\xe8\x04\x00\x00\x00\x48\xc7\x45\xe0\x03\x00\x00\x00\x48\xc7\x45\xd8\x13\x00\x00\x00\x48\xc7\x45\xd0\x15\x01\x00\x00\x48\xb8\x61\x5b\x64\x4b\xcf\x77\x00\x00\x48\x89\x45\xc8\x48\xc7\x45\xc0\x02\x00\x00\x00\x48\xc7\x45\xb8\x11\x00\x00\x00\x48\xc7\x45\xb0\xc1\x21\x00\x00\x48\xc7\x45\xa8\xe9\x65\x22\x18\x48\xc7\x45\xa0\x33\x08\x00\x00\x48\xc7\x45\x98\xab\x0a\x00\x00\x48\xc7\x45\x90\xad\xaa\x8d\x00\x48\x8b\x45\xf8\x48\x0f\xaf\x45\xf0\x48\x89\x45\x88\x48\x8b\x45\xe8\x48\x0f\xaf\x45\xe0\x48\x0f\xaf\x45\xd8\x48\x0f\xaf\x45\xd0\x48\x0f\xaf\x45\xc8\x48\x89\x45\x80\x48\x8b\x45\xc0\x48\x0f\xaf\x45\xb8\x48\x0f\xaf\x45\xb0\x48\x0f\xaf\x45\xa8\x48\x89\x85\x78\xff\xff\xff\x48\x8b\x45\xa0\x48\x0f\xaf\x45\x98\x48\x0f\xaf\x45\x90\x48\x89\x85\x70\xff\xff\xff\xb8\x00\x00\x00\x00\xc9
最初の 0x55 は関数の開始である push pbx として頻出なので、x86 系の命令と推察できる。さらに、0x48 がよく出てくる。これは x86_64 において 64bit 命令の場合のプレフィックスなので、x86_64 のバイナリの断片である可能性が高い。
さて、これをどうやって実行可能にしようか。
- シェルコードとして exploit するテスターを作る?
- このままリンカに通せないか?
- ゴリ押しで読んで理解する?
残念ながら私は pwn の知識も、シェルコードの知識も無い。
(1) シェルコードとして exploit するテスターを作る?
ウェブから shell code tester みたいな検索をしてシェルコードとして実行してみたが、segmentation fault になってしまう。
(2) このままリンカに通せないか?
一旦 disassemble して、それっぽいラベルをつけておいた。実行が終わる前のところにブレークポイントをつけれるようにもした。
https://onlinedisassembler.com/odaweb/
program.s
.globl main .type main, @function main: push %rbp mov %rsp,%rbp sub $0x18,%rsp movq $0x4f,-0x8(%rbp) movabs $0x14be74f15,%rax mov %rax,-0x10(%rbp) movq $0x4,-0x18(%rbp) movq $0x3,-0x20(%rbp) movq $0x13,-0x28(%rbp) movq $0x115,-0x30(%rbp) movabs $0x77cf4b645b61,%rax mov %rax,-0x38(%rbp) movq $0x2,-0x40(%rbp) movq $0x11,-0x48(%rbp) movq $0x21c1,-0x50(%rbp) movq $0x182265e9,-0x58(%rbp) movq $0x833,-0x60(%rbp) movq $0xaab,-0x68(%rbp) movq $0x8daaad,-0x70(%rbp) mov -0x8(%rbp),%rax imul -0x10(%rbp),%rax mov %rax,-0x78(%rbp) mov -0x18(%rbp),%rax imul -0x20(%rbp),%rax imul -0x28(%rbp),%rax imul -0x30(%rbp),%rax imul -0x38(%rbp),%rax mov %rax,-0x80(%rbp) mov -0x40(%rbp),%rax imul -0x48(%rbp),%rax imul -0x50(%rbp),%rax imul -0x58(%rbp),%rax mov %rax,-0x88(%rbp) mov -0x60(%rbp),%rax imul -0x68(%rbp),%rax imul -0x70(%rbp),%rax mov %rax,-0x90(%rbp) mov $0x0,%eax break: leaveq
Linux で $ gcc program.sとすれば a.out として一応実行可能にはなるが、出力等は特にない。gdb でレジスタをみたけどよくわからなかった。
(3) ゴリ押しで読んで理解する?
普通の CTF 慣れした人とは全然違うアプローチになってしまうが、そのまま読んでみた。x86 のことも全然知らなかったので勉強した。
命令一覧
- push op
- rsp をスタックを積む方向に動かす(デクリメント)
- op の値をそこに格納
- mov op1, op2
- op1 => op2
- movq
- mov と同じだが 64bit であることを明示
- movabs
- これも、オペランドが64bitのときにGASが付与した名前であり、mov と同じ
- sub op1, op2
- op2 - op1 => op1
- imul op1, op2
- op1 * op2 => op1
- leaveq
- rsp => rbp
- rbp を pop
レジスタ一覧
- rbp: 64bit Base Pointer
- rsp: 64bit Stack Pointer
- rax: 64bit Accumulator
- eax: 32bit Accumulator
- X: eXtend 16bit
- アキュムレータは8bit CPU では AL だったが、16bit になった際に AX になった
- E: Enhanced 32bit
- x86 では AX から EAX になった
- R: Register 64bit
- x86_64 では EAX から RAX になった
記号
- %: レジスタの前につける
- $: 定数の前につける
BP, SP, push の動き
知識が揃ったところで読んでいった。手作業で計算した。結果はこうなった。
レジスタ rbp: rsp の値 rax: 306772346d7d スタック 元の rbp の値 <- --底部-- メモリ rsp-0x90: 306772346d7d rsp-0x88: 6c31645f7072 rsp-0x80: 73757033725f7634 rsp-0x78: 666c61677b rsp-0x70: 8daaad rsp-0x68: aab rsp-0x60: 833 rsp-0x58: 182265e9 rsp-0x50: 21c1 rsp-0x48: 11 rsp-0x40: 2 rsp-0x38: 77cf4b645b61 rsp-0x30: 115 rsp-0x28: 13 rsp-0x20: 3 rsp-0x18: 4 rsp-0x10: 14be74f15 rsp-0x8: 4f rsp: ?
imulで計算した結果を保存しているらしき -0x78 ~ -0x90 を抽出するとこうなる
666c61677b
73757033725f7634
6c31645f7072
306772346d7d
ASCII として読むとフラグになっている。
正攻法は、シェルコードとして実行して segfault か何かで止まったところを gdb でメモリ確認するといいのかもしれないが、まだ私にはできなかった。
web, widthless
http://web.chal.csaw.io:5018/ が与えられている。
ML 購読フォームがある、作りかけのサイトという体裁。
- Email の POST のフォームは HTTP としては動作するが特に進展しない。csrf_token が設定されている。
- レスポンスヘッダにはクッキーがある程度で怪しいところは無い。
ソースを読むとヒントと思われるメッセージがあった。
<!-- zwsp is fun! -->
また、developer tool で見ると、body 下部に #8203; などの特殊文字がぎっしり入っていた。
zwsp で調べると、"ゼロ幅スペースステガノグラフィ"に関連するツールを見つけた。
HTML 全体をこのツールに通す
const fs = require('fs'); const zwspSteg = require('zwsp-steg'); const html = fs.readFileSync('index.html', 'utf-8'); const decoded = zwspSteg.decode(html); console.log(decoded);
YWxtMHN0XzJfM3o= が得られた。base64 でデコードすると、alm0st_2_3zとなる。これをフォームに入力すると新たな本文が追加される。
/ahsdiufghawuflkaekdhjfaldshjfvbalerhjwfvblasdnjfbldf/<pwd>
pwd は alm0st_2_3z のことだと判断し、http://web.chal.csaw.io:5018/ahsdiufghawuflkaekdhjfaldshjfvbalerhjwfvblasdnjfbldf/alm0st_2_3z へアクセスしてみる。すると今度は HTML 全体にゼロ幅スペースが散りばめられていた。
また zwsp-steg で処理を試みるも、ブラウザでレンダーされたものをコピペしてもうまくデコードできなかった。きちんと curl で無加工のペイロードを取ってくる。
$ curl http://web.chal.csaw.io:5018//ahsdiufghawuflkaekdhjfaldshjfvbalerhjwfvblasdnjfbldf/alm0st_2_3z -o index.html
zwsp-steg へまた通すと、755f756e6831645f6d33 が得られた。2桁ずつ ASCII 読みすると u_unh1d_m3 となる。
これをフォームで送信すると次のヒントが出る。
/19s2uirdjsxbh1iwudgxnjxcbwaiquew3gdi/<pwd1>/<pwd2>
pwd1, pwd2 はこれまで得た文字列だと判断し、
http://web.chal.csaw.io:5018//19s2uirdjsxbh1iwudgxnjxcbwaiquew3gdi/alm0st_2_3z/u_unh1d_m3 へ。
するとフラグを得られる。