• 【frida实战】“一行”代码教你获取WeGame平台中所有的lua脚本


    导读

    开发环境

    版本号描述
    操作系统Win11-21H2内部版本号22000.588
    PythonPython3.7.1
    frida.exe15.0.18

    预备知识

    luaL_loadbuffer

    函数原型(https://www.lua.org/manual/5.1/manual.html#luaL_loadbuffer):

    int luaL_loadbuffer (lua_State *L,				// Lua句柄
                         const char *buff,			// lua脚本
                         size_t sz,					// lua脚本字节数
                         const char *name);			// 标记(通过分析可以发现这里是文件名)
    
    • 1
    • 2
    • 3
    • 4

    将缓冲区加载为 Lua 块。此函数用于加载lua_load指向的缓冲区中的块。 name是块名称,用于调试信息和错误消息。

    WeGame中使用的是Lua51.dll,它导出了两个函数luaL_loadbufferluaL_loadbufferx,通过IDA分析可以猜测,luaL_loadbufferx只是比luaL_loadbuffer多了个参数a5,其他参数都是一样的。
    在这里插入图片描述

    frida拦截器Interceptor

    frida的Interceptor模块中,attach函数原型为Interceptor.attach(target, callbacks[, data]),实现了对参数target进行hook的操作,下面是简单的参数分析:

    • target: 这是一个 NativePointer,指定您想要拦截调用的函数的地址。我们可以通过Module.getExportByName()来获取可执行文件的导出函数作为该参数,frida会自动处理。
    • callbacks: 顾名思义,这是hook的回调函数集合,里面包含了hook前和hook后两个回调函数。
      • onEnter(args): 该回调函数的参数args为hook函数的参数数组,每个参数都是一个NativePointer对象,我们可以在这里获取参数信息,或者篡改参数内容。
      • onLeave(retval): 该调用是执行完目标函数后的处理,参数retval是一个NativePointer对象,是目标函数的返回值,我们可以修改该指针内容达到hook的目的。
    • data: 可选参数,暂时忽略掉。

    示例:

    Interceptor.attach(Module.getExportByName('libc.so', 'read'), {
      onEnter(args) {
        this.fileDescriptor = args[0].toInt32();
    
    	// this上下文信息:包含寄存器信息context、返回值地址returnAddress、线程ID threadId等
        console.log('Context information:');
        console.log('Context  : ' + JSON.stringify(this.context));
        console.log('Return   : ' + this.returnAddress);
        console.log('ThreadId : ' + this.threadId);
        console.log('Depth    : ' + this.depth);
        console.log('Errornr  : ' + this.err);
      },
      onLeave(retval) {
        if (retval.toInt32() > 0) {
          /* do something with this.fileDescriptor */
        }
      }
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    ps: callbacks是一个对象,frida实现中,会在初始化的时候,给该对象填充上下文信息信息,这也就是为什么示例中的this可以访问context、returnAddress、threadId这些内容。

    ps2: 之前一直纠结onEnter中生成的变量,怎么在onLeave中使用,其实直接通过this就可以了this.tempVal=333;。这都是对js的基础理解和应用。

    NativeFunction创建目录

    frida调用dll函数,通过NativeFunction创建一个函数对象,设置响应的参数和返回值。

    本文使用的是WinExec函数调用了cmd命令中的mkdir指令来实现创建目录,具体实现看下面的代码。

    ps: 也可以使用c函数中的system,其原型如下:

    分析思路

    挂起启动WeGame

    为了保证hook所有的lua,需要挂起启动WeGame,使用命令frida -f 应用程序全路径即可挂起启动目标进程,命令行示例:

    D:\Python\Python371\Scripts\frida.exe -f "G:\Program Files (x86)\WeGame\wegame.exe"
    
    
    • 1
    • 2

    WeGame需要管理员权限启动,否则会报0x000002e4的启动失败错误。
    在这里插入图片描述

    hook函数luaL_loadbufferx保存所有js

    var fnWinExec = new NativeFunction(
    	Module.findExportByName('Kernel32.dll', 'WinExec'),
    	'int', 
    	['pointer', 'int'],
    	'stdcall'
    );
    
    function ez_fnWinExec(jsStr) {
        console.log( 'ez_fnWinExec: ', jsStr );
    	
    	var cStrPointer = Memory.allocUtf8String(jsStr);
    	fnWinExec(cStrPointer, 0);
    }
    
    Interceptor.attach(Module.getExportByName('Lua51.dll', 'luaL_loadbufferx'), {
      onEnter(args) {
      	// 不可以使用数组解构赋值!!!
      	//		var [_,a,b]=[0,1,3]
        // var [L, buff, sz, name] = args;
        var buff = args[1];
        var sz = args[2];
        var name = args[3];
        console.log( name.readCString(), sz.toInt32(), buff );
    
    	// 过滤非lua文件
    	if (!name.readCString().endsWith('.lua')){
    		return;
    	}
    	
    	var pth = 'D:\\_TMP\\wegame\\' + name.readCString();
    	// 创建文件夹
    	var dir_ = pth.substr(0, pth.lastIndexOf('\\')+1);
    	ez_fnWinExec('cmd.exe /c mkdir '+ dir_);
        // 保存文件
        var f = new File(pth, 'wb');
        var data = buff.readByteArray(sz.toInt32());
        f.write(data);
        f.close();
      },
      onLeave(retval) {
      }
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    一开始想的是不需要调用函数WinExec,优化一下可以写成“一行”代码的,后来发现多级目录,得自己创建目录,又加上了一堆的代码。。。
    所以,就不再是“一行”代码实现了。

    参考资料

    **ps:**文章中内容仅用于技术交流,请勿用于违规违法行为。

  • 相关阅读:
    创建vue3项目并引用elementui
    人工智能在电力系统中的应用前景怎么样
    企业侧功能
    精神心理科医生:抑郁症正在好转的5种表现
    第一章 Linux及Linux Shell简介
    【Leetcode】120.三角形最小路径和
    springboot+nodejs+vue网上选课系统
    (Java高级教程)第一章Java多线程基础-第一节4:synchronized关键字(监视器锁monitor lock)和volatile关键字
    online java
    Linux常用命令学习
  • 原文地址:https://blog.csdn.net/kinghzking/article/details/125590584