如何防止Lua的loadstring被钩子劫持,保护其内部代码内容?
Lua中loadstring/load的反劫持方案
我在Lua中使用loadstring时,会被攻击者通过钩子代码劫持,导致传入的代码内容被窃取。攻击者的钩子会提前保存原生函数副本,替换_G表中的loadstring/load和tostring,伪造函数名称绕开tostring(loadstring)的验证,还能通过提前保存的writefile/print副本输出窃取的内容。
攻击者的钩子代码示例:
-- Save copies of orignal functions local o_load = _G["load"] local o_loadstring = _G["loadstring"] local o_unpack = _G["unpack"] local o_pairs = _G["pairs"] local o_writefile = _G["writefile"] local o_print = _G["print"] local o_tostring = _G["tostring"] -- Dynamic function names local load_name = tostring(_G["load"]) local loadstring_name = tostring(_G["loadstring"]) local tostring_name = tostring(_G["tostring"]) -- For multiple instances of loadstring or load being used local files_loadstring = 1 local files_load = 1 -- Hide function names _G["tostring"] = function(...) local args = {...} local arg = o_unpack(args) if arg == _G["tostring"] then return tostring_name end if arg == _G["loadstring"] then return loadstring_name end if arg == _G["load"] then return load_name end local ret = { o_tostring(o_unpack(args)) } local value = o_unpack(ret) return value end -- Hook loadstring _G["loadstring"] = function(...) local args = {...} o_print("loadstring called") local str = "" for k, v in o_pairs(args) do str = str .. o_tostring(v) .. " " end o_writefile("hook_loadstring"..o_tostring(files_loadstring)..".lua", str) o_print("file written to hook_loadstring"..o_tostring(files_loadstring)..".lua") files_loadstring = files_loadstring +1 local ret = { o_loadstring(o_unpack(args)) } str = "" for k, v in o_pairs(ret) do str = str .. o_tostring(v) .. " " end return o_unpack(ret) end -- Hook load _G["load"] = function(...) local args = {...} o_print("load called") local str = "" for k, v in o_pairs(args) do str = str .. o_tostring(v) .. " " end o_writefile("hook_load"..o_tostring(files_load)..".lua", str) o_print("file written to hook_load"..o_tostring(files_load)..".lua") files_load = files_load +1 local ret = { o_load(o_unpack(args)) } str = "" for k, v in o_pairs(ret) do str = str .. o_tostring(v) .. " " end return o_unpack(ret) end -- bellow is the loadstring function with a lua and all it's content is getting hooked and writen in a file loadstring("local a = 1;")()
以下是可行的反劫持方案:
1. 提前缓存原生函数到局部变量(优先级最高)
如果你的代码能在攻击者钩子之前执行,直接将原生load/loadstring存到局部变量中——局部变量不受_G表污染的影响:
-- 必须放在所有代码(包括攻击者钩子)最开头执行 local native_load = load local native_loadstring = loadstring -- 后续执行动态代码时直接使用局部变量 native_loadstring("local a = 1;")()
2. 从debug库获取原生函数(环境允许时使用)
Lua的debug库可以访问虚拟机内部的原生函数,绕过_G的污染:
local function get_native_loaders() -- 通过匿名函数获取原始全局环境 local dummy = function() end local raw_env = debug.getupvalue(dummy, 1) return raw_env.load, raw_env.loadstring end -- 获取原生函数 local native_load, native_loadstring = get_native_loaders() -- 使用原生函数执行代码 native_loadstring("local a = 1;")()
注意:如果debug库也被攻击者劫持,此方法失效。
3. 加密动态代码内容(退而求其次的防护)
即使无法阻止劫持,也可以对要执行的代码进行加密,让攻击者只能拿到加密后的内容:
-- 示例:简单异或加密(实际使用更安全的加密算法) local function xor_cipher(str, key) local result = "" local key_len = #key for i = 1, #str do local byte = string.byte(str, i) ~ string.byte(key, (i-1)%key_len + 1) result = result .. string.char(byte) end return result end -- 提前加密代码 local encrypted_code = xor_cipher("local a = 1;", "your_secret_key") -- 执行时解密再加载 loadstring(xor_cipher(encrypted_code, "your_secret_key"))()
4. 检测函数是否为原生C函数
原生load/loadstring是C实现的,而攻击者的钩子是Lua函数,可以通过debug.getinfo的what字段区分:
local function is_native_function(func) if type(func) ~= "function" then return false end local info = debug.getinfo(func, "S") return info and info.what == "C" end -- 验证当前loadstring是否被篡改 if not is_native_function(loadstring) then error("loadstring has been hooked, aborting!") end
注意:若攻击者篡改了debug.getinfo,此方法失效。
内容的提问来源于stack exchange,提问作者Alvee
相关产品推荐
相关产品推荐

