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代码中的签名 省时省力 例如
以免写错心态炸裂