文章内容为参考 http://betamao.me/posts/2022/python-pyarmor-crack/ 完成解密后,整理了解密中遇到的苦难和关键点后写成的,部分内容不完善,仅分享一下思路。
拿到某安全设备的代码,运行的py文件全部是加密的,大概长这样
通过特征可以知道是pyarmor
加密。
尝试闲鱼上找大佬解密,但是结果不尽人意。
遂自己研究一下。
查阅一些资料后,得知这个加密方式,运行时会有一个动态链接库,在设备的python搜索路径中,找到了这个库:_pytransform.so
下面开始通过分析库中的加密方式,还原python代码。
还原第一层:pyc
在加密库中找到这个__pyarmor__
方法的实现。
根据一些特征,可以看出是aes加密,拿到key和iv就可以,这里key和iv的具体的生成流程太长了,可以直接下个断点看一下就ok。(经过实际测试发现,目前所有我接触到的该厂商的python加密,key和iv都是一样的,应该就是默认配置下的加密)
这里没有什么复杂操作,直接aes解开后,还原成pyc文件即可。
尝试使用反编译工具来反编译pyc文件时,报错了。
使用dis库来反编译,可以看到进入__armor_enter__
后,后面的opcode都是不正常的,能看出来就是被加密了。
参考网上的资料,可以得知,这个加密库,把指令都加密了,运行时调用__armor_enter__
来解密,运行结束后又调用__armor_exit__
重新加密。
直接cat还原出的pyc文件,还是可以看到部分解密后的明文。这是因为pyc中包含多个部分,比如co_code
(指令)、consts
(常量)、co_varnames
(变量)等。而通过dis反编译时也可以发现,只有co_code部分是加密的。
还原第二层:co_code
还原第一层后,执行时,会进入到__armor_enter__
中,同样,根据字符信息,找到这个函数的定义
调用PyEval_GetFrame拿到python运行时的存储栈帧信息的对象,进入到真正的解密函数中。
根据pyCodeObject中的co_flag参数的不同,进入不同的解密方法中。
这里的每种解密方法都大差不差,全部都是异或,只是一些细节上有点小区别,不再展开说了。
需要注意的一些点就是,这里解密时拿到的co_code,是整个python模块或文件全部的co_code,也就是包含了调用__armor_enter__
的指令和后边被加密的指令。所以在解密时,需要截掉没有加密的__armor_enter__
调用部分。我遇到的是py2.7,所以需要去掉16个字节的co_code
这里解密也涉及到密钥,还是老方法,直接下断点看一下就OK。
解密后发现还是有点问题,前面说的只加密了co_code部分,但是pyc中还有常量、变量等。经过一番查阅后,得知常量中也是可能存在PyCodeObject对象的(py去掉头文件后就是这个东西),所以在还原co_code后,还需要递归去还原常量中的co_code。
这样基本完成了解密,但是还不够。解密到现在这种程序,基本所有部分都是明文了,但是因为存在开始部分存在__armor_enter__
,导致反编译工具是没办法还原成py源码的。
到这一步有两种方法:
- 用dis将code反编译成一条条指令,然后扔给ai。(实测还原的效果很好,但是太费token了,并且速度很慢,有上千个文件。。。)
- 第二种就是继续修复co_code,知道反编译工具可以正常反编译。
修复pyc文件
这里大量参考了文章 http://betamao.me/posts/2022/python-pyarmor-crack/
首先要去掉文件开头的__armor_enter__
调用。这部分在解密时就有所体现,不同python版本,这一部分的长度是不同的,在python2.7下就是16,直接去掉前面16个字节就行了。
但是删掉开始的16个字节后,因为有一些指令,需要使用绝对偏移来跳转,还需要逐个遍历每条指令,如果用了绝对偏移,就要再后面的操作数上减掉16.
这一部分又涉及python指令相关的知识。。。
大概看了下,python2.7种,分为两种指令:有操作数和无操作数。
- 有操作数:每个指令占3个字节,其中第一个字节是指令,后两个字节是操作数。
- 无操作数:每个指令1个字节
这两种指令的判断方式就是是否大于90。
需要绝对偏移的指令,在dis.hasjabs中可以看到。
了解这些之后,就可以修复掉偏移的问题。
最后还要删掉结尾的__armor_exit__
调用。
这里特征也比较明显,会用LOAD_GLOBAL
加载__armor_exit__
,直接上代码
end
完成这些操作后,基本可以将95%的py文件反编译成源代码。
也有一些很特殊的,会反编译失败,具体原因没有分析了,但是这部分代码还是可以用dis来反编译成指令,再借助ai来还原成源代码。