N=1

主にコンピュータ技術関連のことを投稿。 / 投稿は個人の意見であり所属団体の立場を代表するものではありません。

CSAW CTF Quals 2020 Write-up

https://ctf.csaw.io

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 で調べると、"ゼロ幅スペースステガノグラフィ"に関連するツールを見つけた。

github.com

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 へ。

するとフラグを得られる。