首页
社区
课程
招聘
[原创] 浅尝Pickle反序列化流程及reduce利用
发表于: 2024-10-22 22:23 1743

[原创] 浅尝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_BINUNICODESHORT_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()

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2024-10-23 09:06 被栀花谢了春红编辑 ,原因:
收藏
免费 0
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//