unidbg(1)

参考

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打开

image-20250506161523292

项目内容解析

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位,在这里区分

位数和架构

1
2
for64Bit() 
for32Bit()

分别对应了64和32的arm架构

进程名

1
.setProcessName("com.twogoat.114514")

这里最好还是修改为原apk对应的包名

后端

1
addBackendFactory

先回顾一下处理器、操作系统、应用程序的基本关系,大体上是下面这样。

image.png

Unidbg中为

image.png

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
setRootDir()

为设置程序的根目录 以便于面对文件读写时可以锁定到对应的文件

具体使用

创建类

这里我们使用参考中给予的样例进行分析

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);
}

image-20250508211054584

这里我们假设想要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
resolveClass

可以使用第二个可变参数进行类的信息描述

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代码中的签名 省时省力 例如

image-20250508214158766

以免写错心态炸裂