战舰工具 1.47 逆向分析与授权绕过全记录

张开发
2026/4/3 15:58:25 15 分钟阅读
战舰工具 1.47 逆向分析与授权绕过全记录
战舰工具 1.47 逆向分析与授权绕过全记录一、目标概述对一款基于 Python 开发、使用 Nuitka 编译为原生代码的 Windows 桌面程序进行逆向分析目标是理解其授权验证机制并实现绕过。程序基本信息主程序main.exe279MBPE32 x64运行时Python 3.8 PyQt5打包方式NuitkaPython → C → 原生机器码加密体系AES-128-CBC RSA-2048加密 RSA-1024签名通信协议HTTP 明文传输数据层加密二、环境搭建硬件环境宿主机macOSApple Silicon / ARM虚拟机Parallels Desktop 运行 Windows 11 ARM模拟器MuMu 模拟器Android用于运行游戏本体分析工具工具用途平台IDA Pro 9.3静态反编译分析Macida-pro-mcpIDA 的 MCP 服务插件AI 辅助分析MacFrida 17.9.1动态注入、Hook、内存操作Windows VMPython 3.14编写分析脚本Windows VMPython 3.8与目标程序匹配的运行时Windows VMpycryptodomeAES/RSA 加解密双平台Charles/mitmproxy网络抓包备选Mac关键环境问题Windows VM 为 ARM 架构x64dbg 无法运行丧失了最常用的动态调试手段Frida 的Interceptor.attach在 ARM Windows 上对 Python C API 函数 hook完全无效需要通过ctypesReadProcessMemory/WriteProcessMemory替代 Frida 的内存扫描功能三、逆向分析过程第一阶段初步识别问题程序用什么方式打包能否直接还原源码分析过程用file命令确认是 PE32 x64 可执行文件在二进制中搜索特征字符串发现Nuitka标识尝试pyinstxtractor→ 失败不是 PyInstaller分析 PE 节区结构发现.rsrc段高达229MB结论程序由 Nuitka 编译业务逻辑在.text段37MB 原生机器码标准库 bytecode 在.rsrc段。影响Nuitka 编译的代码无法还原为 Python 源码只能通过反汇编/反编译分析 C 级别的代码。第二阶段静态字符串分析问题程序的授权验证逻辑是什么从二进制中提取关键信息服务器地址http://111.229.156.130 API 接口 /api/new/GetValidateZJ?key{授权码}mm{RSA加密机器码} /api/new/ValidateLoginZJ?key{授权码} /api/new/GetNoticeInfo?typezj 自定义模块 keyHelper → RSA 公钥管理、AES 密钥推导 aeshelper → AES 加解密、IV 生成、哈希校验 httpOperator → HTTP 请求、签名验证、响应处理 mainForm → 主界面逻辑第三阶段网络协议分析问题请求和响应的格式是什么方案搭建 HTTP 代理拦截流量由于程序直连 IP 地址非域名无法用 hosts 文件重定向。最终方案1. 在 Windows 上添加本地 IPnetsh interface ip add address 以太网 111.229.156.130 2. 设置端口转发netsh interface portproxy 3. 本地运行 fake_server.py 作为中间人后改用更简洁的HTTP_PROXY环境变量方案setHTTP_PROXYhttp://127.0.0.1:8888 main.exe成果请求格式GET /api/new/GetValidateZJ?key{授权码}mm{RSA加密数据}响应格式{AES加密数据的Base64}.{RSA签名的Base64}mm参数包含 RSA 加密的机器码 IV第四阶段内存取证问题解密后的响应 JSON 长什么样由于无法直接解密服务器响应不知道 AES Key 和 IV转而从程序内存中搜索解密后的明文。关键工具用 Pythonctypes调用 Windows API 扫描进程内存kernel32.ReadProcessMemory(handle,address,buffer,size,bytes_read)成果找到解密后的 JSON{msg:不一致请检查,rs:-1637,t:1775072039,rd:5018565}第五阶段提取 AES 密钥最难的问题问题AES Key 是什么如何推导这是整个逆向过程中最困难的环节耗时最长尝试了多种方法尝试 1从二进制常量推导失败从 Nuitka 常量表提取到混淆常量22、)02j45_*1操作maketrans(012,210)、translate、b64decodeRSA 公钥被拆分为 9 行尝试各种组合运算拼接、translate、b64decode、hash均无法得到正确的 AES Key。尝试 2内存暴力搜索失败将进程内存 dump 到文件1GB编写 C 程序暴力搜索// 遍历内存中每个 16 字节块尝试作为 AES Key 解密已知密文for(longi0;imemsz-16;i){CCCrypt(kCCDecrypt,kCCAlgorithmAES128,kCCOptionECBMode,key,16,NULL,ct_last,16,dec,16,outlen);// 验证 PKCS7 padding}测试了 8.3 亿个候选 Key全部失败。原因AES Key 在解密完成后被 Python 垃圾回收不在 memdump 中。尝试 3IDA 静态分析部分成功通过 IDA Pro ida-pro-mcp 插件反编译 Nuitka 生成的 C 代码定位到KeyVault.__init__0x140BF4600和get_aes_key0x140BF38A0识别出str.maketrans、str.translate、PyUnicode_Concat字符串拼接等操作确认 Key 由 3 个 part 拼接后经过变换生成但 Nuitka 的反编译代码过于复杂充满引用计数操作无法完全重建算法。尝试 4Frida 进程内注入执行成功最终方案既然无法逆向算法就让程序自己算出 Key。# 通过 Frida 注入到 main.exe 进程直接调用 Nuitka 编译的函数var codeMemory.allocUtf8String(import keyHelper\nvault keyHelper.KeyVault()\nkey vault.get_aes_key()\nopen(C:/Users/a/aes_key.txt,w).write(repr(key))\n);var gstatePyGILState_Ensure();PyRun_SimpleString(code);PyGILState_Release(gstate);结果AES KEY: 22**)02j45_*116 字节 ASCII 字符串AES-128-CBC。第六阶段验证加密流程问题AES Key 正确吗IV 怎么处理通过 Frida 注入验证# 加密ivget_random_bytes(16)cipherAES.new(KEY,AES.MODE_CBC,iv)encryptedivcipher.encrypt(pad(plaintext,16))enc_b64base64.b64encode(encrypted).decode()# 用程序自己的函数解密decaeshelper.AesHelper.decrypt_aes(enc_b64,key)# MATCH: True ✅关键发现decrypt_aes从密文前 16 字节提取 IV后面才是实际密文。第七阶段获取成功响应格式问题成功验证的 JSON 包含哪些字段用正版激活码通过 Frida 调用httpOperator.getValidate()resulthttpOperator.getValidate(正版激活码)# 返回 tuple:# ({key: xxx, type: v1, start: 2026-04-02 08:21:58,# end: 2026-04-09 08:21:58, canuse: True,# t: 1775090246, rd: 9430648, rs: 6658, es: True}, True)关键字段canuse: True→ 授权可用es: True→ 验证通过end→ 到期时间成功响应没有msg字段第八阶段实现绕过问题如何在不连接服务器的情况下让任意激活码通过验证失败的尝试方法结果原因修改二进制文件中的 IP 地址程序无法启动完整性校验Frida hook Python C API无输出ARM Windows 兼容性问题NOP 条件跳转指令无效检查不在被测函数中伪造加密响应验证失败RSA 签名无法伪造替换 RSA 公钥无效Nuitka 缓存了公钥引用内存实时 patch JSON来不及解密→读取在微秒内完成替换httpOperator.getValidate无效Nuitka 内联调用不走模块查找成功的方案核心发现httpOperator.validateHttp可以被 Python 级别替换getValidate的内部流程getValidate(key) → requests.get(server_url) # 联系服务器 → verify_signature1(response) # 验签Nuitka 内联 → decrypt_aes(encrypted, aes_key) # 解密Nuitka 内联 → validateHttp(result_dict, rs) # 验证结果 ← 可替换 → return (dict, True/False)validateHttp通过 Python 模块字典查找调用因此可以替换为自定义函数。最终 bypass 代码importhttpOperatordeffake_validateHttp(*args,**kwargs):ifargsandisinstance(args[0],dict):dargs[0]d.pop(msg,None)# 删除错误消息d[end]2099-12-31 23:59:59# 到期时间改为 2099d[start]2026-01-01 00:00:00d[canuse]Trued[es]Trued[type]v1d[key]CRACKEDreturn(args[0]ifargselse{},True)httpOperator.validateHttpfake_validateHttp工作原理程序用任意激活码向真实服务器发送请求服务器返回加密的错误响应“验证失败或绑定不一致”getValidate正常解密响应签名和加密都是真实的能通过验证getValidate调用validateHttp处理结果我们的假validateHttp原地修改传入的 dictPython dict 是引用传递删除错误消息设置canuseTrue、esTrue修改到期时间返回(modified_dict, True)程序认为验证成功四、遇到的关键问题问题 1ARM Windows 环境限制x64dbg 无法在 ARM Windows 上运行Frida 的Interceptor.attach对大部分函数无效解决用 ctypes ReadProcessMemory 替代 Frida 内存操作用 PyGILState_Ensure PyRun_SimpleString 注入 Python 代码问题 2Nuitka 编译的函数不可 HookPython 级别的 monkeypatch 对 Nuitka 编译的函数无效Nuitka 在 C 层面缓存了函数引用不走 Python 模块字典查找解决逐一测试所有httpOperator的函数发现validateHttp仍通过模块字典调用问题 3AES 密钥无法通过静态分析获取密钥推导算法太复杂IDA 反编译的 Nuitka C 代码难以阅读内存暴力搜索失败Key 被垃圾回收解决通过 Frida 注入利用PyGILState_Ensure获取 GIL直接在进程内执行 Python 代码调用keyHelper.KeyVault().get_aes_key()问题 4RSA 签名无法伪造没有服务器的 RSA 私钥替换程序中的公钥无效Nuitka 缓存解决不伪造签名让程序正常连接服务器获取有效签名的响应然后在validateHttp层面篡改结果五、最难的问题AES 密钥提取是整个过程中最难的环节。难点在于静态分析困难Nuitka 编译的代码充满 Python 对象引用计数操作一个简单的字符串拼接在反编译后变成几十行 C 代码动态分析受限ARM Windows 上没有可用的调试器Frida 的标准 Hook 方案全部失效暴力搜索无效Key 是临时计算值用完即释放1GB 内存 dump 中找不到最终的突破是思路转换不去逆向算法而是让程序自己执行算法。通过 Frida 的PyRun_SimpleString在目标进程中执行 Python 代码直接调用 Nuitka 编译的函数获取返回值。这种方法绕过了所有 Nuitka 的保护。六、逆向思路总结1. 识别打包方式Nuitka vs PyInstaller vs cx_Freeze ↓ 2. 静态字符串分析提取 API 地址、模块名、字段名 ↓ 3. 网络协议分析HTTP 代理拦截请求/响应 ↓ 4. 内存取证搜索解密后的明文确认响应格式 ↓ 5. 密钥提取Frida 进程注入调用目标函数 ↓ 6. 加密验证确认 AES Key IV 机制 ↓ 7. 函数替换测试逐一测试哪些函数可被 monkeypatch ↓ 8. 精准 Hook替换 validateHttp原地修改 dict关键原则能让程序自己算就不要自己逆向算法能替换高层函数就不要去 patch 底层机器码Python 的引用传递特性dict 原地修改是 bypass 的关键七、防护评估该程序的防护等级较高防护措施强度备注Nuitka 编译⭐⭐⭐⭐⭐无法还原源码AES-128-CBC 加密⭐⭐⭐⭐密钥推导复杂RSA 签名验证⭐⭐⭐⭐无法伪造动态 IV⭐⭐⭐基于时间戳 哈希机器绑定⭐⭐⭐RSA 加密机器码二进制完整性校验⭐⭐⭐修改后无法运行薄弱环节validateHttp函数通过 Python 模块字典调用可被运行时替换。如果所有函数都用 Nuitka 的内联调用方式bypass 难度将大幅增加。八、免责声明本文仅用于技术研究和学习目的。逆向分析他人软件可能涉及法律风险请确保在合法授权范围内进行。尊重软件开发者的劳动成果。

更多文章