-
-
[原创] 浅尝Pickle反序列化流程及reduce利用
-
发表于: 2024-10-22 22:23 1743
-
对象被实例化之后如何存储?答:序列化
对象被序列化为字符串,字符串被反序列化为对象。这个转化得过程就是序列化,这个过程是如何进行,且随笔者一同探讨python的反序列化库pickle。
对于自定义类进行序列化时:
通过对比不难看出,第一种写法中,pickle
实际上没有序列化任何数据,反序列化后依然依赖类属性。第二种写法中,pickle
序列化并保存实例属性name,反序列化时可以正确恢复实例的状态。
首先,先简单说明函数参数,根据官方的解释如下:
pickle反序列化源码分析:
在了解了基本参数后,再看函数定义并不难理解,_loads/_dumps
与_load/_dump
区别在于待反序列化的对象或序列化后的对象的表示形式是不是字节对象。其底层实现是基于_Unpickler/_Pickler
类调用load()/dump(obj)
方法。
_Unpickler
类源码分析:
前面提到,调用load相当于调用Unpickler(file).load()
,所以分析load源码:
通过阅读_Unpickler类
以及load
方法,我们可以看出,_Unpickler
维护了两个内存区域:
经过以上分析,我们可以确定,pickle每次会读取一个字节,并执行dispatch[key[0]](slef)
,所以我们为了方便分析,使用pickletools
来进行调试
pickletools是python自带的pickle调试器,有三个功能:反汇编一个已经被打包的字符串、优化一个已经被打包的字符串、返回一个迭代器来供程序使用。
执行结果
以上为反汇编功能:
注释掉serialize = pickletools.optimize(serialize)
会得到以下结果
通过对比,显然,用于向memo存储数据的MEMOIZE
被优化掉了,所以optimize
的优化其实就是认为不必向memo存储已解码的数据。
利用pickletools,我们能很方便地看清楚每条语句的作用、检验我们手动构造出的字符串是否合法……总之,是我们调试的利器。现在手上有了工具,我们开始研究这个字符串是如何被pickle解读的吧。
pickle序列化协议向前兼容,0,1版本可以decode
,后续版本加入不可打印字符串。
下面对pickle反编译后的内容进行分析:
示例代码:
以下为反编译的代码,后续根据以下结果进行分析:
又前面的分析我们可以知道,pickle的流程在通过while循环不断读入一个字节的key进行操作,完整的代码在前面load中
所以反序列化的流程应该从第一个key开始,并且执行语句为
此时第一个key执行结束,继续读取下一个key。
2. GLOBAL
此时key=EMPTY_TUPLE
此时key=NEWOBJ
此时key=EMPTY_DICT
EMPTY_DICT会往站内压入一个{}
空对象。
6. MARK:将标记对象(markobject)入栈
MARK通过分层处理类对象的属性值
执行的操作也很简单,将当前栈压入元数据栈,然后开辟新栈
7. BINUNICODE:Unidode字符串对象入栈
BINUNICODE操作数就是读取给定长度的字节,以Unicode解码。BINUNICODE默认给定长度为4字节无符号数,BINUNICODE8,为长度8字节无符号数。
这里顺便说一下BINBYTES,BINBYTES8,BININT,BININT1,BININT2等
了解几个例子之后,应该对BIN这系列操作码有一定的认识。并对pickle的操作也有一定的认识
8. TUPLE
接上一个mark,我们大概可以推断出,在完第一个MARK到第二个MARK前,此时元数据栈metastack应该为
这是会进行下一个MARK,然后再MARK,并存入四个BININT1。
此时的元数据栈及栈情况
下面及该执行TUPLE操作
TUPLE会调用pop_mark
函数,所以先查看pop_mask的操作
pop_mark的操纵就是恢复上一层的状态,然后将本层处理的结果返回。继续回到TUPLE
知道pop_mark的操纵,那么tuple的操作也就知道了,恢复上一次的状态,将本层的结果append进栈。其实结果就是MARK和最近的TUPLE构成一个元组,并入栈
两次mark-tuple后,
到这里是不是就可以盲猜一下APPEND的作用了?
9. APPENDS
执行完成后,我们接着看一下此时的状态
SETITEMS的操作就是,将栈上数据以键值对的形式读取字典
此时的状态应该是
胜利即在眼前
11. BUILD
流程分析结束,让我们看一下作者写的
反序列化常见的利用多是__reduce__
,这个函数的作用是什么?那不妨来分析一下序列化是__reduce__
起到了什么作用。前面提到dumps本质调用的是Pickler(file, protocol).dump(obj)
,那就分析一下dump的执行流。
分析之前,不妨先分析一下Pickler的类属性,及一些初始化。
下面开始分析dump
下面我们分析save()的流程:
接下来就是save_reduce
这就是带有__reduce__
的dumps的流程,接下来看loads的
接下来就是分析反序列化之后的内容
对于SHORT_BINUNICODE
和SHORT_BINUNICODE
应该能立马反应出来是压栈操作了。
接下来就是分析一下STACK_GLOBAL
,
再到执行TUPLE1
时,dir
作为元组入栈,最后就是REDUCE了
执行函数system("dir")
,此时也就做到反序列化命令执行了
不禁止R
指令码,但是对R
执行的函数有黑名单限制。典型的例子是2018-XCTF-HITB-WEB : Python's-Revenge。给了好长好长一串黑名单:
大可不必,如此羞辱我。platform.popen()不在黑名单内,以下是预期解
其中,map的第一个参数作为函数,第二个参数作为前面函数的参数。
参考文章:
从零开始python反序列化攻击:pickle原理解析 & 不用reduce的RCE姿势
OpCodes*Pickle,ji
e
=
[[
1
,
2
],
3
, (
4
,
5
),
"rainy"
, {
"age"
:
20
,
"birthday"
:
"1027"
}]
# 文件读写
pickle.dump(e,
open
(
"e.pkl"
,
"wb"
))
print
(pickle.load(
open
(
"e.pkl"
,
"rb"
)))
# 字符串读写
serialize
=
pickle.dumps(e)
print
(serialize)
deserialize
=
pickle.loads(serialize)
print
(deserialize)
e
=
[[
1
,
2
],
3
, (
4
,
5
),
"rainy"
, {
"age"
:
20
,
"birthday"
:
"1027"
}]
# 文件读写
pickle.dump(e,
open
(
"e.pkl"
,
"wb"
))
print
(pickle.load(
open
(
"e.pkl"
,
"rb"
)))
# 字符串读写
serialize
=
pickle.dumps(e)
print
(serialize)
deserialize
=
pickle.loads(serialize)
print
(deserialize)
# 类属性 rainy, 所有实例共享,序列化不会包含整个类属性
class
Rainy:
name
=
"rainy"
rainyx
=
Rainy()
print
(pickle.dumps(rainyx))
# b'\x80\x04\x95\x19\x00\x00\x00\x00\x00\x00\x00\x8c\x08__main__\x94\x8c\x05Rainy\x94\x93\x94)\x81\x94.'
# 类属性 rainy, 所有实例共享,序列化不会包含整个类属性
class
Rainy:
name
=
"rainy"
rainyx
=
Rainy()
print
(pickle.dumps(rainyx))
# b'\x80\x04\x95\x19\x00\x00\x00\x00\x00\x00\x00\x8c\x08__main__\x94\x8c\x05Rainy\x94\x93\x94)\x81\x94.'
class
Rainy:
def
__init__(
self
, name:
str
):
self
.name
=
"rainy"
rainyx
=
Rainy(
"rainy"
)
print
(pickle.dumps(rainyx))
# b'\x80\x04\x95,\x00\x00\x00\x00\x00\x00\x00\x8c\x08__main__\x94\x8c\x05Rainy\x94\x93\x94)\x81\x94}\x94\x8c\x04name\x94\x8c\x05rainy\x94sb.'
class
Rainy:
def
__init__(
self
, name:
str
):
self
.name
=
"rainy"
rainyx
=
Rainy(
"rainy"
)
print
(pickle.dumps(rainyx))
# b'\x80\x04\x95,\x00\x00\x00\x00\x00\x00\x00\x8c\x08__main__\x94\x8c\x05Rainy\x94\x93\x94)\x81\x94}\x94\x8c\x04name\x94\x8c\x05rainy\x94sb.'
def
_dump(obj,
file
, protocol
=
None
,
*
, fix_imports
=
True
, buffer_callback
=
None
):
_Pickler(
file
, protocol, fix_imports
=
fix_imports,
buffer_callback
=
buffer_callback).dump(obj)
def
_dumps(obj, protocol
=
None
,
*
, fix_imports
=
True
, buffer_callback
=
None
):
f
=
io.BytesIO()
_Pickler(f, protocol, fix_imports
=
fix_imports,
buffer_callback
=
buffer_callback).dump(obj)
res
=
f.getvalue()
assert
isinstance
(res, bytes_types)
return
res
def
_load(
file
,
*
, fix_imports
=
True
, encoding
=
"ASCII"
, errors
=
"strict"
,
buffers
=
None
):
return
_Unpickler(
file
, fix_imports
=
fix_imports, buffers
=
buffers,
encoding
=
encoding, errors
=
errors).load()
def
_loads(s,
/
,
*
, fix_imports
=
True
, encoding
=
"ASCII"
, errors
=
"strict"
,
buffers
=
None
):
if
isinstance
(s,
str
):
raise
TypeError(
"Can't load pickle from unicode string"
)
file
=
io.BytesIO(s)
return
_Unpickler(
file
, fix_imports
=
fix_imports, buffers
=
buffers,
encoding
=
encoding, errors
=
errors).load()
def
_dump(obj,
file
, protocol
=
None
,
*
, fix_imports
=
True
, buffer_callback
=
None
):
_Pickler(
file
, protocol, fix_imports
=
fix_imports,
buffer_callback
=
buffer_callback).dump(obj)
def
_dumps(obj, protocol
=
None
,
*
, fix_imports
=
True
, buffer_callback
=
None
):
f
=
io.BytesIO()
_Pickler(f, protocol, fix_imports
=
fix_imports,
buffer_callback
=
buffer_callback).dump(obj)
res
=
f.getvalue()
assert
isinstance
(res, bytes_types)
return
res
def
_load(
file
,
*
, fix_imports
=
True
, encoding
=
"ASCII"
, errors
=
"strict"
,
buffers
=
None
):
return
_Unpickler(
file
, fix_imports
=
fix_imports, buffers
=
buffers,
encoding
=
encoding, errors
=
errors).load()
def
_loads(s,
/
,
*
, fix_imports
=
True
, encoding
=
"ASCII"
, errors
=
"strict"
,
buffers
=
None
):
if
isinstance
(s,
str
):
raise
TypeError(
"Can't load pickle from unicode string"
)
file
=
io.BytesIO(s)
return
_Unpickler(
file
, fix_imports
=
fix_imports, buffers
=
buffers,
encoding
=
encoding, errors
=
errors).load()
class
_Unpickler:
def
__init__(
self
,
file
,
*
, fix_imports
=
True
,
encoding
=
"ASCII"
, errors
=
"strict"
, buffers
=
None
):
self
._buffers
=
iter
(buffers)
if
buffers
is
not
None
else
None
self
._file_readline
=
file
.readline
self
._file_read
=
file
.read
# 以上参数前面以及提到,不在解释
self
.memo
=
{}
# 初始化一个字典memo, 用于缓存已解码的对象
# 以下为编码, 错误处理、初始化协议版本号,导入修复标志。
self
.encoding
=
encoding
self
.errors
=
errors
self
.proto
=
0
self
.fix_imports
=
fix_imports
class
_Unpickler:
def
__init__(
self
,
file
,
*
, fix_imports
=
True
,
encoding
=
"ASCII"
, errors
=
"strict"
, buffers
=
None
):
self
._buffers
=
iter
(buffers)
if
buffers
is
not
None
else
None
self
._file_readline
=
file
.readline
self
._file_read
=
file
.read
# 以上参数前面以及提到,不在解释
self
.memo
=
{}
# 初始化一个字典memo, 用于缓存已解码的对象
# 以下为编码, 错误处理、初始化协议版本号,导入修复标志。
self
.encoding
=
encoding
self
.errors
=
errors
self
.proto
=
0
self
.fix_imports
=
fix_imports
def
load(
self
):
"""Read a pickled object representation from the open file.
Return the reconstituted object hierarchy specified in the file.
"""
# Check whether Unpickler was initialized correctly. This is
# only needed to mimic the behavior of _pickle.Unpickler.dump().
if
not
hasattr
(
self
,
"_file_read"
):
raise
UnpicklingError(
"Unpickler.__init__() was not called by "
"%s.__init__()"
%
(
self
.__class__.__name__,))
# 1. 创建一个_Unframer对象,并将read, readinto, readline方法分别设置为_Unframer的方法。_Unframer是一个内部类,用于处理从文件中读取字节流的工作,提供了读取和解析字节流的方法。
self
._unframer
=
_Unframer(
self
._file_read,
self
._file_readline)
self
.read
=
self
._unframer.read
self
.readinto
=
self
._unframer.readinto
self
.readline
=
self
._unframer.readline
# 2. 元数据栈,跟踪对象的层次结构
self
.metastack
=
[]
# 3. 用于存放解构后的对象
self
.stack
=
[]
# 4. 是对stack.append的引用,用于快速向栈中添加对象。
self
.append
=
self
.stack.append
# 5. 序列化协议的版本,默认设置为 0。
self
.proto
=
0
# 6. 将读取方法self.read和操作的dispatch字典(存储字节码与操作函数的映射)赋值为局部变量,方便后续高效调用。
read
=
self
.read
dispatch
=
self
.dispatch
# dispatch = {} 为定义的一个类属性
# 7. 持续从文件中读取字节
try
:
while
True
:
# 7.1 每次读取一个字节(代表一个操作指令)
key
=
read(
1
)
# 7.2 如果没有读到就抛出EOFError
if
not
key:
raise
EOFError
# 7.3 断言读取的字节是字节类型,以确保安全
assert
isinstance
(key, bytes_types)
# 根据读取的字节 `key[0]` 在 `dispatch` 字典中找到对应的处理函数并调用它,这个处理函数会根据读取的操作对反序列化过程进行下一步操作。
dispatch[key[
0
]](
self
)
except
_Stop as stopinst:
# 反序列化过程中会抛出_Stop异常,这个异常用于标识序列化对象的结束。捕获该异常后,返回stopinst.value,即解构后的对象。
return
stopinst.value
def
load(
self
):
"""Read a pickled object representation from the open file.
Return the reconstituted object hierarchy specified in the file.
"""
# Check whether Unpickler was initialized correctly. This is
# only needed to mimic the behavior of _pickle.Unpickler.dump().
if
not
hasattr
(
self
,
"_file_read"
):
raise
UnpicklingError(
"Unpickler.__init__() was not called by "
"%s.__init__()"
%
(
self
.__class__.__name__,))
# 1. 创建一个_Unframer对象,并将read, readinto, readline方法分别设置为_Unframer的方法。_Unframer是一个内部类,用于处理从文件中读取字节流的工作,提供了读取和解析字节流的方法。
self
._unframer
=
_Unframer(
self
._file_read,
self
._file_readline)
self
.read
=
self
._unframer.read
self
.readinto
=
self
._unframer.readinto
self
.readline
=
self
._unframer.readline
# 2. 元数据栈,跟踪对象的层次结构
self
.metastack
=
[]
# 3. 用于存放解构后的对象
self
.stack
=
[]
# 4. 是对stack.append的引用,用于快速向栈中添加对象。
self
.append
=
self
.stack.append
# 5. 序列化协议的版本,默认设置为 0。
self
.proto
=
0
# 6. 将读取方法self.read和操作的dispatch字典(存储字节码与操作函数的映射)赋值为局部变量,方便后续高效调用。
read
=
self
.read
dispatch
=
self
.dispatch
# dispatch = {} 为定义的一个类属性
# 7. 持续从文件中读取字节
try
:
while
True
:
# 7.1 每次读取一个字节(代表一个操作指令)
key
=
read(
1
)
# 7.2 如果没有读到就抛出EOFError
if
not
key:
raise
EOFError
# 7.3 断言读取的字节是字节类型,以确保安全
assert
isinstance
(key, bytes_types)
# 根据读取的字节 `key[0]` 在 `dispatch` 字典中找到对应的处理函数并调用它,这个处理函数会根据读取的操作对反序列化过程进行下一步操作。
dispatch[key[
0
]](
self
)
except
_Stop as stopinst:
# 反序列化过程中会抛出_Stop异常,这个异常用于标识序列化对象的结束。捕获该异常后,返回stopinst.value,即解构后的对象。
return
stopinst.value
self
.memo
=
{}
# 初始化一个字典memo, 用于缓存已解码的对象
self
.stack
=
[]
# 用于存放解构后的对象
# sppend属于stack,指向栈顶
self
.append
=
self
.stack.append
# 是对stack.append的引用,用于快速向栈中添加对象
self
.memo
=
{}
# 初始化一个字典memo, 用于缓存已解码的对象
self
.stack
=
[]
# 用于存放解构后的对象
# sppend属于stack,指向栈顶
self
.append
=
self
.stack.append
# 是对stack.append的引用,用于快速向栈中添加对象
class
Rainy:
def
__init__(
self
, name:
str
):
self
.name
=
"rainy"
rainyx
=
Rainy(
"rainy"
)
serialize
=
pickle.dumps(rainyx)
# serialize = pickletools.optimize(serialize)
pickletools.dis(serialize)
class
Rainy:
def
__init__(
self
, name:
str
):
self
.name
=
"rainy"
rainyx
=
Rainy(
"rainy"
)
serialize
=
pickle.dumps(rainyx)
# serialize = pickletools.optimize(serialize)
pickletools.dis(serialize)
0
: \x80 PROTO
4
2
: \x95 FRAME
44
11
: \x8c SHORT_BINUNICODE
'__main__'
21
: \x94 MEMOIZE (as
0
)
22
: \x8c SHORT_BINUNICODE
'Rainy'
29
: \x94 MEMOIZE (as
1
)
30
: \x93 STACK_GLOBAL
31
: \x94 MEMOIZE (as
2
)
32
: ) EMPTY_TUPLE
33
: \x81 NEWOBJ
34
: \x94 MEMOIZE (as
3
)
35
: } EMPTY_DICT
36
: \x94 MEMOIZE (as
4
)
37
: \x8c SHORT_BINUNICODE
'name'
43
: \x94 MEMOIZE (as
5
)
44
: \x8c SHORT_BINUNICODE
'rainy'
51
: \x94 MEMOIZE (as
6
)
52
: s SETITEM
53
: b BUILD
54
: . STOP
0
: \x80 PROTO
4
2
: \x95 FRAME
44
11
: \x8c SHORT_BINUNICODE
'__main__'
21
: \x94 MEMOIZE (as
0
)
22
: \x8c SHORT_BINUNICODE
'Rainy'
29
: \x94 MEMOIZE (as
1
)
30
: \x93 STACK_GLOBAL
31
: \x94 MEMOIZE (as
2
)
32
: ) EMPTY_TUPLE
33
: \x81 NEWOBJ
34
: \x94 MEMOIZE (as
3
)
35
: } EMPTY_DICT
36
: \x94 MEMOIZE (as
4
)
37
: \x8c SHORT_BINUNICODE
'name'
43
: \x94 MEMOIZE (as
5
)
44
: \x8c SHORT_BINUNICODE
'rainy'
51
: \x94 MEMOIZE (as
6
)
52
: s SETITEM
53
: b BUILD
54
: . STOP
0: \x80 PROTO 4
2: \x95 FRAME 37
11: \x8c SHORT_BINUNICODE
'__main__'
21: \x8c SHORT_BINUNICODE
'Rainy'
28: \x93 STACK_GLOBAL
29: ) EMPTY_TUPLE
30: \x81 NEWOBJ
31: } EMPTY_DICT
32: \x8c SHORT_BINUNICODE
'name'
38: \x8c SHORT_BINUNICODE
'rainy'
45: s SETITEM
46: b BUILD
47: . STOP
0: \x80 PROTO 4
2: \x95 FRAME 37
11: \x8c SHORT_BINUNICODE
'__main__'
21: \x8c SHORT_BINUNICODE
'Rainy'
28: \x93 STACK_GLOBAL
29: ) EMPTY_TUPLE
30: \x81 NEWOBJ
31: } EMPTY_DICT
32: \x8c SHORT_BINUNICODE
'name'
38: \x8c SHORT_BINUNICODE
'rainy'
45: s SETITEM
46: b BUILD
47: . STOP
class
Rainy:
def
__init__(
self
, name:
str
):
self
.name
=
"rainy"
self
.pos
=
[(
1
,
0
,
2
,
7
), (
1
,
1
,
2
,
5
)]
rainyx
=
Rainy(
"rainy"
)
serialize
=
pickle.dumps(rainyx, protocol
=
2
)
serialize
=
pickletools.optimize(serialize)
pickletools.dis(serialize)
class
Rainy:
def
__init__(
self
, name:
str
):
self
.name
=
"rainy"
self
.pos
=
[(
1
,
0
,
2
,
7
), (
1
,
1
,
2
,
5
)]
rainyx
=
Rainy(
"rainy"
)
serialize
=
pickle.dumps(rainyx, protocol
=
2
)
serialize
=
pickletools.optimize(serialize)
pickletools.dis(serialize)
0
: \x80 PROTO
2
2
: c GLOBAL
'__main__ Rainy'
18
: ) EMPTY_TUPLE
19
: \x81 NEWOBJ
20
: } EMPTY_DICT
21
: ( MARK
22
: X BINUNICODE
'name'
31
: X BINUNICODE
'rainy'
41
: X BINUNICODE
'pos'
49
: ] EMPTY_LIST
50
: ( MARK
51
: ( MARK
52
: K BININT1
1
54
: K BININT1
0
56
: K BININT1
2
58
: K BININT1
7
60
: t
TUPLE
(MARK at
51
)
61
: ( MARK
62
: K BININT1
1
64
: K BININT1
1
66
: K BININT1
2
68
: K BININT1
5
70
: t
TUPLE
(MARK at
61
)
71
: e APPENDS (MARK at
50
)
72
: u SETITEMS (MARK at
21
)
73
: b BUILD
74
: . STOP
0
: \x80 PROTO
2
2
: c GLOBAL
'__main__ Rainy'
18
: ) EMPTY_TUPLE
19
: \x81 NEWOBJ
20
: } EMPTY_DICT
21
: ( MARK
22
: X BINUNICODE
'name'
31
: X BINUNICODE
'rainy'
41
: X BINUNICODE
'pos'
49
: ] EMPTY_LIST
50
: ( MARK
51
: ( MARK
52
: K BININT1
1
54
: K BININT1
0
56
: K BININT1
2
58
: K BININT1
7
60
: t
TUPLE
(MARK at
51
)
61
: ( MARK
62
: K BININT1
1
64
: K BININT1
1
66
: K BININT1
2
68
: K BININT1
5
70
: t
TUPLE
(MARK at
61
)
71
: e APPENDS (MARK at
50
)
72
: u SETITEMS (MARK at
21
)
73
: b BUILD
74
: . STOP
...
try
:
while
True
:
key
=
read(
1
)
if
not
key:
raise
EOFError
assert
isinstance
(key, bytes_types)
dispatch[key[
0
]](
self
)
except
_Stop as stopinst:
return
stopinst.value
...
...
try
:
while
True
:
key
=
read(
1
)
if
not
key:
raise
EOFError
assert
isinstance
(key, bytes_types)
dispatch[key[
0
]](
self
)
except
_Stop as stopinst:
return
stopinst.value
...
dispatch[key[
0
]](
self
)
dispatch[key[
0
]](
self
)
0
: \x80 PROTO
4
0
: \x80 PROTO
4
# 此时key=PROTO
# dispatch[key[0]](self) => dispatch[PROTO[0]] = load_proto(self)
def
load_proto(
self
):
proto
=
self
.read(
1
)[
0
]
# HIGHEST_PROTOCOL = 5, 协议版本要在0到5
if
not
0
<
=
proto <
=
HIGHEST_PROTOCOL:
raise
ValueError(
"unsupported pickle protocol: %d"
%
proto)
self
.proto
=
proto
dispatch[PROTO[
0
]]
=
load_proto
# 此时key=PROTO
# dispatch[key[0]](self) => dispatch[PROTO[0]] = load_proto(self)
def
load_proto(
self
):
proto
=
self
.read(
1
)[
0
]
# HIGHEST_PROTOCOL = 5, 协议版本要在0到5
if
not
0
<
=
proto <
=
HIGHEST_PROTOCOL:
raise
ValueError(
"unsupported pickle protocol: %d"
%
proto)
self
.proto
=
proto
dispatch[PROTO[
0
]]
=
load_proto
2
: c GLOBAL
'__main__ Rainy'
2
: c GLOBAL
'__main__ Rainy'
# key = GLOBAL
def
load_global(
self
):
# 读取一行作为模块名,到这里就不难理解,为什么文章开头要求必须实现readline方法。
module
=
self
.readline()[:
-
1
].decode(
"utf-8"
)
# 读取一行作为类名
name
=
self
.readline()[:
-
1
].decode(
"utf-8"
)
# 通过self.find_class(),因此反序列化子类可以覆盖这种形式的查找——即通过子类化(继承并重写)self.find_class()方法,来自定义类的查找方式。
klass
=
self
.find_class(module, name)
self
.append(klass)
dispatch[GLOBAL[
0
]]
=
load_global
# key = GLOBAL
def
load_global(
self
):
# 读取一行作为模块名,到这里就不难理解,为什么文章开头要求必须实现readline方法。
module
=
self
.readline()[:
-
1
].decode(
"utf-8"
)
# 读取一行作为类名
name
=
self
.readline()[:
-
1
].decode(
"utf-8"
)
# 通过self.find_class(),因此反序列化子类可以覆盖这种形式的查找——即通过子类化(继承并重写)self.find_class()方法,来自定义类的查找方式。
klass
=
self
.find_class(module, name)
self
.append(klass)
dispatch[GLOBAL[
0
]]
=
load_global
18
: ) EMPTY_TUPLE
18
: ) EMPTY_TUPLE
def
load_empty_tuple(
self
):
# 前面代码提到, self.append为self.append = self.stack.append
# 所以这里就是往站内压入一个空元组
self
.append(())
dispatch[EMPTY_TUPLE[
0
]]
=
load_empty_tuple
def
load_empty_tuple(
self
):
# 前面代码提到, self.append为self.append = self.stack.append
# 所以这里就是往站内压入一个空元组
self
.append(())
dispatch[EMPTY_TUPLE[
0
]]
=
load_empty_tuple
19
: \x81 NEWOBJ
19
: \x81 NEWOBJ
def
load_newobj(
self
):
# 从栈顶取出一个值作为创建对象的初始化参数,也就是前面append的数组
args
=
self
.stack.pop()
# 取出前面压入的类
cls
=
self
.stack.pop()
# 实例化对象
obj
=
cls
.__new__(
cls
,
*
args)
# 将实例化的对象入栈
self
.append(obj)
dispatch[NEWOBJ[
0
]]
=
load_newobj
def
load_newobj(
self
):
# 从栈顶取出一个值作为创建对象的初始化参数,也就是前面append的数组
args
=
self
.stack.pop()
# 取出前面压入的类
cls
=
self
.stack.pop()
# 实例化对象
obj
=
cls
.__new__(
cls
,
*
args)
# 将实例化的对象入栈
self
.append(obj)
dispatch[NEWOBJ[
0
]]
=
load_newobj
20
: } EMPTY_DICT
20
: } EMPTY_DICT
def
load_empty_dictionary(
self
):
self
.append({})
dispatch[EMPTY_DICT[
0
]]
=
load_empty_dictionary
def
load_empty_dictionary(
self
):
self
.append({})
dispatch[EMPTY_DICT[
0
]]
=
load_empty_dictionary
def
load_mark(
self
):
# 前面提到metastack是一个元数据栈,用于跟踪对象的层次结构
# 这个元数据栈,将当前的栈加入到元数据栈
# 这里不妨就取名为第一层吧
self
.metastack.append(
self
.stack)
# 清空栈,用于处理下一层,不如就取名为第二层
self
.stack
=
[]
# 还是拿到栈顶
self
.append
=
self
.stack.append
dispatch[MARK[
0
]]
=
load_mark
def
load_mark(
self
):
# 前面提到metastack是一个元数据栈,用于跟踪对象的层次结构
# 这个元数据栈,将当前的栈加入到元数据栈
# 这里不妨就取名为第一层吧
self
.metastack.append(
self
.stack)
# 清空栈,用于处理下一层,不如就取名为第二层
self
.stack
=
[]
# 还是拿到栈顶
self
.append
=
self
.stack.append
dispatch[MARK[
0
]]
=
load_mark
def
load_binunicode(
self
):
# < 小端序, I 标识四字节无符号整数
# '<I'标识读取四个字节,以小端序读取
# 这里为读取四个字节小端无符号整数作为读取字符串的长度。
len
,
=
unpack(
'<I'
,
self
.read(
4
))
if
len
> maxsize:
raise
UnpicklingError(
"BINUNICODE exceeds system's maximum size "
"of %d bytes"
%
maxsize)
# 将当前读取的字符串入栈
self
.append(
str
(
self
.read(
len
),
'utf-8'
,
'surrogatepass'
))
dispatch[BINUNICODE[
0
]]
=
load_binunicode
def
load_binunicode(
self
):
# < 小端序, I 标识四字节无符号整数
# '<I'标识读取四个字节,以小端序读取
# 这里为读取四个字节小端无符号整数作为读取字符串的长度。
len
,
=
unpack(
'<I'
,
self
.read(
4
))
if
len
> maxsize:
raise
UnpicklingError(
"BINUNICODE exceeds system's maximum size "
"of %d bytes"
%
maxsize)
# 将当前读取的字符串入栈
self
.append(
str
(
self
.read(
len
),
'utf-8'
,
'surrogatepass'
))
dispatch[BINUNICODE[
0
]]
=
load_binunicode
# BINBYTES,读取字节,默认为四字节小端序整数
def load_binbytes(self):
# BINBYTES8指示read(8)字节而已
len, = unpack(
'<I'
, self.read(4))
if
len > maxsize:
raise
UnpicklingError(
"BINBYTES exceeds system's maximum size "
"of %d bytes"
% maxsize)
self.append(self.read(len))
dispatch[BINBYTES[0]] = load_binbytes
# BININT,读取小端序,有符号整数。
def load_binint(self):
self.append(unpack(
'<i'
, self.read(4))[0])
dispatch[BININT[0]] = load_binint
# BININT1
def load_binint1(self):
self.append(self.read(1)[0])
dispatch[BININT1[0]] = load_binint1
# BININT2
def load_binint2(self):
# H表示两字节无符号短整形
self.append(unpack(
'<H'
, self.read(2))[0])
dispatch[BININT2[0]] = load_binint2
# BINBYTES,读取字节,默认为四字节小端序整数
def load_binbytes(self):
# BINBYTES8指示read(8)字节而已
len, = unpack(
'<I'
, self.read(4))
if
len > maxsize:
raise
UnpicklingError(
"BINBYTES exceeds system's maximum size "
"of %d bytes"
% maxsize)
self.append(self.read(len))
dispatch[BINBYTES[0]] = load_binbytes
# BININT,读取小端序,有符号整数。
def load_binint(self):
self.append(unpack(
'<i'
, self.read(4))[0])
dispatch[BININT[0]] = load_binint
# BININT1
def load_binint1(self):
self.append(self.read(1)[0])
dispatch[BININT1[0]] = load_binint1
# BININT2
def load_binint2(self):
# H表示两字节无符号短整形
self.append(unpack(
'<H'
, self.read(2))[0])
dispatch[BININT2[0]] = load_binint2
21: ( MARK
22: X BINUNICODE
'name'
31: X BINUNICODE
'rainy'
41: X BINUNICODE
'pos'
49: ] EMPTY_LIST
50: ( MARK
51: ( MARK
52: K BININT1 1
54: K BININT1 0
56: K BININT1 2
58: K BININT1 7
60: t TUPLE (MARK at 51)
21: ( MARK
22: X BINUNICODE
'name'
31: X BINUNICODE
'rainy'
41: X BINUNICODE
'pos'
49: ] EMPTY_LIST
50: ( MARK
51: ( MARK
52: K BININT1 1
54: K BININT1 0
56: K BININT1 2
58: K BININT1 7
60: t TUPLE (MARK at 51)
metastack
=
[[<__main__.Rainy
object
at
0x000001A6E21B5CD0
>, {}]]
stack
=
[
'name'
,
'rainy'
,
'pos'
, []]
metastack
=
[[<__main__.Rainy
object
at
0x000001A6E21B5CD0
>, {}]]
stack
=
[
'name'
,
'rainy'
,
'pos'
, []]
metastack = [[<__main__.Rainy object at 0x0000014452766090>, {}], [
'name'
,
'rainy'
,
'pos'
, []], []]
stack = [1, 0, 2, 7]
metastack = [[<__main__.Rainy object at 0x0000014452766090>, {}], [
'name'
,
'rainy'
,
'pos'
, []], []]
stack = [1, 0, 2, 7]
def pop_mark(self):
// items为当前栈,也就是[1, 0, 2, 7]这个数据
items = self.stack
// 跳回上一层
self.stack = self.metastack.pop()
// 指向上一层的栈顶
self.append = self.stack.append
return
items
def pop_mark(self):
// items为当前栈,也就是[1, 0, 2, 7]这个数据
items = self.stack
// 跳回上一层
self.stack = self.metastack.pop()
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!