使用r2pickledec逆向分析Python Pickle序列化技术

本文详细介绍了如何使用Radare2和r2pickledec工具逆向分析Python Pickle序列化数据,包括Pickle指令集解析、内存结构分析、实际案例逆向以及二进制修补技术,揭示了Pickle反序列化的安全风险。

逆向分析Python Pickle与r2pickledec工具

2023年6月1日 - 作者:Dennis Goodlett

R2pickledec是首个支持所有协议5(当前最新)指令的Pickle反编译器。本文将介绍Python Pickle的基本概念、工作原理,以及如何使用Radare2和r2pickledec进行逆向分析。后续博文将深入探讨Pickle的高级混淆技术。

什么是Pickle?

Pickle是Python内置的序列化算法,能够将任何Python对象转换为字节流以便存储或网络传输。Pickle因其危险性而闻名——永远不要从不可信源反序列化Pickle数据,否则可能导致远程代码执行。详情请参阅官方文档。

Pickle基础

Pickle实现为简单的汇编语言,仅包含68条指令,主要基于栈操作。指令名称直观易懂,例如empty_dict会将空字典压入栈中。

栈通常只允许访问顶部项(某些情况下可访问多项)。如需访问其他项,必须使用memo(备忘录)。memo是实现为正整数索引的字典,常见memoize指令会将栈顶项复制到memo的下一个索引中。之后如需使用该对象,可通过binget n获取索引n处的对象。

建议通过实际操作学习Pickle:在Radare2中启用描述功能(e asm.describe = true)查看指令说明,反编译自己构建的简单Pickle并理解指令含义。

安装Radare2和r2pickledec

推荐使用Radare2(简称r2)进行Pickle逆向。包管理器提供的r2版本可能较旧,但通常不影响使用(Pickle架构支持已添加多年)。如遇问题,建议从源码安装。

本教程主要使用r2pickledec反编译器插件,该插件仅依赖r2库,因此只要r2正常运行,r2pickledec即可工作。通过r2pm安装:

1
2
$ r2pm -U             # 更新包数据库
$ r2pm -ci pickledec  # 全新安装

验证安装成功:

1
2
3
4
5
$ r2 -a pickle -qqc 'pdP?' -
Usage: pdP[j]  Decompile python pickle
| pdP   Decompile python pickle until STOP, eof or bad opcode
| pdPj  JSON output
| pdPf  Decompile and set pick.* flags from decompiled var names

使用Radare2和r2pickledec逆向真实Pickle

假设您入侵了一台Web服务器,该服务器允许公司员工对客户账户执行特权操作。在探查过程中,发现服务器使用Pickle文件恢复状态。以下Base64编码的Pickle文件可能包含有趣信息:

(Base64编码内容省略)

解码后保存为test.pickle,使用r2打开文件。执行x查看十六进制内容,pd显示反汇编(附加?可查看命令帮助,如pd?):

1
2
3
$ r2 -a pickle test.pickle
[0x00000000]> x
[0x00000000]> pd

从反汇编可知这是有效的Pickle文件,包含requests.sessionsSession字符串,表明可能导入了requests库并使用会话功能。

使用pdPf @0 ~..命令进行反编译:

  • pdPf:r2pickledec的反编译命令(pdP?查看详情),f参数让反编译器为每个变量名设置r2标志,便于重命名和跳转
  • @0:指定从偏移量0开始执行命令(避免寻址错误)
  • ~..:使用r2内置分页器(也可使用|less

反编译结果生成类Python代码(以下为片段,注释由反编译器添加):

1
2
3
4
5
6
## VM stack start, len 1
## VM[0] TOP
str_xb = "__main__"
str_x16 = "Api"
g_Api_x1c = _find_class(str_xb, str_x16)
# ... 更多代码 ...

建议从返回语句开始分析(即Pickle的最终输出)。跳转到文件末尾(G命令)可见:

1
2
3
4
5
6
str_x5f4 = "baseurl"
str_x5fe = "https://example.com/"
dict_x21 = {str_x24: what_x5f3, str_x5f4: str_x5fe}
what_x616 = g_Api_x1c.__new__(g_Api_x1c, *())
what_x616.__setstate__(dict_x21)
return what_x616

返回的what_x616变量是g_Api_x1c.__new__调用的结果。g_前缀表示反编译器识别为全局导入(此处提示为Api类)。x1cx616表示对象在Pickle中的创建偏移量(后续修补时使用)。

使用标志重命名变量:fr pick.g_Api_x1c pick.api(标志可tab补全,f命令列出所有标志)。

搜索auth字段发现认证信息:

1
2
3
4
str_x30e = "auth"
str_x315 = "admin"
str_x31d = "Pickles are fun"
tup_x32f = (str_x315, str_x31d)

这表明auth元素设置为元组("admin", "Pickles are fun")——我们刚刚窃取了管理员凭证!

修补操作

虽然真实渗透测试中不推荐此操作,但我们可以修改Pickle以指向恶意服务器。首先查找当前URL:

1
2
str_x5f4 = "baseurl"
str_x5fe = "https://example.com/"

通过变量名中的x5fe定位或直接跳转到pick.str_x5fe标志:

1
2
[0x00000000]> s pick.str_x5fe
[0x000005fe]> pd 1

使用以下r2命令将URL修改为https://doyensec.com/

1
2
3
4
5
6
[0x000005fe]> oo+           # 以读写模式重新打开文件
[0x000005fe]> pd 3          # 确认后续指令
[0x000005fe]> r+ 1          # 为新URL增加字节空间
[0x000005fe]> wa short_binunicode "https://doyensec.com/"  # 写入新指令
[0x000005fe]> pd 3          # 验证指令未被破坏
[0x000005fe]> pdP @0 |tail  # 确认修补成功

JSON与自动化

如需批量处理100个文件,可使用r2pipe进行脚本化。大多数r2命令添加j后缀可输出JSON格式(如pdPj生成包含偏移量的AST)。利用此功能可编写自动解析器定位并修补baseurl元素。

r2内置JSON处理功能:~{}美化输出,~{=}输出gron格式便于搜索。例如:

1
2
[0x000005fe]> pdPj @0 ~{=}https
[0x00000000]> pdPj @0 ~{stack[0].value[1].args[0].value[1][1].offset}

还可通过外部命令处理:pdPj |jq可搜索AST模式(如返回所有PY_GLOBAL类型对象)。

结论

r2pickledec插件简化了Pickle逆向过程,并充分利用r2的强大功能。本文仅浅尝辄止,更多功能请参阅r2手册。敬请关注后续关于Python Pickle混淆技术的博文。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计