一、目标

最近又开始研究Unidbg了,费了好大劲,没有跑起来。今天就先找个软柿子捏捏看。

今天的目标是 之前研究的 某段子App签名计算方法(一)

  • 某段子App版本 5.5.10

二、步骤

先搭起框架来

Unidbg模拟执行某段子so实操教程(一) 先把框架搭起来-LMLPHP

/unidbg/unidbg-android/src/test/java/ 下面新建一个 com/fenfei/test 包, 我们的例子都放在这个包下。

然后再创建一个 RunZy

public class RunZy extends AbstractJni {
    public static void main(String[] args) throws IOException {
        // 1、需要调用的Apk文件所在路径
        String apkFilePath = "/Users/fenfei/Desktop/zy/cn.xxxxchuanxxxx.tieba_5.5.10_505100.apk";
        // 2、需要调用函数所在的Java类完整路径,比如a/b/c/d等等,注意需要用/代替.
        String classPath = "com/izxxyxx/network/NetCrypto";
        // 3、需要调用方法,再jadx中找到对应的方法,然后点击下面的Smail,复制方法的Smail代码。
        String methodSign = "sign(Ljava/lang/String;[B)Ljava/lang/String;";
        RunZy runZyObj = new RunZy(apkFilePath, classPath);
        runZyObj.destroy();
    }

    // ARM模拟器
    private final ARMEmulator emulator;
    // vm
    private final VM vm;
    // 载入的模块
    private final Module module;

    private final DvmClass TTEncryptUtils;


    /**
     *
     * @param apkFilePath  需要执行的apk文件路径
     * @param classPath    需要执行的函数所在的Java类路径
     * @throws IOException
     */
    public RunZy(String apkFilePath, String classPath) throws IOException {
        // 创建app进程,包名可任意写
        emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("com.fenfei.RunZy").build(); // 创建模拟器实例,要模拟32位或者64位,在这里区分
        final Memory memory = emulator.getMemory(); // 模拟器的内存操作接口
        // 作者支持19和23两个sdk
        memory.setLibraryResolver(new AndroidResolver(23));

        // 创建DalvikVM,利用apk本身,可以为null
        vm = ((AndroidARMEmulator) emulator).createDalvikVM(new File(apkFilePath));
        vm.setVerbose(true);
        vm.setJni(this);
        new AndroidModule(emulator, vm).register(memory);

        // (关键处1)加载so,填写so的文件路径
        DalvikModule dm = vm.loadLibrary("net_crypto", false);

        // 调用jni
        dm.callJNI_OnLoad(emulator);
        module = dm.getModule();

        //emulator.traceCode(module.base, module.base + module.size);

        // (关键处2)加载so文件中的哪个类,填写完整的类路径
        TTEncryptUtils = vm.resolveClass(classPath);
    }

    /**
     * 关闭模拟器
     * @throws IOException
     */
    private void destroy() throws IOException {
        emulator.close();
        System.out.println("emulator destroy...");
    }

}

跑 native_init

从之前的分析我们知道,在执行 sign函数之前,需要执行 native_init

// runZyObj.initCall();
    private void initCall(){
        TTEncryptUtils.callStaticJniMethod(emulator,"native_init()V");
    }

执行一下

java.lang.UnsupportedOperationException: com/izxxyxx/common/base/BaseApplication->getAppContext()Landroid/content/Context;
	at com.github.unidbg.linux.android.dvm.AbstractJni.callStaticObjectMethodV(AbstractJni.java:402)

这个报错好解决,我们重写 callStaticObjectMethodV 来实现这个函数

@Override
    public DvmObject<?> callStaticObjectMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
        switch (signature) {
            case "com/izxxyxx/common/base/BaseApplication->getAppContext()Landroid/content/Context;":
                return vm.resolveClass("android/content/Context", vm.resolveClass("android/content/ContextWrapper", vm.resolveClass("android/content/Context"))).newObject(signature);
        return super.callStaticObjectMethodV(vm,dvmClass,signature,vaList);
    }

这个 getAppContext 我们之前的文章实现过,这里就依葫芦画瓢。

再跑一下,Ok,native_init 算是跑过了。

执行sign

通过之前的分析我们知道,sign的入参有两个,第一个参数是个字符串,实际是个url,第二个参数也是这个so里面的加密结果,一个buf。我们从hook结果里面找一个入参来玩玩。

String InBuf = "50027f7f7f7f8e8e8e8e8e1......";

String ret = runZyObj.getSign(methodSign
        ,new StringObject(runZyObj.vm, "https://zyadapi.izxxyxx.com/ad/popup_ad")
        ,hexStringToBytes(InBuf));

// Out Rc=v2-1ff7402d2b4fa9a4c39b3853262f18fd
System.out.printf("ret:%s\n", ret);


/**
 * 调用so文件中的指定函数
 * @param methodSign 传入你要执行的函数信息,需要完整的smali语法格式的函数签名
 * @param args       是即将调用的函数需要的参数
 * @return 函数调用结果
 */
private String getSign(String methodSign, Object ...args) {
    // 使用jni调用传入的函数签名对应的方法()
    Object value = TTEncryptUtils.callStaticJniMethodObject(emulator, methodSign, args).getValue();
    return value.toString();
}

再跑一下

java.lang.UnsupportedOperationException: android/content/Context->getClass()Ljava/lang/Class;
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:349)

没明白这个 getClass 是干啥用的,不管了,先实现再说

@Override
public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
    switch (signature) {
        case "android/content/Context->getClass()Ljava/lang/Class;":
            return vm.resolveClass("java/lang/Class");
    }
    return super.callObjectMethodV(vm, dvmObject, signature, vaList);
}

继续跑

java.lang.UnsupportedOperationException: java/lang/Class->getSimpleName()Ljava/lang/String;
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:349)

这次是找我们要个 getSimpleName 这个值是啥呀?我也不知道,后面我再教大家找这个值的方法,这里先写死一个值吧。

case "java/lang/Class->getSimpleName()Ljava/lang/String;":
	return new StringObject(vm, "izxxyxx");

继续跑一下,

java.lang.UnsupportedOperationException: cn/xiaochuankeji/tieba/common/debug/AppLogReporter->reportAppRuntime(Ljava/lang/String;Ljava/lang/String;)V
	at com.github.unidbg.linux.android.dvm.AbstractJni.callStaticVoidMethodV(AbstractJni.java:576)

遇上 debug 之类的要敏感,这个报错后面分析的时候会用到。 这里我们就先实现 reportAppRuntime

@Override
public void callStaticVoidMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {

    switch (signature) {
        case "cn/xiaochuankeji/tieba/common/debug/AppLogReporter->reportAppRuntime(Ljava/lang/String;Ljava/lang/String;)V":
            return;
    }

	throw new UnsupportedOperationException(signature);

}

因为这个函数没有返回值,所以我们直接return即可。 继续跑......

java.lang.UnsupportedOperationException: android/content/Context->getFilesDir()Ljava/io/File;
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:349)

要读文件?先实现一把

case "android/content/Context->getFilesDir()Ljava/io/File;":
	return vm.resolveClass("java/io/File");

继续跑

java.lang.UnsupportedOperationException: java/lang/Class->getAbsolutePath()Ljava/lang/String;
	at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:349)

获取路径?我们也给他实现一个

case "java/lang/Class->getAbsolutePath()Ljava/lang/String;":
	return new StringObject(vm, "/sdcard");

再来

java.lang.UnsupportedOperationException: android/os/Debug->isDebuggerConnected()Z
	at com.github.unidbg.linux.android.dvm.AbstractJni.callStaticBooleanMethodV(AbstractJni.java:154)

判断是否被调试?这我哪能让你得逞

@Override
public boolean callStaticBooleanMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
    switch (signature) {
        case "android/os/Debug->isDebuggerConnected()Z":
            return Boolean.FALSE;
    }
    return super.callStaticBooleanMethodV(vm,dvmClass,signature,vaList);
}

必须是要告诉你,我根本木有在调试呀。

java.lang.UnsupportedOperationException: android/os/Process->myPid()I
	at com.github.unidbg.linux.android.dvm.AbstractJni.callStaticIntMethodV(AbstractJni.java:174)

要pid?给你一个

@Override
public int callStaticIntMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
    switch (signature) {
        case "android/os/Process->myPid()I":
            return 123;
    }

    return super.callStaticIntMethodV(vm,dvmClass,signature,vaList);
}

终极一跑

ret:v2-ABC1ff7402d2b4fa9a4c39b3853262f18fd
emulator destroy...

欧耶,结果出来了。

结果很忧伤

我们之前Hook的结果是 v2-1ff7402d2b4fa9a4c39b3853262f18fd 现在跑出来的结果是 v2-ABC1ff7402d2b4fa9a4c39b3853262f18fd , 不大对劲呀。

以结果轮英雄,我们可以多跑几组,如果确定模拟执行出来的结果都是 加上了固定的 ABC ,那也好办,直接过滤掉就行。

但是我们是写教程了,得搞明白。 怎么搞明白?模拟执行的结果有些不对劲该怎么办? 我们下回分解。

三、总结

Unidbg执行纯算法,那效果是刚刚的。就是这些不纯的so,都玩C了,还非要和jave层勾勾搭搭,故意为难我们。

Unidbg模拟执行某段子so实操教程(一) 先把框架搭起来-LMLPHP

布衣暖菜根香诗书滋味长

TIP: 本文的目的只有一个就是学习更多的逆向技巧和思路,如果有人利用本文技术去进行非法商业获取利益带来的法律责任都是操作者自己承担,和本文以及作者没关系,本文涉及到的代码项目可以去 奋飞的朋友们 知识星球自取,欢迎加入知识星球一起学习探讨技术。有问题可以加我wx: fenfei331 讨论下。

关注微信公众号: 奋飞安全,最新技术干货实时推送

05-25 22:28