unidbg 是凯神 在 2019 年初开源的一个轻量级模拟器,一个基于Java的跨平台解密引擎,专门用于动态分析和逆向工程应用程序。它可以模拟不同CPU架构、操作系统和指令集,从而使用户能够在一个统一的环境中分析各种不同类型的二进制文件。unidbg的主要功能包括模拟执行、内存访问、指令跟踪、函数调用跟踪等。
unidbg 是建立在Unicorn引擎之上的,Unicorn引擎是一个强大的开源CPU模拟器框架,支持多种架构,包括x86、ARM、MIPS等,因此unidbg也能够模拟这些不同的CPU架构。UniDGB的另一个核心组成部分是Capstone引擎,它用于反汇编和指令解码。
unidbg 下载地址: https://github.com/zhkl0228/unidbg
HookZz(Dobby)是一个轻量级、多平台、多架构的漏洞利用钩子框架。Unidbg中是HookZz和Dobby是两个独立的Hook库,因为作者认为HookZz在arm32上支持较好,Dobby在arm64上支持较好。HookZz是inline hook方案,因此可以Hook Sub_xxx,缺点是短函数可能出bug,受限于inline Hook 原理。文档看https://github.com/jmpews/HookZz
hookZz (1)
hookZz(2)
Dobby
xHook 是爱奇艺开源的ndroid PLT hook框架,一个针对 Android 平台 ELF (可执行文件和动态库) 的 PLT (Procedure Linkage Table) hook 库。优点是挺稳定好用,缺点是不能Hook Sub_xxx 子函数。文档看https://github.com/iqiyi/xHook
执行效果
如果代码被混淆,其中90%都是无用的代码,直接看汇编代码,分析会很困难。trace可以将程序运行后,实际用到的汇编代码输出到指定文件中,再去分析。
执行前
执行后效果
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.AbstractJni;
import
com.github.unidbg.linux.android.dvm.DalvikModule;
import
com.github.unidbg.linux.android.dvm.VM;
import
com.github.unidbg.memory.Memory;
import
java.io.
File
;
public
class
Test05 extends AbstractJni {
private final AndroidEmulator emulator;
private final VM vm;
private final Module module;
Test05(){
/
/
创建模拟器
emulator
=
AndroidEmulatorBuilder
.for32Bit().addBackendFactory(new DynarmicFactory(true))
.setProcessName(
"cc.ccc.cc"
)
.build();
/
/
内存调用
Memory memory
=
emulator.getMemory();
/
/
设定 SDK 版本
memory.setLibraryResolver(new AndroidResolver(
23
));
/
/
创建虚拟机
vm
=
emulator.createDalvikVM(new
File
(
"sssss.apk"
));
/
/
jni 日志打印
vm.setVerbose(true);
/
/
jni 设置
vm.setJni(this);
/
/
执行so文件
DalvikModule dm
=
vm.loadLibrary(new
File
(
"ssss.so"
), true);
/
/
获取so 文件模块
module
=
dm.getModule();
/
/
调用JNI——onload 函数
vm.callJNI_OnLoad(emulator,module);
/
/
dm.callJNI_OnLoad(module);
}
}
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.AbstractJni;
import
com.github.unidbg.linux.android.dvm.DalvikModule;
import
com.github.unidbg.linux.android.dvm.VM;
import
com.github.unidbg.memory.Memory;
import
java.io.
File
;
public
class
Test05 extends AbstractJni {
private final AndroidEmulator emulator;
private final VM vm;
private final Module module;
Test05(){
/
/
创建模拟器
emulator
=
AndroidEmulatorBuilder
.for32Bit().addBackendFactory(new DynarmicFactory(true))
.setProcessName(
"cc.ccc.cc"
)
.build();
/
/
内存调用
Memory memory
=
emulator.getMemory();
/
/
设定 SDK 版本
memory.setLibraryResolver(new AndroidResolver(
23
));
/
/
创建虚拟机
vm
=
emulator.createDalvikVM(new
File
(
"sssss.apk"
));
/
/
jni 日志打印
vm.setVerbose(true);
/
/
jni 设置
vm.setJni(this);
/
/
执行so文件
DalvikModule dm
=
vm.loadLibrary(new
File
(
"ssss.so"
), true);
/
/
获取so 文件模块
module
=
dm.getModule();
/
/
调用JNI——onload 函数
vm.callJNI_OnLoad(emulator,module);
/
/
dm.callJNI_OnLoad(module);
}
}
/
/
获取内存操作接口
Memory memory1
=
emulator.getMemory();
/
/
获取进程
id
int
pid
=
emulator.getPid();
/
/
创建虚拟机
VM dalvikVM
=
emulator.createDalvikVM();
/
/
创建虚拟机并指定文件
VM dalvikVM1
=
emulator.createDalvikVM(new
File
(
"ss/ss/apk"
));
/
/
获取已经创建的虚拟机
VM dalvikVM2
=
emulator.getDalvikVM();
/
/
显示当前寄存器的状态 可指定寄存器
emulator.showRegs();
/
/
获取后端CPU
Backend backend
=
emulator.getBackend();
/
/
获取进程名
String processName
=
emulator.getProcessName();
/
/
获取寄存器
RegisterContext context
=
emulator.getContext();
/
/
Trace 读取内存
emulator.traceRead(
1
,
0
);
/
/
trace 写内存
emulator.traceWrite(
1
,
0
);
/
/
trace 汇编
emulator.traceCode(
1
,
0
);
/
/
是否在运行
boolean running
=
emulator.isRunning();
/
/
获取内存操作接口
Memory memory1
=
emulator.getMemory();
/
/
获取进程
id
int
pid
=
emulator.getPid();
/
/
创建虚拟机
VM dalvikVM
=
emulator.createDalvikVM();
/
/
创建虚拟机并指定文件
VM dalvikVM1
=
emulator.createDalvikVM(new
File
(
"ss/ss/apk"
));
/
/
获取已经创建的虚拟机
VM dalvikVM2
=
emulator.getDalvikVM();
/
/
显示当前寄存器的状态 可指定寄存器
emulator.showRegs();
/
/
获取后端CPU
Backend backend
=
emulator.getBackend();
/
/
获取进程名
String processName
=
emulator.getProcessName();
/
/
获取寄存器
RegisterContext context
=
emulator.getContext();
/
/
Trace 读取内存
emulator.traceRead(
1
,
0
);
/
/
trace 写内存
emulator.traceWrite(
1
,
0
);
/
/
trace 汇编
emulator.traceCode(
1
,
0
);
/
/
是否在运行
boolean running
=
emulator.isRunning();
/
/
指定安卓sdk 版本 只支持
19
和
23
memory.setLibraryResolver(new AndroidResolver(
23
));
/
/
拿到一个指针 指向内存地址 通过该指针可操作内存
UnidbgPointer pointer
=
memory.pointer(
0x11111111
);
/
/
获取当前内存映射的情况
Collection<MemoryMap> memoryMap
=
memory1.getMemoryMap();
/
/
根据模块名 来拿某个模块
Module sss
=
memory1.findModule(
"sss"
);
/
/
根据地址 来拿某个模块
Module moduleByAddress
=
memory1.findModuleByAddress(
0x111111
);
/
/
指定安卓sdk 版本 只支持
19
和
23
memory.setLibraryResolver(new AndroidResolver(
23
));
/
/
拿到一个指针 指向内存地址 通过该指针可操作内存
UnidbgPointer pointer
=
memory.pointer(
0x11111111
);
/
/
获取当前内存映射的情况
Collection<MemoryMap> memoryMap
=
memory1.getMemoryMap();
/
/
根据模块名 来拿某个模块
Module sss
=
memory1.findModule(
"sss"
);
/
/
根据地址 来拿某个模块
Module moduleByAddress
=
memory1.findModuleByAddress(
0x111111
);
/
/
推荐指定apk 文件 unidbg会自动做许多固定的操作
VM vvm
=
emulator.createDalvikVM(new
File
(
"ssss.apk"
));
/
/
是否输出jni 运行日志
vvm.setVerbose(true);
/
/
加载so模块 参数二设置是否自动调用init函数
DalvikModule dalvikModule
=
vvm.loadLibrary(new
File
(
"ss.so"
), true);
/
/
设置jni 交互接口 参数需要实现jni接口 推荐使用this 继承AbstractJni
vvm.setJni(this);
/
/
获取JNIEnv 指针 可以作为参数传递
Pointer jniEnv
=
vm.getJNIEnv();
/
/
获取JavaVM 指针
Pointer javaVM
=
vm.getJavaVM();
/
/
调用jni_onload函数
dalvikModule.callJNI_OnLoad(emulator);
vm.callJNI_OnLoad(emulator,dalvikModule.getModule());
/
/
推荐指定apk 文件 unidbg会自动做许多固定的操作
VM vvm
=
emulator.createDalvikVM(new
File
(
"ssss.apk"
));
/
/
是否输出jni 运行日志
vvm.setVerbose(true);
/
/
加载so模块 参数二设置是否自动调用init函数
DalvikModule dalvikModule
=
vvm.loadLibrary(new
File
(
"ss.so"
), true);
/
/
设置jni 交互接口 参数需要实现jni接口 推荐使用this 继承AbstractJni
vvm.setJni(this);
/
/
获取JNIEnv 指针 可以作为参数传递
Pointer jniEnv
=
vm.getJNIEnv();
/
/
获取JavaVM 指针
Pointer javaVM
=
vm.getJavaVM();
/
/
调用jni_onload函数
dalvikModule.callJNI_OnLoad(emulator);
vm.callJNI_OnLoad(emulator,dalvikModule.getModule());
/
/
创建一个vm 对象,相当于 java 层去调用native函数类的实例对象
/
/
DvmObject obj
=
ProxyDvmObject.createObject(vm,this);
/
/
默认获取MainActivity 当有很多类的时候,防止默认指定错误,可以以下指定
DvmObject<?> obj
=
vm.resolveClass(
"com/example/demo01/MainActivity"
).newObject(null);
String signSting
=
"123456"
;
DvmObject dvmObject
=
obj.callJniMethodObject(emulator,
"jniMd52([B)Ljava/lang/String;"
, signSting.getBytes(StandardCharsets.UTF_8));
String result
=
(String) dvmObject.getValue();
System.out.println(
"[symble] Call the so md5 function result is ==> "
+
result);
/
/
创建一个vm 对象,相当于 java 层去调用native函数类的实例对象
/
/
DvmObject obj
=
ProxyDvmObject.createObject(vm,this);
/
/
默认获取MainActivity 当有很多类的时候,防止默认指定错误,可以以下指定
DvmObject<?> obj
=
vm.resolveClass(
"com/example/demo01/MainActivity"
).newObject(null);
String signSting
=
"123456"
;
DvmObject dvmObject
=
obj.callJniMethodObject(emulator,
"jniMd52([B)Ljava/lang/String;"
, signSting.getBytes(StandardCharsets.UTF_8));
String result
=
(String) dvmObject.getValue();
System.out.println(
"[symble] Call the so md5 function result is ==> "
+
result);
ArrayList<
Object
> args
=
new ArrayList<>();
Pointer jniEnv
=
vm.getJNIEnv();
DvmObject object1
=
ProxyDvmObject.createObject(vm, this);
/
/
DvmObject<?> dvmObject
=
vm.resolveClass(
"com/xx/xx/MainActivity"
).newObject(null);
args.add(jniEnv);
/
/
args.add(vm.addLocalObject(object1));
/
/
args.add(null)
args.add(null);
args.add(vm.addLocalObject(new StringObject(vm,
"123456"
)));
Number number
=
module.callFunction(emulator,
0x11AE8
+
1
, args.toArray());
/
/
是个地址
System.out.println(
"[addr] number is ==> "
+
number.intValue());
DvmObject<?>
object
=
vm.getObject(number.intValue());
System.out.println(
"[addr] Call the so md5 function result is ==> "
+
object
.getValue());
ArrayList<
Object
> args
=
new ArrayList<>();
Pointer jniEnv
=
vm.getJNIEnv();
DvmObject object1
=
ProxyDvmObject.createObject(vm, this);
/
/
DvmObject<?> dvmObject
=
vm.resolveClass(
"com/xx/xx/MainActivity"
).newObject(null);
args.add(jniEnv);
/
/
args.add(vm.addLocalObject(object1));
/
/
args.add(null)
args.add(null);
args.add(vm.addLocalObject(new StringObject(vm,
"123456"
)));
Number number
=
module.callFunction(emulator,
0x11AE8
+
1
, args.toArray());
/
/
是个地址
System.out.println(
"[addr] number is ==> "
+
number.intValue());
DvmObject<?>
object
=
vm.getObject(number.intValue());
System.out.println(
"[addr] Call the so md5 function result is ==> "
+
object
.getValue());
DvmObject<?> context
=
vm.resolveClass(
"android/content/Context"
).newObject(null);
list
.add(vm.addLocalObject(context));
DvmObject<?> context
=
vm.resolveClass(
"android/content/Context"
).newObject(null);
list
.add(vm.addLocalObject(context));
IHookZz hookZz
=
HookZz.getInstance(emulator);
/
/
加载HookZz,支持inline hook
hookZz.enable_arm_arm64_b_branch();
/
/
测试enable_arm_arm64_b_branch,可有可无
hookZz.wrap(module.findSymbolByName(
"ss_encrypt"
), new WrapCallback<RegisterContext>() {
/
/
inline wrap导出函数
@Override
public void preCall(Emulator<?> emulator, RegisterContext ctx, HookEntryInfo info) {
Pointer pointer
=
ctx.getPointerArg(
2
);
int
length
=
ctx.getIntArg(
3
);
byte[] key
=
pointer.getByteArray(
0
, length);
Inspector.inspect(key,
"ss_encrypt key"
);
}
@Override
public void postCall(Emulator<?> emulator, RegisterContext ctx, HookEntryInfo info) {
System.out.println(
"ss_encrypt.postCall R0="
+
ctx.getLongArg(
0
));
}
});
hookZz.disable_arm_arm64_b_branch();
hookZz.instrument(module.base
+
0x00000F5C
+
1
, new InstrumentCallback<Arm32RegisterContext>() {
@Override
public void dbiCall(Emulator<?> emulator, Arm32RegisterContext ctx, HookEntryInfo info) {
/
/
通过base
+
offset inline wrap内部函数,在IDA看到为sub_xxx那些
System.out.println(
"R3="
+
ctx.getLongArg(
3
)
+
", R10=0x"
+
Long
.toHexString(ctx.getR10Long()));
}
});
IHookZz hookZz
=
HookZz.getInstance(emulator);
/
/
加载HookZz,支持inline hook
hookZz.enable_arm_arm64_b_branch();
/
/
测试enable_arm_arm64_b_branch,可有可无
hookZz.wrap(module.findSymbolByName(
"ss_encrypt"
), new WrapCallback<RegisterContext>() {
/
/
inline wrap导出函数
@Override
public void preCall(Emulator<?> emulator, RegisterContext ctx, HookEntryInfo info) {
Pointer pointer
=
ctx.getPointerArg(
2
);
int
length
=
ctx.getIntArg(
3
);
byte[] key
=
pointer.getByteArray(
0
, length);
Inspector.inspect(key,
"ss_encrypt key"
);
}
@Override
public void postCall(Emulator<?> emulator, RegisterContext ctx, HookEntryInfo info) {
System.out.println(
"ss_encrypt.postCall R0="
+
ctx.getLongArg(
0
));
}
});
hookZz.disable_arm_arm64_b_branch();
hookZz.instrument(module.base
+
0x00000F5C
+
1
, new InstrumentCallback<Arm32RegisterContext>() {
@Override
public void dbiCall(Emulator<?> emulator, Arm32RegisterContext ctx, HookEntryInfo info) {
/
/
通过base
+
offset inline wrap内部函数,在IDA看到为sub_xxx那些
System.out.println(
"R3="
+
ctx.getLongArg(
3
)
+
", R10=0x"
+
Long
.toHexString(ctx.getR10Long()));
}
});
/
/
加载HookZz
IHookZz hookZz
=
HookZz.getInstance(emulator);
hookZz.wrap(module.base
+
0x1BD0
+
1
, new WrapCallback<HookZzArm32RegisterContext>() {
/
/
inline wrap导出函数
@Override
/
/
类似于 frida onEnter
public void preCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
/
/
类似于Frida args[
0
]
Pointer
input
=
ctx.getPointerArg(
0
);
System.out.println(
"input:"
+
input
.getString(
0
));
};
@Override
/
/
类似于 frida onLeave
public void postCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
Pointer result
=
ctx.getPointerArg(
0
);
System.out.println(
"input:"
+
result.getString(
0
));
}
});
/
/
加载HookZz
IHookZz hookZz
=
HookZz.getInstance(emulator);
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!