最后更新于
最后更新于
此信息来源于 。
我们可以利用 LOAD_NAME / LOAD_CONST 操作码中的 OOB 读取功能来获取内存中的某些符号。这意味着可以使用类似 (a, b, c, ... 数百个符号 ..., __getattribute__) if [] else [].__getattribute__(...)
的技巧来获取您想要的符号(如函数名)。
然后只需精心制作您的利用程序。
源代码非常简短,仅包含 4 行!
您可以输入任意Python代码,它将被编译为。但是在评估该代码对象之前,该代码对象的co_consts
和co_names
将被替换为一个空元组。
因此,在这种情况下,所有包含常量(例如数字、字符串等)或名称(例如变量、函数)的表达式最终可能导致段错误。
段错误是如何发生的?
让我们从一个简单的例子开始,[a, b, c]
可以编译成以下字节码。
但如果co_names
变成空元组会怎么样?LOAD_NAME 2
操作码仍然会被执行,并尝试从原本应该在的内存地址读取值。是的,这是一种越界读取的“特性”。
解决方案的核心概念很简单。CPython中的一些操作码,例如LOAD_NAME
和LOAD_CONST
,对越界读取存在漏洞。
它们从consts
或names
元组中的索引oparg
中检索对象(这就是co_consts
和co_names
在底层的名称)。我们可以参考关于LOAD_CONST
的以下简短片段,了解CPython在处理到LOAD_CONST
操作码时的操作。
通过这种方式,我们可以使用OOB功能从任意内存偏移获取一个“name”。要确保它的名称和偏移量是什么,只需不断尝试LOAD_NAME 0
,LOAD_NAME 1
... LOAD_NAME 99
... 您可能会发现大约在oparg > 700时会找到一些内容。当然,您也可以尝试使用gdb查看内存布局,但我认为这样做可能不会更容易?
一旦我们检索到那些有用的名称/常量偏移,我们如何从该偏移获取一个名称/常量并使用它呢?这里有一个技巧:
假设我们可以从偏移5(LOAD_NAME 5
)中获取一个__getattribute__
名称,且co_names=()
,然后只需执行以下操作:
注意,不必将其命名为
__getattribute__
,您可以将其命名为更短或更奇怪的名称
您可以通过查看其字节码来理解其背后的原因:
注意LOAD_ATTR
也从co_names
中检索名称。如果名称相同,Python会从相同的偏移量加载名称,因此第二个__getattribute__
仍然从偏移量=5加载。利用这个特性,我们可以在内存附近使用任意名称。
对于生成数字应该是微不足道的:
0: not [[]]
1: not []
2: (not []) + (not [])
...
由于长度限制,我没有使用常量。
首先,这里是一个用于查找这些名称偏移量的脚本。
以下内容用于生成真正的Python利用。
它基本上执行以下操作,对于我们从__dir__
方法中获取的字符串:
如果您想看到您的公司在HackTricks中做广告或下载PDF格式的HackTricks,请查看!
获取
探索我们的独家[NFTs]收藏品,
加入 💬 或 或 关注我们的Twitter 🐦 。
通过向和 github仓库提交PR来分享您的黑客技巧。