Python pickle 中的 POP 指令码

本文探讨了Python pickle模块中POP指令的作用,主要防止无限递归构造对象。当Pickler检测到递归时,通过POP放弃中间对象并利用memo中的实例,而Unpickler如何处理POP指令来完成反序列化。同时,文章分析了为何Pickler不优化指令序列的原因。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

事情起源于笔者 2019 年的一篇文章:

从零开始python反序列化攻击:pickle原理解析 & 不用reduce的RCE姿势

前言 不经意间就起了这么长一个题目。昨晚用了4h左右的时间看皮ckle源码(主要是_Unpickler部分),发掘出了几种新的漏洞利用方式,目前网上暂未见资料,因此我们决定写一篇文章分享一下。本文详细介绍了pyckle.l...

正在上传…重新上传取消​知乎专栏阮行止上海洛谷网络科技有限公司 讲师

文中提到,注入恶意对象引发 RCE 之后,常常由于返回的反序列化对象不符合业务代码的预期,造成程序 crash。解决方案是使用 POP 指令码(字节码是数字 )把恶意对象弹出,再压一个正常的对象进去,这样 返回的就是普通的对象,以达成无副作用 RCE 的目的。如下图所示:0pickle.dumps

▲ 先构造恶意对象,再弹出,最后压入正常对象。绿色框内为恶意对象

然后今天收到私信,问 POP 这个指令码有什么用。

这个问题笔者当年确实没有想过。今天翻了一下源码,发现 POP 指令主要是为了防止无限递归构造对象的。下面我们根据 源码进行说明。pickler

0x00 Unpickler 对 POP 的处理方式

首先,我们知道 Unpickler 是一个图灵完备的虚拟机。它的指令编码方式如下:先是一个字节的 op code,然后紧跟操作数。至于操作数的表示方式,也是首先用一个字节表示类型,然后紧跟着操作数。这个虚拟机的语言大体上是一个 LL(1) 型文法,所以 Unpickler 仅通过简单地重复执行「读入一个字节的操作数 - 调用对应 handler」,就可以完成反序列化工作。

Unpickler 每次读入操作数,就查表找到 handler 并调用,handler 会吃掉一些字符,构造一个对象或进行其他操作。具体的 pickle 虚

<think>嗯,用户想用Python的字典来实现一个通讯录程序,包括查询、插入、删除和退出功能。首先,我需要理清楚字典的基本操作,因为字典很适合用来存储键值对,比如用姓名作为键,联系人的其他信息作为值。 用户提到需要四个功能,所以程序的结构应该是一个循环,显示菜单,然后根据用户输入执行相应操作。主循环可能需要用while True来持续运行,直到用户选择退出。菜单的显示可以用print语句列出选项,然后通过input获取用户的选择。 接下来是各个功能的具体实现。插入联系人需要用户输入姓名,然后检查是否已经存在。如果不存在,就添加新的条目到字典里。这里可能需要嵌套字典,比如每个联系人的信息包括电话、地址等,所以值也是一个字典。例如contacts[name] = {'phone': phone, 'address': address}。 查询联系人就是根据输入的姓名查找字典中是否存在对应的键,存在的话就输出详细信息,否则提示不存在。删除功能类似,先检查存在性,然后使用del语句或者pop方法删除条目。 退出功能的话,可以用break跳出循环,或者直接exit()结束程序。不过更优雅的方式是让主循环自然结束。 需要注意的是,用户输入可能有大小写的问题,比如姓名的大小写不一致导致查找失败。可能需要统一转换为小写或者大写,或者在比较时忽略大小写。但用户可能希望保留原始的大小写格式,所以最好在存储时保持原样,查找时也区分大小写,或者提示用户注意输入的正确性。 另外,通讯录需要持久化保存的话,可能需要使用文件读写,但用户的问题中没有提到这点,可能暂时只需要在内存中运行,所以每次程序关闭后数据会丢失。如果需要保存,可以考虑使用json模块来读写文件,但可能超出当前问题的范围。 代码结构方面,先初始化一个空字典contacts = {}。然后进入主循环,显示菜单,选择操作。使用if-elif结构处理不同的选项。每个功能封装成函数可能会更清晰,比如add_contact(), search_contact(), delete_contact(),但用户可能希望代码简洁,所以直接在循环中处理也行。 测试各个功能是否正确,比如插入重复姓名时是否提示,删除不存在的联系人是否处理得当。此外,输入验证也很重要,比如电话是否为数字,地址是否非空等,但用户的问题中可能暂时不需要复杂的验证,保持简单即可。 最后,确保代码符合Python的语法规范,比如缩进正确,变量命名清晰。然后按照用户的要求,生成相关问题,比如字典的其他应用场景,如何处理重名,或者如何扩展功能等。</think>根据Python字典特性实现的通讯录程序方案如下: ### 一、实现思路 1. 使用字典存储联系人信息,键为姓名,值为包含详细信息的嵌套字典 2. 通过`while`循环保持程序持续运行 3. 用`input()`接收用户操作指令 4. 通过条件判断执行对应功能 ### 二、完整实现代码 ```python contacts = {} def show_menu(): print("\n=== 通讯录管理系统 ===") print("1. 查询联系人") print("2. 插入联系人") print("3. 删除联系人") print("4. 退出系统") while True: show_menu() choice = input("请输入操作编号(1-4): ") # 查询功能 if choice == '1': name = input("请输入查询姓名: ") if name in contacts: print(f"姓名: {name}") print(f"电话: {contacts[name]['phone']}") print(f"地址: {contacts[name]['address']}") else: print("联系人不存在") # 插入功能 elif choice == '2': name = input("请输入姓名: ") if name in contacts: print("该联系人已存在") else: phone = input("请输入电话: ") address = input("请输入地址: ") contacts[name] = { 'phone': phone, 'address': address } print("联系人添加成功") # 删除功能 elif choice == '3': name = input("请输入删除姓名: ") if name in contacts: del contacts[name] print("联系人已删除") else: print("联系人不存在") # 退出系统 elif choice == '4': print("系统已退出") break else: print("输入错误,请重新输入") ``` ### 三、关键实现解析 1. **数据结构设计**:使用双层字典结构,外层字典的键为姓名,值为包含电话和地址的嵌套字典。这种结构支持$O(1)$时间复杂度的查询操作[^1] 2. **输入验证机制**:插入联系人时检查键名存在性,避免数据覆盖;删除操作前执行存在性检查,保证操作有效性[^2] 3. **操作隔离设计**:每个功能模块通过条件判断隔离,通过`show_menu()`函数实现界面与逻辑分离 4. **循环控制**:使用`while True`保持程序持续运行,直到收到退出指令 ### 四、功能扩展建议 1. 增加数据持久化存储(使用`pickle`模块或文件存储) 2. 添加联系人修改功能 3. 实现模糊搜索功能 4. 增加电话号码格式验证
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值