pyarmor 插件机制实战详解:为混淆脚本注入自定义 License 校验逻辑
开发工具应用安全【免费下载链接】pyarmorA tool used to obfuscate python scripts, bind obfuscated scripts to fixed machine or expire obfuscated scripts.项目地址https://gitcode.com/gh_mirrors/py/pyarmor点击查看免费下载本文基于 pyarmor 官方仓库plugins/目录下的示例文档与配套源码系统讲解 Pyarmor 7 的插件Plugin机制如何通过两行注释标记把一段自定义 Python 检查代码注入到入口脚本中实现多网卡 MAC 绑定、Docker 容器 ID 校验、NTP 联网时间校验、多机器授权与 GPU 绑定等高级 License 控制。读完本文后你将掌握插件的注入原理含源码级实现、五类官方示例的完整操作流程以及编写私有校验插件的通用代码模式。版本提示官方文档明确声明plugins/目录下的示例是为 Pyarmor 7 编写的在 Pyarmor 8 中不可用见 plugins/README.md 开头的声明。当前仓库的src/cli/目录实现的是 8.x 版本8 的插件体系与本文介绍的内联插件机制不同文末会专门说明差异。一、插件的定位扩展 License 类型与注入检查代码官方文档对插件的定义非常简洁Plugin usually is used to extend license type, or insert some extra check code to obfuscated scripts to improve the security.即插件用于两类目的扩展 License 类型——让授权文件license.lic中的--expired / -x用户数据承载自定义含义多 MAC、容器 ID、机器序列号列表、GPU UUID 等向混淆后的脚本插入额外检查代码——在脚本运行时校验本机环境不满足则抛异常拒绝运行。文档给出的官方示例包括校验所有网卡 MAC 地址Check all the mac address校验 Docker 容器 IDCheck docker container id校验互联网时间Check internet time为多机器签发 LicenseCreate License For Multiple Machines绑定特定 GPUCheck GPU文档同时强调仓库中的示例代码只是指引强烈建议将私有校验逻辑写在自己的插件脚本中而不是照搬公开示例。二、工作原理两行注释如何触发插件注入这是理解 7 代插件机制的核心。整个机制围绕入口脚本中的两个注释标记展开# {PyArmor Plugins} # PyArmor Plugin: check_multi_mac()第一行# {PyArmor Plugins}是插件代码插入锚点混淆时--plugin指定的插件文件的全部源码会被插入到该行之后第二行# PyArmor Plugin: check_multi_mac()中的前缀# PyArmor Plugin:会被剥离最终变成一条真正的调用语句check_multi_mac()从而让插件函数在运行时执行。这一机制在源码中可以找到直接证据。混淆入口encrypt_script()在 src/utils.py 中逐行扫描源码并处理标记stub_marker # {PyArmor Plugins} inline_marker # PyArmor Plugin: call_markers # pyarmor_, # pyarmor_ for line in lines: if line.startswith(stub_marker): k n 1 else: i line.find(inline_marker) if i -1: plist.append((n if k -1 else n1, i, inline_marker)) ... if k -1: logging.info(Patch this script with plugins) lines[k:k] _patch_plugins(plugins) for n, i, m in plist: c if m[2] else lines[n] lines[n][:i] c lines[n][ilen(m):]几个值得注意的细节除了# PyArmor Plugin: xxx()这种内联标记还支持# pyarmor_xxx()与# pyarmor_xxx()两种调用标记形式剥离前缀后会保留适合写装饰器仓库里的 plugins/assert_armored.py 就是典型的装饰器式检查点插件_patch_plugins()src/utils.py把插件文件全文读取后拼成一行插入锚点之后即插件代码最终成为入口脚本的一部分随后随脚本一起被混淆——插件代码不会以明文形式出现在 dist 中插件文件本身的查找逻辑在search_plugins()src/utils.py先按相对路径查找找不到时依次在$PYARMOR_PATH/plugins与用户目录下的plugins目录中搜索插件名前加表示只剥离前缀、不重复插入源码。--plugin命令行参数的定义在 src/pyarmor.py它是一个可重复的append参数cparser.add_argument(--plugin, destplugins, metavarNAME, actionappend, helpInsert extra code to entry script, it could be used multiple times)因此一次混淆可以挂多个插件。入口脚本模板 plugins/foo.py 正好演示了这种组合同一个脚本中通过四个# PyArmor Plugin: ...注释依次挂载check_multi_mac()、check_ntp_time()、check_docker()、check_multiple_machine()四个检查函数。三、示例 1校验全部网卡 MAC 地址最完整的示例这是仓库中最复杂、最具代表性的插件涉及动态库 Python 插件 License 签发三部分由两个文件组成plugins/extra_hdinfo.c——C 语言源码编译后导出get_multi_mac函数遍历所有网卡接口跳过 loopback 与 NOARP 接口把每个接口的 MAC 以xx:xx:xx:xx:xx:xx.格式18 字节/个写入缓冲区plugins/check_multi_mac.py——Python 插件脚本。插件的运行时逻辑分两步见 plugins/check_multi_mac.py完整性自校验对/usr/lib/extra_hdinfo.so计算 sha384与插件内硬编码的lib_hdinfo_checksum比对防止有人篡改动态库来绕过校验def _check_lib_hdinfo(lib_hdinfo_filename): from hashlib import sha384 lib_hdinfo_checksum 7838938a424b273c1e9782a1f1aebe3b70421d83a73a091e2502a5206931f0a58c7dee1d4d2d0e313ae6b992f5fa865d with open(lib_hdinfo_filename, rb) as f: checksum sha384(f.read()).hexdigest() if not checksum lib_hdinfo_checksum: raise RuntimeError(unexpected %s % lib_hdinfo_filename)MAC 匹配校验通过ctypes.cdll.LoadLibrary加载动态库调用get_multi_mac取回全部 MAC与 License 中保存的期望值逐字节比对m cdll.LoadLibrary(lib_hdinfo_filename) size 1024 t_buf c_char * size buf t_buf() if (m.get_multi_mac(buf, size) -1): raise RuntimeError(cound not get mac addresses) if buf.value.decode() ! expected_mac_addresses: raise RuntimeError(license not for this machine)完整操作流程第 1 步编译动态库并计算其 sha384gcc -shared -o extra_hdinfo.so -fPIC extra_hdinfo.c sha384sum extra_hdinfo.so第 2 步把上面得到的 sha384 值填入 plugins/check_multi_mac.py 中的lib_hdinfo_checksum。第 3 步在入口脚本 plugins/foo.py 中插入两行注释标记# {PyArmor Plugins} # PyArmor Plugin: check_multi_mac()第 4 步带插件执行混淆pyarmor obfuscate --plugin check_multi_mac foo.py如果插件文件不在当前路径使用绝对路径pyarmor obfuscate --plugin /path/to/check_multi_mac foo.py第 5 步在目标机器上采集 MAC 并签发 Licenseextra_hdinfo.c中带#ifdef APP守卫见 plugins/extra_hdinfo.c加-DAPP编译后变成一个可执行程序用于打印目标机器的全部 MACgcc -DAPP -o hdinfo extra_hdinfo.c ./hdinfo然后生成 License 并把它绑定进混淆产物-x参数承载的就是插件将来用get_license_data()读出的期望 MAC 串pyarmor licenses -x 70:a1:23:f0:94.08:00:27:51:d9:fe CODE-0001 pyarmor obfuscate --plugin /path/to/check_multi_mac \ --with-license licenses/CODE-0001/license.lic foo.py第 6 步部署把dist路径下的所有文件拷贝到目标机器把extra_hdinfo.so拷贝到目标机器的/usr/lib插件中硬编码了该路径lib_hdinfo_filename /usr/lib/extra_hdinfo.so。四、示例 2校验 Docker 容器 IDplugins/check_docker.py 演示了把授权绑定到特定容器的用法。插件核心逻辑只有几行def check_docker(): cid None with open(/proc/self/cgroup) as f: for line in f: if line.split(:, 2)[1] namesystemd: cid line.strip().split(/)[-1] break if cid is None or cid ! get_license_data(): raise RuntimeError(license not for this machine)它从/proc/self/cgroup中解析 systemd 命名空间下的 cgroup 末段作为容器 ID再与 License 用户数据比对。操作流程# 1. 在入口脚本插入标记 # {PyArmor Plugins} # PyArmor Plugin: check_docker() # 2. 混淆 pyarmor obfuscate --plugin check_docker foo.py # 或绝对路径 pyarmor obfuscate --plugin /path/to/check_docker foo.py # 3. 以容器 ID 的 sha256 作为用户数据签发 License再混淆 pyarmor licenses -x f56b1824e453126ab5426708dbbed41d0232f6f2ab21de1c40da934b68a5d8a2 CODE-0002 pyarmor obfuscate --with-license licenses/CODE-0002/license.lic \ --plugin check_docker foo.py五、示例 3校验互联网时间NTP 到期检查plugins/check_ntp_time.py 是一个内嵌了完整 NTP 客户端NTPPacket/NTPStats/NTPClient三个类实现 NTP v2/v3 报文打包解包、时间戳转换的插件。校验函数如下def check_ntp_time(NTP_SERVEReurope.pool.ntp.org): EXPIRED_DATE _get_license_data() c NTPClient() response c.request(NTP_SERVER, version3) if response.tx_time time.mktime(time.strptime(EXPIRED_DATE, %Y%m%d)): raise RuntimeError(License is expired)它向 NTP 服务器默认europe.pool.ntp.org可自行替换为NTP_SERVER参数指定的服务器发起请求用服务器回传的tx_time与 License 中的到期日比较从而让本地系统时间无法被回拨来绕过到期限制。操作流程# 入口脚本标记 # {PyArmor Plugins} # PyArmor Plugin: check_ntp_time() pyarmor obfuscate --plugin check_ntp_time foo.py # 签发一个 2020-10-31 到期的 License pyarmor licenses -x 20201031 CODE-0003 pyarmor obfuscate --with-license licenses/CODE-0003/license.lic \ --plugin check_ntp_time foo.py注意文件开头的注释未混淆状态下调试该脚本时可解开顶部两行注释from pytransform import pyarmor_init; pyarmor_init()否则运行时模块pytransform在开发环境不可用。六、示例 4一份 License 授权多台机器plugins/check_multiple_machine.py 展示了白名单式授权License 用户数据以分号分隔多个硬件序列号插件取本机硬盘序列号get_hd_info(HT_HARDDISK)后判断其是否在白名单中if _get_hd_info() not in _get_license_data().split(;): raise RuntimeError(This license is not for this machine)签发与混淆假设三台机器的硬盘序列号分别为ta1、ta2、ta3pyarmor licenses -x ta1;ta2;ta3 CODE-0004 pyarmor obfuscate --with-license licenses/CODE-0004/license.lic \ --plugin check_multiple_machine foo.py七、示例 5绑定特定 GPUNvidiaplugins/check_gpu.py 用于把计算型代码绑定到特定 GPU。其思路是nvidia-smi -L上报的 GPU UUID 被认为是全局唯一的因此可以将其作为 License 用户数据。插件通过子进程调用nvidia-smi -L用正则\(?uuid: \S\)?提取 UUID 列表再与get_user_data()比对示例按单 GPU 场景编写UUID 需为小写多 GPU 场景需自行改造out subprocess.run([nvidia-smi, -L], checkTrue, capture_outputTrue) out out.stdout.decode(utf-8).lower() uuid_list re.findall(r\(uuid: \S\), out) ... if gpu_uuid ! get_user_data().decode(utf-8).lower(): raise RuntimeError(A GPU matching the license is not found)文档给出的使用步骤把check_gpu.py拷贝到你的项目目录在同目录创建测试脚本test_gpu.py# {PyArmor Plugins} # PyArmor Plugin: check_gpu() def test(): print(It works!)用get_gpu_list()或直接运行nvidia-smi -L获取本机 GPU UUID签发 Licensepyarmor licenses --expired 2022-02-21 -x gpu-70ef1701-4072-9722-cc0b-7c7e75ff76db gpu_test_license混淆pyarmor obfuscate --plugin check_gpu --with-license licenses/gpu_test_license/license.lic --advanced 2 --exact test_gpu.py。文档提到作者在super模式下测试过该示例。另外注意该插件额外使用了get_expired_days()打印剩余天数属于 8 代运行时的 API可见此示例与 7 代内联机制是混用的改造时要结合自身 Pyarmor 版本核实可用 API。八、插件开发的通用代码模式通读五个示例可以提炼出 7 代插件的两个共性模式编写私有插件时值得直接借鉴。模式 1跨模式读取 License 用户数据所有示例都包含一个形如get_license_data()的辅助函数如 plugins/check_multi_mac.py、plugins/check_docker.pydef get_license_data(): try: from pytransform import _pytransform except Exception: # For super mode from pytransform import get_user_data return get_user_data().decode() from ctypes import py_object, PYFUNCTYPE prototype PYFUNCTYPE(py_object) dlfunc prototype((get_registration_code, _pytransform)) rcode dlfunc().decode() index rcode.find(;, rcode.find(*CODE:)) return rcode[index1:]其原理是普通模式下通过ctypes从运行时扩展_pytransform导出函数get_registration_code拿到注册码字符串后从*CODE:之后的分号处截取用户数据super 模式下运行时模块的获取方式不同则退化为直接调用pytransform.get_user_data()。从源码结构看encrypt_script()的白名单src/utils.py 中的flist包含get_registration_code、get_expired_days、get_hd_info等函数名也印证了这些运行时 API 是被插件机制官方支持的。模式 2对辅助文件做哈希自校验只要插件依赖运行时文件动态库、外部脚本等都应像check_multi_mac那样先对其计算 sha384 并与硬编码值比对否则攻击者替换该文件即可绕过检查。九、版本差异Pyarmor 8 的插件体系需要特别强调plugins/目录的示例依赖pyarmor obfuscate --plugin内联注入机制官方明确其在 Pyarmor 8 中不再适用。8 代引入了两套不同的扩展点参见 docs/reference/environments.rst 与 src/cli/plugin.py构建期插件post-build pluginPython 脚本必须定义__all__导出插件类名可覆写post_script、post_build、post_key、post_runtime、post_bcc等静态方法分发逻辑见 src/cli/plugin.py用于处理输出路径、改写 import、给 key 文件加注释、按平台重命名/签名pyarmor_runtime扩展等。启用方式为pyarmor cfg plugins 脚本名不带.py后缀搜索顺序为当前路径 → 本地路径./.pyarmor/→ 全局路径~/.pyarmor/Hook钩子嵌入混淆脚本、随脚本一起运行的普通 Python 脚本存放在.pyarmor/hooks/下如foo.py对应hooks/foo.py可使用__pyarmor__、__assert_armored__两个特殊函数。因此若你在使用 Pyarmor 8/9 并希望实现运行时机器绑定类检查8 的对应做法是把校验逻辑写成hook 脚本例如仓库中的 plugins/assert_armored.py 展示了基于assert_armored检查点的装饰器写法而本文的 5 个示例应视为 7 代机制的权威参考与私有插件开发的逻辑范本。十、要点小结事项说明适用版本本文示例针对 Pyarmor 78 请用 hook / cfg plugins 体系注入锚点# {PyArmor Plugins}处插入插件源码# PyArmor Plugin: xxx()剥离前缀后成为调用参数形式--plugin NAME可多次出现非当前路径使用绝对路径License 数据载体pyarmor licenses -x 数据的用户数据由插件经get_registration_code/get_user_data读取安全要点依赖的运行时文件如extra_hdinfo.so必须做 sha384 自校验部署注意示例 1 需将extra_hdinfo.so放到目标机/usr/lib示例 3 需要目标机可访问 NTP 服务插件机制的价值在于把授权数据 → 运行时校验这一闭环完全交给开发者自定义-x用户数据的内容与解释逻辑都由插件自己定义这使 pyarmor 的授权模型可以扩展到容器、集群、GPU 等标准硬件绑定覆盖不到的场景。赞分享开发工具应用安全【免费下载链接】pyarmorA tool used to obfuscate python scripts, bind obfuscated scripts to fixed machine or expire obfuscated scripts.项目地址https://gitcode.com/gh_mirrors/py/pyarmor点击查看免费下载相关推荐Pyarmor 定制与扩展实战用 pyarmor cfg、Plugin 脚本与 Hook 脚本深度改造混淆流程Pyarmor 定制与扩展实战用 pyarmor cfg、Plugin 脚本与 Hook 脚本深度改造混淆流程 本文基于 Pyarmor 官方文档的 Cust开发工具应用安全Pyarmor 混淆脚本全解Bootstrap 脚本、Runtime Package 与 Runtime Key 的协作机制Pyarmor 混淆脚本全解Bootstrap 脚本、Runtime Package 与 Runtime Key 的协作机制 本文基于 Pyarmor 官方文开发工具应用安全Pyarmor finder 脚本筛选机制详解用 includes、excludes 与 data_files 精确控制混淆范围Pyarmor finder 脚本筛选机制详解用 includes、excludes 与 data_files 精确控制混淆范围 Pyarmor 在执行 py开发工具应用安全上一篇highlight.io 请求代理Request Proxying完整指南用自有域名规避广告拦截器确保会话回放与错误上报不被阻断下一篇终极游戏库管理方案Playnite便携版让你的游戏收藏随身携带创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考