初学Python PIckle

初学Python PIckle


前言

前几天比赛碰到没学过的知识点了,补一波,自己还是太菜了

Pickle:Python序列化、反序列化

pickle.dumps( ) == 序列化(将一个对象打包成一堆看不懂的字符串)

1
2
3
4
5
6
7
8
9
10
11
12
import pickle
import pickletools
import os
class test():
def __init__(self):
self.text = "admin"
self.data = 123456
self.dir = ["CTF","egg","dkk"]
payload = test()
print(pickle.dumps(payload))

# b'\x80\x03c__main__\ntest\nq\x00)\x81q\x01}q\x02(X\x04\x00\x00\x00textq\x03X\x05\x00\x00\x00adminq\x04X\x04\x00\x00\x00dataq\x05J@\xe2\x01\x00X\x03\x00\x00\x00dirq\x06]q\x07(X\x03\x00\x00\x00CTFq\x08X\x03\x00\x00\x00eggq\tX\x03\x00\x00\x00dkkq\neub.'

pickle.loads( ) == 反序列化 (将一堆看不懂的字符串转化为对象)

1
2
3
4
5
6
7
8
9
10
11
12
import pickle
import pickletools
class test():
def __init__(self):
self.text = "admin"
self.data = 123456
self.dir = ["CTF","egg","dkk"]
payload = test()
size = pickle.dumps(payload)
print(str(pickle.loads(size).__dict__))

# {'text': 'admin', 'data': 123456, 'dir': ['CTF', 'egg', 'dkk']}

pickle.loads机制

1
2
import pickle
pickle.__file__# 查看pickle源码位置

pickle.loads是一个供我们调用的接口。其底层实现是基于_Unpickler类。代码实现如下

再看_Unpickler的源码,好像是对栈的一些操作,俺也看不太懂,溜了溜了

pickletools调试

基本操作

pickletools是python自带的pickle调试器,有三个功能:
pickletools.dis(pickle, out=None, memo=None, indentlevel=4)反汇编一个已经被打包的字符串、
pickletools.optimize(picklestring)优化一个已经被打包的字符串、
pickletools.genops(pickle)返回一个迭代器来供程序使用。

反汇编结果中,BINPUT指令没有了,所谓“优化”,其实就是把不必要的PUT指令给删除掉。这个PUT意思是把当前栈的栈顶复制一份,放进储存区,我们并不需要这个操作,可以省略掉这些PUT指令.

pickle:语法严格、向前兼容

pickle构造出的字符串,有很多个版本。在pickle.loads时,可以用Protocol参数指定协议版本,例如指定为0号版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import pickle
import pickletools
import os
class test():
def __init__(self):
self.text = "admin"
self.data = 123456
self.dir = ["CTF","egg","dkk"]
payload = test()
dumps = (pickle.dumps(payload,protocol=0))
dumps = pickletools.optimize(dumps)
pickletools.dis(dumps)

# b'ccopy_reg\n_reconstructor\np0\n(c__main__\ntest\np1\nc__builtin__\nobject\np2\nNtp3\nRp4\n(dp5\nVtext\np6\nVadmin\np7\nsVdata\np8\nI123456\nsVdir\np9\n(lp10\nVCTF\np11\naVegg\np12\naVdkk\np13\nasb.'

可以看到与上面的序列化结果(默认版本号:3)有所区别

0号起码还是可读的,之后的版本加入了一大堆不可打印字符

pickle协议是向前兼容的
所以可以使用0号版本的字符串可以直接交给pickle.loads()

举一个🌰


第一个字节\x80 源码注释#identify pickle protocol机器看到这个操作符,立刻再去字符串读取一个字节,得到\x03。解释为“这是一个3号协议序列化的字符串”。
第二个字节c它连续读取两个字符串module和name(用\n分割),接下来把module.name这个东西压进栈。我们这里就是把__main__.test放进栈里
第三个字节) push empty tuple
第四个字节\x81 从栈中先弹出一个元素,记为args;再弹出一个元素,记为cls。接下来,执行cls.__new__(cls, *args),然后把得到的东西进栈,也就是我们实例化的对象test放进栈
第五个字节}push empty dict
第六个字节Xcounted UTF-8 string argument
第七个字节]push empty list 把当前栈这个整体,作为一个list,压进前序栈,把当前栈清空。这个操作也叫load_mark
与它相反的另一个操作pop_mark
X K按照传入的类型执行进栈的操作
e目前的
s向dict添加key+value
b把当前栈栈顶存进state,然后弹掉,把当前栈栈顶记为inst,然后弹掉,利用state这一系列的值来更新实例inst。把得到的对象扔进当前栈.

reduce 利用

class__reduce__方法,在pickle反序列化的时候会被执行。其底层的编码方法,就是利用了R指令码


下面这个里面没有reduce方法

例题:高校公益赛webtmp

彻底过滤了R指令码

1
2
if b'R' in base64.b64decode(pickle_data):
return 'No... I don\'t like R-things. No Rabits, Rats, Roosters or RCEs.'