1.了解unicorn与unidbg
2.源码学习unidbg的常用api
3.了解unidbg_hook
4.了解unidbg_patch
1.教程Demo
2.IDEA
3.IDA
开源地址
Unicorn 是一个由新加坡南洋理工大学团队在2015年开源的CPU模拟器框架,它支持多种架构,包括X86/X64/ARM/ARM64/MIPS等。Unicorn 的主要特点是:
开源地址
Unidbg(Unicorn Debugger)是一个开源的轻量级模拟器,主要设计用于模拟执行Android平台上的Native代码。它由凯神在2019年开源,基于Maven构建,使用Java语言编写,可以在IDE中打开和运行。Unidbg能够模拟Android Native函数的执行,让逆向工程师和安全研究人员能够分析和理解二进制文件的运行行为。它支持模拟系统调用和JNI调用,使得可以在模拟环境中执行依赖这些调用的代码。Unidbg基于Unicorn项目,Unidbg的优势在于它提供了一种隐蔽的监控手段,可以模拟复杂的Native环境,帮助用户进行深入的动态分析。由于其开源特性,Unidbg得到了社区的广泛支持和持续更新,成为了Android Native逆向分析领域中一个强有力的工具。
竞争者: AndroidNativeEmu 和继任者 ExAndroidNativeEmu (Unidbg优点:模拟实现了更多的系统调用和 JNI)
1.下载idea
社区版下载链接
2.下载源代码,并用idea打开,并配置好sdk
3.文件结构解析:
23 和 19 分别对应于 sdk23(Android 6.0) 和 sdk19(Android 4.4)的运行库环境,处理 64 位 SO 时只能选择 SDK23。
基本类型直接传递,int、long、boolean、double 等。
对于其他数据类型需要借助resolveClass
构造,例如Context
符号调用
偏移调用
1.获取 HookZz 实例:
2.wrap_hook函数:
wrap
函数有两个重载,一个基于符号寻址,一个基于地址寻址,本质没区别,符号寻址的最终也是会调用symbol.getAddress()
参数里的WrapCallback的泛型接口有三个RegisterContext(函数 Hook)
、HookZzArm32RegisterContext(针对ARM32位)
和HookZzArm64RegisterContext(针对ARM64位)
因为可以访问某个寄存器的值,所以适用于inline hook
而在HookZzArm64RegisterContext
中则是通过以下的方法去获取对应的寄存器的值
3.instrument_inline_hook函数
4.replace替换函数*
Console Debugger(控制台调试器)是 Unidbg 提供的一个强大工具,允许用户在模拟执行过程中设置断点、单步调试、查看和修改内存及寄存器等操作,从而深入分析目标程序的行为。
替换返回值
Patch 就是直接对二进制文件进行修改,Patch本质上只有两种形式
百度云
阿里云
哔哩哔哩
教程开源地址
PS:解压密码都是52pj,阿里云由于不能分享压缩包,所以下载exe文件,双击自解压
白龙unidbg教程
凯神博客
app逆向
【转载】Unidbg Hook 大全
特性 |
描述 |
使用场景 |
|
模拟执行 |
执行目标 SO 文件中用户关注的函数,获取与真机等价的结果;替代 Frida/Xposed Call 进行 RPC 调用。 |
监控观察 |
观察样本对环境的信息获取与修改;监控所有类型的外部信息访问,包括系统调用、库函数、JNI 调用、文件读写等。 |
辅助算法分析和还原 |
提供 Hook/Debug/Trace 等分析能力,结合时间旅行调试器(Time-Travel Debugging),无疑是Android Native 上强大的分析神器。 |
优点 |
|
低成本 |
减少设备成本和改机成本,无需购置和维护大量真机或租借云手机。 |
灵活性 |
可以模拟或代理所有函数调用接口,方便模拟设备环境变化。 |
监控能力 |
能够监控 Native 层的详细执行流,包括 JNI 调用和文件访问。 |
分析能力 |
结合时间旅行调试器,提供强大的算法分析和还原能力。 |
缺点 |
|
学习成本高 |
尤其是环境补全(补环境)部分,如果补得不好,即使跑出结果也无法使用。 |
执行速度慢 |
基于 Unicorn 的模拟执行速度相比真机慢很多,尽管有 Dynarmic 等方案可以提高速度,但牺牲了部分辅助算法还原的能力。 |
功能限制 |
没有为特定场景做专门的优化,也没有提供配置管理功能;没有实现对所有系统调用的良好模拟,可能导致某些逻辑处理失败。 |
扩展性差 |
作为一个 Java 项目,Unidbg 无法作为 IDA 或 Ghidra 插件,也难以轻松嵌入到其他项目中,不如 Python 项目灵活。 |
├── README.md
├── LICENSE
├── .gitignore
├── pom.xml
├── mvnw
├── mvnw.cmd
├── test.sh
├── test.cmd
├── .mvn
/
│ └── wrapper
/
├── assets
/
│ ├──
*
.dll
│ └──
*
.so
├── backend
/
├── unidbg
-
api
/
│ └── src
/
├── unidbg
-
ios
/
│ └── src
/
├── unidbg
-
android
/
│ ├── pom.xml
│ ├── pull.sh
│ └── src
/
│ ├── main
/
│ │ ├── java
/
│ │ │ └── com
/
github
/
unidbg
/
│ │ │ └── net
/
fornwall
/
jelf
│ │ └── resources
/
│ ├── test
/
│ │ ├── java
/
│ │ ├── native
/
android
/
│ │ └── resources
/
└── .mvn
/
├── README.md
├── LICENSE
├── .gitignore
├── pom.xml
├── mvnw
├── mvnw.cmd
├── test.sh
├── test.cmd
├── .mvn
/
│ └── wrapper
/
├── assets
/
│ ├──
*
.dll
│ └──
*
.so
├── backend
/
├── unidbg
-
api
/
│ └── src
/
├── unidbg
-
ios
/
│ └── src
/
├── unidbg
-
android
/
│ ├── pom.xml
│ ├── pull.sh
│ └── src
/
│ ├── main
/
│ │ ├── java
/
│ │ │ └── com
/
github
/
unidbg
/
│ │ │ └── net
/
fornwall
/
jelf
│ │ └── resources
/
│ ├── test
/
│ │ ├── java
/
│ │ ├── native
/
android
/
│ │ └── resources
/
└── .mvn
/
log4j.logger.com.github.unidbg.linux.file=DEBUG
log4j.logger.com.github.unidbg.linux.file=DEBUG
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;
private
final
VM vm;
public
MainActivity() {
emulator = AndroidEmulatorBuilder.for32Bit()
.addBackendFactory(
new
DynarmicFactory(
true
))
.build();
Memory memory = emulator.getMemory();
LibraryResolver resolver =
new
AndroidResolver(
23
);
memory.setLibraryResolver(resolver);
vm = emulator.createDalvikVM();
vm.setVerbose(
false
);
DalvikModule dm = vm.loadLibrary(
new
File(
"unidbg-android/src/test/resources/example_binaries/armeabi-v7a/libnative-lib.so"
),
false
);
dm.callJNI_OnLoad(emulator);
}
public
void
crack() {
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;
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 main =
new
MainActivity();
System.out.println(
"load offset="
+ (System.currentTimeMillis() - start) +
"ms"
);
main.crack();
}
}
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;
private
final
VM vm;
public
MainActivity() {
emulator = AndroidEmulatorBuilder.for32Bit()
.addBackendFactory(
new
DynarmicFactory(
true
))
.build();
Memory memory = emulator.getMemory();
LibraryResolver resolver =
new
AndroidResolver(
23
);
memory.setLibraryResolver(resolver);
vm = emulator.createDalvikVM();
vm.setVerbose(
false
);
DalvikModule dm = vm.loadLibrary(
new
File(
"unidbg-android/src/test/resources/example_binaries/armeabi-v7a/libnative-lib.so"
),
false
);
dm.callJNI_OnLoad(emulator);
}
public
void
crack() {
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;
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 main =
new
MainActivity();
System.out.println(
"load offset="
+ (System.currentTimeMillis() - start) +
"ms"
);
main.crack();
}
}
emulator = AndroidEmulatorBuilder.for32Bit()
.addBackendFactory(
new
DynarmicFactory(
true
))
.build();
emulator = AndroidEmulatorBuilder.for32Bit()
.addBackendFactory(
new
DynarmicFactory(
true
))
.build();
.setRootDir()
方法名 |
返回类型 |
描述 |
getMemory() |
Memory |
获取内存操作接口。 |
getPid() |
int |
获取进程的 PID。 |
createDalvikVM() |
VM |
创建虚拟机。 |
createDalvikVM(File apkFile) |
VM |
创建虚拟机并指定 APK 文件路径。 |
getDalvikVM() |
VM |
获取已创建的虚拟机。 |
showRegs() |
void |
显示当前寄存器状态,可指定寄存器。 |
getBackend() |
Backend |
获取后端 CPU。 |
getProcessName() |
String |
获取进程名。 |
getContext() |
RegisterContext |
获取寄存器上下文。 |
traceRead(long begin, long end) |
void |
Trace 读内存操作。 |
traceWrite(long begin, long end) |
void |
Trace 写内存操作。 |
traceCode(long begin, long end) |
void |
Trace 汇编指令执行。 |
isRunning() |
boolean |
判断当前 Emulator 是否正在运行。 |
LibraryResolver resolver =
new
AndroidResolver(
23
);
memory.setLibraryResolver(resolver);
LibraryResolver resolver =
new
AndroidResolver(
23
);
memory.setLibraryResolver(resolver);
方法名 |
返回类型 |
描述 |
setLibraryResolver(AndroidResolver resolver) |
void |
设置 Android SDK 版本解析器,目前支持 19 和 23 两个版本。 |
getStackPoint() |
long |
获取当前栈指针的值。 |
pointer(long address) |
UnidbgPointer |
获取指针,指向指定内存地址,可通过指针操作内存。 |
getMemoryMap() |
Collection<MemoryMap> |
获取当前内存的映射情况。 |
findModule(String moduleName) |
Module |
根据模块名获取指定模块。 |
findModuleByAddress(long address) |
Module |
根据地址获取指定模块。 |
loadLibrary(File file, boolean forceLoad) |
ElfModule |
加载 SO 文件,会调用 Linker.do_dlopen() 方法完成加载。 |
allocatestack(int size) |
UnidbgPointer |
在栈上分配指定大小的内存空间。 |
writestackstring(String value) |
UnidbgPointer |
将字符串写入栈内存中。 |
writestackBytes(byte[] value) |
UnidbgPointer |
将字节数组写入栈内存中。 |
malloc(int size, boolean runtime) |
UnidbgPointer |
分配指定大小的内存空间,返回指向该内存的指针。 |
vm常用Api |
|
|
vm = emulator.createDalvikVM();
vm.setVerbose(
false
);
DalvikModule dm = vm.loadLibrary(
new
File(
"unidbg-android/src/test/resources/example_binaries/armeabi-v7a/libnative-lib.so"
),
false
);
dm.callJNI_OnLoad(emulator);
vm = emulator.createDalvikVM();
vm.setVerbose(
false
);
DalvikModule dm = vm.loadLibrary(
new
File(
"unidbg-android/src/test/resources/example_binaries/armeabi-v7a/libnative-lib.so"
),
false
);
dm.callJNI_OnLoad(emulator);
方法名 |
返回类型 |
描述 |
createDalvikVM(File apkFile) |
VM |
创建虚拟机,指定 APK 文件,file可为空 |
setVerbose(boolean verbose) |
void |
设置是否输出 JNI 运行日志。 |
loadLibrary(File soFile, boolean callInit) |
DalvikModule |
加载 SO 模块,参数二设置是否自动调用 init 函数。 |
setJni(Jni jni) |
void |
设置 JNI 交互接口,推荐实现 AbstractJni 。 |
getJNIEnv() |
Pointer |
获取 JNIEnv 指针,可作为参数传递。 |
getJavaVM() |
Pointer |
获取 JavaVM 指针,可作为参数传递。 |
callJNI_OnLoad(Emulator<?> emulator, Module module) |
void |
调用 JNI_OnLoad 函数。 |
addGlobalObject(DvmObject<?> obj) |
int |
向 VM 添加全局对象,返回该对象的 hash 值。 |
addLocalObject(DvmObject<?> obj) |
int |
向 VM 添加局部对象,返回该对象的 hash 值。 |
getObject(int hash) |
DvmObject<?> |
根据 hash 值获取虚拟机中的对象。 |
resolveClass(String className) |
DvmClass |
解析指定类名,构建并返回一个 DvmClass 对象。 |
getPackageName() |
String |
获取 APK 包名。 |
getVersionName() |
String |
获取 APK 版本名称。 |
getVersionCode() |
String |
获取 APK 版本号。 |
openAsset(String assetName) |
InputStream |
打开 APK 中的指定资源文件。 |
getManifestXml() |
String |
获取 AndroidManifest.xml 文件的文本内容。 |
getSignatures() |
CertificateMeta[] |
获取 APK 签名信息。 |
findClass(String className) |
DvmClass |
通过类名获取已经加载的类(DvmClass 对象)。 |
getEmulator() |
Emulator<?> |
获取模拟器对象 emulator 。 |
/**
* 加载指定名称的库文件。
* @param libname 库文件的名称,不包括前缀 "lib" 和后缀 ".so"(例如 "example" 对应 "libexample.so")。
* @param forceCallInit 是否强制调用库的初始化函数(如 JNI_OnLoad)。
* @return 加载后的 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);
/**
* 加载指定名称的库文件。
* @param libname 库文件的名称,不包括前缀 "lib" 和后缀 ".so"(例如 "example" 对应 "libexample.so")。
* @param forceCallInit 是否强制调用库的初始化函数(如 JNI_OnLoad)。
* @return 加载后的 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);
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) {
UnidbgPointer fnPtr = objectType.findNativeFunction(emulator, method);
vm.addLocalObject(thisObj);
List<Object> list =
new
ArrayList<>(
10
);
list.add(vm.getJNIEnv());
list.add(thisObj.hashCode());
if
(args !=
null
) {
for
(Object arg : args) {
if
(arg
instanceof
Boolean) {
list.add((Boolean) arg ? VM.JNI_TRUE : VM.JNI_FALSE);
continue
;
}
else
if
(arg
instanceof
Hashable) {
list.add(arg.hashCode());
if
(arg
instanceof
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) {
DvmObject<?> obj = ProxyDvmObject.createObject(vm, arg);
list.add(obj.hashCode());
vm.addLocalObject(obj);
continue
;
}
list.add(arg);
}
}
return
Module.emulateFunction(emulator, fnPtr.peer, list.toArray());
}
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) {
UnidbgPointer fnPtr = objectType.findNativeFunction(emulator, method);
vm.addLocalObject(thisObj);
List<Object> list =
new
ArrayList<>(
10
);
list.add(vm.getJNIEnv());
list.add(thisObj.hashCode());
if
(args !=
null
) {
for
(Object arg : args) {
if
(arg
instanceof
Boolean) {
list.add((Boolean) arg ? VM.JNI_TRUE : VM.JNI_FALSE);
[注意]APP应用上架合规检测服务,协助应用顺利上架!