众所周知的模拟调用so骚操作?

众所周知,授人以鱼不如授人以渔,那我来点鱼。。。网。
起因是之前有人在说解包可以直接调用相关的so文件,比各种IDA分析简单多了。从某种意义上,是对的,但是这个是相对的,比如有的就一个xxtea,何必搞这么复杂,而且有的传入参数压根看不懂结构,但是方法很简单,也没必要。不过最近正好遇上这么一个可以用到的情况,果断开始研究一下。

首先是某游戏的so:

根据经验是lua的,具体不知道,反正ida喂进去跑着,然后模拟器启动,frida勾文件读取上。
let xx = Module.findExportByName(null, “fopen”)

Interceptor.attach(xx, {
onEnter: function (args) {
console.log(xxr)
console.log(’ called from:\n’ +Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join(‘\n’) + ‘\n’);//SO打印堆栈
},
onLeave: function (retval) {
},
});

由于解压apk发现图片素材都是astc的后缀,那么这上面可以加个过滤:if(xxr.indexOf(“1zhouniantag.astc”) != -1)

总之,打印堆栈后可以发现一堆

根据经验,这种引擎有好几个搜索目录,所以一个资源会在多个目录都调用打开一次,所以前面的是FileExist的可以先不管,因为只是确认(不代表所有游戏都是这样),下面有个FileData的,看起来就是对的了。

回ida,差不多也好了,搜索一下

进去后没发现啥奇怪的,或者没有看起来是解密的

还记得IDA堆栈么,跟着往回找,懒得放过程了:
0x367b24d libgame.so!_ZN7cocos2d11CCFileUtils11getFileDataEPKcS2_Pm+0x44(目前在这)
0x3680267 libgame.so!_ZN7cocos2d18CCFileUtilsAndroid13doGetFileDataEPKcS2_Pmb+0xb6
0x36801ab libgame.so!_ZN7cocos2d18CCFileUtilsAndroid11getFileDataEPKcS2_Pm+0x12
0x37baa15 libgame.so!_ZN7cocos2d12CZHelperFunc11getFileDataEPKcS2_Pm+0x28(找到的解密在这)
0x3680749 libgame.so!_ZN7cocos2d7CCImage17initWithImageFileEPKcNS_12EImageFormatE+0x48
0x369f46d libgame.so!_ZN7cocos2d14CCTextureCache8addImageEPKc+0x740

这个4DBB04就是解密方法了。
但是这个方法进去后一层套一层,最后给我一个完全没法看的东西:

这样一来,能处理的方法就不多了,要么,大佬直接看出来是什么操作,直接写解密代码。要么,伪代码拿去VS里面修修,直接用,但是这个一层套一层的,不确定能不能修。
但是回到4DBB04这个方法,看看参数:
(void *s1, unsigned int a2, size_t *a3, int a4)

用frida勾一下看看输入的参数都是啥

let funcName2 = soModule.base.add(0x4Dbb04 + 1)
Interceptor.attach(funcName2, {
onEnter: function (args) {
console.log(“000:” + hexdump(args[0].add(0)));
console.log(“111:” + args[1]);
console.log(“222:” + hexdump(Memory.readPointer(args[2])));
console.log(“333:” + args[3]);
}
});

S1一看就是输入的文件数据
A2去查查对应文件,哦,文件大小

A3看起来就文件名,或者就一个内存地址,看IDA里面没实际用上
A4就传了个0,应该是计数器之类

再看看IDA,进行一波分析,结合frida给我们的信息,大概可以弄成这样

再勾一下输出结果,嗯,是没问题了,不过保险起见我们先输出一个对应文件的解密。
Frida里面这样:
onLeave: function (retval) {
if(ggs == 2){
let qqqq = Memory.readInt(a3v4)//这个是前面进入函数的时候,把a3地址记录到全局变量的
var dex_buffer = ptr(retval).readByteArray(qqqq);
var dex_path = “/data/data/我是名字/files/tmp/” + soModule.base.toString(16) + “_” + qqqq.toString(16) + “.dec”;
var fd = new File(dex_path, “wb”);
fd.write(dex_buffer);
fd.flush();
fd.close();
}
},
然后我们拿到一个c39c000_7090.dec文件,打开看看

和原文件对比一下

很棒,孩子很喜欢(遮掉的是标识,不重要)

什么特点呐:传入参数很简单,文件本身、大小、计数器和一段内存,但是解密方法乱七八糟看不懂。好,模拟调用一下so就应该能解决。但是,后面全是踩坑的经验。

一句话总结:建议用ExAndroidNativeEmu而不是原版AndroidNativeEmu

都是坑的教训:
首先,python调用so的方法主要是用AndroidNativeEmu,而这个又是基于Unicorn的。到这里没啥,我直接github找第一个,但是这里就出问题了。
排行第一的:GitHub - AeonLucid/AndroidNativeEmu: Allows you to partly emulate an Android native library.
无论我怎么处理,最后都有问题,结果发现他这个太过蛋疼了,很多很多坑,从初始化到依赖项全是坑,而且各种资料太少了。哪怕输入完全正确,输出也能乱掉,折磨了我一晚上,在我尝试用各种方法研究每一步的处理时,我发现
GitHub - maiyao1988/ExAndroidNativeEmu: An improved version of AndroidNativeEmu,Allow running android elf on PC

经过测试很正常,很好孩子很喜欢。所以以下教程都基于Ex版本。

首先下载一下,解压到随便啥地方。然后去游戏里面把libgame.so也拿过来。
里面有个例子:example_bb.py
我们直接拿这玩意修改就行,照葫芦画瓢
lib_module = emulator.load_library(“libgame.so”, do_init=False)
把so加载上去,然后就开始下一步,对照IDA构建参数
由IDA和frida的信息可以知道
s1=input_data 输入数据
a2 = input_len 输入数据长度
a3 = out_len 传入的一个指针,但是看代码这个指针最后写入输出的长度
a4=count 计数器,传的0
那么我们python也这么构造一下

写段代码,去文件夹循环加载加密文件,调用call4Dbb04这个方法解密。
for i in os.walk(“x”):
for f in i[2]:
if not f.endswith(“.astc”):
continue
fn = join(i[0], f)
with open(fn, ‘rb’) as a:
data = a.read()
if data[:6] != b"标识":
continue
dec_data = call4Dbb04(data)
#print(os.path.abspath(fn))
if dec_data[:2] == b"PK":
fn += “.zip”

    print(fn)
    with open(fn + ".dec", "wb") as w:
        w.write(dec_data)

然后对dec文件进行批量转换(因为是astc,还不能直接看),astcenc-avx2批量一下就行。
最终结果:

image

大概就这样吧,写得一塌糊涂我也不知道有人能看懂不。反正这种调用也不算随随便便就能用,如果相同引擎且有符号表还有可能,但是除了改名滚服游戏还没遇到过。好了我去挖个坑把自己埋了。

image

8 个赞