吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 10467|回复: 12
收起左侧

[原创] 某LuaJIT引擎的内存校验机制分析

  [复制链接]
monvvv 发表于 2020-2-22 16:21
本帖最后由 monvvv 于 2020-2-22 16:21 编辑

前言

本来没什么值得分析的,但因为是用LuaJIT写的,而网上关于LuaJIT的内容很少,所以请忽略修改的部分,当作是Lua/LuaJIT的简单介绍吧。
PS:我省略了一部分具体的信息。

第一次尝试

打开游戏,随便找一个比较大的数值,打开CE,搜索数值,发现能够搜索到。

1

1
image-20200222132031271.png
然后修改之,然后游戏就喜闻乐见的闪退了。

分析

重新打开游戏,搜索数值,在数据上按F5(或右键单击->Find out what acess this address),能看到有好几条指令在持续不断的访问。

3

3
点击Show disassembler,能看到这几条指令都是属于一个名为LScript.dll的DLL文件的。而通过DLL导出的符号可以猜到其是LuaJIT库。

4

4

5

5

Dump & Inject

于是乎,我们直接将游戏的脚本直接用OD载入,在DLL被载入后输入bp luaL_loadbuffer。该函数声明为:

int luaL_loadbuffer(lua_State*L,const char*buff,size_t size,const char*name)

然后通过hook这里,我们就可以将游戏脚本dump出来,将自己的代码注入到游戏里。比如:

// create hook
int luaL_loadbuffer_new(lua_State*L,const char*buff,size_t size,const char*name) {
    luaL_dofile(l, filename);
    Dump(name, buff, size);
    // call original
}

将脚本Dump出来后,基本可以断定游戏的主要逻辑都写在lua里,于是注入自己的脚本:

-- 由于游戏检测到修改后的应对方式是退出,我们可以尝试hook os.exit
local ffi = require("ffi")
local debug = require("debug")
ffi.cdef[[
    void OutputDebugStringA(const char* lpDebugString);
]]
os.exit = function ()
    -- 打印os.exit的调用栈
    ffi.C.OutputDebugStringA(debug.traceback())
    -- call os_exit_original
end

打开游戏随便修改一下数据,打印出的内容如下(我删掉了一些信息):

 stack traceback:
  fuck.lua:10: in function 'Osexit'
  pm.lua:216: in function 'exit'
  sys.lua:19233: in function 'run'
  sys.lua:20363: in function '__index'
  da.lua:8192: in function '材料显示'
  da.lua:3023: in function 'cfun'
  sys.lua:14846: in function 'run'
  [C]: in function 'xpcall'

重点在__index这一行,__index是一个metamethod(元方法),当读取一个table的键值时被调用,所以大抵的检测逻辑可能为:

function fk_ce(data)
    local mt = {
        __index = function (t, k)
            if is_cheated(data[k]) then
                run(os.exit)
            end
        end
    }
    setmetetable(data, mt)
end

Bytecode

目前解析LuaJIT Bytecode的工具有三个,分别为:

  • luajit自带一个解析命令(-bl)
  • luajit-lang-toolkit(-bx)
  • ljd(反编译,不过不再维护,很多bug)

这里使用luajit-lang-toolkit来解析dump出的Lua文件。

$ cd luajit-lang-toolkit
$ luajit run.lua -bx sys.lua > sys.txt

然后找到__index的实现:

2b 02 00 00             | 0001    UGET     2   0      ; self
37 02 00 02             | 0002    TGETS    2   2   0  ; "__p"
36 02 01 02             | 0003    TGETV    2   2   1
0a 02 00 00             | 0004    ISEQP    2   0
54 02 2a 80             | 0005    JMP      2 => 0048
34 02 01 00             | 0006    GGET     2   1      ; "type"
2b 03 00 00             | 0007    UGET     3   0      ; self
37 03 02 03             | 0008    TGETS    3   3   2  ; "__数据"
36 03 01 03             | 0009    TGETV    3   3   1
3e 02 02 02             | 0010    CALL     2   2   2
07 02 03 00             | 0011    ISNES    2   3      ; "table"
54 02 04 80             | 0012    JMP      2 => 0017
2b 02 00 00             | 0013    UGET     2   0      ; self
37 02 02 02             | 0014    TGETS    2   2   2  ; "__数据"
36 02 01 02             | 0015    TGETV    2   2   1
48 02 02 00             | 0016    RET1     2   2
34 02 04 00             | 0017 => GGET     2   4      ; "string"
37 02 05 02             | 0018    TGETS    2   2   5  ; "reverse"
34 03 06 00             | 0019    GGET     3   6      ; "tostring"
2b 04 00 00             | 0020    UGET     4   0      ; self
37 04 02 04             | 0021    TGETS    4   4   2  ; "__数据"
36 04 01 04             | 0022    TGETV    4   4   1
3e 03 02 00             | 0023    CALL     3   0   2
3d 02 00 02             | 0024    CALLM    2   2   0
2b 03 00 00             | 0025    UGET     3   0      ; self
37 03 00 03             | 0026    TGETS    3   3   0  ; "__p"
36 03 01 03             | 0027    TGETV    3   3   1
04 02 03 00             | 0028    ISEQV    2   3
54 02 12 80             | 0029    JMP      2 => 0048
34 02 07 00             | 0030    GGET     2   7      ; "Sys"
37 02 08 02             | 0031    TGETS    2   2   8  ; "run"
25 03 09 00             | 0032    KSTR     3   9      ; "debug_msg"
27 04 01 00             | 0033    KSHORT   4   1
32 05 04 00             | 0034    TNEW     5   4
3b 01 01 05             | 0035    TSETB    1   5   1
2b 06 00 00             | 0036    UGET     6   0      ; self
37 06 02 06             | 0037    TGETS    6   6   2  ; "__数据"
36 06 01 06             | 0038    TGETV    6   6   1
3b 06 02 05             | 0039    TSETB    6   5   2
34 06 04 00             | 0040    GGET     6   4      ; "string"
37 06 05 06             | 0041    TGETS    6   6   5  ; "reverse"
2b 07 00 00             | 0042    UGET     7   0      ; self
37 07 00 07             | 0043    TGETS    7   7   0  ; "__p"
36 07 01 07             | 0044    TGETV    7   7   1
3e 06 02 00             | 0045    CALL     6   0   2
3c 06 00 00             | 0046    TSETM    6   0      ; 4.5035996273705e
                        | +15
3e 02 04 01             | 0047    CALL     2   1   4
2b 02 00 00             | 0048 => UGET     2   0      ; self
37 02 02 02             | 0049    TGETS    2   2   2  ; "__数据"
36 02 01 02             | 0050    TGETV    2   2   1
48 02 02 00             | 0051    RET1     2   2
                        | .. uv ..
02 c0                   | upvalue local 2
                        | .. kgc ..
0e 64 65 62 75 67 5f 6d | kgc: "debug_msg"
73 67                   | 
08 72 75 6e             | kgc: "run"
08 53 79 73             | kgc: "Sys"
0d 74 6f 73 74 72 69 6e | kgc: "tostring"
67                      | 
0c 72 65 76 65 72 73 65 | kgc: "reverse"
0b 73 74 72 69 6e 67    | kgc: "string"
0a 74 61 62 6c 65       | kgc: "table"
0b 5f 5f ca fd be dd    | kgc: "__数据"
09 74 79 70 65          | kgc: "type"
08 5f 5f 70             | kgc: "__p"
                        | .. knum ..
07 80 80 c0 99 04       | knum num: 4.5036e+15
73 65 6c 66 00          | uv0: name: self

LuaJIT的bytecode设计很简洁,将其转为Lua源码也不难,或者直接使用ljd进行反编译也可行,下面是等价的更易读的Lua代码:

function __index(t, k)
    if self.__p[k] ~= nil and type(self.__数据[k]) ~= "table" then
        if string.reverse(tostring(self.__数据[k])) ~= self.__p[k] then
            -- 保存信息,退出
        end
    else
        return self.__数据[k]
    end
end

问题

根据上面的实现很容易能看出,要绕过判断只要把对应的table__p内的数据也修改掉就可以。但事实上,并非这样。

为了说明,打开LuaJIT,输入以下代码:

a = "1234567"
b = "7654321"
print(a, b, a == b)
-- output:
-- 1234567 7654321 false

然后打开CE,将b的值改为"1234567",结果输出如下:

-- output:
-- 1234567 1234567 false

这和LuaJIT内==所采用的方法有关:

/* lj_obj */

/* GCobj reference */
typedef struct GCRef {
  uint32_t gcptr32;        /* Pseudo 32 bit pointer. */
} GCRef;

typedef LJ_ALIGN(8) union TValue {
  GCRef gcr;        /* GCobj reference (if any). */
}
typedef const TValue cTValue;

/* Compare two objects without calling metamethods. */
int lj_obj_equal(cTValue *o1, cTValue *o2)
{
  if (itype(o1) == itype(o2)) {
    if (tvispri(o1))
      return 1;
    if (!tvisnum(o1))
      return gcrefeq(o1->gcr, o2->gcr);
  } else if (!tvisnumber(o1) || !tvisnumber(o2)) {
    return 0;
  }
  return numberVnum(o1) == numberVnum(o2);
}

字符串在LuaJIT内的结构为:

/* String object header. String payload follows. */
typedef struct GCstr {
  GCHeader;
  uint8_t reserved;        /* Used by lexer for fast lookup of reserved words. */
  uint8_t unused;
  MSize hash;                /* Hash of string. */
  MSize len;                /* Size of string. */
  char str[len];    /* 我自己加的,实际是用的宏 */
} GCstr;
#define strdata(s)        ((const char *)((s)+1))

所以即使修改了字符串值,但由于字符串是否相等是通过引用对比而非值对比确定的,a == b依然为false

结束

想要解决这个问题也很简单,只要将b的引用修改为a的引用,或者直接通过注入的Lua脚本修改(比如将Bytecode第4行的ISEQP删除掉)。

测试

搜索b_str_address - 0x16(sizeof GCstr)然后将指针改为b_str_address - 0x16,输出:1234567 1234567 true

而在游戏内,可以通过将__p内的引用改为其他数据的引用来实现将一个数据修改为另一个已存在的数据(0x11327F90内的数据是原先__p内的字符串,由于缺少了引用被GC删除了)。

6

6

7

7

参考

  1. Lua Source.
  2. Lua Manual Metatables
  3. LuaJIT 2.0.4 Source
  4. LuaJIT bytecode
  5. luajit-lang-toolkit
  6. ljd

免费评分

参与人数 9吾爱币 +12 热心值 +8 收起 理由
hgfty1 + 1 谢谢@Thanks!
魅夜 + 1 + 1 谢谢@Thanks!
weigaojing + 1 + 1 谢谢@Thanks!
v0id_alphc + 1 + 1 用心讨论,共获提升!
庞晓晓 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
青鸢 + 1 + 1 谢谢@Thanks!
二娃 + 2 + 1 我很赞同!
wmsuper + 3 + 1 谢谢@Thanks!
gitai + 1 + 1 用心讨论,共获提升!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

 楼主| monvvv 发表于 2020-3-31 16:42
casper_lin 发表于 2020-3-31 15:33
楼主,请问luajit-lang-toolkit-master这个工具怎么使用呢

项目readme里有
陆想想 发表于 2020-2-22 16:37
hhxxhg 发表于 2020-2-22 16:59
Ars 发表于 2020-2-22 17:19
感谢楼主分享,支持一下!
青鸢 发表于 2020-2-23 00:42
所以大佬能不能出一起关于hook的教程啊_(:з)∠)_
chen4321 发表于 2020-2-23 11:54
666,lua我是在ce上接触到的,写点小脚步用起来好像很好用
jing2005134 发表于 2020-2-23 16:46
没太看懂,是找到地址后直接修改为游戏内已经存在的任意一个值都可以吗?
v0id_alphc 发表于 2020-2-27 08:25
厉害了,顶一个
Hmily 发表于 2020-3-29 22:20
感谢科普,加精鼓励。
casper_lin 发表于 2020-3-31 15:33
楼主,请问luajit-lang-toolkit-master这个工具怎么使用呢
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-12-27 01:07

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表