IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Python知识库 -> Python pickle 中的 POP 指令码 -> 正文阅读

[Python知识库]Python pickle 中的 POP 指令码

事情起源于笔者 2019 年的一篇文章:

从零开始python反序列化攻击:pickle原理解析 & 不用reduce的RCE姿势

前言 不经意间就起了这么长一个题目。昨晚用了4h左右的时间看皮ckle源码(主要是_Unpickler部分),发掘出了几种新的漏洞利用方式,目前网上暂未见资料,因此我们决定写一篇文章分享一下。本文详细介绍了pyckle.l...

正在上传…重新上传取消?知乎专栏阮行止上海洛谷网络科技有限公司 讲师

文中提到,注入恶意对象引发 RCE 之后,常常由于返回的反序列化对象不符合业务代码的预期,造成程序 crash。解决方案是使用 POP 指令码(字节码是数字 )把恶意对象弹出,再压一个正常的对象进去,这样 返回的就是普通的对象,以达成无副作用 RCE 的目的。如下图所示:0pickle.dumps

▲ 先构造恶意对象,再弹出,最后压入正常对象。绿色框内为恶意对象

然后今天收到私信,问 POP 这个指令码有什么用。

这个问题笔者当年确实没有想过。今天翻了一下源码,发现 POP 指令主要是为了防止无限递归构造对象的。下面我们根据 源码进行说明。pickler

0x00 Unpickler 对 POP 的处理方式

首先,我们知道 Unpickler 是一个图灵完备的虚拟机。它的指令编码方式如下:先是一个字节的 op code,然后紧跟操作数。至于操作数的表示方式,也是首先用一个字节表示类型,然后紧跟着操作数。这个虚拟机的语言大体上是一个 LL(1) 型文法,所以 Unpickler 仅通过简单地重复执行「读入一个字节的操作数 - 调用对应 handler」,就可以完成反序列化工作。

Unpickler 每次读入操作数,就查表找到 handler 并调用,handler 会吃掉一些字符,构造一个对象或进行其他操作。具体的 pickle 虚拟机操作码、Unpickler 工作方式可以去我的知乎文章查看。

POP 操作码对应的 op code 字节是 ,注释是 。0discard topmost stack item

乍一看,这个 POP 指令确实没啥用处。如果我先将一些对象压入栈,再弹出去,那我何不当初就不把这些对象压栈呢?抱着这样的疑问,笔者重新阅读了 Pickler 的源码,看看什么情况下会产生 POP 这个指令。

0x02 Pickler 何时会产生 POP 指令

查找产生 POP 指令的代码,一共有三处,分别是 、 、 方法。save_reducesave_tuplesave_frozenset

由于 是用于储存 方法,比较特殊,我们先看平凡的 方法在何时产生 POP 指令:reduce__reduce__save_tuple

<span style="color:#333333"><span style="color:#444444"><span style="background-color:#f6f6f6"><span style="color:#333333"><strong>def</strong></span> <span style="color:#880000"><strong>save_tuple</strong></span>(<span style="color:#333333"><strong>self</strong></span>, obj):
    <span style="color:#333333"><strong>if</strong></span> <span style="color:#333333"><strong>not</strong></span> <span style="color:#bc6060">obj:</span> <span style="color:#888888"># tuple is empty</span>
        <span style="color:#333333"><strong>if</strong></span> <span style="color:#333333"><strong>self</strong></span>.<span style="color:#bc6060">bin:</span>
            <span style="color:#333333"><strong>self</strong></span>.write(EMPTY_TUPLE)
        <span style="color:#bc6060">else:</span>
            <span style="color:#333333"><strong>self</strong></span>.write(MARK + TUPLE)
        <span style="color:#333333"><strong>return</strong></span>

    n = len(obj)
    save = <span style="color:#333333"><strong>self</strong></span>.save
    memo = <span style="color:#333333"><strong>self</strong></span>.memo
    <span style="color:#333333"><strong>if</strong></span> n <= <span style="color:#880000">3</span> <span style="color:#333333"><strong>and</strong></span> <span style="color:#333333"><strong>self</strong></span>.proto >= <span style="color:#880000">2</span>:
        <span style="color:#333333"><strong>for</strong></span> element <span style="color:#333333"><strong>in</strong></span> <span style="color:#bc6060">obj:</span>
            save(element)
        <span style="color:#888888"># Subtle.  Same as in the big comment below.</span>
        <span style="color:#333333"><strong>if</strong></span> id(obj) <span style="color:#333333"><strong>in</strong></span> <span style="color:#bc6060">memo:</span>
            get = <span style="color:#333333"><strong>self</strong></span>.get(memo[id(obj)][<span style="color:#880000">0</span>])
            <span style="color:#333333"><strong>self</strong></span>.write(POP * n + get)
        <span style="color:#bc6060">else:</span>
            <span style="color:#333333"><strong>self</strong></span>.write(_tuplesize2code[n])
            <span style="color:#333333"><strong>self</strong></span>.memoize(obj)
        <span style="color:#333333"><strong>return</strong></span>

    <span style="color:#888888"># proto 0 or proto 1 and tuple isn't empty, or proto > 1 and tuple</span>
    <span style="color:#888888"># has more than 3 elements.</span>
    write = <span style="color:#333333"><strong>self</strong></span>.write
    write(MARK)
    <span style="color:#333333"><strong>for</strong></span> element <span style="color:#333333"><strong>in</strong></span> <span style="color:#bc6060">obj:</span>
        save(element)

    <span style="color:#333333"><strong>if</strong></span> id(obj) <span style="color:#333333"><strong>in</strong></span> <span style="color:#bc6060">memo:</span>
        <span style="color:#888888"># Subtle.  d was not in memo when we entered save_tuple(), so</span>
        <span style="color:#888888"># the process of saving the tuple's elements must have saved</span>
        <span style="color:#888888"># the tuple itself:  the tuple is recursive.  The proper action</span>
        <span style="color:#888888"># now is to throw away everything we put on the stack, and</span>
        <span style="color:#888888"># simply GET the tuple (it's already constructed).  This check</span>
        <span style="color:#888888"># could have been done in the "for element" loop instead, but</span>
        <span style="color:#888888"># recursive tuples are a rare thing.</span>
        get = <span style="color:#333333"><strong>self</strong></span>.get(memo[id(obj)][<span style="color:#880000">0</span>])
        <span style="color:#333333"><strong>if</strong></span> <span style="color:#333333"><strong>self</strong></span>.<span style="color:#bc6060">bin:</span>
            write(POP_MARK + get)
        <span style="color:#bc6060">else:</span>   <span style="color:#888888"># proto 0 -- POP_MARK not available</span>
            write(POP * (n+<span style="color:#880000">1</span>) + get)
        <span style="color:#333333"><strong>return</strong></span>

    <span style="color:#888888"># No recursion.</span>
    write(TUPLE)
    <span style="color:#333333"><strong>self</strong></span>.memoize(obj)</span></span></span>

我们看这一段长注释:

这里有一个细节。当调用 时, 肯定不在 中。save_tupleid(obj)memo

(笔者注:如果 在 中,那么 Pickler 会选择生成GET指令码,让 Unpickler 直接从memo取出并压栈,而不是选择调用 生成构造 tuple 的指令序列,让 Unpickler 重新构建对象)id(obj)memosave_tuple(self, obj)

从而,可以推断出 是在本函数执行的过程中,已经被构造出来,并放进了 的。而现在我们又要构造一次 ,显然是产生了递归。objmemoobj

所以,现在应该把当前栈里的东西弹空,并要求 Unpickler 直接引用 中的实例,作为这个元组 反序列化的结果。memo

注释写得非常清楚明了,笔者也刷新了自己对 memo 的认识:?memo 不仅可以帮助 Unpickler 复用对象,还可以用于防无限递归!当检测到对象递归时,Pickler 会通过 POP 放弃自己在栈中生成的中间对象,并提示 Unpickler 采用 memo 中已经构造好的实例。

那么我们很容易构造一个对象,使得 Pickler 产出 POP 指令:

逐行解释一下指令序列。

  1. 泡菜协议是版本3
  2. 压入一个整数1
  3. 压入 ,注意这是 class 而非 instance,接下来要实例化这个类__main__.Pointer
  4. 将栈顶元素存进 (这是一个无用的 PUT)memo[0]
  5. 压入一个空的元组
  6. 以栈顶那个空的 tuple 作为参数,实例化栈内第二个元素(即 类)。完成后,栈里面只剩下一个初始的 对象PointerPointer
  7. 把栈顶元素(即这个 对象)存进Pointermemo[1]
  8. 压入一个空的 dict
  9. 将栈顶元素存进 (这是一个无用的 PUT)memo[2]

0x03 为什么 Pickler 不优化指令序列

一般情况下,Pickler 会将每个中间对象都存进 memo,所以指令序列中往往存在大量的 PUT,但这大部分的 PUT 都不会被 GET 引用,可以安全地删去( 可以完成这个任务)。另外,上文讨论过,如果产生了 POP 指令,说明肯定有一些压栈步骤是无效的,也应该可以抵消。那么,Pickler 为什么不做这些优化呢?pickletools.optimize()

下面我们分别讨论「删去无用 PUT」和「抵消 POP」这两种优化是否能实现。

在设计上,Pickler 被考虑需要用于序列化非常大的对象(实践上也确实如此,PyTorch、numpy 的导入导出便是采用了 pickle 来序列化动辄几个 GB 的对象)。Pickler 是将生成的指令序列写到一个 buffered file 里面( 是采用 这个 file-like 对象),且边构造边写入pickle.dumpsio.BytesIO

Pickler 对 file 的使用非常克制,一共只使用到了 这一个方法,连输出缓冲区 都是通过自己实现 来完成。笔者认为,这证明 Pickler 的设计者希望各种各样「可写入字节流的对象」都可以用于用于write_Framer

在这个基础上,能不能实现「删去无用的 PUT 指令」呢?

其次是性能问题。发现「递归的存在性」是简单的,但是要生成等效的简化指令序列,并不是一个轻松的工作。

  Python知识库 最新文章
Python中String模块
【Python】 14-CVS文件操作
python的panda库读写文件
使用Nordic的nrf52840实现蓝牙DFU过程
【Python学习记录】numpy数组用法整理
Python学习笔记
python字符串和列表
python如何从txt文件中解析出有效的数据
Python编程从入门到实践自学/3.1-3.2
python变量
上一篇文章      下一篇文章      查看所有文章
加:2022-01-16 13:00:43  更:2022-01-16 13:01:10 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/16 3:12:37-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码