jadx 配合frida 定位到关键函数处
通过frida 主动调用
入参 = >
Me3v+nhLnwjf6KapD5+O6m47bPjxMaHJYqujau/xOjHa0nOUeMz33EJlI9Ivs3BvfRzoGIYNBKeFVQwk06iUm2n9hzyCf1gF9UGpZetm2GfIhnTrA8vTycY7Qelb033om3oWx9gjaUs9YwsDkMzEAiJPnDrnf98AAAxhqU1DsbOC/97C1K/oG81Z3D1OTyW+9iNWrk9a+g68H0+EHBl17tUxnVXvMNxv+Udh53pLQejOQUG69FSbOqIiCaky/Qf+tbRkD13kYP1tnZCxjSw99bw==
native层解密结果 =>
H4sIAAAAAAAAADXLsQqDMBCA4VcpNyvcRROTbBItdOjUvkDQKwjRQnIOpfTd6+L8/f8XMpc9CXjJO1cgOU58m8FDb5RGpAE7i20wY3/FxtqAdlRdOAQqmKPEcyyfIrwOUfh4FSqqSdVkL0QeW08OzuS5rHxfUloKeDKN65wx2mlNFWxveezTxOWgV0yFf3++e75CnwAAAA==
所以我们就用unidbg 来模拟执行一下这个so, 看看当传入入参,能否得到与frida主动调用相同的结果
ida 打开这个so,看看jni_onload, 静态注册, jni_onload初始化一些变量。
ok 上unidbg, 跑一下jni_onload函数
linglingbang() {
// 创建模拟器实例,进程名建议依照实际进程名填写,可以规避针对进程名的校验
emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("com.llb").build();
// 获取模拟器的内存操作接口
final Memory memory = emulator.getMemory();
// 设置系统类库解析
memory.setLibraryResolver(new AndroidResolver(23));
// 创建Android虚拟机,传入APK,Unidbg可以替我们做部分签名校验的工作
vm = emulator.createDalvikVM(new File("unidbg-android\\src\\test\\java\\com\\llb\\t.apk"));
// 加载目标SO
DalvikModule dm = vm.loadLibrary(new File("unidbg-android\\src\\test\\java\\com\\llb\\libencrypt.so"), true); // 加载so到虚拟内存
//获取本SO模块的句柄,后续需要用它
module = dm.getModule();
vm.setJni(this); // 设置JNI
vm.setVerbose(true); // 打印日志
dm.callJNI_OnLoad(emulator); // 调用JNI OnLoad
};
跑完后 报错如下:
一个字, 补
@Override
public DvmObject> callStaticObjectMethod(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {
switch (signature) {
// case "android/os/SystemProperties->get(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;":
// return new StringObject(vm, "FA6AT0306923");
case "android/app/ActivityThread->currentPackageName()Ljava/lang/String;":
return new StringObject(vm, "com.cloudy.llb");
// case "android/app/ActivityThread->currentActivityThread()Landroid/app/ActivityThread;":
// return vm.resolveClass("android/app/ActivityThread").newObject(null);
}
return super.callStaticObjectMethod(vm, dvmClass, signature, varArg);
}
补完后,运行结果如下:
接着补
@Override
public DvmObject> callStaticObjectMethod(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {
switch (signature) {
// case "android/os/SystemProperties->get(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;":
// return new StringObject(vm, "DD6BA0306923");
case "android/app/ActivityThread->currentPackageName()Ljava/lang/String;":
return new StringObject(vm, "com.cloudy.llb");
case "android/app/ActivityThread->currentActivityThread()Landroid/app/ActivityThread;":
return vm.resolveClass("android/app/ActivityThread").newObject(null);
}
return super.callStaticObjectMethod(vm, dvmClass, signature, varArg);
}
补完后,报错如下
继续补:
@Override
public DvmObject> callStaticObjectMethod(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {
switch (signature) {
case "android/os/SystemProperties->get(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;":
return new StringObject(vm, "FA6AT0306923");
case "android/app/ActivityThread->currentPackageName()Ljava/lang/String;":
return new StringObject(vm, "com.cloudy.linglingbang");
case "android/app/ActivityThread->currentActivityThread()Landroid/app/ActivityThread;":
return vm.resolveClass("android/app/ActivityThread").newObject(null);
}
return super.callStaticObjectMethod(vm, dvmClass, signature, varArg);
}
补完后,运行结果如下
继续补
@Override
public DvmObject> callObjectMethod(BaseVM vm, DvmObject> dvmObject, String signature, VarArg varArg) {
switch (signature) {
case "android/net/wifi/WifiInfo->getMacAddress()Ljava/lang/String;":
return new StringObject(vm, "02:00:00:00:00:00");
case "java/lang/Object->getConnectionInfo()Landroid/net/wifi/WifiInfo;":
return vm.resolveClass("android/net/wifi/WifiInfo").newObject(null);
case "android/app/ContextImpl->getSystemService(Ljava/lang/String;)Ljava/lang/Object;":
return vm.resolveClass("java/lang/Object").newObject(null);
case "android/app/ContextImpl->getPackageManager()Landroid/content/pm/PackageManager;":
return vm.resolveClass("android/content/pm/PackageManager").newObject(null);
case "android/app/ActivityThread->getSystemContext()Landroid/app/ContextImpl;":
return vm.resolveClass("android/app/ContextImpl").newObject(null);
}
return super.callObjectMethod(vm, dvmObject, signature, varArg);
}
补完后, 运行结果如下
jni 终于补完了,接下来正式进行调用了, Java代码如下 ,native函数只有一个String参数
打开ida, 查看注册函数对应的地址
unidbg代码如下
public String xPreAuthencode(){
// args list
List
运行结果如下, 与frida 主动调用的结果不一致
这是为何? 我们不妨看看最后一处调用,也就是
JNIEnv->NewStringUTF("92ec2acd6a6a610561e17e5ea5732d39")
这里的地址是 166cd
我们打开ida, 查看下代码。
166cd 处实在执行 v9(v10, v11) 的逻辑,我们有理由怀疑, 当 !V7 条件满足时, 就直接返回一串数据了, 而不是解密的结果, 所以 sub_138AC 也大概率是签名校验函数
我们进入到sub_138AC 一瞧究竟.
果不其然, 是在进行签名校验.
我们得找个方法过它得签名校验, 也就是修改v7得结果。
if ( !v7 )
{
v9 = v8->NewStringUTF;
v10 = v3;
v11 = v6;
return v9(v10, v11);
}
我们来看看上面这部分代码对应得汇编代码
可以看到,当签名校验通过后, 会跳转到 loc_16610 进行aes计算, 这个正是我们希望得。
可以看到有个指令是 CBNZ (compare branch never zero ) 也就是 R0 不等于0时候,
就跳转到 loc_16610, 这个指令有个好兄弟,是兄弟也是对头, 那就是 CBZ(compare branch zero)
意为 当R0 等于0 时 就跳转到 loc_16610 。 所以如果我们用 CBZ 替换掉 CBNZ 指令,那么就意味着,当签名校验不通过得时候, 就执行aes 解密, 但是当签名校验正确得时候,反而不走加密得流程了。
理清了这层逻辑后,用unidbg进行patch
public void patchVerify(){
int patchCode = 0x000020B1; //
emulator.getMemory().pointer(module.base + 0x16604 + 1).setInt(0, patchCode);
}
ptach完后,再次运行,结果如下
终于成功了!!! 与frida 主动调用结果一致。