前言
参考:
https://zhuanlan.zhihu.com/p/637003652
生成器
生成器(Generator)是 Python 中一种特殊的迭代器,它可以通过简单的函数和表达式来创建。
生成器的主要特点是能够逐个产生值,并且在每次生成值后保留当前的状态,以便下次调用时可以继续生成值。这使得生成器非常适合处理大型数据集或需要延迟计算的情况。
在 Python 中,生成器可以使用yield
关键字来定义。yield 用于产生一个值,并在保留当前状态的同时暂停函数的执行。当下一次调用生成器时,函数会从上次暂停的位置继续执行,直到遇到下一个 yield 语句或者函数结束。
def f():
a=1
while True:
yield a
a+=1
f=f()
print(next(f)) #输出1
print(next(f)) #输出2
print(next(f)) #输出3
什么意思呢?这里实现了惰性计算,即仅在需要时才生成值,而不是一次性生成整个序列
生成器表达式
一种在 Python 中创建生成器的紧凑形式,类似于列表推导式
语法与列表推导式([表达式 for 变量 in 列表]
)类似,但是使用圆括号而不是方括号
生成器表达式会逐个生成值,而不是一次性生成整个序列,这样可以节省内存空间,特别是在处理大型数据集时非常有用
在一个有逻辑范围的情况下下可以通过生成器表达式去实现,如计数1-100:
a=(i+1 for i in range(100))
#next(a)
for value in a:
print(value)
生成器的属性
gi_code
:生成器对应的 code 对象gi_frame
:生成器对应的 frame(栈帧)对象gi_running
:生成器函数是否在执行。生成器函数在yield以后、执行yield的下一行代码前处于frozen状态,此时这个属性的值为0gi_yieldfrom
:如果生成器正在从另一个生成器中 yield 值,则为该生成器对象的引用;否则为 Nonegi_frame.f_locals
:一个字典,包含生成器当前帧的本地变量
对于gi_frame
,这是一个与生成器(generator)和协程(coroutine)相关的属性。它指向生成器或协程当前执行的帧对象(frame object),如果这个生成器或协程正在执行的话。帧对象表示代码执行的当前上下文,包含了局部变量、执行的字节码指令等信息。
使用生成器的gi_frame
属性来获取生成器的当前帧信息:
def my_generator():
yield 1
yield 2
yield 3
gen = my_generator()
# 获取生成器的当前帧信息
frame = gen.gi_frame
# 输出生成器的当前帧信息
print("Local Variables:", frame.f_locals)
print("Global Variables:", frame.f_globals)
print("Code Object:", frame.f_code)
print("Instruction Pointer:", frame.f_lasti)
栈帧
栈帧(stack frame)是Python虚拟机执行的载体之一,它是一个数据结构,用于存储代码运行时的数据
每个栈帧都有自己的局部变量、操作数栈、异常处理信息等
栈帧是一个栈结构,每个栈帧都有一个指向上一个栈帧的指针,这样就形成了一个栈帧链
栈帧的创建和销毁是由Python虚拟机自动完成的,当一个函数被调用时,就会创建一个栈帧,当函数返回时,就会销毁这个栈帧
其数据结构:
typedef struct _frame PyFrameObject;
struct _frame {
PyObject_VAR_HEAD
struct _frame *f_back; /* previous frame, or NULL 前一个栈帧 */
PyCodeObject *f_code; /* code segment */ // 代码段
PyObject *f_builtins; /* builtin symbol table (PyDictObject) 内置符号表,字典 */
PyObject *f_globals; /* global symbol table (PyDictObject) 全局符号表 字典 */
PyObject *f_locals; /* local symbol table (any mapping) 当前作用域 映射 */
PyObject **f_valuestack; /* points after the last local */ // 指向最后一个局部变量的后面
PyObject *f_trace; /* Trace function */ // 跟踪函数
int f_stackdepth; /* Depth of value stack */ // 值栈深度
char f_trace_lines; /* Emit per-line trace events? */ // 每行跟踪事件
char f_trace_opcodes; /* Emit per-opcode trace events? */ // 每个操作码跟踪事件
/* Borrowed reference to a generator, or NULL */
PyObject *f_gen; // 生成器
int f_lasti; /* Last instruction if called */ // 最后一条指令
int f_lineno; /* Current line number. Only valid if non-zero */ // 当前行号
int f_iblock; /* index in f_blockstack */ // f_blockstack的索引
PyFrameState f_state; /* What state the frame is in 状态 */ // 栈帧的状态
PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */ // try 和 loop 块,长度为CO_MAXBLOCKS=20
PyObject *f_localsplus[1]; /* locals+stack, dynamically sized */ // 局部变量和栈,动态地长度
};
这里我们直接看几个关键的属性:
f_locals
:一个字典,包含了函数或方法的局部变量。键是变量名,值是变量的值f_globals
:一个字典,包含了函数或方法所在模块的全局变量。键是全局变量名,值是变量的值f_code
:一个代码对象(code object),包含了函数或方法的字节码指令、常量、变量名等信息f_lasti
:整数,表示最后执行的字节码指令的索引f_back
:指向上一级调用栈帧的引用,用于构建调用栈
利用栈帧沙箱逃逸
原理:通过生成器的栈帧对象通过f_back
(返回前一帧)从而逃逸出去获取globals
全局符号表
demo:
s3cret="this is flag"
codes='''
def waff():
def f():
yield g.gi_frame.f_back
g = f() #生成器
frame = next(g) #获取到生成器的栈帧对象
print(frame)
print(frame.f_back)
print(frame.f_back.f_back)
b = frame.f_back.f_back.f_globals['s3cret'] #返回并获取前一级栈帧的globals
return b
b=waff()
'''
locals={}
code = compile(codes, "test", "exec")
exec(code,locals)
print(locals["b"])
类似于nodejs原型链污染里的__proto__
,逐步往外取得栈帧,从而得到外部的全局globals
,最终拿到1.py里的全局变量s3cret
这里也可以使用f_locals
去代替f_globals
效果是相同的,但是要注意,locals
返回的是局部符号表,它包含了在当前函数或方法内部定义的变量。这些局部变量只在当前函数或方法的执行过程中存在,并且只能在该函数或方法内部访问。当函数执行完毕后,这些局部变量就会被销毁。