Pickle反序列化初探
本文最后更新于 2026年5月18日 晚上
第三次纳新有一个pickle反序列化,现在我们来系统学习一下
魔术方法
1 | |
__init__:构造函数,创建对象时调用。__del__:析构函数,对象销毁时调用。__str__:转为字符串时调用。__reduce__:Pickle 序列化时调用。__reduce_ex__:__reduce__的新版本,会先于__reduce__调用,如果不存在才调用__reduce__。__setstate__:pickle模块中,反序列化时恢复对象状态。
重要内置函数
1 | |
Pickle
基本用法
在python中,我们使用pickle这样一个模块进行序列化和反序列化的操作
pickle.dump()pickle.load()pickle.dumps()pickle.loads()
其中两个dump函数是把python对象转换为二进制对象的,两个load函数是把二进制对象转换为python对象的.
而s函数是指对字符串进行反序列化和序列化操作,另外两个函数是对文件进行操作.
PVM 的核心:栈与存储
Pickle 的本质不是一种数据格式(像 JSON),而是一种基于栈的指令集语言。
PVM 运行的时候只有两个关键区域:
- 栈 (Stack):绝大多数指令都在这里发生。数据压入栈,指令弹出数据并处理。
- 备忘录 (Memo):一个索引数组,用来存储对象的引用。主要为了解决循环引用(比如一个列表里包含了它自己)。
常用操作码(Opcodes)详解
要实现手动编写 payload,你只需要掌握这几个核心指令:
| 指令 | 描述 | 具体写法 | 栈上的变化 |
|---|---|---|---|
| c | 获取一个全局对象或import一个模块 | c[module]\n[instance]\n | 获得的对象入栈 |
| o | 寻找栈中的上一个MARK,以之间的第一个数据(必须为函数)为callable,第二个到第n个数据为参数,执行该函数(或实例化一个对象) | o | 这个过程中涉及到的数据都出栈,函数的返回值(或生成的对象)入栈 |
| i | 相当于c和o的组合,先获取一个全局函数,然后寻找栈中的上一个MARK,并组合之间的数据为元组,以该元组为参数执行全局函数(或实例化一个对象) | i[module]\n[callable]\n | 这个过程中涉及到的数据都出栈,函数返回值(或生成的对象)入栈 |
| N | 实例化一个None | N | 获得的对象入栈 |
| S | 实例化一个字符串对象 | S’xxx’\n(也可以使用双引号、'等python字符串形式) | 获得的对象入栈 |
| V | 实例化一个UNICODE字符串对象 | Vxxx\n | 获得的对象入栈 |
| I | 实例化一个int对象 | Ixxx\n | 获得的对象入栈 |
| F | 实例化一个float对象 | Fx.x\n | 获得的对象入栈 |
| R | 选择栈上的第一个对象作为函数、第二个对象作为参数(第二个对象必须为元组),然后调用该函数 | R | 函数和参数出栈,函数的返回值入栈 |
| . | 程序结束,栈顶的一个元素作为pickle.loads()的返回值 | . | 无 |
| ( | 向栈中压入一个MARK标记 | ( | MARK标记入栈 |
| t | 寻找栈中的上一个MARK,并组合之间的数据为元组 | t | MARK标记以及被组合的数据出栈,获得的对象入栈 |
| ) | 向栈中直接压入一个空元组 | ) | 空元组入栈 |
| l | 寻找栈中的上一个MARK,并组合之间的数据为列表 | l | MARK标记以及被组合的数据出栈,获得的对象入栈 |
| ] | 向栈中直接压入一个空列表 | ] | 空列表入栈 |
| d | 寻找栈中的上一个MARK,并组合之间的数据为字典(数据必须有偶数个,即呈key-value对) | d | MARK标记以及被组合的数据出栈,获得的对象入栈 |
| } | 向栈中直接压入一个空字典 | } | 空字典入栈 |
| p | 将栈顶对象储存至memo_n | pn\n | 无 |
| g | 将memo_n的对象压栈 | gn\n | 对象被压栈 |
| 0 | 丢弃栈顶对象 | 0 | 栈顶对象被丢弃 |
| b | 使用栈中的第一个元素(储存多个属性名: 属性值的字典)对第二个元素(对象实例)进行属性设置 | b | 栈上第一个元素出栈 |
| s | 将栈的第一个和第二个对象作为key-value对,添加或更新到栈的第三个对象(必须为列表或字典,列表以数字作为key)中 | s | 第一、二个元素出栈,第三个元素(列表或字典)添加新值或被更新 |
| u | 寻找栈中的上一个MARK,组合之间的数据(数据必须有偶数个,即呈key-value对)并全部添加或更新到该MARK之前的一个元素(必须为字典)中 | u | MARK标记以及被组合的数据出栈,字典被更新 |
| a | 将栈的第一个元素append到第二个元素(列表)中 | a | 栈顶元素出栈,第二个元素(列表)被更新 |
| e | 寻找栈中的上一个MARK,组合之间的数据并extends到该MARK之前的一个元素(必须为列表)中 | e | MARK标记以及被组合的数据出栈,列表被更新 |
Pker工具
手写opcode操作码非常麻烦并且不好理解,有一个pker工具进行了封装,可以实现大部分的功能
pker支持这三种操作
- 变量赋值:
- 左值可以是变量名,dict或list的item,对象成员
- 右值可以是基础类型字面量,函数调用
- 函数调用
- return:可返回0~1个参数
pker内置了三种函数
1 | |
那么,INST和OBJ有什么区别???

return可以返回一个对象
1 | |
常见用法
下面介绍几种常见的打法以及如何用pker工具实现
我们来看一个完全没有保护的pickle反序列化
1 | |
打法一:基础 RCE
1 | |
1 | |
下面有三种pker的实现方式
1 | |
打法二:修改变量
可以参考第三次纳新
Bypass
Reference
Pickle反序列化初探
https://www.sunynov.top/2026/05/08/Pickle反序列化初探/