Python脚本保护方案汇总(一)

Python脚本保护方案汇总(一)

Python 语言凭借其语法简单明了、易于学习、可读性好、应用领域广泛、拥有强大的库和框架、高平台兼容性等优势,吸引了一大批开发人员。然而,作为脚本语言,Python 由解释器直接通过源码(字符流)解释执行,其脚本代码的安全性问题令很多开发人员颇为头疼。如果你正在寻找保护 Python 脚本安全的方法或思路, 那么本文汇总的 Python 脚本保护方案将为你提供有价值的参考。

本文将介绍当前主流的 Python 脚本保护方案,由于篇幅限制,先介绍其中三种。

读者群体

  1. Python脚本开发者
  2. 对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, 这个工具的混淆策略主要针对变量名,函数名,类名,导入模块函数名,内置函数名等名称进行随机字符串混淆,达到逆向者无法通过名字直接得到有用信息。

步骤

  1. 安装pyminifier, pip install pyminifier
  2. 执行加密操作,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文件进行加密。

步骤

  1. 把Python脚本编译成pyc
  2. 使用DS Protector对pyc加密
  3. 再对Python.exe进行保护

具体步骤可以参考官网:https://lm.virbox.com/docs/site/sdk_tools/ds_Protect/#python

效果

不足

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

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

未完待续…

滚动至顶部
售前客服
周末值班
电话

电话

13910187371