首页
论坛
课程
招聘
[原创]2022腾讯游戏安全安卓客户端初赛wp
2022-4-28 11:48 11337

[原创]2022腾讯游戏安全安卓客户端初赛wp

2022-4-28 11:48
11337

外挂流程

这个外挂是一个典型的andlua程序,核心特征就是主要逻辑都在lua里面,外挂启动就会执行lua,由于lua被andlua官方加密了,所以外挂需要在内存中经过lua虚拟机加载函数时解密再运行,先判断了是否占用27042 和23946端口,以及/data/local/tmp下有没有re.frida.sever文件,进行反调试,然后申请root权限,创建一个随机文件名的空文件,同时将assert文件夹下的aes文件,进行rc4解密,解密后的文件字节流,写入前面创建的空文件中,并用root权限启动了这个随机文件名的文件进程,这个进程根据偏移修改了游戏数据,实现了飞天和人物移动加速的两个外挂功能。

外挂流程图

image-20220418052603077

具体的分析思路(提一句,我是调试分析完再写的wp,里面的函数经过分析后确定功能之后被我重命名了。)

首先先查看外挂的结构

 

image-20220418054405607

 

发现是一个andlua程序,同时lua被加密了,用010打开是这样的,所有的lua都是类型这种结构

 

image-20220418054451999

 

assert文件夹下面有个aes文件不太一样,里面和lua的格式不太一样,怀疑是外挂会解密后,新进一个进程,但是ps -A 并没有看到叫aes的进程

 

image-20220418054605098

 

andlua的核心在lua里面,所以需要先把lua解密出来,这里我绕了一些弯路,这里是还没看出来是andlua官方加密,自己去逆了很久的lua的虚拟机

 

image-20220418054915339

 

image-20220418054927268

 

image-20220418054934546

 

在这里跟了很久,发现了解密的地方,同时为了避免麻烦,我hook j_lua_load这个函数,这里就出现了问题了,dump出来的是luas,用unluac反编译后,符号名出现丢失,代码逻辑是正常,但是没有符号可以说就是绝路,dump下来,是这样的

 

image-20220418055219123

 

继续逆虚拟机,找了很久,似乎没法通过frida和ida dump的方式,将符号问题搞定,这里我搜了一些关于andlua的解密方法,在b站上找到了https://www.bilibili.com/video/BV1W5411o7H3?spm_id_from=333.999.0.0,这个andlua的官方加解密,然后发现视频中lua 加密的跟这题lua 加密后的很像,又搜了一下andlua官方加密里面是有base64的,之前逆它解密方法的时候,也看到了,所以就开始用视频的方式dump出luac,用视频给的工具包,下载到本地。

 

image-20220418055532239

 

在/storage/emulated/0/ExaGear按照视频,将本地的 ExaGear文件夹复制过去

 

image-20220418060219899

 

,然后将外挂中需要解密的lua,都放入src文件夹中

 

image-20220418060234475

 

最后退出,运行ExaGear,点击luatools,自动解密开始

 

image-20220418060321282

 

在dst文件夹下,生成解密好的luac文件

 

image-20220418060351071

 

在用unlua53这个app,将这些luac反编译回lua,可以看到反编译后效果非常好,符号名也都有,很清晰

 

image-20220418060530763

 

这里main.lua文件是主要逻辑,可以看到进程在启动的时候,会检测23946和27042的端口,也就是ida和frida的常用默认端口,还检测了/data/local/tmp下有无re.frida.server相关的文件

 

image-20220418060902791

 

同时又创建了一个随机进程名的空白文件,并用shell命令运行了这个文件

 

image-20220418060942551

 

难怪之前的找aes什么的进程,是找不到的,名字都是随机的,同时解密assert/aes文件,看了下解密方法是rc4,密钥也是明文zyp

 

image-20220418061100011

 

image-20220418061148068

 

rc4没魔改,我直接在本地解密aes文件

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
# coding=utf-8
 
 
 
DEFAULT_KEY = ""
 
 
def rc4(data, key=DEFAULT_KEY, skip=1024):
    x = 0
    box = range(256)
 
    x = 0
    for i in range(256):
        x = (x + box[i] + ord(key[i % len(key)])) % 256
        tmp = box[i]
        tmp2 = box[x]
        box[i] = box[x]
        box[x] = tmp
 
    x = 0
    y = 0
    out = []
    if skip > 0:
        for i in range(skip):
            x = (x + 1) % 256
            y = (y + box[x]) % 256
            box[x], box[y] = box[y], box[x]
 
    for char in data:
        x = (x + 1) % 256
        y = (y + box[x]) % 256
        box[x], box[y] = box[y], box[x]
        k = box[(box[x] + box[y]) % 256]
        out.append(chr(ord(char) ^ k))
 
    return out
 
 
if __name__ == '__main__':
    # handle input file or stream
    import sys
 
    tt = open("aes", "rb")
    ww = tt.read()
    decrypt_files=rc4(ww, "zyp", 0)
    decttt=open("dectt","wb")
    decttt.write("".join(decrypt_files))

因为外挂运行了这个文件,所以这个文件肯定是很重要的,解密后的文件发现是一个elf文件,用ida打开,有很明显的加壳,就两个区段,是很不正常,搜了下字符串,原来是upx的壳子

 

image-20220418061717535

 

从github上下了一份upx的代码,看看能不能upx -d直接脱了

 

image-20220418061959358

 

很舒服,是可以直接拖了的,哈哈,然后就可以用ida愉快的分析了,就是ida打开后也没符号,而且进程运行之后就死掉了,应该是出题人防止被附加吧,为了方便调试,我将调用kill和exit的地方都nop掉了

 

image-20220418062207468

 

但是还是闪退,这和upx的脱壳有关系了,每次upx -d的程序基本都运行的有点问题,可能是目前解压缩壳的问题,不过对我分析影响不是非常大,由于没符号,先从入口开始调试libc_init, 找到了这里,提一句,我是调试分析完再写的wp,里面的函数经过分析后确定功能之后被我重命名了。

 

image-20220418062345879

 

继续跟到这里

 

image-20220418062502831

 

这里基本确实是主函数,这里两个函数,猜测分别是两个功能的实现,后面的分析也验证了我的想法,一个一个的去跟,里面有比较严重的混淆,f5不了,慢慢审汇编,多用f4、f7、f8、f9,先跟mmain函数

 

image-20220418062939314

 

前面一大团字符串解密,直接跳到后面去看

 

image-20220418063032141

 

到这里的流程静态很难看,本地测试了下外挂直接运行,也是有效果的,也就是独立于游戏的,直接远程启动外挂进程就好了

 

image-20220418113021870

 

跟进去

 

image-20220418113449349

 

发现这里是获取fps游戏的pid,查看下参数,通过这个pidof来获取的

 

image-20220418113528391

 

到这里拿到pid

 

image-20220418113617575

 

继续f7

 

image-20220418113642635

 

每次调试这里都会跑飞,我只能在下一句汇编上下断才行,继续下一个函数

 

image-20220418113726386

 

查看参数为libUE4.so,应该是找到so基地址的

 

image-20220418113738900

 

跟进去看看,跟平常获取libue4.so基地址差不多

 

image-20220418113918529

 

查看参数

 

image-20220418113930887

 

这里是打开/proc/pid/maps,然后将每一行都和libUE4.so字符串做对比

 

image-20220418114019139

 

如果查询到了,再将地址拿出并转换

 

image-20220418114045516

 

image-20220418114141950

 

退出来,进入下一个函数

 

image-20220418114234478

 

这里发现是将libue4的基地址加上了0x4924570

 

[libue4.so_base+0x4924570]

 

跟进去,最终跟到一个syscall

 

image-20220418114450093

 

syscall的调用号是0x178,也就是读取,将libue4的基地址+0x4924570所对应的四个字节取出来了,继续下一个函数

 

image-20220418114703727

 

将取出来的数字+0x20,又经过相同的函数,和上面一样的分析,读取了4个字节到本地了

 

yk1=[libue4.so_base+0x4924570]

 

ykquestion=[yk1]+0x20

 

继续分析下一个函数

 

image-20220418115114364

 

同样将上面取出的字节加上了0x70,再次进行读取

 

yk2=[ykquestion]+0x70

 

yk3=[yk2]

 

继续跟进下一个函数

 

image-20220418115948729

 

发现这里是用libue4.so的基地址+0x4877034,然后依旧是同样的函数利用syscall,读取了4个字节

 

jf=libue4.so_base+0x4877034

 

jf1=[jf]

 

继续分析下一个函数,这里是一个远程读写的api,读取的jf1地址的0x78个字节到本地

 

image-20220418120335444

 

读取的数据

 

image-20220418120603001

 

发现是三个地址,这三个地址和后面的分析有关,继续看

 

image-20220418120719931

 

读取了yk3地址的4个字节到本地

 

yk4=[yk3]

 

image-20220418120944331

 

继续yk4+0x10的地址,取出4个字节

 

yk5=yk4+0x10

 

yk6=[yk5]

 

继续跟

 

image-20220418121052880

 

发现这里是一个大循环

 

image-20220418121429092

 

应该是遍历actor数组的,同时发现后面有拿到每个actor的name,并且对比

 

总结下这里在干什么,并举例

1
2
3
4
5
6
7
8
9
10
11
yk6=[yk5]
#取出yk5地址的4个字节
 
yk7=ak6-0x4000
#判断yk7是否小于0,小于后面的逻辑不走了,进入下一个循环
((yk6)>>14)<<2)的值去选择之前弄的三个地址其中之一
yk8=0xbaf2f000(三地址中之一)+(yk7清零最高4位二进制)<<2)
yk9=[yk8]
 
[yk9]=类似这种editorcube8
#拿到字符串

通过strstr来对比取出的字符串是否为FirstPersonC

 

image-20220418122100436

 

经过我的调试,发现这里actor数组都是固定的,index为0x23时,就找到了,调试了很多次都是一样的,也为我后续写外挂提供了很大便利

 

在index为0x23时,这时加的数值为0x90,字符串对比成功

 

image-20220418123641754

 

image-20220418123712537

 

pk4=[yk3+0x90]

 

字符串对比成功后,进入下一个逻辑

 

image-20220418123753316

 

找到人物的偏移

 

总结下,然后这里运行完,发现可以加速了,但是无法飞天

1
2
3
4
5
6
7
8
9
10
11
12
13
yk1=[libue4.so_base+0x4924570]
 
ykquestion=[yk1]+0x20
yk2=[ykquestion]+0x70
 
yk3=[yk2]
pk4=[yk3+0x90]
 
lk1=pk4+0x2f0
 
lk2=[lk1]+0x164
 
往 lk2这里地址写入了0x460ca000

这里我开始用ue4dumper,将符号dump下来查看究竟是修改了什么

 

image-20220418150128360

 

经过这个文件的对比的查找,上面的那份偏移可以这么改

1
2
3
4
5
6
7
8
9
10
11
gworld=[libue4.so_base+0x4924570]
 
ULevel=[yk1]+0x20
yk2=[Ulevel]+0x70
 
Actors*=[yk2]
Character.Pawn.Actor=[Actors*+0x90]
 
CharacterMovementComponent*=Character.Pawn.Actor+0x2f0
 
MaxWalkSpeed=[CharacterMovementComponent*]+0x164

往 人物的MaxWalkSpeed这里地址写入了0x460ca000,增加了最大移动速度,可以越跑越快

 

继续飞天功能的分析,上面的函数无法在找出写数据的操作了,说明在别的函数里面,发现是在这个函数里面

 

image-20220418124237656

 

跟进去,因为分析过了一遍,我根据功能重命名了一下

 

image-20220418124418142

 

跟进

 

image-20220418124710012

 

先fopen了 /proc/filesystem , 本地测试了是这个东西,

 

image-20220417230246829

 

然后它在找这个

 

image-20220417230314471

 

在我上图倒数第二行,找到之后设置了为1 ,又fopen了这个文件

 

image-20220417230726716

 

image-20220417230805442

 

很多挂载的东西,但是还是想找这个

 

image-20220417230905994

 

最终拼接拿到了这个

 

image-20220417231200552

 

往这个文件里写入了0x30 ,应该是开启selinux的意思,这个函数分析完毕,继续下一个

 

image-20220418124822062

 

这里和移动加速的地方差不多,就是获取fps游戏进程的pid

 

image-20220418124903004

 

跟进去看看

 

image-20220417231851347

 

但是这里是附加的意思,让游戏成为自己的子进程

 

image-20220417232036242

 

这里阻塞游戏,ok,ok这个函数分析结束,下一个

 

image-20220418125226259

 

跟进去,发现拿出ro.build.version ,查看安卓的版本,为了后面的查找libc的路径有关系,不同安卓版本会不一样,分析下一个函数

 

image-20220418125331709

 

外挂先获取自己的libc的mprotect的指针,然后进去拿到fps的libc基地址

 

image-20220417234626421

 

通过寄存器知道了这个函数拿fps进程的libc和外挂进程的目的,是拿到外挂的libc的mprotect的偏移,加上fps的libc基地址拿到fps的libc中mprotect的函数地址,方便后面的ptrace调用

 

image-20220418005957738

 

分析下一个函数

 

image-20220418125737114

 

跟进去,这里libue4.so基地址+0x1e00000+0x1d6000作为参数传入

 

image-20220418125815567

 

image-20220418125827525

 

伪代码也很清晰,可以看到这里是ptrace利用getregs在保存寄存器,也就是保护上下文

 

继续跟下去,这里有四个ptrace

 

image-20220418010902785

 

伪代码挺清晰的

 

image-20220418010851977

 

这里是修改了libue4.so基地址+0x1e00000+0x1d6000所在页的读写权限,利用mprotect函数。

 

继续下一个函数分析,这里libue4.so基地址+0x1e00000+0x1d6000+0x428为参数

 

image-20220418130334885

 

这里是关键函数,跟进去

 

image-20220418130427964

 

这里ptrace参数为5,是写入数据的意思,在libue4.so基地址+0x1e00000+0x1d6000+0x428这个地址上写入了0xe3a00801这四个字节

 

再次运行游戏,按音量键,发现飞天了,说明这里就是飞天的关键

 

继续分析下一个函数

 

image-20220418130609603

 

跟进去

 

image-20220418130619075

 

查看伪代码,原来是退出ptrace,全部函数分析完毕!

 

总结一下飞天的实现,通过ptrace让游戏成为外挂进程的子进程,然后通过先拿出外挂进程libc中mprotect函数地址,算出mprotect的偏移,再拿到游戏的libc基地址,算出游戏进程的mprotect,再利用ptrace操纵寄存器的效果,调用mprotect函数,修改想修改内存的读写权限,然后再通过ptrace 写入数据,最后退出ptrace,进程运行结束

1
[libue4.so基地址+0x1e00000+0x1d6000+0x428]=0xe3a00801

这里通过反编译libUE4.so,找到了这里

 

RT _ZNK27UCharacterMovementComponent11GetGravityZEv
.text:01FD640C _ZNK27UCharacterMovementComponent11GetGravityZEv

 

image-20220418145055572

 

很明显是这里修改了跳跃高度,每跳一次,高度直接太高,相当于飞天了。

外挂实现

这里我是用frida脚本实现了外挂功能,首先游戏并没有任何保护,所以可以选择远程读写或者注入,但是有些奇怪的这个外挂的两个功能,飞天是mprotect 页然后修改数据,但是人物移动速度是直接修改的,远程读写需要考虑权限,注入需要考虑修改的时机,基于两者我都想要,所以还是选择了frida来实现,通过之前得到的偏移,frida本身也提供内存权限的修改, 游戏启动后,attach方式启动,可以实现两个外挂的功能,飞天利用了人物actor在数组中下标一直不变,所以我这里不需要去遍历数组再通过字符串对比的方式找到人物,根据我之前记录人物的下标,就可以直接拿到人物actor了。

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
function cheat()
{
    var libc_addr = Module.findBaseAddress("libUE4.so");
    //console.log("libUE4 addr is :0x%x", libc_addr);
 
    //fly sky
    var tt=ptr(libc_addr).add(0x1e00000);
    var tt1=tt.add(0x1d6000);
    var tt2=tt1.add(0x428);
    Memory.protect(tt2, 4096, 'rwx');
    var tt4=0xe3a00801;
    tt2.writeU32(tt4);
 
    // speed up
    var yk1=ptr(libc_addr).add(0x4924570);
    var jf=ptr(libc_addr).add(0x4877034);
    var mid1=yk1.readU32();
    var ykquest=mid1+0x20;
    var yk2=ptr(ykquest).readU32()+0x70
    //console.log("yk2",yk2.toString(16));
    var yk3=ptr(yk2).readU32()
    var jf1=jf.readU32()
    console.log("jf1",jf1.toString(16));
    var fun1=ptr(jf1).readU32();
    var fun2=ptr(jf1).add(4).readU32();
    var fun3=ptr(jf1).add(8).readU32();
    console.log("fun2",fun1.toString(16));
    console.log("fun2",fun2.toString(16));
    console.log("fun3",fun3.toString(16));
    var actor=ptr(yk3).add(0x90).readU32()
    var actor1=ptr(actor).add(0x2f0);
    var actor2=actor1.readU32();
    var actor3=ptr(actor2).add(0x164);
    var speed=actor3.readU32();
    console.log("actor3",speed.toString(16));
    Memory.protect(actor3, 4096, 'rwx');
    ptr(actor3).writeU32(0x460ca000);
 
 
 
}
setImmediate(cheat);

总结下不足

  1. 混淆没去,第一次参加没啥经验,实际上重建控制流是可行的,f5是对mov pc这个东西反编译不了,所以patch成b xx比较好
  2. 第二个就是没分析的很细,尤其是偏移,做决赛的题目的时候,注重关注了这点
  3. 有啥不懂的,欢迎交流233

【看雪培训】《Adroid高级研修班》2022年夏季班招生中!

最后于 2022-4-28 11:49 被YenKoc编辑 ,原因:
收藏
点赞4
打赏
分享
最新回复 (10)
雪    币: 2044
活跃值: 活跃值 (5481)
能力值: ( LV8,RANK:120 )
在线值:
发帖
回帖
粉丝
珍惜Any 活跃值 1 2022-4-28 12:00
2
1
YK大佬太强了
雪    币: 3553
活跃值: 活跃值 (3878)
能力值: ( LV10,RANK:177 )
在线值:
发帖
回帖
粉丝
YenKoc 活跃值 2 2022-4-28 12:28
3
0
珍惜Any YK大佬太强了
雪    币: 3735
活跃值: 活跃值 (526)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
Papaya. 活跃值 2022-4-28 14:13
4
0
YK大佬太强了
雪    币: 6171
活跃值: 活跃值 (3604)
能力值: ( LV13,RANK:239 )
在线值:
发帖
回帖
粉丝
sunfishi 活跃值 3 2022-4-28 15:06
5
0
mark
雪    币: 1286
活跃值: 活跃值 (613)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
guangzisam 活跃值 2022-4-28 15:15
6
0
希望作者用lua演示简单的外挂。最好最简单的apk作为目标,引大家入门
雪    币: 6645
活跃值: 活跃值 (2739)
能力值: ( LV4,RANK:45 )
在线值:
发帖
回帖
粉丝
v0id_ 活跃值 2022-4-28 16:21
7
0
YK大佬太强了
雪    币: 4635
活跃值: 活跃值 (2361)
能力值: ( LV4,RANK:55 )
在线值:
发帖
回帖
粉丝
Jev0n 活跃值 2022-4-28 17:43
8
0
mark
雪    币: 379
活跃值: 活跃值 (99)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
mb_sswhsjvz 活跃值 2022-4-29 01:47
9
0
YK大佬太强了
雪    币: 1748
活跃值: 活跃值 (736)
能力值: ( LV4,RANK:45 )
在线值:
发帖
回帖
粉丝
M00yy 活跃值 2022-4-29 09:35
10
0
YK大佬太强了
雪    币: 4
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
IndexGuc 活跃值 2022-5-12 11:37
11
0
分析到改移动速度这里就研究不下去了
游客
登录 | 注册 方可回帖
返回