遇到一个unity的单机游戏,这个游戏是别人改过的,想对比和原版有什么区别,使用frida去dump内存中的so,无奈frida直接被退出,但是游戏还是正常进入的,如下图:
而原版是没有验证的,
Hook pthread_create看下发现是在一个叫libmod.so之后闪退的
解压apk看下,lib\arm64-v8a\目录下并没有libmod.so
那大概率可能是从assets目录下解压自己的资源,再加载自己的so,但是我们并不需要直接去找哪个so有没有在assets目录下,因为上面hook pthread_create打印堆栈已经有给我们地址了,直接去那个目录找就行了,但其实有原包的情况下,直接对比两个apk也能看出多出了什么文件,把多出的那几个文件找出来,并用010打开就能知道是什么文件了,用010打开是为了看文件格式,有些文件为了隐藏,去掉了后缀名,有些是改了后缀名,但其实这些都没用,用010打开都能看出文件是什么格式的文件,除非有些是加密过的。
ida打开libmod.so,查看init_array段
妥妥的ollvm的特征。
随便进入一个函数,如下图,
根本没法看,我们也不准备看这些乱七八糟的代码。
其实直接硬改后面两个线程启动的地方就能绕过验证。也就是在两个线程启动的地方直接nop
但这里还有一种方法绕过验证
frida spawn启动后,frida被退出,然后游戏还能正常进入,而且mod功能也是正常的,看最后一次线程被启动的地方,进入0x1971e4函数,在分析这个函数之前,我们先理解一个正常开发的逻辑,正常一个检测或一个功能,通常都是封装成一个函数,方便查看,而且便于之后维护,基于这点,我们直接在函数中找call,也就是sub_xxxx开头的
进入0x1971e4看下,
没几个sub_xxxx,经过查看,函数内有很多处都有调用sub_188374 ,进入看下,是个syscall
好家伙,难怪hook各种系统函数都无效,而且入参是56,对应信号就是openat ,循环读取了什么文件,hook sub_19F974函数,代码如下:
var soName = "libmod.so"
function myfun()
{
var moduleBaseAddress = Module.getBaseAddress(soName);
console.log(soName + "_address:", moduleBaseAddress);
var nativePointer = moduleBaseAddress.add(0x19F974);
Interceptor.attach(nativePointer,
{
onEnter: function (args)
{
this.v1051= this.context.x0
var str3 = this.context.x3.readUtf8String();
console.log("str3:" + str3);
},
onLeave: function (retval)
{
console.log("this.v1051:",this.v1051.readUtf8String());
}
}
);
}
function hook_dlopen()
{
var is_can_hook = false;
Interceptor.attach(Module.findExportByName(null, "dlopen"),
{
onEnter: function (args)
{
var pathptr = args[0];
if (pathptr !== undefined && pathptr != null)
{
var path = ptr(pathptr).readCString();
if (path.indexOf(soName) >= 0)
{
this.is_can_hook = true;
}
}
},
onLeave: function (retval)
{
if (this.is_can_hook)
{
myfun()
}
}
}
);
Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"),
{
onEnter: function (args)
{
var pathptr = args[0];
if (pathptr !== undefined && pathptr != null)
{
var path = ptr(pathptr).readCString();
if (path.indexOf(soName) >= 0)
{
this.is_can_hook = true;
}
}
},
onLeave: function (retval)
{
if (this.is_can_hook)
{
myfun()
}
}
}
);
}
setImmediate(hook_dlopen);
读取了各种文件,它的最后一个参数也是先经过init_array中的函数先解密
对于openat 的描述是:函数执行成功返回文件描述符,失败返回-1.
那我们就直接在sub_188374函数中直接返回1吧,结果是可以,并正常运行,
其实直接对syscall直接返回1是不严谨的,因为有些情况下这样操作会导致别的问题出现,只是这游戏刚好这么操作没有出现别的问题。
frida也能正常附加并dump
之后就可以愉快的使用dump的so和原版的so进行比较
总结:
文章写的很乱,没什么技术含量,有些防护做了一些花里胡哨的操作,有时候也不用硬杠,但前提是知道hook哪些关键函数,知道一些函数的作用
欢迎加入QQ群:812701781,542863693,欢迎分享一些骚操作(备注看的什么文章)
微信公众号:MoneyHoneyCome