首页
论坛
课程
招聘
luajit逆向之注入文件
2022-3-22 08:32 13776

luajit逆向之注入文件

2022-3-22 08:32
13776

自己制作的一个小工具, 工作场景为某些cocos2dx手游, 虽然lua可以直接拿到lua_state指针注入, 但在某些场景下直接注入文件是更加方便的, 所以自己研究了一下字节码, 笔记是以前写的. 思路很清晰, 自己动手做的话容易犯小错误

准备

Luajit版本2.1.0 beta2
https://luajit.org/download/LuaJIT-2.1.0-beta2.zip
mingw64

luajit二进制文件结构

010editor中有bt模板, 但是由于版本变化, 指令解析存在问题, 结构解析无问题

 

示例lua文件代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
--target.lua
function Test(x)
    print("Test " .. x)
end
 
Test("enter main==>test()")
require("addLib")
 
function fun1()
    print("Test in target.lua fun1 function...")
end
function fun2()
    print("Test in target.lua fun2 function...")
end
 
print("Test in target.lua main function...")
print(add(1,2))

模板信息

 

可以看到, 在luajit的二进制文件中, proto的顺序是依次往下的嵌套关系, 主函数体在最外层, 也就是倒数第二个

 

在proto结构中, 存有头信息, 二进制字节码, 常量信息.

 

模板信息

 

在头信息中我们要关心的主要有size, complex_constants_count, instructions_count三个字段, 他们的大小由变长数字uleb128描述, 互相转化的方法如下

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
43
44
45
46
int ReadUleb128(unsigned char*& buf)
{
    unsigned int result;
    unsigned char cur;
 
    result = *buf++;
    if (result > 0x7f)
    {
        cur = *buf++;
        result = (result & 0x7f) | (unsigned int)((cur & 0x7f) << 7);
        if (cur > 0x7f)
        {
            cur = *buf++;
            result |= (unsigned int)(cur & 0x7f) << 14;
            if (cur > 0x7f)
            {
                cur = *buf++;
                result |= (unsigned int)(cur & 0x7f) << 21;
                if (cur > 0x7f)
                {
                    cur = *buf++;
                    result |= (unsigned int)cur << 28;
                }
            }
        }
    }
    return result;
}
void WriteUleb128(unsigned char*& buf, int x)
{
    unsigned int denominator = 0x80;
    unsigned char flag = 0x80;
 
    for (int i = 0; i < 5; i++)
    {
        if (x < denominator)
        {
            buf[i] = (unsigned char)x;
            i++;
            buf += i;
            return;
        }
        buf[i] = (flag) | (unsigned char)(x % denominator);
        x = x >> 7;
    }
}

在我们修改文件的时候需要修正这几条数据.

总体思路

修改二进制数据, 插入require("inject")语句. 在luajit中, require语句由三条指令构成, 主要是加载require字符串, 待注入文件名, call三步

\OP** \A** \B** \C/D** \Description**
GGET dst str A = _G[D]
 

字节码 36 00 00 00

 

第一个字节为opcode, 第二个字节为寄存器编号, 第三个字节为常量池下表(从下往上数)

\OP** \A** \D** \Description**
KSTR dst str Set A to string constant D
 

字节码为27 01 01 00

 

第一个字节为opcode, 第二个字节为寄存器编号, 第三个字节为常量池下表(从下往上数)

\OP** \A** \B** \C/D** \Description**
CALL base lit lit Call: A, ..., A+B-2 = A(A+1, ..., A+C-1)
 

42 00 02 01

 

这里我有点没太明白, 按照官网表格的注释A+C-1=0, 参数为0个. 不过可以确定的是第二个字节为require的寄存器编号

  1. 将三条指令插入指令数组最前面, 将字符串插入常量池最前面
  2. 更新heade中的complex_constants_count和instructions_count
  3. 更新size, 因为uleb128不定长, 所以最后更新这个数据

过程

首先跳过前置段, 找到main, 然后解析main proto的信息

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
43
44
45
46
47
48
49
50
struct ProtoHeader
{
    int protoSize, complexCnt, numericCnt, instructionCnt;
    unsigned char flags, argCnt, frameSize, upValueCnt;
};
struct Proto
{
    ProtoHeader ph;
    unsigned char* instructions, * constants;
    int instructionsSize, constantsSize;
};
 
void IngoreSeg(unsigned char*& buf, int n)
{
    while (n--)
    {
        int size = ReadUleb128(buf);
        buf += size;
    }
}
Proto* ReadProto(unsigned char* buf)
{
    unsigned char* ed = buf;
    int size = ReadUleb128(ed);
    ed += size;
 
    Proto* proto = new Proto;
    proto->ph.protoSize = ReadUleb128(buf);
    proto->ph.flags = ReadU8(buf);
    proto->ph.argCnt = ReadU8(buf);
    proto->ph.frameSize = ReadU8(buf);
    proto->ph.upValueCnt = ReadU8(buf);
    proto->ph.complexCnt = ReadUleb128(buf);
    proto->ph.numericCnt = ReadUleb128(buf);
    proto->ph.instructionCnt = ReadUleb128(buf);
 
    proto->instructionsSize = 4 * proto->ph.instructionCnt;//每条指令4个字节
    proto->instructions = new unsigned char[proto->instructionsSize];
    memcpy(proto->instructions, buf, proto->instructionsSize);
    buf += 4 * proto->ph.instructionCnt;
 
    proto->constantsSize = ed - buf;
    proto->constants = new unsigned char[proto->constantsSize];
    memcpy(proto->constants, buf, proto->constantsSize);
    buf += proto->constantsSize;
    return proto;
}
 
IngoreSeg(filePtr, targetSeg);
Proto* proto = ReadProto(filePtr);

将原本的信息解析完毕后, 需要插入常量和指令

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
void InsertInstruction(Proto* proto, string& name)
{
    unsigned char* tmp;
 
    proto->ph.complexCnt += 2;
    tmp = new unsigned char[proto->constantsSize + 64];
    unsigned char* st = tmp;
    WriteString(tmp, string("require"));
    WriteString(tmp, name);
    memcpy(tmp, proto->constants, proto->constantsSize);
    proto->constantsSize += tmp - st;
    proto->constants = st;
 
    //_G[complexCnt-1]="require" _G[complexCnt-2]="inject"
    tmp = new unsigned char[proto->instructionsSize + 12];
    memcpy(tmp + 12, proto->instructions, proto->instructionsSize);
    unsigned char injectCode[] = { 0x36, 0x00, 0x00, 0x00,
                                    0x27, 0x01, 0x01, 0x00,
                                    0x42, 0x00, 0x02, 0x01 };
    injectCode[2] = proto->ph.complexCnt - 1;//第二个字节为require所处位置
    injectCode[6] = proto->ph.complexCnt - 2;//第二个字节为inject所处位置
    memcpy(tmp, injectCode, 12);
    proto->instructions = tmp;
    proto->ph.instructionCnt += 3;
    proto->instructionsSize += 3 * 4;
}
 
InsertInstruction(proto, name);

最后更新字段数据

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
void CalcProtoSize(Proto* proto)//uleb128不定长, 须计算所有长度
{
    unsigned char* buf = new unsigned char[4096];
    unsigned char* st = buf;
 
    WriteU8(buf, proto->ph.flags);
    WriteU8(buf, proto->ph.argCnt);
    WriteU8(buf, proto->ph.frameSize);
    WriteU8(buf, proto->ph.upValueCnt);
    WriteUleb128(buf, proto->ph.complexCnt);
    WriteUleb128(buf, proto->ph.numericCnt);
    WriteUleb128(buf, proto->ph.instructionCnt);
    proto->ph.protoSize = (buf - st) + proto->constantsSize + proto->instructionsSize;
}
void WriteProtoIntoBuffer(unsigned char*& buf, Proto* proto)
{
    WriteUleb128(buf, proto->ph.protoSize);
    WriteU8(buf, proto->ph.flags);
    WriteU8(buf, proto->ph.argCnt);
    WriteU8(buf, proto->ph.frameSize);
    WriteU8(buf, proto->ph.upValueCnt);
    WriteUleb128(buf, proto->ph.complexCnt);
    WriteUleb128(buf, proto->ph.numericCnt);
    WriteUleb128(buf, proto->ph.instructionCnt);
 
    memcpy(buf, proto->instructions, proto->instructionsSize);
    buf += proto->instructionsSize;
    memcpy(buf, proto->constants, proto->constantsSize);
    buf += proto->constantsSize;
}
 
CalcProtoSize(proto);
WriteProtoIntoBuffer(filePtr, proto);

结束

最终效果成功注入

 

模板信息

 

完整代码见https://github.com/stickycookie/luajitInject

 

参考资料:https://www.anquanke.com/post/id/87281

 

​ https://en.wikipedia.org/wiki/LEB128

 

​ https://www.mickaelwalter.fr/reverse-engineering-luajit/


看雪招聘平台创建简历并且简历完整度达到90%及以上可获得500看雪币~

最后于 2022-3-22 09:51 被编辑 ,原因:
收藏
点赞4
打赏
分享
最新回复 (7)
雪    币: 4565
活跃值: 活跃值 (802)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
ldljlzw 活跃值 2022-3-22 09:22
2
0
谢谢楼主这么好的东西
雪    币: 659
活跃值: 活跃值 (293)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
vcdemon 活跃值 2022-3-25 16:42
3
0
大佬
雪    币: 6645
活跃值: 活跃值 (2739)
能力值: ( LV4,RANK:45 )
在线值:
发帖
回帖
粉丝
v0id_ 活跃值 2022-3-25 17:33
4
0
mark
雪    币: 2690
活跃值: 活跃值 (1849)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
caolinkai 活跃值 2022-4-27 09:53
5
0
6666
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
alwinwd 活跃值 2022-4-30 19:25
6
0
不错。可以研究luajit反编译了
雪    币: 6167
活跃值: 活跃值 (3603)
能力值: ( LV13,RANK:239 )
在线值:
发帖
回帖
粉丝
sunfishi 活跃值 3 2022-4-30 20:25
7
0
mark
雪    币: 1144
活跃值: 活跃值 (291)
能力值: ( LV4,RANK:45 )
在线值:
发帖
回帖
粉丝
t0hka1 活跃值 1 2022-5-21 17:42
8
0
mark
游客
登录 | 注册 方可回帖
返回