首页
社区
课程
招聘
[原创]使用LIEF打造类似王者X耀的静态代码注入
发表于: 2021-3-31 16:45 23077

[原创]使用LIEF打造类似王者X耀的静态代码注入

2021-3-31 16:45
23077

使用lief工具写一个类似于腾讯so保护这样一个东西。本篇记录lief工具的使用和TX加固的原理,不会对ELF文件格式进行记录。

首先,lief是一个文件解析工具,可以解析ELF、PE、DEX等,用途比较广泛。

github 地址:https://github.com/lief-project/LIEF

我使用的是python版,在使用过程中经常出现毛病,所以并不建议使用python版。最好是不怕麻烦自己写一个,就可以避免很多不稳定导致的fix代码。

首先,王者荣耀采用的是il2cpp,比较关键的so有3个,libil2cpp.so、libGameCore.so、libtprt.so。其中libtprt.so为保护so。

王者荣耀加载被保护so(il2cpp或GameCore)时,会优先加载链接库。所以,整个流程unity启动后,会优先加载libtprt.so。

图1

libtprt.so对自身的tptext节进行了加密,在加载过程中执行init_array进行解密,跟踪mprotect,可以看到解密代码:

使用lief快速解密:

tptext节里的函数主要用于初始化各类保护,会被jni_onload调用。现在tprt已经被成功加载,现在就需要解密libil2cpp.so

由于libGameCore.so小一点,分析起来方便,所以后面就不分析il2cpp了。先观察ELF结构,可以发现它的LOAD段格外多。

个人分析他比原生多了3个LOAD段,新增的第一个LOAD段(04),里面是.dynamic 和 .init_array。很好理解,因为强行添加了一个依赖库和init_array,所以需要生成新的.dynamic,至于init_array个人猜测是添加字符串混淆之类的东西。

新增的第二个LOAD段(05),是新的.rel.plt,用ida分析,可以明显看出有增加项,且增加项在新增的第三个LOAD段(06):

跳转到0x228cfcc,可以看到这是一个got表,并且它的plt表在新增的第二个LOAD段(05)。

对添加的LOAD段有一定了解了,再看一下修改后的.dynamic有什么变化:

需要关注的,我已经添加了红色方框。大部分只是修改偏移,指向新的节(JMPREL、INIT_ARRAY),值得注意的是DT_INIT,这个是init段,比init_array更早执行。更适合用来解密。

通过取off_1cc的地址 - off_1cc的值(地址为base+0x1cc,值是自己设置的,为1cc)所以算出来是当前so的基址,然后调用sub_2289b58并且传入基址。

sub_2289b58就是解密函数,网上对它的分析很多了,总的就是调用g_tprt_pfn_array(“.text”,base,3)对当前的text段进行解密。(“.text”这个字符串,在新增的第三个LOAD(06)中,意味着是写死的)

所以总结一下:

MTP对SO新增了3个LOAD段,第一个LOAD新增.dynamic、.init_array,第二个LOAD段是映射新的.rel.plt、.plt、.text节,第三个LOAD段用来映射.got、.data节。

MTP在program header 之后添加了一段汇编,在init时执行,获取当前SO的基址,传入解密函数,并调用libtprt.so的导出函数g_tprt_pfn_array进行text节解密。

由于一次写完大概率会很乱思路不够清晰,所以分几步写,方便理解,也好记录。

首先,生成一个SO,很简单,有一个PrintLog函数没有调用,所以LOG只有一条:

执行以下代码:

可以看到函数PrintLog优先于JNI函数执行,并且传入的参数为基址。

关于重定位

需要记住的几个总结:

rel.plt ->got -> plt -> extern表(导入函数)

rel.plt ->got -> text(导出)

编译一个so,里面的示范代码:

十分简单,就是一个打印log,将编译好的so拖入ida,可以看到,虽然我只写了一个函数,实际上so运行时还需要许多其他的函数。

执行以下代码:

将intermediate.so改名为libnative-lib.so,在apk里能成功执行。不报错就是胜利!

ida打开intermediate.so可以看到相关的函数,虽然把plt解析成了函数,不过不影响。

再对比libadd.so,可以看到,函数内容是一致的:

成功运行不报错~

有两种方式,TX是让plt-》got表之间的偏移不变,就不需要修改plt表。

如果修改了plt、got之间的偏移,就需要通过修改字节码来实现正常运行。

首先可以看到plt表有三条指令,获取当前地址,添加偏移,跳转。

第一个红框,由于是0不好理解,我换成0x01. 那么就是取当前地址+0x0100000

第二个红框,0x62,向r12添加 0x62000

第三个红框,0xf00c,通过a8028 - a801c = c 。所以低12位为添加的偏移-》0x00c、0x1fc

由于其他代码与之前的相同,就没必要重复粘贴了。将新增的plt表修复相关代码粘贴出来。

修复plt表后,执行so,就可以看到我们注入的decrypt函数优先于JNI函数执行了。为什么需要修复plt表呢?是因为decrypt中使用了android_log这个系统函数(在正常解密函数中无法避免使用系统函数)

到此,只需要将libadd.so中decrypt函数替换成真正的解密代码就行了。甚至是简单异或都可以实现so加密。

 
 
 
 
 
 
 
 
tprt = lief.parse(name)
tptext_section = tprt.get_section(".tptext").content
 
print(len(tptext_section))
offset = tprt.get_section(".tptext").offset
out = b""
for i in range(len(tptext_section)):
    out += (tptext_section[i] ^ 0xb8).to_bytes(1,byteorder='little')
 
 
print(len(out))
tpp = tprt_bin[:offset]+out+tprt_bin[offset+len(tptext_section):]
with open("tpp.so", 'wb') as fp:
    fp.write(tpp)
tprt = lief.parse(name)
tptext_section = tprt.get_section(".tptext").content
 
print(len(tptext_section))
offset = tprt.get_section(".tptext").offset
out = b""
for i in range(len(tptext_section)):
    out += (tptext_section[i] ^ 0xb8).to_bytes(1,byteorder='little')
 
 
print(len(out))
tpp = tprt_bin[:offset]+out+tprt_bin[offset+len(tptext_section):]
with open("tpp.so", 'wb') as fp:
    fp.write(tpp)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
"""
  添加init段,并且调用ADD_FUNC_NAME并传入一个参数(当前So的基址)
"""
INIT_PROC_SIZE = 0x2c
INIT_PROC_CONTENT = [0xC0,0x46,0xFF,0xB5,0x83,0xB0,0x6D,0x46,0x00,0xA3,0x14,0x3B,0x19,0x1C,0x1F,0x68,0xC9,0x1B,0x29,0x60,0x5E,0x68,0x03,0xD0,0x76,0x18,0x6E,0x60,0x28,0x68,0xB0,0x47,0x03,0xB0,0xFF,0xBD]# 腾讯代码里扣出来的嘿嘿
 
def add_init_proc():
 
    binary = lief.parse(TARGET_BIN)
    # 检测.dynamic节的空位是否足够,如果小于3个就要拓展dynamic节内容
    free_dynamic_entry = 0
    for entry in binary.dynamic_entries:
        if entry.tag == lief.ELF.DYNAMIC_TAGS.NULL:
            free_dynamic_entry += 1
    print("free dynamic entry num:", free_dynamic_entry)
    assert free_dynamic_entry > 3
 
    #获取位于segment header 后的 offset,用于添加init_proc
    init_proc_offset = binary.header.program_header_offset + \
                       binary.header.program_header_size * (binary.header.numberof_segments + 2 )
    print("init_proc offset:", hex(init_proc_offset))
 
 
    # 添加init入口
    if not binary.has(ELF.DYNAMIC_TAGS.INIT):
        # 先用0占位,直接写入偏移,lief工具会有点问题
        # 如果不出问题 binary.add(ELF.DynamicEntry(ELF.DYNAMIC_TAGS.INIT,init_proc_offset + 8))
        binary.add(ELF.DynamicEntry(ELF.DYNAMIC_TAGS.INIT,0))
 
    else:
        init_entry = binary.get(ELF.DYNAMIC_TAGS.INIT)
        print("[x] binary has init_proc:", init_entry)
        exit(1)
 
    binary.write("libnative-lib.so")
 
    # 手动修复 DynamicEntry 中的 value
    outbin = lief.parse("libnative-lib.so")
    out_dynamic = outbin.get_section(".dynamic")
    ADD_FUNC = outbin.get_symbol(ADD_FUNC_NAME).value
 
    num = 0
    for entry in outbin.dynamic_entries:
        if entry.tag == ELF.DYNAMIC_TAGS.INIT:
            break
        num+=1
 
    init_entry_offset =  out_dynamic.offset + (num * out_dynamic.entry_size)
    print(hex(init_entry_offset))
 
    patch_file("libnative-lib.so",init_entry_offset+4,struct.pack("<I", init_proc_offset + 8 + 1))
 
 
    global INIT_PROC_CONTENT
    #前四位 为 init——proc 的 偏移 ,后四位 为 要调用的 函数地址
    print("init_proc_offset :",hex(init_proc_offset))
    print("ADD_FUNC :", hex(ADD_FUNC))
 
    INIT_PROC_CONTENTS = list(struct.pack("<I", init_proc_offset)) + list(struct.pack("<I", ADD_FUNC)) + INIT_PROC_CONTENT
    patch_file("libnative-lib.so",init_proc_offset,INIT_PROC_CONTENTS)
"""
  添加init段,并且调用ADD_FUNC_NAME并传入一个参数(当前So的基址)
"""
INIT_PROC_SIZE = 0x2c
INIT_PROC_CONTENT = [0xC0,0x46,0xFF,0xB5,0x83,0xB0,0x6D,0x46,0x00,0xA3,0x14,0x3B,0x19,0x1C,0x1F,0x68,0xC9,0x1B,0x29,0x60,0x5E,0x68,0x03,0xD0,0x76,0x18,0x6E,0x60,0x28,0x68,0xB0,0x47,0x03,0xB0,0xFF,0xBD]# 腾讯代码里扣出来的嘿嘿
 
def add_init_proc():
 
    binary = lief.parse(TARGET_BIN)
    # 检测.dynamic节的空位是否足够,如果小于3个就要拓展dynamic节内容
    free_dynamic_entry = 0
    for entry in binary.dynamic_entries:
        if entry.tag == lief.ELF.DYNAMIC_TAGS.NULL:
            free_dynamic_entry += 1
    print("free dynamic entry num:", free_dynamic_entry)
    assert free_dynamic_entry > 3
 
    #获取位于segment header 后的 offset,用于添加init_proc
    init_proc_offset = binary.header.program_header_offset + \
                       binary.header.program_header_size * (binary.header.numberof_segments + 2 )
    print("init_proc offset:", hex(init_proc_offset))
 
 
    # 添加init入口
    if not binary.has(ELF.DYNAMIC_TAGS.INIT):
        # 先用0占位,直接写入偏移,lief工具会有点问题
        # 如果不出问题 binary.add(ELF.DynamicEntry(ELF.DYNAMIC_TAGS.INIT,init_proc_offset + 8))
        binary.add(ELF.DynamicEntry(ELF.DYNAMIC_TAGS.INIT,0))
 
    else:
        init_entry = binary.get(ELF.DYNAMIC_TAGS.INIT)
        print("[x] binary has init_proc:", init_entry)
        exit(1)
 
    binary.write("libnative-lib.so")
 
    # 手动修复 DynamicEntry 中的 value
    outbin = lief.parse("libnative-lib.so")
    out_dynamic = outbin.get_section(".dynamic")
    ADD_FUNC = outbin.get_symbol(ADD_FUNC_NAME).value
 
    num = 0
    for entry in outbin.dynamic_entries:
        if entry.tag == ELF.DYNAMIC_TAGS.INIT:
            break
        num+=1
 
    init_entry_offset =  out_dynamic.offset + (num * out_dynamic.entry_size)
    print(hex(init_entry_offset))
 
    patch_file("libnative-lib.so",init_entry_offset+4,struct.pack("<I", init_proc_offset + 8 + 1))
 
 
    global INIT_PROC_CONTENT
    #前四位 为 init——proc 的 偏移 ,后四位 为 要调用的 函数地址
    print("init_proc_offset :",hex(init_proc_offset))
    print("ADD_FUNC :", hex(ADD_FUNC))
 
    INIT_PROC_CONTENTS = list(struct.pack("<I", init_proc_offset)) + list(struct.pack("<I", ADD_FUNC)) + INIT_PROC_CONTENT
    patch_file("libnative-lib.so",init_proc_offset,INIT_PROC_CONTENTS)
 
 
 
 
 
//ELF.h 查看rel.plt的格式,r_offset 指向got表的地址,r_info高8位为类型,后24位为
 
// Relocation entry, without explicit addend.
 struct Elf32_Rel {
   Elf32_Addr r_offset; // Location (file byte offset, or program virtual addr)
   Elf32_Word r_info;   // Symbol table index and type of relocation to apply
 
   // These accessors and mutators correspond to the ELF32_R_SYM, ELF32_R_TYPE,
   // and ELF32_R_INFO macros defined in the ELF specification:
   Elf32_Word getSymbol() const { return (r_info >> 8); }
   unsigned char getType() const { return (unsigned char)(r_info & 0x0ff); }
   void setSymbol(Elf32_Word s) { setSymbolAndType(s, getType()); }
   void setType(unsigned char t) { setSymbolAndType(getSymbol(), t); }
   void setSymbolAndType(Elf32_Word s, unsigned char t) {
     r_info = (s << 8) + t;
   }
 };
 
 
 // Symbol table entries for ELF32.
 struct Elf32_Sym {
   Elf32_Word st_name;     // Symbol name (index into string table)
   Elf32_Addr st_value;    // Value or address associated with the symbol
   Elf32_Word st_size;     // Size of the symbol
   unsigned char st_info;  // Symbol's type and binding attributes
   unsigned char st_other; // Must be zero; reserved
   Elf32_Half st_shndx;    // Which section (header table index) it's defined in
 
   // These accessors and mutators correspond to the ELF32_ST_BIND,
   // ELF32_ST_TYPE, and ELF32_ST_INFO macros defined in the ELF specification:
   unsigned char getBinding() const { return st_info >> 4; }
   unsigned char getType() const { return st_info & 0x0f; }
   void setBinding(unsigned char b) { setBindingAndType(b, getType()); }
   void setType(unsigned char t) { setBindingAndType(getBinding(), t); }
   void setBindingAndType(unsigned char b, unsigned char t) {
     st_info = (b << 4) + (t & 0x0f);
   }
 };
//ELF.h 查看rel.plt的格式,r_offset 指向got表的地址,r_info高8位为类型,后24位为
 
// Relocation entry, without explicit addend.
 struct Elf32_Rel {
   Elf32_Addr r_offset; // Location (file byte offset, or program virtual addr)
   Elf32_Word r_info;   // Symbol table index and type of relocation to apply
 
   // These accessors and mutators correspond to the ELF32_R_SYM, ELF32_R_TYPE,
   // and ELF32_R_INFO macros defined in the ELF specification:
   Elf32_Word getSymbol() const { return (r_info >> 8); }
   unsigned char getType() const { return (unsigned char)(r_info & 0x0ff); }
   void setSymbol(Elf32_Word s) { setSymbolAndType(s, getType()); }
   void setType(unsigned char t) { setSymbolAndType(getSymbol(), t); }
   void setSymbolAndType(Elf32_Word s, unsigned char t) {
     r_info = (s << 8) + t;
   }
 };
 
 
 // Symbol table entries for ELF32.
 struct Elf32_Sym {
   Elf32_Word st_name;     // Symbol name (index into string table)

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 17
支持
分享
打赏 + 1.00雪花
打赏次数 1 雪花 + 1.00
 
赞赏  imwhite   +1.00 2021/05/13 方便留个qq或者v吗,关于tx so保护有我事想请教
最新回复 (27)
雪    币: 9024
活跃值: (6245)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
2
 楼主牛逼。
2021-4-1 01:57
0
雪    币: 268
活跃值: (3238)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
3
 楼主牛逼。
2021-4-1 12:58
0
雪    币: 1441
活跃值: (5660)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
4
楼主牛皮
2021-4-2 19:44
0
雪    币: 5502
活跃值: (3315)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
这个注入有意思
2021-4-5 02:47
0
雪    币: 144
活跃值: (335)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
顶一个
2021-4-5 19:55
0
雪    币: 3368
活跃值: (14038)
能力值: ( LV9,RANK:230 )
在线值:
发帖
回帖
粉丝
7
用xposed直接Hook code里面的函数就行了
2021-4-6 15:01
0
雪    币: 252
活跃值: (1204)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
静态注入,so操作,mark
2021-4-9 10:15
0
雪    币: 116
活跃值: (1012)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
牛逼 虽然看不懂
2021-4-11 23:18
0
雪    币: 197
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
10

这种方式so加固会有兼容性问题吗

最后于 2021-5-11 11:12 被imwhite编辑 ,原因: 表达有误
2021-5-11 11:00
0
雪    币: 597
活跃值: (1923)
能力值: ( LV3,RANK:25 )
在线值:
发帖
回帖
粉丝
11
毕竟这是改ELF文件格式,多少都会有影响
2021-5-11 13:44
1
雪    币: 197
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
12
博主方便给个联系方式吗,有些问题请教下
2021-5-13 15:25
0
雪    币: 197
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
13

我想问下最后修改plt偏移,got_address_list 是指什么,为什么我算出的got和plt数量不一样?求大神解答下

最后于 2021-5-17 14:07 被imwhite编辑 ,原因:
2021-5-17 13:49
0
雪    币: 597
活跃值: (1923)
能力值: ( LV3,RANK:25 )
在线值:
发帖
回帖
粉丝
14
imwhite 我想问下最后修改plt偏移,got_address_list&nbsp;是指什么,为什么我算出的got和plt数量不一样?求大神解答下
got_address_list 就是got表的地址存成list,貌似代码粘漏了。你干脆直接复制got表和plt表,就不会数量不一样了。
2021-5-19 11:00
0
雪    币: 200
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
15
大神能留一下联系方式吗?有不懂的地方,非常非常想请教
2021-5-19 19:35
0
雪    币: 200
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
16
第二个python文件,patch_file找不到定义,麻烦有空指点一下
2021-5-19 19:42
0
雪    币: 197
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
17
ELForPE 第二个python文件,patch_file找不到定义,麻烦有空指点一下
就是简单的二进制patch的功能,替换指定位置的数据
2021-5-21 10:19
0
雪    币: 197
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
18
八重嘤 got_address_list 就是got表的地址存成list,貌似代码粘漏了。你干脆直接复制got表和plt表,就不会数量不一样了。
got_address_list 的赋值可以贴出来吗,我直接获取add_got.size/4和add_plt.size/12的数值相差很大,这部分怎么对应呢
2021-5-21 19:08
0
雪    币: 597
活跃值: (1923)
能力值: ( LV3,RANK:25 )
在线值:
发帖
回帖
粉丝
19
=。=代码不在了,因为只添加了libadd.so中比targetSO多出来的plt与got,搞得很麻烦,后来我就改了。你留个联系方式我加你吧
2021-5-24 10:29
0
雪    币: 597
活跃值: (1923)
能力值: ( LV3,RANK:25 )
在线值:
发帖
回帖
粉丝
20
imwhite got_address_list 的赋值可以贴出来吗,我直接获取add_got.size/4和add_plt.size/12的数值相差很大,这部分怎么对应呢
=-=回复错位置惹
2021-5-24 10:29
0
雪    币: 197
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
21
八重嘤 =-=回复错位置惹
376420785 加我qq可以吗- -
2021-5-24 13:49
1
雪    币: 597
活跃值: (1923)
能力值: ( LV3,RANK:25 )
在线值:
发帖
回帖
粉丝
22
imwhite 376420785 加我qq可以吗- -
搜出来咋是个17年群啊,你确认一下呢
2021-5-24 14:31
0
雪    币: 197
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
23
八重嘤 搜出来咋是个17年群啊,你确认一下呢
忘记禁了,现在好了,再试下
2021-5-25 15:29
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
24
大佬有没有联系方式
2022-11-11 21:12
0
雪    币: 1692
活跃值: (2292)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
25
厉害了
2022-11-15 00:33
0
游客
登录 | 注册 方可回帖
返回
//