ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

cocos2d-js/lua游戏脚本解密套件:从解包到反编译的完整实践

cocos2d-js/lua游戏脚本解密套件:从解包到反编译的完整实践 简介在游戏开发和运维中脚本加密与反编译是常见需求。对于基于cocos2d-js和cocos2d-lua引擎的游戏发布包中的脚本常被编译为字节码或经XXTEA加密导致崩溃定位、资源复用和MOD开发困难。理解JSC/Lua字节码结构及XXTEA加密原理是还原可读代码的关键。通过解包、文件头识别、批量解密和反编译工具链可高效处理此类产物。本文提供一套完整的解密套件实践涵盖工具选型、自动化流程及常见陷阱帮助开发者合法地分析自有或授权产品提升问题排查效率。 如果你做过 cocos2d-js 或者 cocos2d-lua 游戏大概率遇到过这种场景线上版本出了崩溃你打开打包机上的 release 包发现所有业务代码都变成了一堆压缩或加密过的脚本之前在源码里能直接搜到的关键词现在一个都搜不到。又或者你想给一个已经停止维护的老项目做 mod、做汉化却发现资源和脚本包根本打不开。我自己就是在这种场景里反复折腾慢慢整理出一套针对 cocos2d-js/lua 游戏产物的“解密套件”。它不是一个单点工具而是由解包、文件识别、xxtea 解密、字节码反编译几层组成的工作流用来把发布产物还原成能读、能分析、能改的状态辅助崩溃定位、mod 开发、汉化和资源复用。提前说清楚边界这套东西的使用对象应当是你自己公司的产品、你合法拥有的软件包或者经过授权允许做二次开发的 mod/汉化项目。不要去碰还在运营中的商业游戏更不要拿来做外挂、灰产或者盗版分发。解密是一种工程能力用在哪、怎么用底线自己心里有数。下面我把套件的整体思路、关键脚本和踩过的坑完整记录下来希望对正在跟 cocos2d 系产品死磕的同学有帮助。1. 别急着解密先认清 js 和 lua 两套产物的目录结构很多人拿到一个游戏包就急着上工具结果解出来的东西要么是乱码要么是空壳。原因很简单你没搞清楚这个版本到底是 cocos2d-js 还是 cocos2d-lua是 debug 包还是 release 包脚本是编译过还是只是加密过。方向错了后面所有操作都是白费。1.1 cocos2d-js 常见产物形态cocos2d-js 打包后Android APK 里通常能看到assets/目录下边再分src/和res/。src/放脚本res/放图片、音频、plist 等资源。开发阶段你看到的是一堆.js源文件比如main.js、project.js、app.js。到了 release 阶段如果构建配置里开了字节码选项这些.js就会变成.jsc本质上是 JavaScriptCore 引擎的字节码快照。还有一种常见情况项目开了 xxtea 加密但只对src/里的脚本加密res/下的资源还是明文。这时候你把res/里的图片直接拖出来能看但src/里的脚本全都是乱码。反过来也有团队只加密资源不加密脚本所以第一步不是猜而是打开目录结构挨个看。iOS 平台的 cocos2d-js 略有不同解密后的包在Payload/xxx.app里资源通常被塞进Resources子目录也可能整个被打成assets.pkg或类似的自定义包需要先在启动流程里看引擎到底从哪个路径读取文件。1.2 cocos2d-lua 常见产物形态cocos2d-lua 的情况更复杂一点因为涉及 Lua 版本。老项目一般用 Lua 5.1新一些用的是 LuaJIT 2.0 或 2.1。打包后脚本目录通常叫src/入口是main.lua其余业务脚本分布在src/app、src/scripts之类的地方。release 模式下lua 源文件会被编译成.luac或者直接编译为.lua后缀的二进制文件但文件头已经变了。如果你在包里看到一个src/main.lua用编辑器打开是明文但同目录下的app/里所有.lua都是乱码别慌这很常见。有些团队只把核心逻辑加密入口文件保留明文方便引擎启动时拼接路径和读取初始化配置。另外cocos2d-lua 也经常把所有资源压成一个 zip 文件比如assets.zip或data.zip引擎在首次启动时自动解压到可写目录。这种情况下你直接看 APK 里可能只有压缩包真正的明文资源要到运行后才会出现得用模拟器、真机或直接解析 zip 的方式把那一层也解开。1.3 通过文件头识别加密策略这一步是整个套件的地基。我会先在十六进制工具里过一遍可疑文件确认前几个字节文件头说明文本、window、var等明文 JS直接可读1b 4c 75 61\x1bLuaLua 5.1/5.2 编译产物1b 4c 4a\x1bLJLuaJIT 编译产物58 58 54 45 41XXTEA带 xxtea 签名的加密文件纯随机字节无规律可能是 xxtea也可能是自定义加密这个识别表看起来简单但非常关键。它能帮你判断该走哪个分支明文 JS 直接美化Lua 字节码走反编译带签名或乱码走 xxtea 解密。版本不同、加密有无都会让同一个套件走完全不一样的路径。2. 加密层到底套了几层jsc、luac 和 xxtea 的原理拆解解密最大的误区是认为只要破解一层就完事了。实际上一个 release 包往往在文件层面做了多次处理脚本先被编译成字节码然后整个文件再被 xxtea 加密最后还可能有自定义魔数头。所以你得学会区分“哪层是字节码哪层是加密哪层只是容器格式”。2.1 JSC 字节码不是普通压缩而是引擎级编译cocos2d-js 的.jsc是 JavaScriptCore 引擎的字节码快照。它和 JS 源码最大的区别在于变量名、函数名、注释、空行全部消失只剩下字节码指令和常量表。理论上一个.jsc可以通过 JSC 直接执行但不能像源码一样直接阅读。JSC 字节码还和 JavaScriptCore 版本强相关不同 iOS 系统或不同版本 Android WebView 的字节码可能不通用。这也是为什么很多 cocos2d-js 游戏在做热更新时需要区分平台和引擎版本去生成.jsc。反编译.jsc的难点就在这社区工具要么只支持某个特定版本要么还原结果非常残缺。我遇到过的另一个情况是很多团队的 “jsc” 其实只是把 JS 源码用 xxtea 加密后的文件后缀还叫.jsc。你解密后拿到的不是字节码而是明文源码。这种情况下所谓“反编译”根本不存在只要走完 xxtea 解密再用格式美化工具一整理代码就回来了。2.2 Lua/LuaJIT 字节码版本错一个就寸步难行Lua 字节码比 JS 字节码要“规矩”很多因为它有一套相对稳定的指令集格式。\x1bLua开头的文件是标准 Lua 编译器产物后面跟着版本号、格式版本、字节序、int 类型大小等 header 信息。\x1bLJ开头的则是 LuaJIT 的字节码LuaJIT 的指令集设计更底层跟标准 Lua 差异很大。反编译这些字节码时工具必须和编译时的 Lua 版本精确匹配。Lua 5.1 的字节码你用面向 5.2 的工具去解基本是一堆垃圾LuaJIT 2.0 和 2.1 的字节码格式也完全不同。所以套件里一定要保留“按版本分类的工具链”而不是一个 unluac 通吃所有文件。实际操作中最好先读取文件头里的版本号再自动路由到对应工具这样才不会被大量报错淹没。2.3 xxtea 加密层如何区分“文件加密”和“代码编译”xxtea 是 cocos2d-x 体系里最常用的对称加密算法使用方式在引擎的FileUtils中配置。典型代码是这样的FileUtils::getInstance()-setXXTEAKeyAndSign(2dxLua, 6, XXTEA, 5);2dxLua是密钥XXTEA是签名。引擎在处理文件时若检测到签名就会用密钥解密加密数据。默认密钥在大量老项目里都没有改过所以很多人打包后以为自己做了加密保护实际上密钥还是社区公开的默认值等于只穿了件透明外套。文件加密和代码编译是两条独立轴线一个文件可以既被编译成字节码又被 xxtea 加密也可以只是被 xxtea 加密但内部还是明文 JS/Lua。遇到.jsc或.luac文件时先判断它是否带有 xxtea 签名或乱码特征有的话先解密再判断解密后是文本源码还是字节码。这个顺序不能搞反。3. 套件的具体构成解包工具、xxtea 脚本和反编译器的选型我整理套件时没有追求一个“全自动一键逆向”的工具因为根本没有这种东西。我的做法是把每一步拆开用最趁手的小工具拼成流水线每种工具只解决一个问题然后靠脚本把它们串起来。3.1 先把安装包拆开我常用的几个解包手段拿到 APK 或 IPA 后解包是第一步。Linux 和 macOS 上直接用unzip就能处理大多数安装包Windows 下可以选 Bandizip 或 7-Zip。APK 本质是一个 zip 容器直接改扩展名也能看到内容但如果包含了 APK 签名校验打开时会提示文件被破坏这时可以用apktool先解资源、再解代码。解出原始资源后很多 cocos2d 游戏并不是直接把资源放在assets/而是打成一个自定义包这时需要进一步解析。最简单的办法是在模拟器里把游戏跑起来等引擎首次启动自动解压完再进入应用沙箱目录把解密后的资源复制出来。模拟器的文件系统经常可以直接浏览比如/data/data/包名/files下就是引擎运行时的可写目录。这个方式比纯静态解包省力得多因为引擎自己已经把 xxtea 解密和 zip 解压都做完了。3.2 一个能直接用的 xxtea 解密脚本如果只能静态处理文件就需要自己写 xxtea 解密脚本。我用的是 Python 加xxtea库接口不复杂。下面这段是核心逻辑实际使用时按你安装的库微调接口即可import xxtea import sys # 密钥和签名从引擎配置或二进制中定位得到 key b2dxLua sign bXXTEA def decrypt_file(input_path, output_path): with open(input_path, rb) as f: data f.read() # 剥离引擎自定义签名如果没有签名就直接解密 if sign and data.startswith(sign): data data[len(sign):] # 有些构建会在签名后附带4字节长度或版本号按需跳过 # if data[:4] b\x01\x00\x00\x00: # data data[4:] try: # 这里参数取决于 xxtea 库的封装常见是 padding 可选 plain xxtea.decrypt(data, key, paddingFalse) except Exception as e: print(f[-] {input_path}: {e}) return with open(output_path, wb) as f: f.write(plain) print(f[] {input_path} - {output_path}) if __name__ __main__: if len(sys.argv) 3: print(fUsage: {sys.argv[0]} input output) sys.exit(1) decrypt_file(sys.argv[1], sys.argv[2])注意xxtea库的 API 不同版本差异很大有的库不提供padding参数有的库支持sign参数。所以这段代码更多是思路参考你实际装好库之后用两条已知明文和密文对照调一下即可。密钥定位是另一个核心问题。如果游戏没有改默认密钥那就直接用2dxLua。如果改了可以在 APK 的 so 文件或 iOS 二进制里搜索可读字符串常能找到xxtea或相关配置。再不行就在反编译出来的 Java/Kotlin 代码里搜setXXTEAKeyAndSign的调用链。这一步涉及到具体包结构没法给一个通杀方案得按项目来。3.3 luac/LuaJIT 反编译器怎么选Lua 字节码的反编译工具相对成熟但仍远谈不上完美。我常用的工具列表工具适用目标优点缺点unluac标准 Lua 5.1/5.2/5.3/5.4 字节码还原度较高支持 LuaJIT 2.0 部分版本对异常字节码容易直接崩掉ljdLuaJIT 2.0/2.1 字节码专门针对 LuaJIT还原后的伪代码可读性一般luadecLua 5.1/5.2 字节码对函数定义还原较好项目活跃度一般版本兼容有限luajit-decompLuaJIT 字节码轻量很多场景下只输出反汇编不还原成伪代码实际使用中lua 5.1 的unluac是我遇到最多能成功还原出接近源码结构的工具。LuaJIT 的反编译要难得多尤其 2.1 的字节码许多情况下只能得到可读性较差的反汇编或者直接还原失败。所以如果你确认目标是 LuaJIT 2.1我建议提前调整预期能还原出“可读的伪代码”就已经算成功了。3.4 jsc 和混淆 JS 的还原思路JS 这边的情况比较简单如果能拿到解密后的 JS 明文交给 Prettier 或 js-beautify 就能恢复缩进和换行阅读性提升很大。如果代码还被做了混淆比如变量名缩短、字符串拼接、控制流扁平化那就需要借助 AST 工具慢慢还原。对于真正的.jsc字节码社区可用的反编译工具有限且基本要跟 JSC 版本精确对应。我通常的路线是先确认它确实不是 xxtea 加密文件然后去对应版本的 JSC 反编译项目里找可执行文件如果工具不支持就退回字节码反汇编级别看常量表和指令。另一种更务实的方法是在运行时用引擎日志或 hook 机制从内存中导出已经被 JSC 解析过的字符串这比硬啃字节码高效得多。4. 零到一搭一套自动化解密工作流单点工具有了接下来要把它们串成一套流程。目标很简单输入一个目录自动识别文件类型自动解密/反编译输出结构清晰的结果。4.1 入口文件定位先看启动脚本和引擎配置遇到一个陌生包我习惯先看根目录下有没有main.lua、main.js或project.json这类入口文件。入口文件里通常写明了脚本目录、资源目录和初始场景。这一步能帮你快速了解项目的代码组织方式也能判断它用的是哪套加密逻辑。比如 cocos2d-lua 的main.lua里常会有对src/和res/的路径拼接还可能在config.lua里加载全局配置。cocos2d-js 的project.json会声明jsList、engine、resourceRoot等关键字段。把这些信息抓出来你就知道该把解析重点放在哪个目录。4.2 写一个批量解密脚本单文件处理显然不够用。我的套件里有一个批量脚本遍历目标目录根据文件头自动决定处理方式最终输出到decrypted/目录#!/bin/bash find $1 -type f \( -name *.jsc -o -name *.luac -o -name *.lua -o -name *.js -o -name *.bytes \) | while read f; do python3 tools/decrypt_one.py $f decrypted/${f#*/} donedecrypt_one.py的核心逻辑是读取文件头分类到不同分支明文 JS 直接复制带XXTEA签名的先解密\x1bLua调 unluac\x1bLJ调 ljd。这样一条命令就能把整个包处理完。当然批量脚本也有副作用比如有些资源文件其实是二进制数据但不是脚本误判会导致无意义的报错。所以我会在脚本里加白名单和黑名单只处理我关心的目录。4.3 重打包不只是替换文件那么简单如果你把解密工具链用于 mod 或汉化那最后往往会面临重打包。APK 重打包后必须重新签名否则 Android 不会安装。iOS 这边更麻烦要重签名、重打 IPA还要处理描述文件和设备信任问题。对于还在运营的游戏修改过的包很可能触发服务器的完整性校验所以我的建议是重打包只用于单机、测试或已授权项目千万别拿它去碰在线游戏。另一个思路是不重打包而是利用引擎的 search path 机制。很多 cocos2d 游戏运行时允许从外部可写目录读取同名文件覆盖内置资源这意味着模拟器或 root 设备上你只要把解密后的文件放到正确位置游戏就会优先加载免去了重签名和过校验的麻烦。5. 实操中最容易踩的五个坑这些坑是我自己在反复解包中积累的不是看文档能看出来的。每一条都对应过一次至少半小时的排错过程。5.1 字节码版本和引擎版本错位解包一个 cocos2d-lua 老游戏发现unluac怎么跑都报错检查文件头是\x1bLua没错版本号也匹配 Lua 5.1但依然失败。后来发现引擎编译字节码时可能开启了 “去掉调试信息” 或 “strip” 选项导致工具解析不了。这不是工具坏了而是字节码里没有足够的调试符号很多反编译器在缺乏upvalue或局部变量表时就会放弃。解决思路是换一种能容忍缺失信息的工具或者直接理解它反汇编出来的指令。遇到这种项目我会先看指令流里能不能还原函数调用关系再手动标出关键分支。5.2 密钥是动态拼接的不是一串静态字符串很多项目不会傻到把密钥一次性写死而是拆成几段在初始化时拼接。这样你在二进制里直接搜2dxLua搜不到搜XXTEA也找不到完整签名。我遇到过一个项目密钥是1234 abcd 版本号版本号正好来自配置文件所以得先读配置再拼出完整 key才能解开脚本。这类动态密钥在分析时要多留一个心眼不要只搜字符串还要看setXXTEAKeyAndSign的调用位置去追 key 的来源。如果反编译后的 Java/Kotlin 代码可读直接在调用链上搜索字符串拼接逻辑通常很快。5.3 LuaJIT 反编译结果的“伪可读”陷阱LuaJIT 反编译出来的伪代码很多情况下语法上是可读的但语义可能变化很大。比如局部变量数量对不上、循环结构被展开、函数调用顺序被调换。这会让新手误以为自己拿到的是可信源码于是照着改结果一跑就崩。我的经验是反编译结果只能作为理解和参考不能作为直接修改的基础。真要修改尽量在同一份字节码上做小范围改动或者干脆用基于字节码的 patch而不要试图重写整段伪代码。5.4 jsc 还原后的变量名全部丢失JavaScriptCore 字节码反编译回来的 JS变量名大概率是a、b、c这类占位符甚至可能只是一堆临时寄存器名。你很难从这些变量名里猜出业务含义。所以我一般不会纠结于还原完整变量名而是先靠字符串常量定位业务逻辑比如搜索报错提示、服务器接口路径、本地存储 key 等。字符串在字节码常量表里通常保留得很完整这比代码结构还原容易得多。5.5 解密工具处理大文件的性能问题一键批处理可能会遇到性能问题尤其是 unluac 和 ljd 这类基于指令级解析的工具对超大 Lua 函数非常吃力。某个项目的一个副本模块文件有几十 MB单文件处理花了好几分钟而且内存占用极高。后来我把脚本改成按文件头做预过滤跳过明显的资源文件并且对超过阈值的大文件单独分线程处理才算能跑完整个包。日常使用中我还会给批量脚本加一个日志输出记录每个文件的处理时长和结果状态。这样一旦某个文件卡住我能立刻定位而不是看着终端窗口一直滚动到失控。6. 这套套件的使用边界和长期维护建议最后这部分我想聊一些更根本的东西。工具写出来容易但怎么用、怎么长期维护才是真正决定它能陪你走多远的关键。6.1 搞清楚你手中“解密权”的范围我会反复强调这一点只有当你对目标包体拥有合法授权时解密和反编译才是正当的工程行为。自己公司的产品、已经获得源码或资源使用权的项目、公开的、明确授权可二创的 mod 社区项目这些都没问题。但如果是别人正在运营的商业游戏你去解包、扒脚本、试图改收费逻辑或绕过安全验证那就越界了。从技术角度讲今天的加密手段也在不断升级单纯靠解密套件去对抗商业级安全方案不仅效率低而且风险完全不成比例。作为从业者我更建议把时间花在真正能创造价值的地方比如用这套工具去理解引擎内部机制、优化自研项目性能、或者做有意思的 mod 创作。6.2 维护套件的经验解密套件本质上是一个长期积累的过程不是写完就一劳永逸。我的习惯是把所有工具脚本放到独立仓库按照引擎版本和 Lua 版本分目录每个目录里记录测试过的样本和对应结果。这样当我下一次拿到类似版本的项目直接查找历史记录不用重新踩一遍坑。另外我会把每次遇到的加密变体都补充进识别规则里。比如某次发现文件头不是 xxtea 签名而是一个 4 字节长度前缀加 xxtea 数据我就会在decrypt_one.py里加一个分支。这个更新迭代的过程其实就是套件真正有价值的地方。最后一个小建议别追求“一套脚本通吃所有游戏”。每个项目都有自己的怪癖与其写一个充满各种条件分支的重型框架不如保持核心功能简单、插件化遇到新类型时单独加一个模块。我自己吃过过度设计的亏把脚本抽象得面目全非结果遇到新项目反而改不动了。工具是拿来用的不是拿来表演架构艺术的。本文还有配套的精品资源点击获取
返回列表