热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

【游戏安全】看我如何通过hook攻击LuaJIT

译者:興趣使然的小胃预估稿费:200RMB投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿一、前言如果你在游戏行业摸爬滚打已久,你肯定听说过Lua这个名词。作为一门强大的脚本语言,

https://img.php1.cn/3cd4a/1eebe/cd5/8343fdbffb0056b5.webp

译者:興趣使然的小胃

预估稿费:200RMB

投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿

一、前言

如果你在游戏行业摸爬滚打已久,你肯定听说过Lua这个名词。作为一门强大的脚本语言,Lua已经嵌入到数千种视频游戏中,提供了各种API接口,以便工程人员在游戏客户端以及服务器上添加各种功能。

我会不断强调一个观点:为了让攻击技术更加便捷、更加可靠以及更加高效,最好的方法就是攻击游戏引擎,而不是攻击游戏本身。Hook一大堆函数、定位一大堆地址本身是个很好的办法,然而这意味着只要游戏更新版本,你就需要更新你所使用的偏移量。相反,如果你hook了游戏使用的那些库,这些问题就会迎刃而解。

Lua的普及性使得它成为hook的理想目标。此外,由于游戏开发者使用Lua来添加内容及功能,因此游戏所包含的Lua环境就成为拥有大量功能的强大主机环境。

出于性能要求,使用LuaJIT来替代vanilla Lua是非常常见的场景。因此,在本文中我会探讨如何攻击LuaJIT。只要稍作修改,这种攻击技术也可以应用于vanilla Lua。


二、注入Lua代码

为了创建Lua环境,我们需要调用luaL_newstate返回一个lua_State对象,然后将其作为参数,调用luaL_openlibs即可。有人可能想通过劫持luaL_newstate的执行来注入代码,然而这种方法并不能奏效。因为此时程序库还没有加载,因此加载脚本不会起到任何作用。然而,我们可以劫持luaopen_jit函数,这个函数正是打开程序库时所调用的最后一个函数(参考此处)。

动态链接LuaJIT时,我们可以查找导出表来定位这个函数:

http://p1.qhimg.com/t01e5fd413bc1fc0df0.png

静态链接LuaJIT时,我们可以使用一些特征字符串来进行定位:

http://p9.qhimg.com/t01269755b2029eba06.png

一旦找到这个函数,hook就不是件难事。然而,在hook之前,我们需要找到两个函数:luaL_loadfilex这个函数用来加载我们的Lua脚本,lua_pcall这个函数用来执行Lua脚本。动态链接时,我们可以在导出表中找到这两个函数;静态链接时,我们可以使用“=stdin”字符串来定位第一个函数(参考此处):

http://p4.qhimg.com/t019ae20ae15c7cbe7e.png

定位第二个函数需要多费点功夫,因为该函数没有关联某个特征字符串。然而幸运的是,该函数在内部调用时(参考此处),位于“=(debug command)”之后:

http://p8.qhimg.com/t01b96bfffab9e4a0ec.png

注意,上图中我们还能观察到luaL_loadbuffer函数的地址,牢记这一点,回头要用到。

识别出这些地址后,我们就可以开始写hook代码了:

typedef void* lua_State;
typedef int (*_luaL_loadfilex)(lua_State *L, const char *filename, const char *mode);
_luaL_loadfilex luaL_loadfilex;
typedef int (*_luaopen_jit)(lua_State *L);
_luaopen_jit luaopen_jit_original;
typedef int (*_lua_pcall)(lua_State *L, int nargs, int nresults, int errfunc);
_lua_pcall lua_pcall;
int luaopen_jit_hook(lua_State *L)
{
    int ret_val = luaopen_jit_original(L);
    luaL_loadfilex(L, "C:\test.lua", NULL) || lua_pcall(L, 0, -1, 0);
    return ret_val;
}
BOOL APIENTRY DllMain(HMODULE mod, DWORD reason, LPVOID res)
{
    switch (reason) {
    case DLL_PROCESS_ATTACH: {
            luaL_loadfilex = (_luaL_loadfilex)LOADFILEEX_ADDR;
            lua_pcall = (_lua_pcall)PCALL_ADDR;
            HookCode(OPENJIT_ADDR, luaopen_jit_hook, (void**)&luaopen_jit_original);
            break;
        }
    }
    return TRUE;
}

我的hook代码如上所示,使用的是自己开发的hook引擎。你可以使用Detours或者自己的引擎。需要牢记的是,hook点应该位于DLL中,以便注入到进程中。

现在,创建Lua环境时,“C:test.lua”就会被加载到这个环境中。通常情况下,我首先会注入代码,使用debug.sethook来劫持对Lua函数的所有调用以及相应的参数,以便后续分析:

lua jit.off()
FILEPATH = "C:LuaJitHookLogs" STARTINGTIME = os.clock() GDUMPED = false
function dumpGlobals() local fname = FILEPATH .. "globals" .. STARTING_TIME .. ".txt" local globalsFile = io.open(fname, "w") globalsFile:write(table.show(G, "G")) globalsFile:flush() globalsFile:close() end
function trace(event, line) local info = debug.getinfo(2)
if not info then return end
if not info.name then return end
if string.len(info.name) <= 1 then return end
if (not GDUMPED) then
    dumpGlobals()
    GDUMPED = true
end
local fname = FILE_PATH .. "trace_" .. STARTING_TIME .. ".txt"
local traceFile = io.open(fname, "a")
traceFile:write(info.name .. "()n")
local a = 1
while true do
    local name, value = debug.getlocal(2, a)
    if not name then break end
    if not value then break end
    traceFile:write(tostring(name) .. ": " .. tostring(value) .. "n")
    a = a + 1
end
traceFile:flush()
traceFile:close()
end debug.sethook(trace, "c")

这段代码可以提取到一堆有价值的全局信息以及跟踪信息,存放在“C:LuaJitHookLogs”目录下。


三、详细分析

如果你非常熟悉Lua,你可以跳过这个部分,不然的话,你可以跟着我分析这个脚本的具体内容。

首先,我调用了jit.off函数,因为debug库无法劫持由jit引擎实时编译的那些调用代码。

在dumpGlobals函数内部,我将名为_G的表打印出来。这是个全局对象表,Lua使用这个表来跟踪全局域内的所有内容,并将跟踪结果以“key, value”键值对形式保存在该表中。你肯定能够想到,这个表的价值非常高。根据你的具体情况,你可能需要晚一点再调用dumpGlobals函数,因为有些游戏在调用第一个函数时并没有把所有的全局变量分配完毕。

我使用了debug.sethook(trace, "c")语句,使Lua在每个函数调用完成之前调用trace这个函数。在trace函数内部,我调用了debug.getinfo(2)以获取被劫持的函数名称。由于trace函数为当前正在使用的函数,也就是说该函数在栈上的级别为1,因此被劫持的函数的级别为2。然后我循环调用了debug.getlocal(2, a),其中a的值从1开始不断累加,直至该语句返回空值(nil)为止。通过这种方式,我们可以循环遍历级别为2的栈,找到所有的本地变量,并将查找结果以键值对的形式保存起来。对某个游戏这样处理后,我找到了如下信息,你可以根据这些信息猜到这是哪个游戏:

type()
(*temporary): table: 074FA7D0
{
    IsDestroyed = false,
    NumOfSpawnDisables = 0,
    SpawnOrderMinionNames = 
    {
        "Super",
        "Melee",
        "Cannon",
        "Caster",
    },
    WillSpawnSuperMinion = 0,
}

我们可以调用type来确定对象的类型,但对象本身就可以告诉我们关于该游戏的一些有趣信息。

如果我们愿意的话,我们可以使用debug.setlocal将参数改成某些函数。


四、劫持Lua代码

在许多情况下,我们需要劫持整个Lua脚本。通过这种方式,我们不需要将跟踪结果拼接起来,就可以详细分析游戏所用的脚本,理解脚本具体功能。Lua代码可以以文件或者缓冲区的形式加载到LuaJIT环境中。由于分析磁盘上的文件比较容易,因此我们会重点关注使用缓冲区的这种情况以及luaL_loadbuffer这个函数。

这个函数实际上有两种表现形式:分别为luaL_loadbuffer以及luaL_loadbufferex。这两个函数基本相同,第一个函数会调用第二个函数,只不过会把最后一个参数设为NULL(参考此处)。你可以会认为,只要hook luaL_loadbufferex这个函数,我们就可以搞定这两种情况,然而事实并非如此。由于LuaJIT主要是针对性能优化而设计的,因此通常情况下luaL_loadbufferex会以内联形式使用。然而,这两个函数最终都会调用如下这个函数(参考此处):

int lua_loadx(lua_State *L, lua_Reader reader, void *data, const char *chunkname, const char *mode);

LuaJIT偏向于使用内联代码,导致这个函数也变成内联形式。然而,这里最有用的是lua_Reader reader,这个回调函数知道如何将void *data转换为包含Lua代码的缓冲区。当LuaJIT加载位于缓冲区中的代码时,reader变为reader_string的地址(参考此处),而void *data所指向的char*字符串即为具体的Lua代码。

reader_string不是内联函数,因为它的地址可以作为回调指针来传递,因此我们可以在luaL_loadbuffer内部找到这个地址:

http://p3.qhimg.com/t01f995a7aaeabaf8df.png

利用这个地址,我们可以构造一个hook,来劫持并显示已加载的所有Lua缓冲区:

typedef const char* (*_reader_string)(lua_State *L, void *ud, size_t *size);
_reader_string reader_string_original;
const char* reader_string_hook(lua_State *L, void *ud, size_t *size)
{
    if (((size_t*)ud)[1] > 0)
        MessageBoxA(NULL, ((char**)ud)[0], "LuaJITHook", MB_OK);
    return reader_string_original(L, ud, size);
}
// from DllMain DLL_PROCESS_ATTACH
HookCode(READERSTRING_ADDR, reader_string_hook, (void**)&reader_string_original);

当然使用对话框来显示并不是优雅的解决办法,不要在意这个细节,你理解我的意思就可以了。


五、总结

这种方法非常强大。许多游戏提供了Lua脚本功能,可以实现自动化、拉高游戏视图以及ESP(透视)黑科技等。不同的游戏使用Lua的方法有所不同,但他们的工作原理都与本文的例子相似。

你可以在这段hook代码的基础上进行修改,添加扫描功能,自动定位这些函数,比如,你可以使用XenoScan这个库来完成这个任务。

如果你有什么意见或者建议,可以随时发表评论,也可以关注我的推特了解我最新发布的信息。


推荐阅读
  • 本文介绍了设计师伊振华受邀参与沈阳市智慧城市运行管理中心项目的整体设计,并以数字赋能和创新驱动高质量发展的理念,建设了集成、智慧、高效的一体化城市综合管理平台,促进了城市的数字化转型。该中心被称为当代城市的智能心脏,为沈阳市的智慧城市建设做出了重要贡献。 ... [详细]
  • Commit1ced2a7433ea8937a1b260ea65d708f32ca7c95eintroduceda+Clonetraitboundtom ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • 利用Visual Basic开发SAP接口程序初探的方法与原理
    本文介绍了利用Visual Basic开发SAP接口程序的方法与原理,以及SAP R/3系统的特点和二次开发平台ABAP的使用。通过程序接口自动读取SAP R/3的数据表或视图,在外部进行处理和利用水晶报表等工具生成符合中国人习惯的报表样式。具体介绍了RFC调用的原理和模型,并强调本文主要不讨论SAP R/3函数的开发,而是针对使用SAP的公司的非ABAP开发人员提供了初步的接口程序开发指导。 ... [详细]
  • 预备知识可参考我整理的博客Windows编程之线程:https:www.cnblogs.comZhuSenlinp16662075.htmlWindows编程之线程同步:https ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • 如何在服务器主机上实现文件共享的方法和工具
    本文介绍了在服务器主机上实现文件共享的方法和工具,包括Linux主机和Windows主机的文件传输方式,Web运维和FTP/SFTP客户端运维两种方式,以及使用WinSCP工具将文件上传至Linux云服务器的操作方法。此外,还介绍了在迁移过程中需要安装迁移Agent并输入目的端服务器所在华为云的AK/SK,以及主机迁移服务会收集的源端服务器信息。 ... [详细]
  • 本文介绍了在使用Python中的aiohttp模块模拟服务器时出现的连接失败问题,并提供了相应的解决方法。文章中详细说明了出错的代码以及相关的软件版本和环境信息,同时也提到了相关的警告信息和函数的替代方案。通过阅读本文,读者可以了解到如何解决Python连接服务器失败的问题,并对aiohttp模块有更深入的了解。 ... [详细]
  • Linux环境变量函数getenv、putenv、setenv和unsetenv详解
    本文详细解释了Linux中的环境变量函数getenv、putenv、setenv和unsetenv的用法和功能。通过使用这些函数,可以获取、设置和删除环境变量的值。同时给出了相应的函数原型、参数说明和返回值。通过示例代码演示了如何使用getenv函数获取环境变量的值,并打印出来。 ... [详细]
  • 浏览器中的异常检测算法及其在深度学习中的应用
    本文介绍了在浏览器中进行异常检测的算法,包括统计学方法和机器学习方法,并探讨了异常检测在深度学习中的应用。异常检测在金融领域的信用卡欺诈、企业安全领域的非法入侵、IT运维中的设备维护时间点预测等方面具有广泛的应用。通过使用TensorFlow.js进行异常检测,可以实现对单变量和多变量异常的检测。统计学方法通过估计数据的分布概率来计算数据点的异常概率,而机器学习方法则通过训练数据来建立异常检测模型。 ... [详细]
  • 本文讨论了在手机移动端如何使用HTML5和JavaScript实现视频上传并压缩视频质量,或者降低手机摄像头拍摄质量的问题。作者指出HTML5和JavaScript无法直接压缩视频,只能通过将视频传送到服务器端由后端进行压缩。对于控制相机拍摄质量,只有使用JAVA编写Android客户端才能实现压缩。此外,作者还解释了在交作业时使用zip格式压缩包导致CSS文件和图片音乐丢失的原因,并提供了解决方法。最后,作者还介绍了一个用于处理图片的类,可以实现图片剪裁处理和生成缩略图的功能。 ... [详细]
  • cocos2dx-lua使用UIListView制作二级折叠菜单
    折叠菜单,用过jqueryaccordion的同学都知道是啥玩艺儿~,图片效果就是介样:cocos2dx不带有此控件,因此我们动手来实现一个。原理很简单,展开的时候往listview里i ... [详细]
  • 整整的花了一个下午的时候,才在lua中调用了动态链接库。比起其他脚本语言,lua的调用方式算是比较繁琐的,但是lua的编程思想非常的统一& ... [详细]
  • redis知识汇总[随笔记录]
      ... [详细]
author-avatar
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有