Everything Silver writeup [AlpacaHack Round 4(Rev)]

問題

alpacahack.com

バイナリを実行するとflag?と聞かれ、適当に入力するとWrong...と言われます。

解法

このプログラムでは、forkで生成した子プロセスの動作をptraceで制御しています*1

子プロセスの処理

  1. 入力を受け取り、rdiにセット
  2. call raxでスタック上のint3命令で埋め尽くした領域に飛ぶ

親プロセスの処理 無限ループの中で、

  1. 子プロセスがint3を踏むのをwaitpidで待ち構える
  2. 子プロセスが正常終了していたらCorrect! と出力して終了
  3. 子プロセスがrip & 0xff == 0x1fを満たせば、

    3.1. raxが期待通りの値になっているか判定

    3.2. 判定に失敗したらWrong...と出力して終了

  4. 子プロセスのripが指す命令を動的に生成した命令で書き換え、子プロセスの実行を再開させる

親プロセスが生成する命令をデバッガで抽出して逆アセンブルすると以下のようになります。最後の0x7f156432d11fが3. のチェックポイントで、プログラムはここで落ちてしまいます。

0x7f156432d100 lea rsi, [rdi + 0x28]
0x7f156432d104 mov rdx, rdi

0x7f156432d107 mov rcx, rdx
0x7f156432d10a add rdx, 1
0x7f156432d10e mov al, byte [rdx]
0x7f156432d110 imul eax, eax, 0x47
0x7f156432d113 xor byte [rcx], al
0x7f156432d115 add byte [rcx], 0x35
0x7f156432d118 cmp rdx, rsi
0x7f156432d11b jne [rip-0x14]

0x7f156432d107 mov rcx, rdx
0x7f156432d10a add rdx, 1
0x7f156432d10e mov al, byte [rdx]
0x7f156432d110 imul eax, eax, 0x4a
0x7f156432d113 xor byte [rcx], al
0x7f156432d115 add byte [rcx], 0x35
0x7f156432d118 cmp rdx, rsi
0x7f156432d11b jne [rip-0x14]

(中略)

0x7f156432d107 mov rcx, rdx
0x7f156432d10a add rdx, 1
0x7f156432d10e mov al, byte [rdx]
0x7f156432d110 imul eax, eax, 0xffffffbc
0x7f156432d113 xor byte [rcx], al
0x7f156432d115 add byte [rcx], 0x35
0x7f156432d118 cmp rdx, rsi
0x7f156432d11b jne [rip-0x14]

0x7f156432d11d mov eax, dword [rdi]
0x7f156432d11f imul eax, eax, 0xdeadbeef

疑似コードにするとこんな感じです。

for (int i = 0; i < 0x28; i++) {
    int m = (int)((signed byte)(0x47 + 3*i));
    byte v = input[i+1] * m;
    input[i] = (input[i] xor v) + 0x35;
}
// このraxを親が覗き見て判定
int rax = (inputの最初の4byte) * 0xdeadbeef;   

親による判定はcmp命令でなされるので、ブレークポイントを貼れば目標とするraxの値は分かります。 さらにz3などで逆算*2するとinputの最初の5byteが分かり*3、これをflagとしてもう一度実行すればコードの続きを見ることができます。

0x7f156432d11f imul eax, eax, 0xdeadbeef
0x7f156432d125 add rdi, 4
0x7f156432d129 cmp rdi, rsi
0x7f156432d12c jne [rip-0xe]
0x7f156432d11d mov eax, dword [rdi]
0x7f156432d11f imul eax, eax, 0xdf6ebedd

今度はinputの次の4byteを使って同様の判定をしているようなので、先程と同様にしてinputの最初の9byteが計算できます。 これを繰り返せばflagが分かります。*4

最初の9byteを割り出すコード

radare2を使っています。 radare2 python scripting - r2wiki

solve.py

import r2pipe
import z3

def run():
    r = r2pipe.open(filename="silver")
    r.cmd('e dbg.profile=script.rr2')

    r.cmd("aaa")
    r.cmd("ood")
    r.cmd("dko 17 cont") 
    r.cmd('e log.level=1')
    r.cmd("dcu main+35") # after fork
    cmds = []
    while True:
        r.cmd("dcu main+921") # got rip
        rip = int(r.cmd("dr rax").strip(), base=16) - 1
        print(hex(rip))
        if rip == 0xffffffffffffffd9:
            break
        r.cmd("dcu main+1077") # calculated next op
        op = int(r.cmd("dr rax").strip(), base=16).to_bytes(8, "little").hex()
        cmd = r.cmd(f"!rasm2 -d {op}").splitlines()[0]
        print(f"cmd: {cmd}")
        cmds.append((rip, cmd))

        if rip & 0xff == 0x1f:
            r.cmd("dcu main+820") # cmp rdx, rax
            rdx = r.cmd("dr rdx").split()
            rax = r.cmd("dr rax").split()
            print(f"checking if target:{rdx} == rax:{rax}")

    for r, c in cmds:
        print(hex(r), c)

def solve():
    targets = [0x66993576, 0x3bd23991]
    xs = [z3.BitVec(f"n{i}", 32) for i in range(len(targets))]
    
    solver = z3.Solver()
    solver.add((xs[0] * 0xdeadbeef) == targets[0])
    solver.add((xs[1] * 0xdf6ebedd) == targets[1])

    solver.check()
    s = [(solver.model()[xs[i]].as_long()) for i in range(len(targets))]

    target = bytes()
    for t in s:
        target += t.to_bytes(4, "little")
    print(target.hex())

    muls = (list(range(0x47,0x7d+1,3)) + list(range(0xffffff80, 0xffffffbc+1, 3)))
    xs = [z3.BitVec(f"n{i}", 8) for i in range(len(target)+1)]
    solver = z3.Solver()
    for i in range(len(target)):
        v = ((xs[i] ^ ((xs[i+1] * muls[i]) )) + 0x35)
        print(v)
        solver.add(v == target[i])
    solver.check()
    m = solver.model()
    ans = bytes([(m[xs[i]].as_long()) for i in range(len(target)+1)])
    with open("stdin", "wb") as f:
        f.write(ans + b"a" * 0x32)
    print(solver.model())

solve()
run()

script.rr2

#!/usr/bin/rarun2
program=./silver
stdin=./stdin

*1:子プロセスのint3によるSIGTRAPを親プロセスが捕捉する仕組みについてはman ptrace(2)のSignal-delivery-stopの箇所などが詳しいです。

*2:自分で計算するなら、拡張GCDでx * 0xdeadbeef ≡ rax (mod 232) を満たすxを求めて、適当にinput[4]を決め打ちした上で input[0..=3] = x となるinput[0..=3]を求めることになります(多分)。

*3:条件を満たす5byteは複数あるので、ここで逆算した結果がflagの最初の5byteに一致するとは限りません。

*4:flagが2000byteとかあったらこの作業を500回繰り返す必要があって腕が破綻するので、もうちょっと賢いやり方があるかもしれない

NOiSE FEE CTF Challenge writeup

NOiSE FEE CTF Challenge に参加し、1問目と6問目を解いて350点取りました。時間内に解けた問題のwriteupを書きます。



注意:解法のネタバレを含みます。

















1. Process.Collect (50点)

(前略)
Q. FEE-NOiSEの画像を探し出し、通常と違う指を答えよ。

おなじみのhttp://chara.u-stella.co.jpを開いて、通常のフィーちゃんとノイズフィーちゃんで様子が違う指を答えます。

6. Process.Render (300点)

渡された画像からFlagを探し出す問題です。画像は妙に横長(1163x528)な上、何かが見切れており、写真の上半分だけを切り抜いたもののようにも見えます。

画像ファイルのサイズ情報が弄られて下半分が見えなくなっている可能性が最初に思い当たりました。もしそうならば、画像をバイナリファイルとして直接編集して高さの情報を書き換えればうまく行くはずです。

JPEGファイルの詳しいフォーマットは知りませんが、きっと 1163 や 528 という数値がそのまま書いてあるだろうと当たりをつけて、hexdumpしてみます。

> hexdump -C key.jpg | grep "02 10"
00000090  08 02 10 04 8b 03 01 22  00 02 11 01 03 11 01 ff  |......."........|
000228d0  ad 95 f8 75 de 3a 75 a7  47 6d 1a 11 b5 02 10 3f  |...u.:u.Gm.....?|

上の出力から、画像ファイルの0x91バイト目に02 10 04 8bという16進数の並びが存在することが分かります。0x0210=528, 0x048b=1163 なので、ここが画像サイズ情報で間違いなさそうです。ファイルをバイナリエディタで開いて、0x0210の方を適当な大きな値に書き換えてあげれば、めでたく画像(とても良い)が復元できます。

おわりに

残り時間1時間くらいのところで参加して、1問目と6問目を解いた後はGoogle MapとにらめっこしたりGitHubとにらめっこしたりしながら椅子を温めてました。OSINT力が足りない……! 

あとは、3問目の画像を6問目の方法で復元して「画像見てもわかんないなー」とかやっていたのですが、そんな必要全く無かったことに終わったあとで気づきました、不覚……

最後になりましたが、主催のDECLさん、楽しい問題をありがとうございました!

ubuntu impish のパッケージを focal に入れる

初心者が詰まったので一応残しとくやつ(初記事!)

経緯

やったこと

sudo apt-add-repository "deb http://cz.archive.ubuntu.com/ubuntu impish main"
sudo apt-add-repository "deb http://cz.archive.ubuntu.com/ubuntu impish main universe"
sudo apt update

sudo apt install --only-upgrade gdb
sudo apt install --only-upgrade gdbserver

sudo apt-add-repository --remove "deb http://cz.archive.ubuntu.com/ubuntu impish main"
sudo apt-add-repository --remove "deb http://cz.archive.ubuntu.com/ubuntu impish main universe"
sudo apt update

単純にimpishのリポジトリをsource.listに追加している
一度追加したリポジトリを削除しちゃっていいのかとかよく分かってない