这个外挂是一个典型的andlua程序,核心特征就是主要逻辑都在lua里面,外挂启动就会执行lua,由于lua被andlua官方加密了,所以外挂需要在内存中经过lua虚拟机加载函数时解密再运行,先判断了是否占用27042 和23946端口,以及/data/local/tmp下有没有re.frida.sever文件,进行反调试,然后申请root权限,创建一个随机文件名的空文件,同时将assert文件夹下的aes文件,进行rc4解密,解密后的文件字节流,写入前面创建的空文件中,并用root权限启动了这个随机文件名的文件进程,这个进程根据偏移修改了游戏数据,实现了飞天和人物移动加速的两个外挂功能。
首先先查看外挂的结构
发现是一个andlua程序,同时lua被加密了,用010打开是这样的,所有的lua都是类型这种结构
assert文件夹下面有个aes文件不太一样,里面和lua的格式不太一样,怀疑是外挂会解密后,新进一个进程,但是ps -A 并没有看到叫aes的进程
andlua的核心在lua里面,所以需要先把lua解密出来,这里我绕了一些弯路,这里是还没看出来是andlua官方加密,自己去逆了很久的lua的虚拟机
在这里跟了很久,发现了解密的地方,同时为了避免麻烦,我hook j_lua_load这个函数,这里就出现了问题了,dump出来的是luas,用unluac反编译后,符号名出现丢失,代码逻辑是正常,但是没有符号可以说就是绝路,dump下来,是这样的
继续逆虚拟机,找了很久,似乎没法通过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,用视频给的工具包,下载到本地。
在/storage/emulated/0/ExaGear按照视频,将本地的 ExaGear文件夹复制过去
,然后将外挂中需要解密的lua,都放入src文件夹中
最后退出,运行ExaGear,点击luatools,自动解密开始
在dst文件夹下,生成解密好的luac文件
在用unlua53这个app,将这些luac反编译回lua,可以看到反编译后效果非常好,符号名也都有,很清晰
这里main.lua文件是主要逻辑,可以看到进程在启动的时候,会检测23946和27042的端口,也就是ida和frida的常用默认端口,还检测了/data/local/tmp下有无re.frida.server相关的文件
同时又创建了一个随机进程名的空白文件,并用shell命令运行了这个文件
难怪之前的找aes什么的进程,是找不到的,名字都是随机的,同时解密assert/aes文件,看了下解密方法是rc4,密钥也是明文zyp
rc4没魔改,我直接在本地解密aes文件
因为外挂运行了这个文件,所以这个文件肯定是很重要的,解密后的文件发现是一个elf文件,用ida打开,有很明显的加壳,就两个区段,是很不正常,搜了下字符串,原来是upx的壳子
从github上下了一份upx的代码,看看能不能upx -d直接脱了
很舒服,是可以直接拖了的,哈哈,然后就可以用ida愉快的分析了,就是ida打开后也没符号,而且进程运行之后就死掉了,应该是出题人防止被附加吧,为了方便调试,我将调用kill和exit的地方都nop掉了
但是还是闪退,这和upx的脱壳有关系了,每次upx -d的程序基本都运行的有点问题,可能是目前解压缩壳的问题,不过对我分析影响不是非常大,由于没符号,先从入口开始调试libc_init, 找到了这里,提一句,我是调试分析完再写的wp,里面的函数经过分析后确定功能之后被我重命名了。
继续跟到这里
这里基本确实是主函数,这里两个函数,猜测分别是两个功能的实现,后面的分析也验证了我的想法,一个一个的去跟,里面有比较严重的混淆,f5不了,慢慢审汇编,多用f4、f7、f8、f9,先跟mmain函数
前面一大团字符串解密,直接跳到后面去看
到这里的流程静态很难看,本地测试了下外挂直接运行,也是有效果的,也就是独立于游戏的,直接远程启动外挂进程就好了
跟进去
发现这里是获取fps游戏的pid,查看下参数,通过这个pidof来获取的
到这里拿到pid
继续f7
每次调试这里都会跑飞,我只能在下一句汇编上下断才行,继续下一个函数
查看参数为libUE4.so,应该是找到so基地址的
跟进去看看,跟平常获取libue4.so基地址差不多
查看参数
这里是打开/proc/pid/maps,然后将每一行都和libUE4.so字符串做对比
如果查询到了,再将地址拿出并转换
退出来,进入下一个函数
这里发现是将libue4的基地址加上了0x4924570
[libue4.so_base+0x4924570]
跟进去,最终跟到一个syscall
syscall的调用号是0x178,也就是读取,将libue4的基地址+0x4924570所对应的四个字节取出来了,继续下一个函数
将取出来的数字+0x20,又经过相同的函数,和上面一样的分析,读取了4个字节到本地了
yk1=[libue4.so_base+0x4924570]
ykquestion=[yk1]+0x20
继续分析下一个函数
同样将上面取出的字节加上了0x70,再次进行读取
yk2=[ykquestion]+0x70
yk3=[yk2]
继续跟进下一个函数
发现这里是用libue4.so的基地址+0x4877034,然后依旧是同样的函数利用syscall,读取了4个字节
jf=libue4.so_base+0x4877034
jf1=[jf]
继续分析下一个函数,这里是一个远程读写的api,读取的jf1地址的0x78个字节到本地
读取的数据
发现是三个地址,这三个地址和后面的分析有关,继续看
读取了yk3地址的4个字节到本地
yk4=[yk3]
继续yk4+0x10的地址,取出4个字节
yk5=yk4+0x10
yk6=[yk5]
继续跟
发现这里是一个大循环
应该是遍历actor数组的,同时发现后面有拿到每个actor的name,并且对比
总结下这里在干什么,并举例
通过strstr来对比取出的字符串是否为FirstPersonC
经过我的调试,发现这里actor数组都是固定的,index为0x23时,就找到了,调试了很多次都是一样的,也为我后续写外挂提供了很大便利
在index为0x23时,这时加的数值为0x90,字符串对比成功
pk4=[yk3+0x90]
字符串对比成功后,进入下一个逻辑
找到人物的偏移
总结下,然后这里运行完,发现可以加速了,但是无法飞天
这里我开始用ue4dumper,将符号dump下来查看究竟是修改了什么
经过这个文件的对比的查找,上面的那份偏移可以这么改
往 人物的MaxWalkSpeed这里地址写入了0x460ca000,增加了最大移动速度,可以越跑越快
继续飞天功能的分析,上面的函数无法在找出写数据的操作了,说明在别的函数里面,发现是在这个函数里面
跟进去,因为分析过了一遍,我根据功能重命名了一下
跟进
先fopen了 /proc/filesystem , 本地测试了是这个东西,
然后它在找这个
在我上图倒数第二行,找到之后设置了为1 ,又fopen了这个文件
很多挂载的东西,但是还是想找这个
最终拼接拿到了这个
往这个文件里写入了0x30 ,应该是开启selinux的意思,这个函数分析完毕,继续下一个
这里和移动加速的地方差不多,就是获取fps游戏进程的pid
跟进去看看
但是这里是附加的意思,让游戏成为自己的子进程
这里阻塞游戏,ok,ok这个函数分析结束,下一个
跟进去,发现拿出ro.build.version ,查看安卓的版本,为了后面的查找libc的路径有关系,不同安卓版本会不一样,分析下一个函数
外挂先获取自己的libc的mprotect的指针,然后进去拿到fps的libc基地址
通过寄存器知道了这个函数拿fps进程的libc和外挂进程的目的,是拿到外挂的libc的mprotect的偏移,加上fps的libc基地址拿到fps的libc中mprotect的函数地址,方便后面的ptrace调用
分析下一个函数
跟进去,这里libue4.so基地址+0x1e00000+0x1d6000作为参数传入
伪代码也很清晰,可以看到这里是ptrace利用getregs在保存寄存器,也就是保护上下文
继续跟下去,这里有四个ptrace
伪代码挺清晰的
这里是修改了libue4.so基地址+0x1e00000+0x1d6000所在页的读写权限,利用mprotect函数。
继续下一个函数分析,这里libue4.so基地址+0x1e00000+0x1d6000+0x428为参数
这里是关键函数,跟进去
这里ptrace参数为5,是写入数据的意思,在libue4.so基地址+0x1e00000+0x1d6000+0x428这个地址上写入了0xe3a00801这四个字节
再次运行游戏,按音量键,发现飞天了,说明这里就是飞天的关键
继续分析下一个函数
跟进去
查看伪代码,原来是退出ptrace,全部函数分析完毕!
总结一下飞天的实现,通过ptrace让游戏成为外挂进程的子进程,然后通过先拿出外挂进程libc中mprotect函数地址,算出mprotect的偏移,再拿到游戏的libc基地址,算出游戏进程的mprotect,再利用ptrace操纵寄存器的效果,调用mprotect函数,修改想修改内存的读写权限,然后再通过ptrace 写入数据,最后退出ptrace,进程运行结束
这里通过反编译libUE4.so,找到了这里
RT _ZNK27UCharacterMovementComponent11GetGravityZEv
.text:01FD640C _ZNK27UCharacterMovementComponent11GetGravityZEv
很明显是这里修改了跳跃高度,每跳一次,高度直接太高,相当于飞天了。
这里我是用frida脚本实现了外挂功能,首先游戏并没有任何保护,所以可以选择远程读写或者注入,但是有些奇怪的这个外挂的两个功能,飞天是mprotect 页然后修改数据,但是人物移动速度是直接修改的,远程读写需要考虑权限,注入需要考虑修改的时机,基于两者我都想要,所以还是选择了frida来实现,通过之前得到的偏移,frida本身也提供内存权限的修改, 游戏启动后,attach方式启动,可以实现两个外挂的功能,飞天利用了人物actor在数组中下标一直不变,所以我这里不需要去遍历数组再通过字符串对比的方式找到人物,根据我之前记录人物的下标,就可以直接拿到人物actor了。
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__'
:
import
sys
tt
=
open
(
"aes"
,
"rb"
)
ww
=
tt.read()
decrypt_files
=
rc4(ww,
"zyp"
,
0
)
decttt
=
open
(
"dectt"
,
"wb"
)
decttt.write("".join(decrypt_files))
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))
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2022-4-28 11:49
被YenKoc编辑
,原因: