unidbg(1) grand 2025-05-08 2025-05-22 参考 Unidbg 的基本使用(一)
下载源码 zhkl0228/unidbg: Allows you to emulate an Android native library, and an experimental iOS emulation
项目结构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 ├── README.md # 项目介绍和使用指南 ├── LICENSE # 开源许可证文件 ├── .gitignore # Git 忽略文件配置 ├── pom.xml # Maven 配置文件,定义了项目的依赖和构建配置 ├── mvnw # 脚本文件,用于 Maven Wrapper (Linux/Mac) ├── mvnw.cmd # 脚本文件,用于 Maven Wrapper (Windows) ├── test.sh # 测试脚本 (Linux/Mac) ├── test.cmd # 测试脚本 (Windows) ├── .mvn/ # Maven 配置目录 │ └── wrapper/ # Maven Wrapper 相关配置 ├── assets/ # 存放模拟过程中使用的资源文件 │ ├── *.dll # Windows 动态链接库文件 │ └── *.so # Linux/Android 动态链接库文件 ├── backend/ # 后端逻辑实现,包含核心模拟功能 ├── unidbg-api/ # 核心接口和抽象类模块 │ └── src/ # API 模块的源代码目录 ├── unidbg-ios/ # iOS 应用模拟模块 │ └── src/ # iOS 模拟模块的源代码目录 ├── unidbg-android/ # Android 应用模拟模块 │ ├── pom.xml # Maven 构建文件 │ ├── pull.sh # 拉取 Android 模拟所需依赖文件的脚本 │ └── src/ # unidbg-android 模块的源代码目录 │ ├── main/ │ │ ├── java/ # 核心 Java 源代码 │ │ │ └── com/github/unidbg/ # 包含核心模拟器、文件系统、虚拟机组件 │ │ │ └── net/fornwall/jelf # ELF 文件格式解析实现 │ │ └── resources/ # 资源文件,封装了 JNI 库、Android 系统库等 │ ├── test/ │ │ ├── java/ # 单元测试代码 │ │ ├── native/android/ # 测试 Android 原生库的 C/C++ 源代码 │ │ └── resources/ # 测试资源文件,包含预编译的二进制文件(log4j.properties这个是日志相关配置,可以对open,syscall这类的系统调用进行trace) └── .mvn/ # Maven Wrapper 相关配置目录
使用 idea打开
项目内容解析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 package com.iscc; import com.alibaba.fastjson.util.IOUtils; import com.github.unidbg.AndroidEmulator; import com.github.unidbg.Module; import com.github.unidbg.arm.backend.Backend; import com.github.unidbg.arm.backend.CodeHook; import com.github.unidbg.arm.backend.UnHook; import com.github.unidbg.arm.backend.Unicorn2Factory; import com.github.unidbg.arm.context.RegisterContext; import com.github.unidbg.debugger.Debugger; 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.linux.android.dvm.array.ByteArray; import com.github.unidbg.linux.android.dvm.jni.ProxyDvmObject; import com.github.unidbg.memory.Memory; import com.github.unidbg.utils.Inspector; import com.sun.jna.Pointer; import unicorn.Arm64Const; import unicorn.Unicorn; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.PrintStream; import java.util.ArrayList; import java.util.List; public class chal extends AbstractJni { private final AndroidEmulator emulator; private final VM vm; private final Module module; private final DvmClass swan, pig; private final boolean logging; chal(boolean logging) throws FileNotFoundException { this.logging = logging; emulator = AndroidEmulatorBuilder.for64Bit() .setProcessName("com.twogoat.114514") .addBackendFactory(new Unicorn2Factory(true)) .build(); // 创建模拟器实例,要模拟32位或者64位,在这里区分 final Memory memory = emulator.getMemory(); // 模拟器的内存操作接口 memory.setLibraryResolver(new AndroidResolver(23)); // 设置系统类库解析 vm = emulator.createDalvikVM(new File("F:\\repwn\\unidbg-master\\unidbg-master\\unidbg-android\\src\\test\\java\\com\\iscc\\attachment-12.apk")); // 创建Android虚拟机 vm.setVerbose(logging); // 设置是否打印Jni调用细节 vm.setJni(this); DalvikModule dm = vm.loadLibrary(new File("F:\\repwn\\unidbg-master\\unidbg-master\\unidbg-android\\src\\test\\java\\com\\iscc\\arm64-v8a\\libswan.so"), false); // 加载libttEncrypt.so到unicorn虚拟内存,加载成功以后会默认调用init_array等函数 module = dm.getModule(); // 加载好的libttEncrypt.so对应为一个模块 Debugger attach = emulator.attach(); attach.addBreakPoint(module.base + 0x0000000000021B04); dm.callJNI_OnLoad(emulator); // 手动执行JNI_OnLoad函数 //attach.addBreakPoint(module.base + 0x24188); // emulator.traceCode(module.base, module.base + 0x0000000000063290).setRedirect(traceStream); swan = vm.resolveClass("com/example/mobile03/swan"); pig = vm.resolveClass("pig"); } public chal(AndroidEmulator emulator, VM vm, Module module, DvmClass mMainActivity, DvmClass swan, DvmClass pig, boolean logging) { this.emulator = emulator; this.vm = vm; this.module = module; this.swan = swan; this.pig = pig; this.logging = logging; } void destroy() { IOUtils.close(emulator); if (logging) { System.out.println("destroy"); } } public static void main(String[] args) throws Exception { chal test = new chal(true); test.ttEncrypt(); test.destroy(); } boolean ttEncrypt() { String text = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; // String res = String.valueOf(swan.callStaticJniMethodObject(emulator, "getPvKey()Ljava/lang/String;", text)); String res = String.valueOf(swan.callStaticJniMethodObject(emulator, "getPvKey()Ljava/lang/String;", text)); System.out.println(res);// 执行Jni方法 return true; } }
初始化 1 2 3 4 5 emulator = AndroidEmulatorBuilder .for64Bit() .setProcessName("com.twogoat.114514") .addBackendFactory(new Unicorn2Factory(true)) .build(); // 创建模拟器实例,要模拟32位或者64位,在这里区分
位数和架构
分别对应了64和32的arm架构
进程名 1 .setProcessName("com.twogoat.114514")
这里最好还是修改为原apk对应的包名
后端
先回顾一下处理器、操作系统、应用程序的基本关系,大体上是下面这样。
Unidbg中为
Unidbg 支持了数个新的后端,目前共五个 Backend,分别是 Unicorn、Unicorn2、Dynarmic、Hypervisor、KVM。如果不添加 BackendFactory,默认使用 Unicorn Backend,代码逻辑如下
1 2 3 4 5 6 7 8 9 10 11 12 public static Backend createBackend(Emulator<?> emulator, boolean is64Bit, Collection<BackendFactory> backendFactories) { if (backendFactories != null) { for (BackendFactory factory : backendFactories) { Backend backend = factory.newBackend(emulator, is64Bit); if (backend != null) { return backend; } } } // 默认使用 Unicorn后端 return new UnicornBackend(emulator, is64Bit); }
根目录
为设置程序的根目录 以便于面对文件读写时可以锁定到对应的文件
具体使用 创建类 这里我们使用参考中给予的样例进行分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package com.weibo.xvideo; import org.jetbrains.annotations.NotNull; public final class NativeApi { public NativeApi() { System.loadLibrary("oasiscore"); } @NotNull public final native String d(@NotNull String str); @NotNull public final native String dg(@NotNull String str, boolean z); @NotNull public final native String e(@NotNull String str); @NotNull public final native String s(@NotNull byte[] bArr, boolean z); }
这里我们假设想要hook一下s方法 先使用frida写一个来试试
1 2 3 4 5 6 7 8 9 Java.perform(function(){ var NativeApi=Java.use("com.weibo.xvideo.NativeAp") var stringobj =Java.use("ava.lang.String") var natobj=NativeApi.$new() let arg1 = ""; let arg2 = false; let ret = NativeApi.$new().s(stringToBytes(arg1), arg2); console.log("ret:"+ret); })
而在unidbg中获取到class的实例使用的是
1 DvmClass NativeApi = vm.resolveClass("com/weibo/xvideo/NativeApi");
可以使用第二个可变参数进行类的信息描述
1 2 3 DvmClass Context = vm.resolveClass("android/content/Context"); DvmClass ContextWrapper = vm.resolveClass("android/content/ContextWrapper", Context); DvmClass Application = vm.resolveClass("android/app/Application", ContextWrapper);
实例化 1 2 DvmClass NativeApi = vm.resolveClass("com/weibo/xvideo/NativeApi"); DvmObject<?> nativeApi = NativeApi.newObject(null);
实例化更为简单
调用 1 2 3 4 5 6 public String calls(){ String arg1 = ""; Boolean arg2 = false; String ret = NativeApi.newObject(null).callJniMethodObject(emulator, "s([BZ)Ljava/lang/String;", arg1.getBytes(StandardCharsets.UTF_8), arg2).getValue().toString(); return ret; }
其中想要根据调用的方法的返回值进行一个函数的选择
1 2 3 4 5 6 callJniMethodObject //v callJniMethodInt //int callJniMethod //void callJniMethodBoolean //boolean callJniMethodLong //long 中间加个Static就是为静态函数
再来看一下这里的方法的命名是怎么写的
1 s([BZ)Ljava/lang/String;
JAVA 类型
签名
boolean
Z
byte
B
char
C
short
S
int
I
long
J
float
F
double
D
void
V
class
Lclass;
type[]
[B
这里对应的就是参数的内容 这样写难免犯错误 这里推荐使用jadx的smali代码中的签名 省时省力 例如
以免写错心态炸裂