2025.1.24
首先契机是最近libmsaoaidsec这个so文件的反调试被大佬们打烂了
我从中学习了一下,也快速总结了一些思路
首先如果有frida检测的话,可以先去hook一下dlopen
dlopen
这个函数是liunx环境下一个运行时动态加载链接库(也就是so)的一个函数,通过直接hook这个函数我们可以知道在frida hook时加载到那个函数就会死掉
function hook_dlopen1() {//查看so的加载流程
Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"),//这个是64位后的名字,如果是32位的就直接hook dlopen
{
onEnter: function (args) {
this.fileName = args[0].readCString()
console.log(`dlopen onEnter: ${this.fileName}`)
}, onLeave: function(retval){
console.log(`dlopen onLeave fileName: ${this.fileName}`)
if(this.fileName != null && this.fileName.indexOf("libmsaoaidsec.so") >= 0){
let JNI_OnLoad = Module.getExportByName(this.fileName, 'JNI_OnLoad')
console.log(`dlopen onLeave JNI_OnLoad: ${JNI_OnLoad}`)
}
}
}
);
}过反调试
直接hook后很容易发现在加载libmsaoaidsec后程序直接退出,而且我们同时在onLeave时hook了jni_load函数,但是并没有回显,这可以说明,这个反frida是发生在jni_load之前的

直接反编译libmsaoaidsec看看
直接看init_proc,至于为什么可以看:
在加载库后,系统会调用 call_constructors 来执行 .init_array 中的所有初始化函数,.init_proc 中的代码一般由开发者定义,它可能会调用一些重要的逻辑函数,比如初始化全局变量,设置反调试、密钥初始化等。
这里直接借用正己大佬的图了,可以看到在dlopen后最先的就是init_proc

使用d810插件即可去除

网上做法挺多的,有hook sub_113F4这个函数中的 _system_property_get("ro.build.version.sdk", v1);来让自身断在init前面的,还有直接nop或者删除的,还有直接不让加载libmsaoaidsec这个so的,
但是他具体的混淆,就在与使用pthread_create加载反调试函数,这里就是sub_16D30和sub_175F8,再往里面走还有一个pthread_create_sub_16D30

这里参考的大佬的方法替换对应函数为空函数,当然也可以nop,网上还有hook_dlsym的,这里运行后程序会爆掉没一开始我以为可能是这种方法有问题,而且运行完后进行hook还是hook不上,所以就选择了一个比较暴力的方法过掉反调试——不让其加载libmsaoaidsec这个库
pl:其实方法一也是对的,在加载完反hook脚本后,进行正常hook时,第二次hook后就能附加上了,其实挺奇怪的,至于为什么过掉反调试后仍然崩溃了,可能是因为,第二个pthread_create循环了562717065次?反汇编代码怪怪的
function hook_dlopen(){
var android_dlopen_ext = Module.findExportByName(null,"android_dlopen_ext");
console.log("addr_android_dlopen_ext",android_dlopen_ext);
Interceptor.attach(android_dlopen_ext,{
onEnter:function(args){
var pathptr = args[0];
if(pathptr!=null && pathptr != undefined){
var path = ptr(pathptr).readCString();
if(path.indexOf("libmsaoaidsec.so")!=-1){
console.log("android_dlopen_ext:",path);
hook_call_constructors()
}
}
},
onLeave:function(retvel){
console.log("leave!");
}
})
}
function hook_call_constructors() {
let linker = null;
if (Process.pointerSize === 4) {
linker = Process.findModuleByName("linker");
} else {
linker = Process.findModuleByName("linker64");
}
let call_constructors_addr, get_soname
let symbols = linker.enumerateSymbols();
for (let index = 0; index < symbols.length; index++) {
let symbol = symbols[index];
if (symbol.name === "__dl__ZN6soinfo17call_constructorsEv") {
call_constructors_addr = symbol.address;
} else if (symbol.name === "__dl__ZNK6soinfo10get_sonameEv") {
get_soname = new NativeFunction(symbol.address, "pointer", ["pointer"]);
}
}
console.log(call_constructors_addr)
var listener = Interceptor.attach(call_constructors_addr, {
onEnter: function (args) {
console.log("hooked call_constructors")
var module = Process.findModuleByName("libmsaoaidsec.so")
if (module != null) {
Interceptor.replace(module.base.add(0x175f8), new NativeCallback(function () {
console.log("0x175f8:替换成功")
}, "void", []))
Interceptor.replace(module.base.add(0x16d30), new NativeCallback(function () {
console.log("0x16d30:替换成功")
}, "void", []))
Interceptor.replace(module.base.add(0x163dc), new NativeCallback(function () {
console.log("0x163dc:替换成功")
}, "void", []))
listener.detach()
}
},
})
}function hook_dlopen_libmsaoaidsec() {
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('libmsaoaidsec') >= 0) {
ptr(pathptr).writeUtf8String("");
console.log("rewrite libmsaoaidsec.so ptr ");
}
}
}
});
}总之大致是使用第二种方式进行探索的
正式分析
本文目的是去掉七猫的广告及获得vip字样
具体可以使用mt再查看下方博客(这个改得太详细了,我只改了一两点)
https://bbs.binmt.cc/thread-133587-1-1.html
如果你跟着改完之后再打开七猫后会发现,下方会弹出验签失败的弹窗,要搞清楚这个是从哪来的感觉使用jadx效率还是太过于低下了,所以使用frida hook弹窗类,再打印堆栈
function printStacks() {
console.log(
Java.use("android.util.Log")
.getStackTraceString(
Java.use("java.lang.Throwable").$new()
)
);
}
function main1() {
var toast = Java.use("android.widget.Toast");
toast.show.implementation = function () {
console.log("toast.show: ");
printStacks();
return this.show();
}
}可以看见相关函数,打开jadx后找到,发现相关类一共有abc三个类,遂一起hook




由于我们只需要知道传入值,那么就hook构造函数即可,这里可以看见这个验签错误是在b类中且是传入的
let a = Java.use("com.qimao.qmsdk.tools.SetToast$a");
a["$init"].implementation = function (str, textView, context, i, i2, i3, i4) {
console.log(`a.$init is called: str=${str}, textView=${textView}, context=${context}, i=${i}, i2=${i2}, i3=${i3}, i4=${i4}`);
this["$init"](str, textView, context, i, i2, i3, i4);
};
let b = Java.use("com.qimao.qmsdk.tools.SetToast$b");
b["$init"].implementation = function (str, textView, context, i, i2, i3) {
console.log(`b.$init is called: str=${str}, textView=${textView}, context=${context}, i=${i}, i2=${i2}, i3=${i3}`);
this["$init"](str, textView, context, i, i2, i3);
};
b["run"].implementation = function () {
console.log(`b.run is called`);
this["run"]();
};
let c = Java.use("com.qimao.qmsdk.tools.SetToast$c");
c["$init"].implementation = function (dVar, view, context, i, i2) {
console.log(`c.$init is called: dVar=${dVar}, view=${view}, context=${context}, i=${i}, i2=${i2}`);
this["$init"](dVar, view, context, i, i2);
};
c["run"].implementation = function () {
console.log(`c.run is called`);
this["run"]();
};
其实看一下b类的相关代码就可以发现是包体相关的了
burp抓包
主要抓登录包,这里肯定会有签名

jadx搜索一下/api/v1/init/is-open-sm-code,运气很好,搜到了
点进KMRequestBody继续分析
相当明显,hook确认一下

Java.perform(function(){
var is_sign = false;
let Buffer = Java.use("okio.Buffer");
Buffer["writeUtf8"].overload('java.lang.String').implementation = function (str) {
if (str == "sign") {
is_sign = true;
}
let ret = this.writeUtf8(str);
return ret;
};
let Encryption = Java.use("com.qimao.qmsdk.tools.encryption.Encryption");
Encryption["sign"].implementation = function (str) {
var a = this.sign(str);
if(is_sign)
{
console.log(`Encryption.sign is called: str=${str}`);
console.log(`Encryption.sign result=${a}`);
is_sign = false;
}
return a;
};
})对胃了

从这来的

这下看懂了

逆向so
那问题来了,如何确认so?
总体来讲有两种(?)
1、经验,由于这种加密一般都是自实现的,可以直接通过名字去猜,无非就是多花点时间
2、看见网上大佬使用frida-dump去打印堆栈也是可以的
3、也就是这里使用的方法,感觉并不是很通用,有一定的运气成分,直接查看调用或者查看加载的so进行排除之类的
4、还有就是感觉厂商有可能会将so文件直接命名为java层的类,比如这里的Security类,就直接命名为libsecurity也是有可能的
但在这里,直接查看Security的调用,可以直接确定是libcommon-encryption.so

jni没什么东西还疑似有debug,直接搜索java

frida直接hook
几个可疑的试了一下时候发现应该是Java_com_km_encryption_api_Security_token
一个加盐的md5

function hook_native_addr() {
var base = Module.findBaseAddress("libcommon-encryption.so");
var funcPtr = base.add(0x1fb34);
if(base == null)
{
setTimeout(hook_native_addr,1000);
}
Interceptor.attach(funcPtr, {
onEnter: function (args) {
}, onLeave: function (retval) {
console.log("11111111: "+retval.readUtf8String())
console.log("-----------------------------------------------------------")
}
});
var fun2 = base.add(0x172BC)
Interceptor.attach(fun2, {
onEnter: function (args) {
}, onLeave: function (retval) {
console.log("22222222: "+retval.readUtf8String())
console.log("-----------------------------------------------------------")
}
});
var fun3 = base.add(0x1F1EC)
Interceptor.attach(fun3, {
onEnter: function (args) {
}, onLeave: function (retval) {
console.log("33333333: "+retval.readUtf8String())
console.log("-----------------------------------------------------------")
}
});
}