完整课程入口:996 全套课程体系 | Lua 学习路径 | 幂尔框架 mirs.cn
加密防"整体拖走",混淆防"看懂"。即便脚本被 dump 出还原成源码,混淆后的代码变量名无意义、字符串全是密文、执行顺序被打乱,阅读成本会呈数量级上升。混淆的目标不是让逆向不可能,而是让"读懂并修改某个具体功能"的时间长到不值得。
Lua 源码里 80% 的情报在字符串里:函数名(lib996:attr)、协议名、GM 命令、提示文案。明文字符串哪怕只做全局搜索,破解者也能秒定位"爆率在哪改"。字符串加密的做法:构建期扫描所有字符串常量,替换成 S(0x1A) 形式的解密调用,把真实串放在加密表里;运行时提供一个解密函数,首次访问时解出并缓存。
-- 构建期生成
local K = {"\x88\x12\xAF...", "\x51\xC0\x9B..."} -- 密文表
local function S(i)
local s = K[i]
if not rawget(_G, "_S" .. i) then
_S[i] = xxtea(s, KEY) -- 解出真串并缓存
end
return _S[i]
end
-- 原代码 lib996:attr(actor, 1) 变成
G[ S(17) ]( actor, 1 )
注意解密函数本身是命门:给它再加一层简单变换(每串用不同偏移异或),通用脱壳工具就会失效。
改名最简单也最有效:把所有局部变量、内部函数重命名为 a/b/c,同时删除注释与全部调试信息。配合编译时 strip 掉符号表,反编译输出的可读性断崖式下降。
控制流平坦化把顺序逻辑改写成一个状态机循环:所有代码块拆进 while true do + state 分支里,真实执行顺序由运行时变量决定。逆向者必须人工追踪状态转移才能还原流程。代价是性能损耗与调试困难,只应施加在关键函数(校验、结算)上,全量平坦化得不偿失。
混淆必须在构建流水线里自动完成:源码仓库永远保存明文(开发与热更要靠它),打包脚本统一执行"改名 → 字符串加密 → 平坦化(可选)→ 编译加密"。每次构建混淆种子随机化,同一份源码每次产出的密文都不同,能显著增加比对分析难度。
最后一个提醒:混淆会毁掉 Lua 错误栈的可读性。务必保留一份"混淆名 → 源码行号"的映射表存放在内部,线上报错把映射还原后再排查,否则自己的工程师也读不懂自己的产品。
getrandomtext (path,str)
从文件中随机获取一行字符串,混淆表与种子从文件随机取,避免每次编译表都一样
| 参数 | 类型 | 说明 |
|---|---|---|
| path | string | 文件路径(必填参数) |
| str | string | 指定行(0~1000)传入-1随机取某一行的字符串(必填参数) |
local str = getrandomtext('..\\QuestDiary\\abc.txt',-1)
release_print('getrandomtext',str)
parsetext(text, object)
解析文本,字符串表与映射表用引擎解析口读取
| 参数 | 类型 | 说明 |
|---|---|---|
| text | string | 文本内容(必填参数) |
| object | object | 玩家对象(1-50)(必填参数) |
md5str(str)
MD5加密,混淆后校验产物是否符合预期
| 参数 | 类型 | 说明 |
|---|---|---|
| str | string | “需要加密的文本 |
require(path)
加载文件,混淆产物的加载路径与正常脚本保持一致
| 参数 | 类型 | 说明 |
|---|---|---|
| path | any | 文件名两种加载接口起始路径不同 |
release_print(msg)
打印消息到控制台,混淆后出错信息可读性差,调试口必须留好
| 参数 | 类型 | 说明 |
|---|---|---|
| msg | any | 打印内容 |