unidbg基础使用

简单来说unidbg是通过模拟android虚拟机,来模拟运行apk依赖的动态库中函数的项目,可以快速调用so层中的函数,也可以实现一些hook操作

依赖

  1. jdk
  2. maven
  3. idea

安装教程请移步

导入unidbg

git clone https://github.com/zhkl0228/unidbg.git

或下载zip包,解压到自定义目录

打开idea,配置setting下maven的路径,如果你依赖那一步配置好的话,idea会自动识别,直接导入unidbg项目,导入时记得选导入maven项目,等待项目同步完成

项目中的测试用例:src/test/java/com/kanxue/test2/MainActivity.java,直接执行其中的main方法,得到下面输出,代表导入运行正常

load offset=4822ms
Found: XuE, off=2300ms

Process finished with exit code 0

unidbg使用

unidbg架构基本流程

  1. 创建32位模拟器实例,
    emulator = AndroidEmulatorBuilder.for32Bit().build();
  2. 创建模拟器内存接口
    final Memory memory = emulator.getMemory();
  3. 设置系统类库解析
    memory.setLibraryResolver(new AndroidResolver(23));
  4. 创建 Android 虚拟机
    vm = emulator.createDalvikVM();
  5. 加载 so 到虚拟内存,第二个参数的意思表示是否执行动态库的初始化代码
    DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/java/com/xxx/xxx.so"),true);
  6. 获取 so 模块的句柄
    module = dm.getModule();
  7. 设置 JNI 需要继承AbstractJni
    vm.setJni(this);
  8. 打印日志
    vm.setVerbose(true);
  9. 调用 JNI_Onload
    dm.callJNI_OnLoad(emulator);
  10. 创建 jobject, 如果没用到的话可以不写 ,要用需要调用函数所在的Java类完整路径,比如a/b/c/d等等,注意.需要用/代替
    cNative = vm.resolveClass("com/xxx/xxx")

参数构造

构造调用时会给其传递参数,参数构造常用如下:

  • 基本方式
List<Object> args = new ArrayList<>(10);
  • 兼容格式
// 参数1:JNIEnv *env
args.add(vm.getJNIEnv());

jobject 或 jclass

DvmObject<?> cnative = cNative.newObject(null);
args.add(cnative.hashCode());

如果用不到直接填0即可

args.add(0);
  • 字符串对象
String input = "abcdef";
args.add(vm.addLocalObject(new StringObject(vm, input)));
  • bytes 数组
String str= "abcdef";
byte[] str_bytes = str.getBytes(StandardCharsets.UTF_8);
ByteArray str_bytes _array = new ByteArray(vm,str_bytes );
args.add(vm.addLocalObject(str_bytes _array));
  • bool
// false 填 0,true 填 1
args.add(1);

使用实例

以2024csicn android_re appdebug.apk为例子,程序本身输入38字节flag运行会crash,hook jni返回值也一样crash,AS调试也是当调用jni函数时会crash,排查系统日志adb shell logcat后发现可能是某些编码错误,所以以上hook和调试手段不可行,此时可以用unidbg进行模拟直接调用so相关函数得到返回值。

题目jni相关代码如下:

package com.example.re11113;

public class jni {
    public static native String getiv();

    public static native String getkey();

    static {
        System.loadLibrary("Secret_entrance");
    }
}

要hook getiv()、getkey()的返回值,拿到key、iv就可以进行DES CBC解密,得到flag

在项目src/test/java/下新建目录和要hook的类(和so库中的类一致),下面是个样例(2024csicn android_re的脚本)

package com.example.re11113;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.LibraryResolver;
import com.github.unidbg.Module;
import com.github.unidbg.arm.backend.DynarmicFactory;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.DalvikModule;
import com.github.unidbg.linux.android.dvm.DvmObject;
import com.github.unidbg.linux.android.dvm.VM;
import com.github.unidbg.linux.android.dvm.jni.ProxyDvmObject;
import com.github.unidbg.memory.Memory;

import java.io.File;

/**
 * <a href="https://bbs.pediy.com/thread-263345.htm">CrackMe</a>
 */
public class jni {

    public static void main(String[] args) {
        jni jnii = new jni();
        jnii.crack();
    }

    private final AndroidEmulator emulator;
    private final VM vm;
    private final Module module;

    private jni() {
        emulator = AndroidEmulatorBuilder
                //创建64位模拟器实例,
                .for64Bit()
                //添加后端,推荐使用Dynarmic,运行速度快,但并不支持某些新特性
                .addBackendFactory(new DynarmicFactory(true))
                //指定进程名,推荐以安卓包名做进程名
                .setProcessName("com.example.re11113")
                //生成AndroidEmulator实例
                .build();
        //获取内存操作接口
        Memory memory = emulator.getMemory();
        //指定Android SDK 版本,目前支持19和23两个版本
        LibraryResolver resolver = new AndroidResolver(23);
        //设置系统类库解析
        memory.setLibraryResolver(resolver);

        //创建虚拟机
        vm = emulator.createDalvikVM();
        //设置日志打印是否开启
        vm.setVerbose(true);
        //加载 so 到虚拟内存,第二个参数的意思表示是否执行动态库的初始化代码
        DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/resources/re1/libSecret_entrance.so"), true);
        module = dm.getModule();
        dm.callJNI_OnLoad(emulator);
    }

    private void crack() {
        //创建虚拟机中的对象
        DvmObject<?> obj = ProxyDvmObject.createObject(vm, this);
        //调用so库中相关函数
        String iv = obj.callJniMethodObject(emulator, "getiv()Ljava/lang/String;").getValue().toString();
        String key = obj.callJniMethodObject(emulator, "getkey()Ljava/lang/String;").getValue().toString();

        System.out.println("iv:"+iv);
        System.out.println("key:"+key);

    }
}

运行结果:

Find native function Java_com_example_re11113_jni_getiv => RX@0x40001f58[libSecret_entrance.so]0x1f58
V2YzREx1cHM=
JNIEnv->NewStringUTF("Wf3DLups") was called from RX@0x40001fac[libSecret_entrance.so]0x1fac
Find native function Java_com_example_re11113_jni_getkey => RX@0x40001fec[libSecret_entrance.so]0x1fec
Keys match!
JNIEnv->NewStringUTF("A8UdWaeq") was called from RX@0x400021e8[libSecret_entrance.so]0x21e8
iv:Wf3DLups
key:A8UdWaeq

参考

unidbg教程前置知识之NDK静态、动态注册
逆向工具之unidbg(在pc端模拟执行so文件中的函数)