0x00 TL; DR

本文将通过分析今年几道通过手写Pickle opcode实现bypass的题目总结手撕Pickle的一些tips

以及通过遍历Python AST自动化生成Pickle opcode

repo地址https://github.com/eddieivan01/pker

0x01 Pickle简述

网上资料已经很多了,我就不再向互联网填充冗余信息了(可学习文末链接)

只总结Pickle的几个特点:

  • 非图灵完备的栈语言,没有运算、循环、条件分支等结构
  • 可以实现的操作
    • 构造Python内置基础类型(str, int, float, list, tuple, dict
    • dictlist成员的赋值(无法直接取值)
    • 对象成员的赋值(无法直接取值)
    • callable对象的调用
    • 通过_Pickler.find_class导入模块中的某对象,find_class的第一个参数可以是模块或包,本质是getattr(__import__(module), name)
    • 版本保持向下兼容,通过opcode头解析版本
    • 0号protocol使用\n作操作数的分割

0x02 Opcode简述

Pickle常见opcode,完整的可在$PYTHON/Lib/pickle.py查看

name op params describe e.g.
MARK ( null 向栈顶push一个MARK
STOP . null 结束
POP 0 null 丢弃栈顶第一个元素
POP_MARK 1 null 丢弃栈顶到MARK之上的第一个元素
DUP 2 null 在栈顶赋值一次栈顶元素
FLOAT F F [float] push一个float F1.0
INT I I [int] push一个integer I1
NONE N null push一个None
REDUCE R [callable] [tuple] R 调用一个callable对象 crandom\nRandom\n)R
STRING S S [string] push一个string S 'x'
UNICODE V V [unicode] push一个unicode string V 'x'
APPEND a [list] [obj] a 向列表append单个对象 ]I100\na
BUILD b [obj] [dict] b 添加实例属性(修改__dict__ cmodule\nCls\n)R(I1\nI2\ndb
GLOBAL c c [module] [name] 调用Pickler的find_class,导入module.name并push到栈顶 cos\nsystem\n
DICT d MARK [[k] [v]...] d 将栈顶MARK以前的元素弹出构造dict,再push回栈顶 (I0\nI1\nd
EMPTY_DICT } null push一个空dict
APPENDS e [list] MARK [obj...] e 将栈顶MARK以前的元素append到前一个的list ](I0\ne
GET g g [index] 从memo获取元素 g0
INST i MARK [args...] i [module] [cls] 构造一个类实例(其实等同于调用一个callable对象),内部调用了find_class (S'ls'\nios\nsystem\n
LIST l MARK [obj] l 将栈顶MARK以前的元素弹出构造一个list,再push回栈顶 (I0\nl
EMPTY_LIST ] null push一个空list
OBJ o MARK [callable] [args...] o 同INST,参数获取方式由readline变为stack.pop而已 (cos\nsystem\nS'ls'\no
PUT p p [index] 将栈顶元素放入memo p0
SETITEM s [dict] [k] [v] s 设置dict的键值 }I0\nI1\ns
TUPLE t MARK [obj...] t 将栈顶MARK以前的元素弹出构造tuple,再push回栈顶 (I0\nI1\nt
EMPTY_TUPLE ) null push一个空tuple
SETITEMS u [dict] MARK [[k] [v]...] u 将栈顶MARK以前的元素弹出update到前一个dict }(I0\nI1\nu

对应的实现都可以在pickle._Unpicklerload_*成员函数中查看,选取两个常见的:

pop_mark为将MARK(上的所有元素弹出为一个list,然后push回栈。所以需要这样构造(I0\nI1\nl

读取到INST指令后,往后读两个操作数,调用find_class,然后弹出栈上MARK以上的参数,调用callable对象实例化,所以这样构造(S'ls'\nios\nsystem\n.

0x03 官方的Demo限制了什么

官方给出的安全反序列化是继承了pickle.Pickler类,并重载了find_class方法

父类原本的操作是把module导入sys.module缓存中(并未导入全局或局部作用域),然后getattr取值,所以重载该方法后即可对module和name进行限制

哪些操作符会调用find_class

GLOBAL:c
INST  :i
点击收藏 | 3 关注 | 3
  • 动动手指,沙发就是你的了!
登录 后跟帖