PyArmor 的安全性¶
PyArmor 使用分片式技术来保护 Python 脚本。所谓分片保护,就是单独加密 每一个函数, 在运行脚本的时候,只有当前调用的函数被解密,其他函数都没有 解密。而一旦函数执行完成,就又会重新加密,这是 PyArmor 的特点之一。
例如,下面这个脚本 foo.py:
def hello():
print('Hello world!')
def sum(a, b):
return a + b
if __name == '__main__':
hello()
print('1 + 1 = %d' % sum(1, 1))
PyArmor 会首先加密函数 hello 和 sum ,然后在加密整个模块,进行两次 加密。当运行加密的 hello 的时候, sum 依旧是加密的。hello 执行完 成之后,会被重新加密,然后才开始解密并执行 sum 。
交叉保护机制¶
PyArmor 的核心代码使用 c 来编写,所有的加密和解密算法都在动态链接库中 实现。 首先 _pytransform 自身会使用 JIT 技术,即动态生成代码的方式 来保护自己,加密后的 Python 脚本由动态库 _pytransform 来保护,反过来, 在加密的 Python 的脚本里面,也会来校验动态库,确保其没有进行任何修改。 这就是交叉保护的原理,Python 代码和 c 代码相互进行校验和保护,大大提 高了安全性。
动态库保护的核心有两点:
- 用户不能通过修改代码段指令来获得没有授权的使用。例如,将指令 JZ 修 改为 JNZ ,从而使得认证失败可以继续执行
- 加密 Python 脚本使用的键值不能通过反向跟踪的方式获取到
那么, JIT 是如何来做到的呢?
PyArmor 定义了一套自己的指令系统(基于 GNU lightning),然后把核心函数, 主要是获取键值的算法,加解密的过程等,使用自己的指令系统生成数据代码。 数据代码存放在一个单独的 c 文件中,内容如下:
t_instruction protect_set_key_iv = {
// function 1
0x80001,
0x50020,
...
// function 2
0x80001,
0xA0F80,
...
}
t_instruction protect_decrypt_buffer = {
// function 1
0x80021,
0x52029,
...
// function 2
0x80001,
0xC0901,
...
}
这是两个受保护的函数,每一个受保护的函数里面会有很多小函数段。随后编译 动态库,计算代码段的校验和,使用这个真实的代码段的校验和替换相关的指令, 并对数据代码进行混淆,修改后的文件如下:
t_instruction protect_set_key_iv = {
// function 1, 不混淆
0x80001,
0x50020,
...
// function 2,混淆下面的数据指令
0xXXXXX,
0xXXXXX,
...
}
t_instruction protect_decrypt_buffer = {
// function 1, 不混淆
0x80021,
0x52029,
...
// function 2,混淆下面的数据指令
0xXXXXX,
0xXXXXX,
...
}
使用修改后的文件重新编译生成动态库,这个动态库会发布给客户。
当加密脚本运行的时候,每一次调用被保护的函数的时候,就会进入 JIT 动态 代码保护例程:
读取 functiion 1 的数据代码,动态生成 function 1
执行 function 1:
检查代码段的校验和,如果不一致,退出 检查当前是否有调试器,如果发现,退出 检查执行时间是否太长,如果执行时间太长,退出 如果可能的话,清除硬件断点寄存器 恢复下一个函数 `function 2` 的数据代码
读取 functiion 2 的数据代码,动态生成 function 2
重复步骤 2 的操作
这样循环有限次之后,真正受保护的代码才被执行。总之,主要达到的目的是开 始执行受保护的代码的时候,不能被调试器中断。
为了在 Python 端保护动态库没有被进行任何修改,在加密主脚本的时候,会插 入额外的一段代码来检查和保护动态链接库,详细工作原理参考 对主脚本的特殊处理