Daily AlpacaHack Writeup (2026-03-02 ~ 2026-03-8)
Daily AlpacaHack の Writeup.
(2026/3/2 - 2026/3/8)
Bars (Web, 2026/3/2)
- ページを開くとフラグの長い文字列が見切れた状態で表示されている
- しかしスクロールやキー入力が阻害される
- firefox のデフォルト機能でページのソースコードのみを表示するモードにしてフラグを獲得
glibc's secret function (Rev, 2026/3/3)
memfrobという関数が入力に対して掛けられ, 結果を保存された数列と比較している- man ページ より 42 を XOR する関数 (ジョークで作られた関数?) らしいので, 保存されている数列に 42 を XOR することでフラグが復元される
# memfrob: ^42 expected = [107, 70, 90, 75, 73, 75, 81, 126, 66, 67, 89, 117, 67, 89, 117, 94, 66, 79, 117, 0, 103, 115, 121, 126, 111, 120, 99, 101, 127, 121, 117, 75, 68, 78, 117, 125, 101, 100, 110, 111, 120, 108, 127, 102, 0, 117, 76, 95, 68, 73, 94, 67, 69, 68, 117, 67, 68, 117, 77, 70, 67, 72, 73, 11, 117, 107, 70, 89, 69, 117, 66, 69, 93, 117, 75, 72, 69, 95, 94, 117, 89, 94, 88, 76, 88, 83, 117, 76, 95, 68, 73, 94, 67, 69, 68, 21, 87] flag = "" for num in expected: flag += chr(num ^ 42) print(flag)
Flag Printer 2026 (Misc, 2026/3/4)
- サーバーのコードを読むとインデックスに応じた長さの待機時間を挟みながらフラグを出力する機能となっていた
- Dockerfile を読むと
socatコマンドのオプションに-T5とあり, 5 秒のタイムアウトが設定されていることが分かる - 3 秒おきに適当な文字 (処理しやすいため今回は空白) を送り気長に待つことでフラグ獲得
_, host, port = "nc 34.170.146.252 10951".split() sh = remote(host, port) flag = "" all_time = 0 sh.sendline() while all_time < 60: time.sleep(3) sh.send(" ".encode()) all_time += 3 print("total time:", all_time) sh.interactive()
Alert my Flag (Web, 2026/3/5)
- username を自由に送れるプログラムで, XSS で
alert(flag)を起動させたい - しかし
alertとflagという文字列がフィルタされる - JavaScript の実行自体は可能であるため
evalでalert, flagを動的に作成してフラグ獲得 - 以下を
usernameとして送る
<img src=a onerror='eval("ale"%2B"rt(fla"%2B"g)")'>
magic number (Misc, 2026/3/6)
- Python で
magic == 2508766360454420426020902195377847924746を True にできればフラグが獲得できるコード - magic に代入する部分はユーザー入力で指定可能だが,
execの設定でprint以外のビルトイン関数が使えず, また入力も 20 字以内でないといけない replaceで後からコード -> フラグの順で代入しているため,/*flag*/を文字列として先に組み込むことで/*flag*/の出力位置を制御できる- サーバーはエラーメッセージが読めない状態だが
print関数が使用可能であるため以下でフラグを表示できた
print("/*flag*/")
- 最初頑張って素因数分解などで 20 字以内に 2508766360454420426020902195377847924746 を表現できないか試したがそちらはうまくいかなかった
high and low (Crypto, 2026/3/7)
secretsライブラリを用いて初期化した乱数生成器で作成した乱数を用いてハイアンドローのゲームを行い, 1337 以上のお金を稼ぐ必要がある- 乱数は大きさ N の数列
stateから作成されており, 初期値は予測不可能 stateの他にpという状態を保持しているstateの更新部分について調べると,pがインデックスの役割になり, 毎回の乱数生成でstate[p]を更新したうえでpをインクリメントしているstateに代入される変数xについて循環シフトした数yを乱数として渡すため,yからxを予測可能になっている- 以上より, 毎回の乱数生成の際に
stateに代入される値も計算可能であるため,stateの長さ 624 回分乱数生成すると,stateのすべての状態が既知となる - よって 624 回乱数生成をさせて取得した
stateを用いて問題の乱数生成器を構築することで次の数字が予測可能となり, 賭けで勝てるようになる
import random from pwn import * class RNG: N = 624 M = 397 UPPER_MASK = 0x80000000 LOWER_MASK = 0x7FFFFFFF def __init__(self, init_state): self.state = init_state self.p = 0 def next_value(self): p, q, r = self.p, (self.p+1) % self.N, (self.p + self.M) % self.N a = self.state[p] & self.UPPER_MASK b = self.state[q] & self.LOWER_MASK x = (a | b) ^ self.state[r] self.state[p] = x self.p = q y = ((x >> 11) | ((x << 21) & 0xFFFFF800)) ^ 0xDEADBEEF return y def x_from_y(y): t = y ^ 0xDEADBEEF return ((t << 11) | (t >> 21)) & 0xffffffff _, host, port = "nc 34.170.146.252 6881".split() sh = remote(host, port) N = 624 state_list = [] itr_idx = 0 while len(state_list) < N: print("itr:", itr_idx) itr_idx += 1 prompt = sh.recvuntil("money:".encode()) print(prompt.decode()) money = int(sh.recvline().decode().strip()) prompt = sh.recvuntil("value:".encode()) value = int(sh.recvline().decode().strip()) print("value:", value) state_list.append(x_from_y(value)) prompt = sh.recvuntil("low?".encode()) sh.sendline("h".encode()) prompt = sh.recvuntil("next:".encode()) value = int(sh.recvline().decode().strip()) print("next_val:", value) state_list.append(x_from_y(value)) rng = RNG(init_state=state_list) money = 0 while True: prompt = sh.recvuntil("money:".encode()) print(prompt.decode()) money = int(sh.recvline().decode().strip()) print("money:", money) if money > 1337: sh.interactive() exit() prompt = sh.recvuntil("value:".encode()) value = int(sh.recvline().decode().strip()) print("value:", value, "gen_val:", rng.next_value()) prompt = sh.recvuntil("low?".encode()) next_value = rng.next_value() if value < next_value: sh.sendline("h".encode()) else: sh.sendline("l".encode()) prompt = sh.recvuntil("next:".encode()) value = int(sh.recvline().decode().strip()) print("next_value:", value, "gen_value:", next_value)
guess.js (Misc, 2026/3/8)
- 入力
guessに対して以下のように関数が作成され引数1337で実行される
Function( guess, ` if (${SECRET} === 1337) { return process.env.FLAG; } else { return "Failed..."; } `, )(1337),
SECRETは1337ではないため基本的に失敗するguessは第一引数 (デフォルト引数) として渡されるため, 一つの引数を渡すだけでは1337に上書きされて終わるguessにカンマ区切りで複数のキーワードを指定することで, 複数のデフォルト引数を指定することが可能 (以下が例)
Function( "x=1,y=2", "console.log(x+y);", )() // OUTPUT -> 3
- また Javascript では引数の値に
console.logを含めることでスコープ外の変数にアクセス可能であるため, 以下が成立
secret = "FLAG!"; guess = "x=1,y=(console.log(secret))"; Function( guess, "return 1;", )(1337) // OUTPUT -> FLAG!
- 以上より,
1337で上書きされる引数xとログ出力を含む引数yを作ることでフラグが獲得できる - ペイロードは以下
x=1,y=(console.log(process.env.FLAG))