其他分享
首页 > 其他分享> > 某kucun sig 参数加密分析 unidbg 模拟黑盒调用

某kucun sig 参数加密分析 unidbg 模拟黑盒调用

作者:互联网

仅供学习研究 。请勿用于非法用途,本人将不承担任何法律责任。

前言

使用 frida 辅助分析 so 加密,并使用 unidbg 完成 黑盒调用, apk 版本 5.7.1

分析请求包


这里请求参数里包含 sig,很明显是个 hash 值啊,直接去反编译 apk 分析

分析 java


通过全局搜索关键词 sig 最终定位到 com.akc.im.akc.api.sign.Sign 这个类的 addHttpUrlParams 方法,里面会去调用 MXSecurity.signV1 函数获取 sig 的加密结果


点进去发现这里是个 native 函数,函数的实现在 libmx.so 文件里,调用 signV1 函数需要传递三个String 参数,这里我们静态分析,不能确定传递的是什么,使用 frida hook 这个函数来查看

frida hook java function

function main() {
    Java.perform(function () {
        var MXSecurity = Java.use('com.mengxiang.arch.security.MXSecurity')

        MXSecurity.signV1.implementation = function (a, b, c) {
            console.log('a: ', a);
            console.log('b: ', b);
            console.log('c: ', c);

            var res = this.signV1(a, b, c);
            console.log('res: ', res);

            return res
        }
    })
}

setImmediate(main);

我们直接 hook 这个函数,打印参数,跟返回值

可以看到,这三个分数分别对应的请求的 urlurl 参数里的 noncestr, timestamp,加密结果是 5b47f95d89ef4e29f8a96cb6897049b705de848c
这里我们记录下这些参数,便于后面主动调用时验证参数。
现在我们直接去使用 ida 查看 so 文件逻辑

ida so 分析


这里可以直接看到 signV1 是个静态注册的函数,双进去查看逻辑


这里前面拼接了一些参数,然后调用 digest 函数,进行加密,传了三个参数,分别是 JNIEnv, 加密方式 (sha1), 要加密的内容 bytearray,双击点击去看看逻辑


进来之后就是直接反射调用 java 层的 hash 加密算法,到这里分析算是结束了
已知使用的是 sha1 加密,参数前面已经分析出来了,不过在 so 文件里加了个 secrer 参数。这个目测应该是盐值,只要获取到这个参数就可以直接还原算法,获取的方式有很多 jnitrace, frida hook native 都可以,我们这里不去还原算法,直接使用 unidbg 进行黑盒调用

unidbg 黑盒调用

环境搭建

unidbg github 这里放个网址,直接 clone 下来,使用 idea 加载就完事了
具体使用就不说了,直接开干


先在 unidbg-android/src/test/java/com 下创建文件夹,我这里创建了 xiayu,在创建个一个 Java Class 文件.
下面 resources 下创建文件夹用来存放 so 文件

代码编写

先来个简单的 demo

package com.xiayu;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.LibraryResolver;
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.AbstractJni;
import com.github.unidbg.linux.android.dvm.DalvikModule;
import com.github.unidbg.linux.android.dvm.DvmClass;
import com.github.unidbg.linux.android.dvm.VM;
import com.github.unidbg.memory.Memory;

import java.io.File;
import java.io.IOException;

public class AkuMx1 extends AbstractJni {
    // 初始化一些 apk 常量
    private final AndroidEmulator emulator;
    private final VM vm;
    private final Module module;
    private final DvmClass mxSecurity;

    // APK 路径
    public String apkPath = "/Users/admin/Desktop/android/file/aikucun-5.7.1.apk";
    // so 文件路径
    public String soPath = "unidbg-android/src/test/resources/test_so/libmx.so";

    // 加载指定版本的系统库
    private static LibraryResolver createLibraryResolver() {
        return new AndroidResolver(23);
    }

    // 创建 android 模拟器,这里是 32 位的
    private static AndroidEmulator createARMEmulator() {
        return AndroidEmulatorBuilder.for32Bit().build();
    }

    AkuMx1() {
        emulator = createARMEmulator();
        final Memory memory = emulator.getMemory();
        // 设置 sdk版本 23
        memory.setLibraryResolver(createLibraryResolver());

        //创建DalvikVM,可以载入apk,也可以为null
        vm = emulator.createDalvikVM(new File(apkPath));
        // 设置可以调用 jni 函数
        vm.setJni(this);
        // 打印 jni 函数调用具体的 log
        vm.setVerbose(true);

        // 加载 so 文件
        DalvikModule dm = vm.loadLibrary(new File(soPath), true);
        module = dm.getModule();
        // 加载 java 加密函数所在 类
        mxSecurity = vm.resolveClass("com/mengxiang/arch/security/MXSecurity");
    }

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

    public static void main(String[] args) throws IOException {
        AkuMx1 aku = new AkuMx1();
        aku.run();
        aku.destroy();
    }
}


这里代码写好后跑起来,发现没有任何问题,继续往下写.
上面的分析发现,加密函数是 signV1 我们这里直接去调用这个函数,参数是三个 String 返回值也是个 String

public void run() {
        String a = "https://zuul.aikucun.com/aggregation-center-facade/api/app/index/left/brandlist/v1.0?appid=38741001&did=740a8c1c7b0d6dda519d3ae1ec813689&noncestr=a2a0d0&subuserid=316cb4a4af56818d2cffc5cf3147137a&timestamp=1623998117&token=460f6160e6a0478eb6583609c2afd774&userId=316cb4a4af56818d2cffc5cf3147137a&userid=316cb4a4af56818d2cffc5cf3147137a&zuul=1";
        String b = "a2a0d0";
        String c = "1623998117";

        DvmObject<?> strRc = mxSecurity.callStaticJniMethodObject(
                emulator,
                "signV1(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;",
                vm.addLocalObject(new StringObject(vm, a)),
                vm.addLocalObject(new StringObject(vm, b)),
                vm.addLocalObject(new StringObject(vm, c))
        );
        System.out.println("strRc: " + strRc.getValue());
    }

我们直接在 run 函数里编写代码,先构建那三个参数,就是我们刚 frida hook 出来的,然后去调用 mxSecurity.callStaticJniMethodObject(mxSecurity 是函数所在的类,构造函数里已初始化好了)

我们再次运行,这里成功调用了JNIEnv->NewStringUTF但是返回值是空,这里我们再去分析下 so 文件这个函数

这里可以看到 so 文件进来之后,会先有个 if 判断,条件如果不成立会执行 else 会返回一个空值,这里可以猜测,是 if 判断为成立,什么原因现在还不知道,我们使用 frida 主动调用 signV1 函数试一下

这里可以看到,使用 frida 一切正常,那我们这里是问题呢,有N多种可能,不过最常见的还是上下文缺失,缺少环境问题,这里我们使用 jnitrace 打印下 libmx.so的执行流,具体使用就不说了,自行查看 github 文档

环境依赖


经过 jnitrace 的打印,才发现,调用 signV1 函数之前还需要调用 init 函数也就是上图的函数.
那我们就先调用这个函数


有两个参数,分别是 context, boolean,返回值是 int ,我们直接来构建,这里的 boolean 经过 frida hook 发现是个 null

public void run() {
        // 加载 context 上下文对象
        DvmClass Context = vm.resolveClass("android/content/Context");
        DvmObject<?> strRc1 = mxSecurity.callStaticJniMethodObject(
                emulator, "init(Landroid/content/Context;Z;)I;",
                // Context.newObject(null) 初始化对象,参数直接 null
                vm.addLocalObject(Context.newObject(null)),
                // 这里也是 null
                vm.addLocalObject(null)
        );
        System.out.println("strRc1: " + strRc1);

        String a = "https://zuul.aikucun.com/aggregation-center-facade/api/app/index/left/brandlist/v1.0?appid=38741001&did=740a8c1c7b0d6dda519d3ae1ec813689&noncestr=a2a0d0&subuserid=316cb4a4af56818d2cffc5cf3147137a&timestamp=1623998117&token=460f6160e6a0478eb6583609c2afd774&userId=316cb4a4af56818d2cffc5cf3147137a&userid=316cb4a4af56818d2cffc5cf3147137a&zuul=1";
        String b = "a2a0d0";
        String c = "1623998117";

        DvmObject<?> strRc2 = mxSecurity.callStaticJniMethodObject(
                emulator,
                "signV1(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;",
                vm.addLocalObject(new StringObject(vm, a)),
                vm.addLocalObject(new StringObject(vm, b)),
                vm.addLocalObject(new StringObject(vm, c))
        );
        System.out.println("strRc2: " + strRc2.getValue());
    }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f8dvvvvw-1624002426555)(https://upload-images.jianshu.io/upload_images/13543130-bbc5d1840d6dc20d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
修改后的 run 函数,启动又报错,说是找不到 MessageDigest SHA256,最后经过百度知道,SHA256android里的,java 里是 SHA-256,我们直接重写这个函数,处理这个逻辑

@Override
    public DvmObject<?> callStaticObjectMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
        switch (signature) {
            case "java/security/MessageDigest->getInstance(Ljava/lang/String;)Ljava/security/MessageDigest;":
                StringObject type = vaList.getObjectArg(0);

                String name = "";
                if ("\"SHA256\"".equals(type.toString())) {
                    name = "SHA-256";
                } else {
                    name = type.toString();
                    System.out.println("else name: " + name);
                }

                try {
                    return vm.resolveClass("java/security/MessageDigest").newObject(MessageDigest.getInstance(name));
                } catch (NoSuchAlgorithmException e) {
                    throw new IllegalStateException(e);
                }
        }

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


添加这个函数,继续运行,同样的问题继续添加一个 else if 分支

String name = "";
if ("\"SHA256\"".equals(type.toString())) {
    name = "SHA-256";
} else if ("\"SHA1\"".equals(type.toString())) {
    name = "SHA-1";
} else {
    name = type.toString();
    System.out.println("else name: " + name);
}


继续运行,现在结果出来了,跟前面 hook 到的值进行对比,发现是一样的

最后

像这种比较简单的 hash 算法,可以直接还原,但是如果遇到比较负责的,这时候 unidbg 的优势就体现出来了,直接模拟 android 环境黑盒调用 so 文件函数,还可以使用 sprintboot 启动服务直接通过接口获取加密结果,最终代码放到 github 上了点击查看

标签:黑盒,String,vm,kucun,so,unidbg,com,函数
来源: https://blog.csdn.net/qq_40000081/article/details/118025548