moji天气逆向及unidbg实现
Java层
还挺多结果的,不过忽略掉meizu,huawei这些第三方sdk里面的东西后,还是很容易看出来的。
com.moji.requestcore.encrypt.DefaultEncryptParamImpl.encrypt
com.moji.requestcore.encrypt.DefaultEncryptParamImpl.a
com.moji.mjweather.library.Digest.encodeParams
lte.NCall.IL
其实libGameVMP.so
对我来说不陌生,之前在看某物 dewu app so newSign 参数分析破解这篇文章的时候,因为它奇葩的特性所以记住了。所以在moji这个app是不是也一样?
先hook一下com.moji.mjweather.library.Digest
这个类
android hooking watch class com.moji.mjweather.library.Digest
可以看到每次调用encodeParams
的时候,nativeEncodeParams
都被调用了。
再hook一下nativeEncodeParams
这个函数。
android hooking watch class_method com.moji.mjweather.library.Digest.nativeEncodeParams --dump-args --dump-return
从hook结果可以看到最后确实是调用它生成sign。
这么多个so,哪个才是调用加密的呢?从名字看libencrypt.so
和libEncryptor.so
最可疑。
jnitrace看看。
jnitrace.exe -l libencrypt.so -l libEncryptor.so com.moji.mjweather|tee trace-moji.txt
然后尝试在记录里搜索某个签名
可以看到最终是在libencrypt.so
生成sign的。
so层
从地址可以看出so是64位的,因为手机和apk都支持64位指令,接下来的分析也是针对64位。
打开arm64-v8a
下的libencrypt.so
,函数窗口搜索java
说明是动态注册的,看看JNI_OnLoad
跳转过去
继续跳转
所以真实函数名是parseParams
第二种方法是frida_hook_libart
frida -U -f com.moji.mjweather -l hook_RegisterNatives.js --no-pause
然后跳转到0x3d1a0
另一种方法就是用jnitrace
jnitrace -l libencrypt.so -i RegisterNatives com.moji.mjweather
函数地址0x728b2861a0
减去so基地址0x728b249000
等于0x3d1a0
,跳转过去同样也是parseParams
。
算法其实挺简单的,就是后面拼接个字符串,然后做个MD5就出结果了,可以自行hook MD5Update
函数进行验证,这里就省略了。
unidbg实现
由于unidbg可以自由选择32还是64位,这里选择的是32位的。先搭个框架
public class Moji extends AbstractJni {
private final AndroidEmulator emulator;
private final VM vm;
private final Module module;
public static String pkgName = "com.moji.mjweather";
public static String apkPath = "unidbg-android/src/test/java/com/moji/moji90300.apk";
public static String soPath = "";
public Moji() {
emulator = AndroidEmulatorBuilder.for32Bit().setProcessName(pkgName).build();
Memory memory = emulator.getMemory();
memory.setLibraryResolver(new AndroidResolver(23));
vm = emulator.createDalvikVM(new File(apkPath));
vm.setJni(this);
vm.setVerbose(true);
DalvikModule dm = vm.loadLibrary("encrypt", true);
module = dm.getModule();
dm.callJNI_OnLoad(emulator);
}
public static void main(String[] args) {
Moji test = new Moji();
}
}
开始报错和补函数。
@Override
public DvmObject> callStaticObjectMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
switch (signature) {
case "com/moji/tool/AppDelegate->getAppContext()Landroid/content/Context;": {
return vm.resolveClass("android/content/Context").newObject(null);
}
}
return super.callStaticObjectMethodV(vm, dvmClass, signature, vaList);
}
@Override
public DvmObject> getStaticObjectField(BaseVM vm, DvmClass dvmClass, String signature) {
switch (signature) {
case "android/app/ActivityThread->sCurrentActivityThread:Landroid/app/ActivityThread;": {
return vm.resolveClass("android/app/ActivityThread").newObject(null);
}
}
return super.getStaticObjectField(vm, dvmClass, signature);
}
不知道怎么补,开始摆烂
public void setStaticObjectField(BaseVM vm, DvmClass dvmClass, String signature, DvmObject> value) {
switch (signature) {
case "android/app/ActivityThread->sPackageManager:Landroid/content/pm/IPackageManager;": {
}
}
}
case "android/os/ServiceManager->getService(Ljava/lang/String;)Landroid/os/IBinder;": {
return vm.resolveClass("android/os/IBinder").newObject(null);
}
越来越离谱,一怒之下,决定不补了,尝试直接patch。
跳转到之前获取PackageManager
的地方,也就是0x11315
可以看出getPublicKey
这个函数主要起获取签名的作用,X
查看查看引用
看看checkPubKey
可以看出校验成功会返回1,失败返回0。
查看checkPubKey
的引用
可以看到在关键函数中都有调用,先看看checkKeyOnLoad
(因为对它查看引用的话,可以看到它在JNI_OnLoad
被调用了)
直接修改0x117A2
这条指令,把返回值,也就是r0
的值改为1
public void patchVerify(){
emulator.getMemory().pointer(module.base+0x117A2).setInt(0, 0x4ff00100);
}
public Moji() {
// ...
this.patchVerify();
dm.callJNI_OnLoad(emulator);
}
再次跑起来。
JNI_OnLoad
完成了,接下来是正式调用。
public void call_sign() {
List
public static void main(String[] args) {
Moji test = new Moji();
test.call_sign();
}
又报错了,这是因为之前的checkPubKey
在parseParams
中也被调用了
同样的,patch这条指令
public void patchVerify(){
emulator.getMemory().pointer(module.base+0x117A2).setInt(0, 0x4ff00100);
emulator.getMemory().pointer(module.base+0x21AF4).setInt(0, 0x4ff00100);
}
重新运行,结果就出来了
完整代码
package com.moji;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Module;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.memory.Memory;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class Moji extends AbstractJni {
private final AndroidEmulator emulator;
private final VM vm;
private final Module module;
public static String pkgName = "com.moji.mjweather";
public static String apkPath = "unidbg-android/src/test/java/com/moji/moji90300.apk";
public static String soPath = "";
public Moji() {
emulator = AndroidEmulatorBuilder.for32Bit().setProcessName(pkgName).build();
Memory memory = emulator.getMemory();
memory.setLibraryResolver(new AndroidResolver(23));
vm = emulator.createDalvikVM(new File(apkPath));
vm.setJni(this);
vm.setVerbose(true);
DalvikModule dm = vm.loadLibrary("encrypt", true);
module = dm.getModule();
this.patchVerify();
dm.callJNI_OnLoad(emulator);
}
public void patchVerify(){
emulator.getMemory().pointer(module.base+0x117A2).setInt(0, 0x4ff00100);
emulator.getMemory().pointer(module.base+0x21AF4).setInt(0, 0x4ff00100);
}
@Override
public DvmObject> callStaticObjectMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
switch (signature) {
case "com/moji/tool/AppDelegate->getAppContext()Landroid/content/Context;": {
return vm.resolveClass("android/content/Context").newObject(null);
}
}
return super.callStaticObjectMethodV(vm, dvmClass, signature, vaList);
}
public void call_sign() {
List
总结
- patch一时爽,一直patch一直爽。不过有哪位补函数的高手能完整补出来吗,想学习学习
- 有没有方法能让64位手机,64位app运行在32位环境,习惯了32位环境,分析64位不太习惯
Update 2022-02-09
Android adb安装时强制应用App以32位或者64位运行
指定应用在64位终端下以32位方式模式下运行
adb install --abi armeabi-v7a