moji天气逆向及unidbg实现

moji天气逆向及unidbg实现

Java层

image-20220122095033545

还挺多结果的,不过忽略掉meizu,huawei这些第三方sdk里面的东西后,还是很容易看出来的。

com.moji.requestcore.encrypt.DefaultEncryptParamImpl.encrypt

com.moji.requestcore.encrypt.DefaultEncryptParamImpl.a

image-20220122095313009

com.moji.mjweather.library.Digest.encodeParams

image-20220122095436507

lte.NCall.IL

image-20220122095529619

其实libGameVMP.so对我来说不陌生,之前在看某物 dewu app so newSign 参数分析破解这篇文章的时候,因为它奇葩的特性所以记住了。所以在moji这个app是不是也一样?

先hook一下com.moji.mjweather.library.Digest这个类

android hooking watch class com.moji.mjweather.library.Digest
image-20220122100833766

可以看到每次调用encodeParams的时候,nativeEncodeParams都被调用了。

再hook一下nativeEncodeParams这个函数。

android hooking watch class_method com.moji.mjweather.library.Digest.nativeEncodeParams --dump-args --dump-return

从hook结果可以看到最后确实是调用它生成sign。

image-20220122101503411

这么多个so,哪个才是调用加密的呢?从名字看libencrypt.solibEncryptor.so最可疑。

jnitrace看看。

jnitrace.exe -l libencrypt.so -l libEncryptor.so com.moji.mjweather|tee trace-moji.txt

然后尝试在记录里搜索某个签名

image-20220122102026676

可以看到最终是在libencrypt.so生成sign的。

so层

从地址可以看出so是64位的,因为手机和apk都支持64位指令,接下来的分析也是针对64位。

打开arm64-v8a下的libencrypt.so,函数窗口搜索java

image-20220122103036809

说明是动态注册的,看看JNI_OnLoad

image-20220122103121420

跳转过去

image-20220122103300225

继续跳转

image-20220122103402413

所以真实函数名是parseParams

第二种方法是frida_hook_libart

frida -U -f com.moji.mjweather -l hook_RegisterNatives.js --no-pause
image-20220122103819411

然后跳转到0x3d1a0

image-20220122103916475

另一种方法就是用jnitrace

jnitrace -l libencrypt.so -i RegisterNatives com.moji.mjweather
image-20220122104046572

函数地址0x728b2861a0减去so基地址0x728b249000等于0x3d1a0,跳转过去同样也是parseParams

image-20220122104433356

算法其实挺简单的,就是后面拼接个字符串,然后做个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();
    }
}

开始报错和补函数。

image-20220122105734300
@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);
}
image-20220122105849621
@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);
}
image-20220122110001747

不知道怎么补,开始摆烂

public void setStaticObjectField(BaseVM vm, DvmClass dvmClass, String signature, DvmObject value) {
    switch (signature) {
        case "android/app/ActivityThread->sPackageManager:Landroid/content/pm/IPackageManager;": {

        }
    }
}
image-20220122110156418
case "android/os/ServiceManager->getService(Ljava/lang/String;)Landroid/os/IBinder;": {
    return vm.resolveClass("android/os/IBinder").newObject(null);
}
image-20220122110302330

越来越离谱,一怒之下,决定不补了,尝试直接patch。

image-20220122110444905

跳转到之前获取PackageManager的地方,也就是0x11315

image-20220122110652365
image-20220122110806795

可以看出getPublicKey这个函数主要起获取签名的作用,X查看查看引用

image-20220122110857747

看看checkPubKey

image-20220122111106477

可以看出校验成功会返回1,失败返回0。

查看checkPubKey的引用

image-20220122111907460

可以看到在关键函数中都有调用,先看看checkKeyOnLoad(因为对它查看引用的话,可以看到它在JNI_OnLoad被调用了)

image-20220122112045492
image-20220122112159491

直接修改0x117A2这条指令,把返回值,也就是r0的值改为1

image-20220122112341603
public void patchVerify(){
    emulator.getMemory().pointer(module.base+0x117A2).setInt(0, 0x4ff00100);
}
public Moji() {
    // ...
    this.patchVerify();
    dm.callJNI_OnLoad(emulator);
}

再次跑起来。

image-20220122112634268

JNI_OnLoad完成了,接下来是正式调用。

public void call_sign() {
    List list = new ArrayList<>(10);
    list.add(vm.getJNIEnv());
    list.add(0);
    list.add(vm.addLocalObject(new StringObject(vm, "1")));
    Number ret = module.callFunction(emulator, 0x21a9d, list.toArray());
    System.out.println(vm.getObject(ret.intValue()).getValue().toString());
}
 
 
public static void main(String[] args) {
    Moji test = new Moji();
    test.call_sign();
}
image-20220122112859178

又报错了,这是因为之前的checkPubKeyparseParams中也被调用了

image-20220122113026061
image-20220122113052683

同样的,patch这条指令

public void patchVerify(){
    emulator.getMemory().pointer(module.base+0x117A2).setInt(0, 0x4ff00100);
    emulator.getMemory().pointer(module.base+0x21AF4).setInt(0, 0x4ff00100);
}

重新运行,结果就出来了

image-20220122113215092

完整代码

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 list = new ArrayList<>(10);
        list.add(vm.getJNIEnv());
        list.add(0);
        list.add(vm.addLocalObject(new StringObject(vm, "1")));
        Number ret = module.callFunction(emulator, 0x21a9d, list.toArray());
        System.out.println(vm.getObject(ret.intValue()).getValue().toString());
    }

    public static void main(String[] args) {
        Moji test = new Moji();
        test.call_sign();
    }
}
 
 

总结

  1. patch一时爽,一直patch一直爽。不过有哪位补函数的高手能完整补出来吗,想学习学习
  2. 有没有方法能让64位手机,64位app运行在32位环境,习惯了32位环境,分析64位不太习惯

Update 2022-02-09

Android adb安装时强制应用App以32位或者64位运行

指定应用在64位终端下以32位方式模式下运行

adb install --abi armeabi-v7a 

你可能感兴趣的:(moji天气逆向及unidbg实现)