记一次pyarmor解密

文章内容为参考 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源码的。

到这一步有两种方法:

  1. 用dis将code反编译成一条条指令,然后扔给ai。(实测还原的效果很好,但是太费token了,并且速度很慢,有上千个文件。。。)
  2. 第二种就是继续修复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来还原成源代码。