Lua 脚本加密从零到精通 —— 全方位教程

📅 2026/7/5 2:44:47
Lua 脚本加密从零到精通 —— 全方位教程
​Lua 脚本加密从零到精通 —— 全方位教程本教程基于 Lua 5.2 / 5.3 环境常用于 GameGuardian 脚本从最基础的字符串操作开始逐步深入到高级防反编译技术。你将学会自己编写加密工具并理解如何写出兼容性强、速度极快、难以破解的加密脚本。第一章基础概念 —— 什么是 Lua 脚本加密加密 ≠ 压缩也≠ 非对称密码。对 Lua 脚本而言“加密”通常指对源代码进行变换使其难以阅读和修改但加载后仍能正常运行。常见的加密手段改变字符的表示如十六进制、字节序编译成二进制字节码string.dump加入垃圾代码、混淆控制流修改字节码签名防反编译动态解密执行目标防分析、防篡改、防破解。第二章必备基础 —— Lua 字符串与字节处理所有加密算法的核心是字符串 ↔ 数字的转换。2.1string.byte(s [, i [, j]])返回字符的 ASCII 码数值print(string.byte(A))-- 65print(string.byte(你好,1))-- 230 第一个字节print({string.byte(Lua,1,-1)})-- {76, 117, 97}2.2string.char(...)将数值转为字符逆操作print(string.char(65))-- Aprint(string.char(230,138,185))-- 你UTF-8 三个字节2.3string.format(%02X, num)数值转两位十六进制字符串print(string.format(%02X,65))-- 41print(string.format(%02X,255))-- FF2.4string.gsub(s, pattern, func/string)核心替换函数在加密中常用来对每个字符做变换。2.5 循环遍历字符串fori1,#strdolocalbytestring.byte(str,i)-- 处理end第三章最简单的加密 —— 十六进制编码原理将源码的每个字节转为两位十六进制拼接成一个新字符串。3.1 加密函数functionEncrypt_hex(text)returntext:gsub(.,function(c)returnstring.format(%02X,string.byte(c))end)end效果原文本: Lua 加密后: 4C75613.2 解密函数functionDecrypt_hex(hex_str)returnhex_str:gsub(..,function(h)returnstring.char(tonumber(h,16))end)end3.3 简单测试localsourceprint(Hello)localencEncrypt_hex(source)print(enc)-- 7072696E74282248656C6C6F2229localdecDecrypt_hex(enc)print(dec)-- print(Hello)load(dec)()-- 输出 Hello特点极简但一看就是十六进制容易被识别。不能防分析。第四章加入偏移量和模运算 —— 增加破解难度为了让密文不像直接十六进制可以对每个字节加上一个固定值或使用密码然后对 256 取模。4.1 固定偏移加密functionEncrypt_offset(text,offset)returntext:gsub(.,function(c)returnstring.format(%02X,(string.byte(c)offset)%256)end):gsub( ,)-- 把空格替换为可选endfunctionDecrypt_offset(hex_text,offset)returnhex_text:gsub(..,function(h)returnstring.char((tonumber(h,16)-offset256)%256)end)end使用localoffset13localencEncrypt_offset(Hello,offset)-- 每个字节13后转十六进制localdecDecrypt_offset(enc,offset)print(dec)-- Hello4.2 用密码做偏移将用户输入的密码转换成数值如密码字符串的字节和作为偏移量。enc_string Encrypt_offset(source, user_key)这种方法更灵活但注意密码长度和位数的影响。第五章将加密与解密嵌入脚本 —— 自解密执行真正的“加密脚本”通常是这样的结构加密后的代码存储在某个字符串中脚本运行时先通过解密函数还原出源码再用load()pcall()执行5.1 经典模板-- 解密函数必须出现在加密脚本的开头functionDecrypt(hex_text,key)returnhex_text:gsub(..,function(h)returnstring.char((tonumber(h,16)-key256*999)%256)end)end-- 加密后的密文由加密工具生成localcipher4D7A5F...-- 很长的十六进制串-- 解密并执行localsourceDecrypt(cipher,13)localfunc,errload(source)iffuncthenpcall(func)elseprint(解密失败)end5.2 生成加密脚本的工具代码localkey13localsource_pathgg.prompt({选择要加密的脚本},{/sdcard/test.lua},{file})localfio.open(source_path,r)localdataf:read(*a)f:close()-- 加密localcipherdata:gsub(.,function(c)returnstring.format(%02X,(string.byte(c)key)%256)end):gsub( ,)-- 包裹模板localdecrypt_func[[ function Decrypt(t, k) return (t:gsub(.., function(h) return string.char((tonumber(h,16)-k256*999)%256) end)) end local code Decrypt(]]..cipher..[[, ]]..key..[[) load(code)() ]]-- 写入新文件io.open(source_path..E,w):write(decrypt_func)print(加密完成..source_path..E)注意解密函数中的256*999是为了处理负数取模保证结果在 0-255 之间。第六章编译为二进制字节码 —— 源码不可读string.dump(func, strip)可以将 Lua 函数序列化为二进制Lua 字节码。这是最常用的“加密”手段因为字节码不是人类可读的。6.1 基本编译localsourceprint(I am compiled)localcompiledstring.dump(load(source),true)-- true 表示去掉调试信息更小io.open(/sdcard/compiled.lua,w):write(compiled)生成的 .lua 文件开头是\x1bLua\x52LuaR 签名可以被loadfile直接加载。6.2 加密 编译结合多重保护-- 1. 对源码进行逐字节偏移加密转为 hex-- 2. 将 hex 存储到脚本中运行时解密-- 3. 用 load() 加载解密后的源码-- 4. 最后用 string.dump 把整个加载器编译可选-- 更复杂的流程见【第九章】。6.3 编译后的文件特征和反编译标准编译后的文件以LuaR标识很多反编译器如 unluac、ljd可以解码。所以简单编译并不能保证安全。第七章修改字节码标识LuaR → 其他 —— 防反编译改变string.dump产生字节码中的LuaR字符串可以让通用反编译器无法识别。7.1 替换签名compiledcompiled:gsub(LuaR,LuaS)-- 将LuaR改成任意四个字符反编译器会检查头部的LuaR改掉后它们会报错或拒绝解析。注意Lua 加载器不检查签名所以修改后仍能被load/loadfile执行。但这会破坏 Lua 标准实现部分环境可能不兼容需要测试。7.2 更自由修改内部函数名一些工具还会修改字节码中的函数名、行号等增加分析难度。7.3 示例编译并改签名localsrcload(source)localbytecodestring.dump(src,true)bytecodebytecode:gsub(LuaR,LuaZ)-- 改成任意非标标识io.open(out,w):write(bytecode)许多加密工具如 EM3.0、执念、SSB 等都使用了这种技术。第八章防反编译与控制流混淆除了改签名还可以通过以下方式让反编译器失效或解码错误。8.1 Goto 死循环编写利用goto和标号制造无法被静态分析的控制流。反编译器往往无法正确处理 goto。示例来自资料中的“防反编译方法”bb{}ifbbgx1elsegxjb{}ifgxjb1thengotoexitelseifgxjb2then-- 干扰代码gg.fullNamegg.getFile()...gotoexitelsegotoexitendwhiletruedoend::exit::这种代码很难被反编译成结构化的 Lua。8.2 垃圾代码死代码在加密后的脚本中插入大量无用但合法的代码如循环、冗余变量增加分析难度。8.3 字符串隐藏将敏感字符串如密码、URL拆分成 ASCII 数组运行时拼接防止静态字符串搜索。常见实现localchar{}localstr{72,101,108,108,111}-- Hello 的 ASCIIfori1,#strdochar[i]string.char(str[i])endlocalreal_strtable.concat(char,)更复杂的还会对每个数值进行运算如 16 后存储运行时 -16。8.4 多重加载 / 多次解密第一次加载检验环境第二次才真正解密执行。或者在解密后立即使用pcall(load(load(data)))这种双层加载先加载产生一个 chunk再调用这个 chunk 得到最终函数。示例来自知识库 SSB 加密load(load(data))load(data)返回一个函数然后外层的load把这个函数的结果字符串再次加载然后执行。这种技巧可以让静态分析更困难。8.5 环境检测与破坏加密脚本可以检测是否在调试器/模拟器中运行是则退出。也可以在解密主代码前增加大量无意义但耗时的计算让恶意用户等待。第九章编写你自己的加密工具 —— 从零开始现在你已经掌握了核心算法和技巧可以写一个完整的加密工具了。一个好的工具应该选择要加密的源文件读取源码使用一种或多种加密算法处理生成一个新的 Lua 文件包含解密加载器和密文可选编译 改签名9.1 基础版十六进制 固定偏移-- 文件名encrypt_tool.luagg.alert(简单加密工具 v1.0)localpathgg.prompt({选择脚本},{/sdcard/},{file})ifnotpaththenos.exit()endlocalfio.open(path[1],r)localsourcef:read(*a)f:close()-- 加密参数localkey13localciphersource:gsub(.,function(c)returnstring.format(%02X,(string.byte(c)key)%256)end):gsub( ,)-- 生成解密头localdecryptor[[ function _DEC(t, k) return (t:gsub(.., function(h) return string.char((tonumber(h,16) - k 256*999) % 256) end)) end local _CODE _DEC(]]..cipher..[[,]]..key..[[) load(_CODE)() ]]-- 写入新文件localout_pathpath[1].._enc.luaio.open(out_path,w):write(decryptor)gg.alert(加密完成保存至..out_path)9.2 进阶版加入字节码编译 改签名-- 在生成 decryptor 后不直接写文件而是先编译 decryptor 再改签名localcompiledstring.dump(load(decryptor),true)compiledcompiled:gsub(LuaR,LuaX)-- 防反编译io.open(out_path,w):write(compiled)但是注意这样生成的脚本只能由loadfile加载不能再被看到源码但反编译仍然可能。为了增强可以在解密器中再嵌入一层防反编译代码如 goto 混淆。9.3 高级版数组混淆 随机偏移参考知识库中的 EM3.0、NF3.0 等它们使用对加密后的 hex 串再取出每个字符的 ASCII 码加上一个随机值如 16组成数组。存储格式为逗号分隔的数字。解密时减去该随机值组合还原。这种加密后的脚本里没有明显的 hex 字符串而是一堆数字更难一眼看穿。示例代码核心-- 加密阶段zacEncryption(data)-- 得到 hex 串z{}fori1,#zacdolocalbytestring.byte(zac,i)table.insert(z,byte16)-- 每个字节加16后存储endlocalcipher_arraytable.concat(z,,)-- 解密模板localdecrypt_template[[ local encrypted {]]..cipher_array..[[} local dec {} for i 1, #encrypted do dec[i] string.char(encrypted[i] - 16) end local hex_str table.concat(dec, ) -- 再用 hex 解码 function h2s(h) return h:gsub(..,function(c) return string.char(tonumber(c,16)) end) end local code h2s(hex_str) load(code)() ]]第十章兼容性与性能优化10.1 兼容性避免使用过新的 Lua 特性尽量使用 Lua 5.2 基本函数因为 GG 环境通常基于 Lua 5.2 / 5.3。不要依赖外部库加密工具本身只使用io,string,gg,pcall,load等通用函数。文件路径处理使用gg.getFile()获取当前脚本路径尽量用绝对路径。错误处理加assert或pcall包裹读写操作避免崩溃。10.2 性能解密算法要尽量快使用gsub和tonumber是高效的但循环遍历会慢一些。优先用gsub的模式匹配。减少嵌套调用解密时不要在每一轮都调用函数尽量一气呵成。编译优化string.dump时用true参数去掉调试信息体积更小加载更快。避免解密主代码时执行耗时操作比如垃圾代码不要放在解密路径上放在解密后执行。10.3 体积加密后的脚本大小 密文大小 解密器大小。密文通常比源码大 2~3 倍因为十六进制。可以考虑使用 base64 或自定义编码减小体积但会增加解密时间。如果使用数组存储数字体积更大每个数字变成字符串逗号。需要权衡抗分析 vs 体积。第十一章防解密策略 —— 让黑客头疼即使使用了加密 编译依然可能被动态调试或内存 dump。以下是进阶防护11.1 动态检测修改检查脚本是否被修改计算 checksum比较原始值。检查运行环境是否在 Xposed、Frida、VirtualXposed 中。11.2 多层解密与自修改解密出一段代码后立即执行并且这段代码再解密下一段。或者在解密后立即将内存中的关键函数覆盖掉使用debug库但 GG 可能不支持。11.3 使用load()多次加载知识库中有fori1,100000doload(load(data))end这种循环跑很多次增加动态分析的难度很难下断点因为循环量太大。11.4 字符串“花指令”用不易察觉的方式构造字符串如localaHelocalbllolocalca..b-- Hello但是这种静态仍然可拼接更好的办法是用数组拼接。11.5 自定义 VM / 字节码解释器这是最极端的将一个 Lua 字节码用自己定义的指令集重新编码然后在 Lua 中写一个解释器来运行。这样做基本上无法逆向除非逆向你的解释器。但这非常复杂需要深度 Lua 知识。第十二章综合案例 —— 一个中等强度的加密工具结合前面的所有技术我们来写一个完整的加密工具包含文件选择源码加密hex 偏移存储为数组偏移编译 改签名加入简单的 anti-goto 混淆12.1 加密器代码-- encryptor.lualocalpathgg.prompt({选择要加密的脚本},{/sdcard/},{file})ifnotpaththenos.exit()endlocalfio.open(path,r)localsourcef:read(*a)f:close()-- 加密参数localkey13-- 可以改为用户输入-- 第一步hex 偏移localhex_encsource:gsub(.,function(c)returnstring.format(%02X,(string.byte(c)key)%256)end)-- 第二步将hex字符串转为数字数组 (16)localz{}fori1,#hex_encdotable.insert(z,string.byte(hex_enc,i)16)endlocalarray_strtable.concat(z,,)-- 第三步生成解密器包含反编译干扰localdecryptor[[ local encrypted {]]..array_str..[[} local hex_part {} for i1,#encrypted do hex_part[i]string.char(encrypted[i]-16) end local hex table.concat(hex_part,) local key ]]..key..[[ local function _D(t,k) return (t:gsub(.., function(h) return string.char((tonumber(h,16)-k256*999)%256) end)) end local code _D(hex,key) -- 加一段垃圾跳转 local _ {} if _ nil then goto _end end ::_end:: load(code)() ]]-- 第四步编译并改签名localcompiledstring.dump(load(decryptor),true)compiledcompiled:gsub(LuaR,LuK)-- 避开标准反编译器-- 第五步写入localout_pathpath...encio.open(out_path,w):write(compiled)gg.alert(加密完成文件..out_path)12.2 如何使用这个加密器在 GG 中运行 encryptor.lua选择要加密的源脚本注意源脚本必须是有效的 Lua 5.2 脚本会在同目录生成原文件名.enc直接把.enc当作加密后脚本发给别人别人用 GG 运行因为它是编译后的字节码加载后自动执行解密并运行原代码12.3 加密后脚本的安全性评估直接查看.enc文件得到的是二进制乱码反编译会被签名LuK阻止大部分反编译器只认LuaR。即使绕过签名解密器中有goto混淆会让反编译结果出错或产生次品。密文是以数字数组形式存储不是直接字符串防止静态搜索特征。真正源码只有在_D解密后才在内存中出现可以被抓但需要动态 dump。第十三章如何持续提升 —— 进阶学习方向深入研究 Lua 字节码了解指令结构、常量表、原型。可以手写字节码修改脚本来混淆函数调用。学习 Lua 虚拟机源码理解load、pcall的内部实现可以编写更底层的保护。关注社区新反编译技术现有的反编译器如 unluac、ljd会更新所以加密也要与时俱进。测试解密难度学会使用反编译工具尝试破解自己的加密脚本从而发现弱点。编写自己的混淆器不仅仅是加密数据而是改变控制流、变量名随机化、插入假分支等。结语从最简单的十六进制替换到编译修改签名再到多重加载和 goto 混淆你已经学习了 Lua 脚本加密的几乎所有常见技术。记住没有绝对的安全加密只能延缓破解不能完全阻止。加密的目标是提高破解成本让破解者觉得不值得花时间。兼容性和性能是底线加密不应该让脚本无法运行或慢到不可接受。现在你可以利用教程中的模板结合自己的想象写出独一无二的加密工具。实践是最好的老师多尝试、多测试、多逆向别人的加密脚本你的水平会越来越高。祝你在 Lua 加密之路上从入门到精通