最近遇到了一个采用了某厂商的安全加固
该APP在离线环境下不存在检测功能,如果在线环境下会载入Java层和so层的检测功能。
脱壳使用Xposed框架的ditor可以脱掉,使用frida-dexdump不太行,存在检测。
本文章主要介绍如何绕过so层的检测,关于Java层检测,可以自行研究。
1、hook android_dlopen_ext
经典的hook dlopen函数,主要来看看加载哪个so文件使app退出了
这里属于so文件加载的知识,不了解的朋友可以阅读so文件加载流程
function hook_dlopen() {
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();
console.log("load " + path);
}
}
}
);
}
setImmediate(hook_dlopen)
可以看到加载了
libmsaoaidsec.so使程序退出了
那么久分析该文件
同时,我的pixel3手机是64位ARM架构,后文patch的时候有一些区别
2、hook JNI_ONLoad函数
确定了so文件,接下来需要确定是JNI_ONLoad函数之前存在检测,还是JNI_ONLoad之后存在检测。
我的JNI_Onload函数位置是0x13A4C,64位arm模式
function hook_dlopen(soName = '') {
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) {
hook_JNI_OnLoad()
}
}
}
);
}
function hook_JNI_OnLoad(){
let module = Process.findModuleByName("libmsaoaidsec.so")
Interceptor.attach(module.base.add(0x13A4C), {
onEnter(args){
console.log("call JNI_OnLoad")
}
})
}
//传递参数 libmsaoaidsec.so
setImmediate(hook_dlopen, "libmsaoaidsec.so")
发现app仍然退出,那么进一步缩小了范围,在JNI_Onload之前就是init系列函数了。
本样本so文件的相关段给去掉了,采用搜索函数的方式定位
init_xxx函数的运行结束的时机是在android_dlopen_ext运行时,当android_dlopen_ext运行结束时,init_xxx已经结束了。
我尝试在android_dlopen_ext结束时进行hook init_Proc 发现hook不上,验证了这一说法,同时Android源码也可验证。
解决方法是在.init_proc函数中找一个调用了外部函数的位置
一般是__system_property_get函数
同时本样本存在ollvm控制流混淆
问题不大,d810可以一把去除,主要干扰项是字符串的加密
function hook_dlopen(soName = '') {
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) {
locate_init()
}
}
}
}
);
}
function locate_init() {
let secmodule = null
Interceptor.attach(Module.findExportByName(null, "__system_property_get"),
{
// _system_property_get("ro.build.version.sdk", v1);
onEnter: function (args) {
secmodule = Process.findModuleByName("libmsaoaidsec.so")
var name = args[0];
if (name !== undefined && name != null) {
name = ptr(name).readCString();
if (name.indexOf("ro.build.version.sdk") >= 0) {
// 这是.init_proc刚开始执行的地方,是一个比较早的时机点
console.log("ok")
}
}
}
}
);
}
setImmediate(hook_dlopen, "libmsaoaidsec.so")
3、hook pthread_create
frida的检测通常会使用openat、open、strstr、pthread_create等等函数
检测文件又可以是maps等文件
这里直接 hook pthread_create
因为样本混淆太多了,字符串都是加密状态,hook 其他的函数都需要字符串来定位。
// 创建线程
if (pthread_create(&thread, NULL, thread_function, &arg) != 0) {
perror("pthread_create");
return 1;
}
这是pthread_create函数原型
function hook_dlopen(soName = '') {
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) {
locate_init()
}
}
}
}
);
}
function locate_init() {
let secmodule = null
Interceptor.attach(Module.findExportByName(null, "__system_property_get"),
{
// _system_property_get("ro.build.version.sdk", v1);
onEnter: function (args) {
secmodule = Process.findModuleByName("libmsaoaidsec.so")
var name = args[0];
if (name !== undefined && name != null) {
name = ptr(name).readCString();
if (name.indexOf("ro.build.version.sdk") >= 0) {
hook_pthread_create()
}
}
}
}
);
}
function hook_pthread_create(){
var base = Process.findModuleByName("libmsaoaidsec.so").base
console.log("libmsaoaidsec.so --- " + base)
Interceptor.attach(Module.findExportByName("libc.so", "pthread_create"),{
onEnter(args){
let func_addr = args[2]
console.log("The thread function address is " + func_addr + " offset:" + (func_addr-base).toString(16))
}
})
}
setImmediate(hook_dlopen, "libmsaoaidsec.so")
输出结果
[Pixel 3::xxxx ]-> libmsaoaidsec.so --- 0x733584b000
The thread function address is 0x7446631b3c offset:110de6b3c
The thread function address is 0x7446631b3c offset:110de6b3c
The thread function address is 0x7446631b3c offset:110de6b3c
The thread function address is 0x7446631b3c offset:110de6b3c
The thread function address is 0x7446631b3c offset:110de6b3c
The thread function address is 0x7446631b3c offset:110de6b3c
The thread function address is 0x7446631b3c offset:110de6b3c
The thread function address is 0x7335867544 offset:1c544
The thread function address is 0x7446631b3c offset:110de6b3c
The thread function address is 0x73358668d4 offset:1b8d4
The thread function address is 0x7335871e5c offset:26e5c
The thread function address is 0x7446631b3c offset:110de6b3c
The thread function address is 0x7446631b3c offset:110de6b3c
The thread function address is 0x7446631b3c offset:110de6b3c
The thread function address is 0x7446631b3c offset:110de6b3c
The thread function address is 0x7446631b3c offset:110de6b3c
4、hook 检测函数
关注线程地址:
0x1c544
0x1b8d4
0x26e5c
均为检测函数
那么剩下的就是hook咯
方法比较多,这里采用直接hook 调用处函数,动态进行patch操作
5、总结
- 该so文件存在大量字符串解密,总体使用了模式
mingwen = miwen ^ key[ i % ken_len]
- 使用了OLLVM控制流平坦化混淆,但是没有魔改,使用d810插件可以去除
- 该so文件还存在其他的检测方案,比如:检测了NOP操作,在检测函数里面进行相关值初始化和设置,避免了整体nop