|
|
本帖最后由 正己 于 2025-1-3 16:05 编辑
一、课程目标
1.了解unicorn与unidbg
2.源码学习unidbg的常用api
3.了解unidbg_hook
4.了解unidbg_patch
二、工具
1.教程Demo
2.IDEA
3.IDA
三、课程内容
一.unicorn&unidbg
1.什么是unicorn

开源地址
Unicorn 是一个由新加坡南洋理工大学团队在2015年开源的CPU模拟器框架,它支持多种架构,包括X86/X64/ARM/ARM64/MIPS等。Unicorn 的主要特点是:
1. **多架构支持**:它能够模拟不同架构的CPU指令,这使得它在跨平台分析和测试中非常有用。
2. **高性能**:Unicorn 通过使用JIT(Just-In-Time)编译技术,将模拟的指令翻译成宿主机的本地指令,从而提高了执行效率。
3. **丰富的接口**:Unicorn 提供了多种语言的绑定,包括Python、Java、C#等,使得在不同编程环境中都能方便地使用。
4. **Hook和拦截功能**:Unicorn 允许用户在模拟执行过程中设置Hook,拦截和处理特定的指令或内存访问,这对于逆向工程和动态分析非常有用。
5. **专注于CPU模拟**:与一些既模拟CPU又模拟操作系统的模拟器不同,Unicorn 专注于CPU指令的模拟,提供了简单的Hook接口、内存操作接口和指令执行接口,而不涉及操作系统层面的模拟。
2.什么是unidbg?
开源地址
Unidbg(Unicorn Debugger)是一个开源的轻量级模拟器,主要设计用于模拟执行Android平台上的Native代码。它由凯神在2019年开源,基于Maven构建,使用Java语言编写,可以在IDE中打开和运行。Unidbg能够模拟Android Native函数的执行,让逆向工程师和安全研究人员能够分析和理解二进制文件的运行行为。它支持模拟系统调用和JNI调用,使得可以在模拟环境中执行依赖这些调用的代码。Unidbg基于Unicorn项目,Unidbg的优势在于它提供了一种隐蔽的监控手段,可以模拟复杂的Native环境,帮助用户进行深入的动态分析。由于其开源特性,Unidbg得到了社区的广泛支持和持续更新,成为了Android Native逆向分析领域中一个强有力的工具。
竞争者: AndroidNativeEmu 和继任者 ExAndroidNativeEmu (Unidbg优点:模拟实现了更多的系统调用和 JNI)
3.unidbg的使用场景与优缺点

二.初试unidbg
1.配置资源文件
1.下载idea
社区版下载链接
2.下载源代码,并用idea打开,并配置好sdk
3.文件结构解析:
- ├── README.md [b][size=5]项目介绍和使用指南[/size][/b]
- ├── LICENSE [b][size=5]开源许可证文件[/size][/b]
- ├── .gitignore [b][size=5]Git 忽略文件配置[/size][/b]
- ├── pom.xml [b][size=5]Maven 配置文件,定义了项目的依赖和构建配置[/size][/b]
- ├── mvnw [b][size=5]脚本文件,用于 Maven Wrapper (Linux/Mac)[/size][/b]
- ├── mvnw.cmd [b][size=5]脚本文件,用于 Maven Wrapper (Windows)[/size][/b]
- ├── test.sh [b][size=5]测试脚本 (Linux/Mac)[/size][/b]
- ├── test.cmd [b][size=5]测试脚本 (Windows)[/size][/b]
- ├── .mvn/ [b][size=5]Maven 配置目录[/size][/b]
- │ └── wrapper/ [b][size=5]Maven Wrapper 相关配置[/size][/b]
- ├── assets/ [b][size=5]存放模拟过程中使用的资源文件[/size][/b]
- │ ├── *.dll [b][size=5]Windows 动态链接库文件[/size][/b]
- │ └── *.so [b][size=5]Linux/Android 动态链接库文件[/size][/b]
- ├── backend/ [b][size=5]后端逻辑实现,包含核心模拟功能[/size][/b]
- ├── unidbg-api/ [b][size=5]核心接口和抽象类模块[/size][/b]
- │ └── src/ [b][size=5]API 模块的源代码目录[/size][/b]
- ├── unidbg-ios/ [b][size=5]iOS 应用模拟模块[/size][/b]
- │ └── src/ [b][size=5]iOS 模拟模块的源代码目录[/size][/b]
- ├── unidbg-android/ [b][size=5]Android 应用模拟模块[/size][/b]
- │ ├── pom.xml [b][size=5]Maven 构建文件[/size][/b]
- │ ├── pull.sh [b][size=5]拉取 Android 模拟所需依赖文件的脚本[/size][/b]
- │ └── src/ [b][size=5]unidbg-android 模块的源代码目录[/size][/b]
- │ ├── main/
- │ │ ├── java/ [b][size=5]核心 Java 源代码[/size][/b]
- │ │ │ └── com/github/unidbg/ [b][size=5]包含核心模拟器、文件系统、虚拟机组件[/size][/b]
- │ │ │ └── net/fornwall/jelf [b][size=5]ELF 文件格式解析实现[/size][/b]
- │ │ └── resources/ [b][size=5]资源文件,封装了 JNI 库、Android 系统库等[/size][/b]
- │ ├── test/
- │ │ ├── java/ [b][size=5]单元测试代码[/size][/b]
- │ │ ├── native/android/ [b][size=5]测试 Android 原生库的 C/C++ 源代码[/size][/b]
- │ │ └── resources/ [b][size=5]测试资源文件,包含预编译的二进制文件(log4j.properties这个是日志相关配置,可以对open,syscall这类的系统调用进行trace)[/size][/b]
- └── .mvn/ [b][size=5]Maven Wrapper 相关配置目录[/size][/b]
复制代码
- java
- log4j.logger.com.github.unidbg.linux.file=DEBUG //把INFO改成DEBUG
复制代码
2.项目式学习
- java
- package com.kanxue.test2;
- import com.github.unidbg.AndroidEmulator;
- 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.ProxyDvmObject;
- import com.github.unidbg.linux.android.dvm.VM;
- import com.github.unidbg.memory.Memory;
- import java.io.File;
- public class MainActivity {
- private final AndroidEmulator emulator; // 定义Android模拟器实例
- private final VM vm; // 定义Dalvik虚拟机实例
- public MainActivity() {
- // 创建32位Android模拟器实例,使用Dynarmic后端
- emulator = AndroidEmulatorBuilder.for32Bit()
- .addBackendFactory(new DynarmicFactory(true))
- .build();
- // 获取模拟器的内存管理接口
- Memory memory = emulator.getMemory();
- // 设置系统类库
- LibraryResolver resolver = new AndroidResolver(23);
- memory.setLibraryResolver(resolver);
- // 创建Dalvik虚拟机实例
- vm = emulator.createDalvikVM();
- // 设置是否输出详细的JNI调用日志
- vm.setVerbose(false);
- // 加载指定路径的SO库文件,不自动调用JNI_OnLoad函数
- DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/resources/example_binaries/armeabi-v7a/libnative-lib.so"), false);
- // 手动调用JNI_OnLoad方法
- dm.callJNI_OnLoad(emulator);
- }
- public void crack() {
- // 创建一个vm对象,模拟Java层的对象传递给JNI层
- DvmObject<?> obj = ProxyDvmObject.createObject(vm, this);
- // 记录开始时间
- long start = System.currentTimeMillis();
- // 遍历所有可能的三字符组合,尝试破解
- for (char a : LETTERS) {
- for (char b : LETTERS) {
- for (char c : LETTERS) {
- String str = "" + a + b + c;
- // 调用JNI方法,传入当前组合,判断是否成功
- boolean success = obj.callJniMethodBoolean(emulator, "jnitest(Ljava/lang/String;)Z", str);
- if (success) {
- // 如果成功,输出结果并结束
- System.out.println("Found: " + str + ", off=" + (System.currentTimeMillis() - start) + "ms");
- return;
- }
- }
- }
- }
- }
- private static final char[] LETTERS = {
- 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
- 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
- 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
- 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'
- }; // 定义字母表
- public static void main(String[] args) {
- //记录开始时间
- long start = System.currentTimeMillis();
- // 创建MainActivity实例
- MainActivity main = new MainActivity();
- //输出调用结果
- System.out.println("load offset=" + (System.currentTimeMillis() - start) + "ms");
- // 调用破解方法
- main.crack();
- }
- }
复制代码
- java
- emulator = AndroidEmulatorBuilder.for32Bit()
- .addBackendFactory(new DynarmicFactory(true))
- .build();
复制代码
- 1.for32Bit()的意思是创建32位Android模拟器实例,for64Bit()则是创建64位,apk lib 里只有armeabi-v7a,那就只能选择 32 位,apk lib 里只有arm64-v8a,就选择 64 位。区别:64位的执行速度较快,浮动10%左右;Unidbg 对 ARM32 的支持和完善程度高于 ARM64
- 2.Unidbg 支持了数个后端,目前共五个 Backend,分别是 Unicorn、Unicorn2、Dynarmic(执行速度较快)、Hypervisor、KVM。new DynarmicFactory(true)中的true,标志着在出现异常时是否使用默认后端unicorn。
- java
- .setRootDir() //设置虚拟机的根目录,可以实现io重定向,例如:app读取/data/data/123.txt,rootDir设置为E:/unidbg,那么真正的目录是E:/unidbg/data/data/123.txt,
复制代码
#1.emulator常用Api

- java
- LibraryResolver resolver = new AndroidResolver(23);
- memory.setLibraryResolver(resolver);
复制代码
23 和 19 分别对应于 sdk23(Android 6.0) 和 sdk19(Android 4.4)的运行库环境,处理 64 位 SO 时只能选择 SDK23。

#2.memory常用Api

`vm常用Api`
- java
- // 创建Dalvik虚拟机实例
- vm = emulator.createDalvikVM();
- // 设置是否输出详细的JNI调用日志
- vm.setVerbose(false);
- // 加载指定路径的SO库文件,不自动调用JNI_OnLoad函数
- DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/resources/example_binaries/armeabi-v7a/libnative-lib.so"), false);
- // 手动调用JNI_OnLoad方法
- dm.callJNI_OnLoad(emulator);
复制代码

- 1.`VM dalvikVM = emulator.createDalvikVM(new File("apk file path"))`-创建虚拟机并指定APK文件,加载指定APK文件,unidbg可以帮我们完成一些小操作,例如:解析 Apk 基本信息,Apk 的版本名、版本号、包名、 Apk 签名等信息,减少补环境操作;解析和管理 Apk 资源文件,加载 Apk 后可以通过 `openAsset`获取 APK `assets`目录下的文件。
- 2.`loadLibrary`三个重载方法
- java
- /**
- * 加载指定名称的库文件。
- * @param libname 库文件的名称,不包括前缀 "lib" 和后缀 ".so"(例如 "example" 对应 "libexample.so")。
- * @param forceCallInit 是否强制调用库的初始化函数(如 JNI_OnLoad)。
- * [url=home.php?mod=space&uid=134981]@Return[/url] 加载后的 DalvikModule 对象,封装了加载的库模块。
- */
- DalvikModule loadLibrary(String libname, boolean forceCallInit);
- /**
- * 从原始字节数组中加载指定的库文件。
- * @param libname 库文件的名称,仅用于标识该库,与文件路径无关。
- * @param 传入buffer方便解析elf
- */
- DalvikModule loadLibrary(String libname, byte[] raw, boolean forceCallInit);
- /**
- * 从指定路径的加载ELF。
- * @param elfFile 表示库的 ELF 文件,必须是有效的 ELF 格式文件。例如:new File("unidbg-android/src/test/resources/example_binaries/armeabi-v7a/libnative-lib.so")
- * @param forceCallInit 是否强制调用库的初始化函数(如 JNI_OnLoad)。
- */
- DalvikModule loadLibrary(File elfFile, boolean forceCallInit);
复制代码
#3.jni函数的调用

- java
- //第一个参数传入模拟器实例
- //第二个参数传入要调用的函数在java层方法签名信息
- //第三个参数传入可变参数列表,这里的参数在旧版本有很多需要自己封装,新版则帮我们封装好了,详细看callJniMethod方法
- boolean success = obj.callJniMethodBoolean(emulator, "jnitest(Ljava/lang/String;)Z", str);
- /**
- * 调用 JNI 方法的辅助函数。
- *
- * @param emulator 模拟器实例。
- * @param vm Dalvik 虚拟机实例。
- * @param objectType 调用方法的类(DvmClass)。
- * @param thisObj 方法调用的对象实例(DvmObject)。
- * @param method 要调用的方法签名(例如 "methodName(参数类型)返回类型")。
- * @param args 方法的可变参数列表。
- * @return 方法执行后的返回值(Number 类型),可能是整数或浮点数。
- */
- protected static Number callJniMethod(Emulator<?> emulator, VM vm, DvmClass objectType, DvmObject<?> thisObj, String method, Object... args) {
- // 查找对应的本地函数指针(Native 方法)
- UnidbgPointer fnPtr = objectType.findNativeFunction(emulator, method);
- // 将当前对象添加到本地引用表,防止被垃圾回收
- vm.addLocalObject(thisObj);
- // 创建用于存放函数参数的列表,初始容量为10
- List<Object> list = new ArrayList<>(10);
- // 添加 JNI 环境指针(JNIEnv*)
- list.add(vm.getJNIEnv());
- // 添加 this 对象的引用(jobject)
- list.add(thisObj.hashCode());
- // 处理传入的参数列表
- if (args != null) {
- for (Object arg : args) {
- if (arg instanceof Boolean) {
- // 如果参数是布尔值,转换为 JNI_TRUE 或 JNI_FALSE
- list.add((Boolean) arg ? VM.JNI_TRUE : VM.JNI_FALSE);
- continue;
- } else if (arg instanceof Hashable) {
- // 如果参数实现了 Hashable 接口,表示是 DvmObject 或其子类
- list.add(arg.hashCode()); // 添加对象引用(jobject)
- if (arg instanceof DvmObject) {
- // 将 DvmObject 对象添加到本地引用表
- vm.addLocalObject((DvmObject<?>) arg);
- }
- continue;
- } else if (arg instanceof DvmAwareObject ||
- arg instanceof String ||
- arg instanceof byte[] ||
- arg instanceof short[] ||
- arg instanceof int[] ||
- arg instanceof float[] ||
- arg instanceof double[] ||
- arg instanceof Enum) {
- // 如果参数是 DvmAwareObject、字符串、数组或枚举等类型
- // 创建一个代理 DvmObject 对象
- DvmObject<?> obj = ProxyDvmObject.createObject(vm, arg);
- // 添加对象引用(jobject)
- list.add(obj.hashCode());
- // 将对象添加到本地引用表
- vm.addLocalObject(obj);
- continue;
- }
- // 对于其他类型的参数,直接添加到参数列表
- list.add(arg);
- }
- }
- // 调用本地函数,传入参数列表,并返回结果
- return Module.emulateFunction(emulator, fnPtr.peer, list.toArray());
- }
复制代码
基本类型直接传递,int、long、boolean、double 等。
- 下面几种对象类型unidbg也帮我们封装好了
- String
- byte 数组
- short 数组
- int 数组
- float 数组
- double 数组
- Enum 枚举类型
#4.特殊参数构造
对于其他数据类型需要借助`resolveClass`构造,例如`Context`
- java
- DvmObject<?> context = vm.resolveClass("android/content/Context").newObject(null);
复制代码
#5.符号调用与偏移调用
`符号调用`
- java
- Symbol symbol = module.findSymbolByName("导出符号");
- if (symbol != null){
- //第一个模拟器实例,第二个jnienv,第三个jclass,第四个可变参数
- Number numbers = symbol.call(emulator, vm.getJNIEnv(), vm.addLocalObject(实例类), 可变参数);
- int result = numbers.intValue();
- System.out.println(result);
- //如果返回值是string,可以通过vm.getObject(retval)获取
- System.out.println(vm.getObject(result).getValue());
- }else {
- System.out.println("符号未找到");
- }
复制代码
`偏移调用`
- java
- //第一个模拟器实例,第二个偏移地址(thumb记得+1),第三个jnienv,第四个jclass,第五个可变参数
- Number number = module.callFunction(emulator, 0x11240, vm.getJNIEnv(),vm.addLocalObject(SecurityUtils),vm.addLocalObject(new StringObject(vm, "超级")));
- DvmObject<?> object = vm.getObject(number.intValue());
- System.out.println("result:" + object.getValue());
复制代码
|
-
查看全部评分
总评分:好评 +2
金币 +2
|