Python脚本保护方案汇总(一)
Python 语言凭借其语法简单明了、易于学习、可读性好、应用领域广泛、拥有强大的库和框架、高平台兼容性等优势,吸引了一大批开发人员。然而,作为脚本语言,Python 由解释器直接通过源码(字符流)解释执行,其脚本代码的安全性问题令很多开发人员颇为头疼。如果你正在寻找保护 Python 脚本安全的方法或思路, 那么本文汇总的 Python 脚本保护方案将为你提供有价值的参考。
本文将介绍当前主流的 Python 脚本保护方案,由于篇幅限制,先介绍其中三种。
读者群体
- Python脚本开发者
- 对Python代码安全感兴趣的学习者
pyc文件
.pyc 文件是Python编译脚本后生成缓存文件,可以被解释器直接加载。它的初衷是为了提升脚本的加载速度,对于代码保护的作用仅限于不再以Python源码的形式保存代码了。 算法大致如下:
import marshal
from time import time
import importlib
_bootstrap_external = importlib._bootstrap_external
def _pack_uint32(x):
"""Convert a 32-bit integer to little-endian."""
return (int(x) & 0xFFFFFFFF).to_bytes(4, 'little')
def _code_to_timestamp_pyc(code, mtime=0, source_size=0):
data = bytearray(_bootstrap_external.MAGIC_NUMBER)
data.extend(_pack_uint32(0))
data.extend(_pack_uint32(mtime))
data.extend(_pack_uint32(source_size))
data.extend(marshal.dumps(code))
return data
def codeobject_to_pyc(co, pyc_path):
code_bytes = _code_to_timestamp_pyc(co,int(round(time() * 1000)))
with open(pyc_path, 'wb') as pyc:
pyc.write(code_bytes)
def compile_py(source_path, pyc_path):
with open(source_path, 'rb') as source_file:
source_code = source_file.read()
co = compile(source_code, source_path, 'exec')
codeobject_to_pyc(co, pyc_path)
步骤
直接使用我上面提供的代码, 也可以直接执行命令python3 -m py_compile test.py
.
效果

不足
pyc文件Python版本兼容性不好,有可能执行不了另一个Python版本编译的pyc. uncompyle工具可以直接进行反编译出python源码, 几乎一模一样。
# uncompyle6 version 3.9.2
# Python bytecode version base 3.8.0 (3413)
# Decompiled from: Python 3.8.10 (tags/v3.8.10:3d8993a, May 3 2021, 11:48:03) [MSC v.1928 64 bit (AMD64)]
# Embedded file name: .\test.py
# Compiled at: 2016-08-06 10:43:48
import hashlib, uuid
def calculate_serial(str):
hex_representation = str.encode("utf-8").hex()
md5_hash = hashlib.md5(hex_representation.encode("utf-8")).hexdigest()
return md5_hash
def get_verify_serial():
uuid_str = str(uuid.uuid4())
return calculate_serial(uuid_str)
if __name__ == "__main__":
input_str = input("input string:")
serial = calculate_serial(input_str)
verify_serial = get_verify_serial()
if serial == verify_serial:
print("Serial matches the verification serial.")
else:
print("Serial does not match the verification serial.")
脚本级混淆
比较有名的工具就是pyminifier, 这个工具的混淆策略主要针对变量名,函数名,类名,导入模块函数名,内置函数名等名称进行随机字符串混淆,达到逆向者无法通过名字直接得到有用信息。
步骤
- 安装pyminifier,
pip install pyminifier
。 - 执行加密操作,
pyminifier --obfuscate --nonlatin --replacement-length=32 --gzip ---outfile=.\test-pyminifier.py .\test.py
。
效果
import zlib, base64
exec(zlib.decompress(base64.b64decode('eJzFV81K41AU3ucpLtmYLMxqBBHyFD5AiM2tudDclORGXCoEZAqjGf8Q0WprTNVS1Gp1hhZ8DxlB0I06kPMI3lRXLlyeLJJAAjnn+zkf5zKv7geCuHbo1tickg9/QrZx/e8Umu0RdBq70BscwunZ8svdPnQ3dyHp/YYkS6HZ2oFu3Mj7MSTtGLaP+pCNtt4GGWSDDqTp8GE17/+Cs/g2v249Z1vQWzl+6izJ367Ja+91RT7Sy/sNMxQBdlVILk5MxuuRQK98uD406wHj6JUf2+fmp8iG50wp7EP3KGIOPgmjvlkUNorbD8WhVYLugO6Bhu51fUZBB5r93TPRgRqUV3yHahORqE5OT+iGSxc1HR98ki5hg5dzhm2sQuOvjI8pd9g8DUVBfEBFFPAy+C9luo9vGloZw9ZsjfCHDd1uMr41vURT4Ud3IayusCqxLG571LJMU7Usz2bcslR8n+UXHWybFSuTpo53JiI3NsbnZ9QyAv0qxkeO7zcpMD65//9coXM7DmqFsBIWQOkkbLiSYRkW6FDlmUNTZ2nA7BrxbFFxaUiES8mCfFVlFVswn5Nw/N0ohprWQlp2n44vm+S++Gj4u3bfAVaa8XY=')))
# Created by pyminifier (https://github.com/liftoff/pyminifier)
不足
混淆后的脚本还是源码,可以直接对混淆后的脚本进行调试甚至修改。 我们先获取把数据进行base64解密,并解压缩,得到混淆后源码。
import hashlib
ﻉ𦓴ܩ𞢼𧊚𭵠𩪁鿝𫔚𐭑𐦤𞡘𫄊ﲄ𐢄𖣲𦼕뵦𦵧𤤻ݎﲍ𪄷ﴡ䦕𭇥㧀𞢏𞢜ꇏ𞤱ړ=str
ﻉ𦓴ܩ𞢼𧊚𭵠𩪁鿝𫔚𐭑𐦤𞡘𫄊ﲄ𐢄𖣲𦼕뵦𦵧𤤻ݎﲍ𪄷ﴡ䦕𭇥㧀𞢏𞢜ꇏ𞤱𐰨=input
ﻉ𦓴ܩ𞢼𧊚𭵠𩪁鿝𫔚𐭑𐦤𞡘𫄊ﲄ𐢄𖣲𦼕뵦𦵧𤤻ݎﲍ𪄷ﴡ䦕𭇥㧀𞢏𞢜ꇏ𞤱𠒻=print
ﻉ𦓴ܩ𞢼𧊚𭵠𩪁鿝𫔚𐭑𐦤𞡘𫄊ﲄ𐢄𖣲𦼕뵦𦵧𤤻ݎﲍ𪄷ﴡ䦕𭇥㧀𞢏𞢜ꇏ𞤱ࢯ=hashlib.md5
import uuid
ﻉ𦓴ܩ𞢼𧊚𭵠𩪁鿝𫔚𐭑𐦤𞡘𫄊ﲄ𐢄𖣲𦼕뵦𦵧𤤻ݎﲍ𪄷ﴡ䦕𭇥㧀𞢏𞢜ꇏ𞤱𠼲=uuid.uuid4
def ﻉ𦓴ܩ𞢼𧊚𭵠𩪁鿝𫔚𐭑𐦤𞡘𫄊ﲄ𐢄𖣲𦼕뵦𦵧𤤻ݎﲍ𪄷ﴡ䦕𭇥㧀𞢏𞢜ꇏ𞤱𐫟(ﻉ𦓴ܩ𞢼𧊚𭵠𩪁鿝𫔚𐭑𐦤𞡘𫄊ﲄ𐢄𖣲𦼕뵦𦵧𤤻ݎﲍ𪄷ﴡ䦕𭇥㧀𞢏𞢜ꇏ𞤱ړ):
ﻉ𦓴ܩ𞢼𧊚𭵠𩪁鿝𫔚𐭑𐦤𞡘𫄊ﲄ𐢄𖣲𦼕뵦𦵧𤤻ݎﲍ𪄷ﴡ䦕𭇥㧀𞢏𞢜ꇏ𞤱𦹜=ﻉ𦓴ܩ𞢼𧊚𭵠𩪁鿝𫔚𐭑𐦤𞡘𫄊ﲄ𐢄𖣲𦼕뵦𦵧𤤻ݎﲍ𪄷ﴡ䦕𭇥㧀𞢏𞢜ꇏ𞤱ړ.encode('utf-8').hex()
ﻉ𦓴ܩ𞢼𧊚𭵠𩪁鿝𫔚𐭑𐦤𞡘𫄊ﲄ𐢄𖣲𦼕뵦𦵧𤤻ݎﲍ𪄷ﴡ䦕𭇥㧀𞢏𞢜ꇏ𞤱𐤀=ﻉ𦓴ܩ𞢼𧊚𭵠𩪁鿝𫔚𐭑𐦤𞡘𫄊ﲄ𐢄𖣲𦼕뵦𦵧𤤻ݎﲍ𪄷ﴡ䦕𭇥㧀𞢏𞢜ꇏ𞤱ࢯ(ﻉ𦓴ܩ𞢼𧊚𭵠𩪁鿝𫔚𐭑𐦤𞡘𫄊ﲄ𐢄𖣲𦼕뵦𦵧𤤻ݎﲍ𪄷ﴡ䦕𭇥㧀𞢏𞢜ꇏ𞤱𦹜.encode('utf-8')).hexdigest()
return ﻉ𦓴ܩ𞢼𧊚𭵠𩪁鿝𫔚𐭑𐦤𞡘𫄊ﲄ𐢄𖣲𦼕뵦𦵧𤤻ݎﲍ𪄷ﴡ䦕𭇥㧀𞢏𞢜ꇏ𞤱𐤀
def ﻉ𦓴ܩ𞢼𧊚𭵠𩪁鿝𫔚𐭑𐦤𞡘𫄊ﲄ𐢄𖣲𦼕뵦𦵧𤤻ݎﲍ𪄷ﴡ䦕𭇥㧀𞢏𞢜ꇏ𞤱𥶊():
ﻉ𦓴ܩ𞢼𧊚𭵠𩪁鿝𫔚𐭑𐦤𞡘𫄊ﲄ𐢄𖣲𦼕뵦𦵧𤤻ݎﲍ𪄷ﴡ䦕𭇥㧀𞢏𞢜ꇏ𞤱𞡼=ﻉ𦓴ܩ𞢼𧊚𭵠𩪁鿝𫔚𐭑𐦤𞡘𫄊ﲄ𐢄𖣲𦼕뵦𦵧𤤻ݎﲍ𪄷ﴡ䦕𭇥㧀𞢏𞢜ꇏ𞤱ړ(ﻉ𦓴ܩ𞢼𧊚𭵠𩪁鿝𫔚𐭑𐦤𞡘𫄊ﲄ𐢄𖣲𦼕뵦𦵧𤤻ݎﲍ𪄷ﴡ䦕𭇥㧀𞢏𞢜ꇏ𞤱𠼲())
return ﻉ𦓴ܩ𞢼𧊚𭵠𩪁鿝𫔚𐭑𐦤𞡘𫄊ﲄ𐢄𖣲𦼕뵦𦵧𤤻ݎﲍ𪄷ﴡ䦕𭇥㧀𞢏𞢜ꇏ𞤱𐫟(ﻉ𦓴ܩ𞢼𧊚𭵠𩪁鿝𫔚𐭑𐦤𞡘𫄊ﲄ𐢄𖣲𦼕뵦𦵧𤤻ݎﲍ𪄷ﴡ䦕𭇥㧀𞢏𞢜ꇏ𞤱𞡼)
if __name__=="__main__":
ﻉ𦓴ܩ𞢼𧊚𭵠𩪁鿝𫔚𐭑𐦤𞡘𫄊ﲄ𐢄𖣲𦼕뵦𦵧𤤻ݎﲍ𪄷ﴡ䦕𭇥㧀𞢏𞢜ꇏ𞤱ﰧ=ﻉ𦓴ܩ𞢼𧊚𭵠𩪁鿝𫔚𐭑𐦤𞡘𫄊ﲄ𐢄𖣲𦼕뵦𦵧𤤻ݎﲍ𪄷ﴡ䦕𭇥㧀𞢏𞢜ꇏ𞤱𐰨("input string:")
ﻉ𦓴ܩ𞢼𧊚𭵠𩪁鿝𫔚𐭑𐦤𞡘𫄊ﲄ𐢄𖣲𦼕뵦𦵧𤤻ݎﲍ𪄷ﴡ䦕𭇥㧀𞢏𞢜ꇏ𞤱𐳄=ﻉ𦓴ܩ𞢼𧊚𭵠𩪁鿝𫔚𐭑𐦤𞡘𫄊ﲄ𐢄𖣲𦼕뵦𦵧𤤻ݎﲍ𪄷ﴡ䦕𭇥㧀𞢏𞢜ꇏ𞤱𐫟(ﻉ𦓴ܩ𞢼𧊚𭵠𩪁鿝𫔚𐭑𐦤𞡘𫄊ﲄ𐢄𖣲𦼕뵦𦵧𤤻ݎﲍ𪄷ﴡ䦕𭇥㧀𞢏𞢜ꇏ𞤱ﰧ)
ﻉ𦓴ܩ𞢼𧊚𭵠𩪁鿝𫔚𐭑𐦤𞡘𫄊ﲄ𐢄𖣲𦼕뵦𦵧𤤻ݎﲍ𪄷ﴡ䦕𭇥㧀𞢏𞢜ꇏ𞤱츳=ﻉ𦓴ܩ𞢼𧊚𭵠𩪁鿝𫔚𐭑𐦤𞡘𫄊ﲄ𐢄𖣲𦼕뵦𦵧𤤻ݎﲍ𪄷ﴡ䦕𭇥㧀𞢏𞢜ꇏ𞤱𥶊()
if ﻉ𦓴ܩ𞢼𧊚𭵠𩪁鿝𫔚𐭑𐦤𞡘𫄊ﲄ𐢄𖣲𦼕뵦𦵧𤤻ݎﲍ𪄷ﴡ䦕𭇥㧀𞢏𞢜ꇏ𞤱𐳄==ﻉ𦓴ܩ𞢼𧊚𭵠𩪁鿝𫔚𐭑𐦤𞡘𫄊ﲄ𐢄𖣲𦼕뵦𦵧𤤻ݎﲍ𪄷ﴡ䦕𭇥㧀𞢏𞢜ꇏ𞤱츳:
ﻉ𦓴ܩ𞢼𧊚𭵠𩪁鿝𫔚𐭑𐦤𞡘𫄊ﲄ𐢄𖣲𦼕뵦𦵧𤤻ݎﲍ𪄷ﴡ䦕𭇥㧀𞢏𞢜ꇏ𞤱𠒻("Serial matches the verification serial.")
else:
ﻉ𦓴ܩ𞢼𧊚𭵠𩪁鿝𫔚𐭑𐦤𞡘𫄊ﲄ𐢄𖣲𦼕뵦𦵧𤤻ݎﲍ𪄷ﴡ䦕𭇥㧀𞢏𞢜ꇏ𞤱𠒻("Serial does not match the verification serial.")
简单处理一下内置函数名和导入函数名的混淆,再简单重命名一下函数名和变量,其实逻辑也还是能看出来的。
import hashlib
import uuid
def method1(str):
v1 = str.encode('utf-8').hex()
v2 = hashlib.md5(v1.encode('utf-8')).hexdigest()
return v2
def method2():
v1 = str(uuid.uuid4())
return method1(v1)
if __name__=="__main__":
v1 = input("input string:")
v2 = method1(v1)
v3 = method2()
if v2 == v3:
print("Serial matches the verification serial.")
else:
print("Serial does not match the verification serial.")
数据文件加密
从某种角度来看,Python 脚本也可以视为一种数据文件,因为 Python 解释器会读取脚本内容并进行解释执行。DS Protector 是一款可以直接对文件进行加密的工具,应用场景非常广泛,且不受文件格式限制,因此可以用于对 Python 脚本和pyc文件进行加密。
步骤
- 把Python脚本编译成pyc
- 使用DS Protector对pyc加密
- 再对Python.exe进行保护
具体步骤可以参考官网:https://lm.virbox.com/docs/site/sdk_tools/ds_Protect/#python。
效果

不足
- 加密后的脚本必须由保护后Python.exe执行.
- 如果逆向者会调试的话,在Windows系统上也是可以通过监控ReadFile函数获取到解密后的数据。

转储下来解密的pyc文件就可以使用uncompyle工具反编译了,不过相比于前两种保护方式,这种保护方案已经对逆向者的技术有一定要求了。
未完待续…