抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

介绍

项目地址:zhkl0228/unidbg

UniDbg 是一个开源的基于 Unicorn 的 Android Native 调试框架,专注于仿真 Android Native 层的动态库(如 .so 文件)和调用其内部方法。UniDbg 结合了 Unicorn 的指令仿真能力,封装了一套高层次的 API

Unicorn

Unicorn 是一个强大的开源 CPU 仿真框架,它支持多种架构的指令集模拟,允许用户在多种平台上仿真不同架构的代码。它基于更底层的 QEMU,但封装了更简洁易用的接口

UniDbg 的特点

  1. 基于 Unicorn 的轻量仿真
    • 使用 Unicorn 引擎进行指令仿真,支持多种 CPU 架构(如 ARM、ARM64 等)。
    • 只模拟 Native 层,不需要完整的 Android 系统运行环境。
  2. Android 系统接口模拟
    • 内置了许多 Android 系统调用的仿真(如 JNIEnvlibc.solibandroid_runtime.so)。
    • 模拟常用的系统函数(如 gettimeofdaydlopenmmap 等)。
  3. 高效的动态库加载
    • 支持直接加载 .so 文件,并调用其导出的方法。
    • 自动解析 ELF 格式文件的符号表和重定位。
  4. 动态调试
    • 支持 hook Native 方法,拦截函数调用并查看参数和返回值。
    • 支持断点设置、寄存器查看、内存访问等调试功能。
  5. 跨平台支持
    • UniDbg 是一个基于 Java 的框架,可以在多种平台上运行。
  6. 逆向工程辅助
    • 专为逆向工程设计,支持快速仿真加密函数、动态分析 Native 层逻辑。
  7. 扩展性强
    • 可以通过自定义 Hook、模拟环境等方式扩展其功能。

指令集+1

在 ARM 架构中,函数地址是否需要 +1 通常与 Thumb 指令集 有关。在 ARM 和 Thumb 指令集中,有以下关键点需要注意:

  1. ARM 与 Thumb 指令集
    • ARM 指令是固定 32 位宽度,地址必须是 4 字节对齐。
    • Thumb 指令是 16 位宽度,地址可以是 2 字节对齐。
  2. 指令集的切换(Thumb 模式标志位)
    • ARM 指令集和 Thumb 指令集的切换通过地址最低位是否设置为 1来指示:
      • 最低位为 0:ARM 模式。
      • 最低位为 1:Thumb 模式。
    • 例如,在调用函数时,如果最低位为 1,表示调用的是 Thumb 模式函数。
  3. +1 的问题
    • 在传统动态链接或手动调试时,如果要调用 Thumb 模式的函数,需要在获取的函数地址基础上手动加 1
    • 这是因为程序通常返回的是实际的函数入口地址,而不是带有 Thumb 模式标志的地址。

unidbg入门用例

基本框架

unidbg-master\unidbg-android\src\test\java\com\bytedance\frameworks\core\encrypt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
TTEncrypt(boolean logging) {
this.logging = logging;

emulator = AndroidEmulatorBuilder.for32Bit()//32bit
.setProcessName("com.qidian.dldl.official")
.addBackendFactory(new Unicorn2Factory(true))
.build(); // 创建模拟器实例,要模拟32位或者64位,在这里区分
final Memory memory = emulator.getMemory(); // 模拟器的内存操作接口
memory.setLibraryResolver(new AndroidResolver(23)); // 设置系统类库解析

vm = emulator.createDalvikVM(); // 创建Android虚拟机
vm.setVerbose(logging); // 设置是否打印Jni调用细节
DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/resources/example_binaries/libttEncrypt.so"), false); // 加载libttEncrypt.so到unicorn虚拟内存,加载成功以后会默认调用init_array等函数
dm.callJNI_OnLoad(emulator); // 手动执行JNI_OnLoad函数
module = dm.getModule(); // 加载好的libttEncrypt.so对应为一个模块
// 加载目标类,相当于findClass
TTEncryptUtils = vm.resolveClass("com/bytedance/frameworks/core/encrypt/TTEncryptUtils");
}

unidbg函数

emulator

Unidbg 的 Emulator 是一个高层抽象,封装了 CPU 仿真(基于 Unicorn)、内存管理、动态库加载、系统调用等功能,

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
AndroidEmulator emulator = AndroidEmulatorBuilder
//指定32位CPU
.for32Bit()
//添加后端,推荐使用Dynarmic,运行速度快,但并不支持某些新特性
.addBackendFactory(new DynarmicFactory(true))
//指定进程名,推荐以安卓包名做进程名
.setProcessName("com.github.unidbg")
//设置根路径
.setRootDir(new File("target/rootfs/default"))
//生成AndroidEmulator实例
.build();
//获取内存操作接口
Memory memory = emulator.getMemory();
//获取进程pid
int pid = emulator.getPid();
//创建虚拟机
VM dalvikVM = emulator.createDalvikVM();
//创建虚拟机并指定APK文件
VM dalvikVM = emulator.createDalvikVM(new File("apk file path"));
//获取已创建的虚拟机
VM dalvikVM = 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操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//获取内存操作接口
Memory memory = emulator.getMemory();
//指定Android SDK 版本,目前支持19和23两个版本
memory.setLibraryResolver(new AndroidResolver(23));

//拿到一个指针,指向内存地址,通过该指针可操作内存
UnidbgPointer pointer = memory.pointer(0x4000000);

//获取当前内存映射情况
Collection<MemoryMap> memoryMap = memory.getMemoryMap();

//根据模块名来拿到某个模块
Module module = memory.findModule("module name");

//根据地址拿到某个模块
Module module = memory.findModuleByAddress(0x40000000);

VM操作

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
//推荐指定APK文件,Unidbg会自动做许多固定的操作
VM vm = emulator.createDalvikVM(new File("apk file path"));

//是否输出JNI运行日志
vm.setVerbose(true);

//加载SO模块 参数二设置是否自动调用init函数
DalvikModule dalvikModule = vm.loadLibrary(new File("so file path"), true);

//设置JNI交互接口 参数需实现Jni接口,推荐使用this继承AbstractJni
vm.setJni(this);

//获取JNIEnv指针,可作为参数传递
Pointer jniEnv = vm.getJNIEnv();

//获取JavaVM指针,可作为参数传递
Pointer javaVM = vm.getJavaVM();

//调用JNI_OnLoad函数
vm.callJNI_OnLoad(emulator,dalvikModule.getModule());

//向VM添加全局对象,返回该对象的hash值
int hash = vm.addGlobalObject(dvmObj);

//获取虚拟机中的对象,参数为该对象的hash值
DvmObject<?> object = vm.getObject(hash);

CallMethod

执行JNI函数

  • 创建一个VM对象,此对象相当于在Java层去调用native函数的类的实例对象
1
DvmObject<?> obj = ProxyDvmObject.createObject(vm, this);

注意:Unidbg会根据第二个参数类的包名进行匹配JNI方法,所以此处的this对象所属类的包名应该与目标函数相匹配

  • 使用该对象进行调用JNI方法
1
boolean result = obj.callJniMethodBoolean(emulator, "jnitest(Ljava/lang/String;)Z", str);

参数二是方法的签名,根据参数二找到对应的函数来执行。假设此时的this对象所属类的包名为:com.kanxue.test2,那此时就会调用:

1
jboolean  Java_com_kanxue_test2_jnitest(JNIEnv *env, jobject thiz, jstring str);

执行任意函数

当我们想执行SO中任意函数时,我们也可以通过地址来进行调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//获取JNIEnv *
Pointer jniEnv = vm.getJNIEnv();

//创建jobject对象
DvmObject<?> thiz = vm.resolveClass("com.kanxue.test2").newObject(null);

//准备入参
List<Object> args = new ArrayList<>();
args.add(jniEnv);
args.add(vm.addLocalObject(thiz));
args.add(vm.addLocalObject(new StringObject(vm,"XuE")));

//根据地址调用
Number[] numbers = module.callFunction(emulator, 0x9180 + 1, args.toArray());
System.out.println(numbers[0].intValue());

当入参为非指针和Number类型,都需要将对象先添加到VM,才能够在VM中使用这些对象 当返回值为对象时,此时的Number为该对象在VM中的hash值,需要通过vm.getObject()将对象取出进行使用,例:

1
2
DvmObject<?> object = vm.getObject(numbers[0].intValue());
String result = (String) object.getValue();

执行返回值由参数指针传递的函数

如:

1
void md5(const uint8_t *initial_msg, size_t initial_len, uint8_t *digest);

这种函数我们来看一下我们该如何进行主动执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
String initial = "unidbg";
int initial_length = initial.length();

//开辟一块的空间来存放第一个参数
MemoryBlock initial_msg = emulator.getMemory().malloc(initial_length+1, false);
UnidbgPointer initial_msg_ptr=initial_msg.getPointer();

//将参数1写入
initial_msg_ptr.write(initial.getBytes());

//开辟一块16字节的空间来存放第三个参数
MemoryBlock digest = emulator.getMemory().malloc(16, false);
UnidbgPointer digest_ptr=digest.getPointer();

//准备参数
List<Object> args = new ArrayList<>();
args.add(initial_msg);
args.add(initial_length);
args.add(digest_ptr);
//执行
module.callFunction(emulator, 0x7A8D + 1, args.toArray());

//打印结果
Inspector.inspect(digest_ptr.getByteArray(0, 0x10), "digest");

unidbg中的Hook

Unidbg 的 Hook 功能可以分为两大类:

  1. 内置的第三方 Hook 框架: Dobby(HookZz)、Whale、xHook 。
  2. 基于 Unicorn 的 Hook 机制:Unicorn 提供的底层 Hook 功能,以及 Unidbg 在其基础上封装的 Console Debugger。

HookZz

HookZz 现在叫 Dobby,Unidbg 中是 HookZz 和 Dobby 是两个独立的 Hook 库,因为作者认为 HookZz 在 arm32 上支持较好,Dobby 在 arm64 上支持较好。HookZz 是 inline hook 方案,因此可以 Hook Sub_xxx,缺点是短函数可能出 bug,受限于 inline Hook 原理

wrap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
HookZz hook = HookZz.getInstance(emulator);
hook.wrap(module.base + 0xC09D, new WrapCallback<RegisterContext>() {
@Override
public void preCall(Emulator<?> emulator, RegisterContext ctx, HookEntryInfo info) {
Pointer input = ctx.getPointerArg(0);
System.out.println(input.getString(0));
}

@Override
public void postCall(Emulator<?> emulator, RegisterContext ctx, HookEntryInfo info) {
Pointer result = ctx.getPointerArg(0);
System.out.println(result.getString(0));
}
});

replace

replace函数的第三个参数enablePostCall是一个Boolean类型,其值表示是否使ReplaceCallbackpostCall函数生效。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
HookZz hook = HookZz.getInstance(emulator);
hook.replacemodule.findSymbolByName(""), new ReplaceCallback() {
@Override
public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) {
emulator.getBackend().reg_write(Unicorn.UC_ARM_REG_R0,1);
return HookStatus.RET(emulator,context.getLR());
}

@Override
public void postCall(Emulator<?> emulator, HookContext context) {
System.out.println("postCall!");
super.postCall(emulator, context);
}
},true);

instrument

1
2
3
4
5
6
7
8
HookZz hook = HookZz.getInstance(emulator);
hookZz.instrument(module.base + 0x00000F5C + 1, new InstrumentCallback<Arm32RegisterContext>() {
// 通过base+offset inline wrap内部函数,在IDA看到为sub_xxx那些
@Override
public void dbiCall(Emulator<?> emulator, Arm32RegisterContext ctx, HookEntryInfo info) {
System.out.println("R3=" + ctx.getLongArg(3) + ", R10=0x" + Long.toHexString(ctx.getR10Long()));//打印寄存器
}
});

Dobby

64位模式下推荐使用Dobby,不支持wrap

replace

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Dobby dobby = Dobby.getInstance(emulator);
dobby.replace(module.base+0xAC90, new ReplaceCallback() {

@Override
public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) {
Pointer result = context.getPointerArg(0);
System.out.println("input:" + result.getString(0));
return super.onCall(emulator, context, originFunction);
}

@Override
public void postCall(Emulator<?> emulator, HookContext context) {
super.postCall(emulator, context);
}
},true);

XHook

XHook 是一个针对 Android 平台 ELF 的 PLT (Procedure Linkage Table) hook 库,即它只能用于 hook 导入函数。符号表函数

1
2
3
4
5
6
7
8
9
10
IxHook hook = XHookImpl.getInstance(emulator);
hook.register("libutility.so", "free", new ReplaceCallback() {
@Override
public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) {
System.out.println("free called!");
return HookStatus.RET(emulator,context.getLR());
}
});
//使hook生效
hook.refresh();

Whale

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public void HookByWhale(){
IWhale whale = Whale.getInstance(emulator);
whale.inlineHookFunction(module.findSymbolByName("base64_encode"), new ReplaceCallback() {
Pointer buffer;
@Override
public HookStatus onCall(Emulator<?> emulator, long originFunction) {
RegisterContext context = emulator.getContext();
Pointer input = context.getPointerArg(0);
int length = context.getIntArg(1);
buffer = context.getPointerArg(2);
Inspector.inspect(input.getByteArray(0, length), "base64 input");
return HookStatus.RET(emulator, originFunction);
}

@Override
public void postCall(Emulator<?> emulator, HookContext context) {
System.out.println("base64 result:"+buffer.getString(0));
}
}, true);
}
/*导入函数hook*/
whale.importHookFunction(...);
/*method hook*/
whale.replace(...);

Unicorn Hook

如果想对某个函数进行集中的、高强度的、同时又灵活的调试,Unicorn CodeHook 是一个好选择。比如我想查看目标函数第一条指令的 r1,第二条指令的 r2,第三条指令的 r3,类似于这种需求。

hook_add_new 第一个参数是 Hook 回调,我们这里选择 CodeHook,它是逐条指令 Hook,参数 2 是起始地址,参数 3 是结束地址,参数 4 一般填 null。这意味着从起始地址到终止地址这个执行范围内的每条指令,我们都可以在其执行前处理它。

找到目标函数的代码范围

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
public void HookByUnicorn(){
long start = module.base+0x97C;
long end = module.base+0x97C+0x17A;
emulator.getBackend().hook_add_new(new CodeHook() {
@Override
public void hook(Backend backend, long address, int size, Object user) {
RegisterContext registerContext = emulator.getContext();
if(address == module.base + 0x97C){// hook的目标地址
int r0 = registerContext.getIntByReg(ArmConst.UC_ARM_REG_R0);
System.out.println("0x97C 处 r0:"+Integer.toHexString(r0));
}
}

@Override
public void onAttach(Unicorn.UnHook unHook) {

}

@Override
public void detach() {

}
}, start, end, null);
}

SystemPropertyHook

为了方便处理应用访问设备属性(例如ro.build.id),Unidbg实现了SystemPropertyHook,使用方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
SystemPropertyHook systemPropertyHook = new SystemPropertyHook(emulator);
systemPropertyHook.setPropertyProvider(new SystemPropertyProvider() {
@Override
public String getProperty(String key) {
switch (key){
case "ro.build.id":{
return "get id";
}
case "ro.build.version.sdk":{
return "get sdk";
}
}
return null;
}
});
//使hook生效
memory.addHookListener(systemPropertyHook);

打印函数调用栈

1
2
emulator.getUnwinder().unwind();
emulator.printStackTrace();
特性 emulator.printStackTrace() emulator.getUnwinder().unwind()
输出格式 自动格式化打印调用栈 返回调用栈的原始数据(StackFrame 列表)
灵活性 固定格式,不可定制 可自定义解析、过滤或存储
适用场景 快速查看调用栈 深入分析调用栈或定制输出
是否符号化 自动解析符号表 依赖模块符号表

监控内存读写

Unidbg 提供了一个 TraceMemory 类,可以轻松实现对指定内存范围的访问跟踪。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 定义输出文件
String traceFile = "myMonitorFile";
PrintStream traceStream = null;
try {
traceStream = new PrintStream(new FileOutputStream(traceFile), true);
} catch (FileNotFoundException e) {
e.printStackTrace();
return;
}

// 监控模块内存的读写操作,并将信息输出到文件
emulator.traceRead(module.base, module.base + module.size).setRedirect(traceStream);
emulator.traceWrite(module.base, module.base + module.size).setRedirect(traceStream);

作用:

  • 对指定范围的内存访问进行详细追踪。
  • 自动输出读写行为。

trace

traceCode

1
2
3
4
5
6
7
8
9
String traceFile = "myTraceCodeFile";
PrintStream traceStream = null;
try {
traceStream = new PrintStream(new FileOutputStream(traceFile), true);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
//追踪目标库的汇编代码,并将信息输出定向到指定文件中
emulator.traceCode(module.base, module.base + module.size).setRedirect(traceStream);

要注意 trace 的时机,如果我们想 trace 从某个函数开始的执行流,那就让 traceCode 早于它执行即可。比如想 trace 从 JNI_OnLoad 开始的目标 SO 执行流,在如下的代码位置添加 trace 即可。

1
2
3
4
5
6
DalvikModule dm = vm.loadLibrary("mx", true);
module = dm.getModule();
security = vm.resolveClass("com/mengxiang/arch/security/MXSecurity");
// trace
emulator.traceCode(module.base, module.base + module.size);
dm.callJNI_OnLoad(emulator);

如果想到更早的时机开启追踪,即追踪 init_proc、init_array 这些初始化函数的执行情况,那就需要将 traceCode 放到 loadLibrary 之前调用,但此时还没有获取到module对象。因此最正确的处理办法是使用模块监听器,在模块加载的第一时间开始 trace 。

1
2
3
4
5
6
7
8
9
10
11
12
memory.addModuleListener(new ModuleListener() {
@Override
public void onLoaded(Emulator<?> emulator, Module module) {
if(module.name.equals("libmx.so")){
emulator.traceCode(module.base, module.base+module.size);
}
}
});
DalvikModule dm = vm.loadLibrary("mx", true);
module = dm.getModule();
security = vm.resolveClass("com/mengxiang/arch/security/MXSecurity");
dm.callJNI_OnLoad(emulator);

如果想只 trace 某个函数内的汇编,这里分为两种情况:

  1. 只关注某地址处的某一函数调用,而不关注于该函数在别处的调用
1
2
3
4
5
6
7
8
.text:000000000000E530                 MOV             X0, X19
.text:000000000000E534 MOV X2, X21
.text:000000000000E538 LDR X1, [X8] ; "SHA1"
.text:000000000000E53C BL ._Z6digestP7_JNIEnvPKcP11_jbyteArray ; digest(_JNIEnv *,char const*,_jbyteArray *)
.text:000000000000E540 LDR X8, [X19]
.text:000000000000E544 MOV X1, X0
.text:000000000000E548 LDR X2, [X8,#0x538]
.text:000000000000E54C B loc_E55C

只关注 0xE53C 处的 digest 函数调用,那么需要在 0xE53C 开始 traceCode,0xE540 处停止 trace。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void traceDigest(){
long callAddr = module.base + 0xE53C;

emulator.attach().addBreakPoint(callAddr, new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
traceHook = emulator.traceCode(module.base, module.base+module.size);
return true;
}
});

emulator.attach().addBreakPoint(callAddr + 4, new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
traceHook.stopTrace();
return true;
}
});
}

关注某一函数的所有调用,那么在进入该函数前开始 traceCode,离开该函数停止 trace。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public void traceDigest(){
// digest 函数地址
long callAddr = module.base + 0xd804;

emulator.attach().addBreakPoint(callAddr, new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
RegisterContext registerContext = emulator.getContext();
// 开启 traceCode
traceHook = emulator.traceCode(module.base, module.base+module.size);
// 在 digest 函数执行完后的返回地址处下断点,取消 traceCode
// 由于返回地址可以是module中任意一点
// 那为什么不在 digest 函数的最后一条指令处结束 traceCode,虽然会损失最后一条指令的 trace,但最后一条指令一般是 ret
emulator.attach().addBreakPoint(registerContext.getLR(), new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
traceHook.stopTrace();
return true;
}
});
return true;
}
});
}

traceFunctionCall

1
2
3
4
5
6
7
8
9
10
11
// function trace
Debugger debugger = emulator.attach();
debugger.traceFunctionCall(module, new FunctionCallListener() {
@Override
public void onCall(Emulator<?> emulator, long callerAddress, long functionAddress) {
}
@Override
public void postCall(Emulator<?> emulator, long callerAddress, long functionAddress, Number[] args) {
System.out.println("onCallFinish caller=" + UnidbgPointer.pointer(emulator, callerAddress) + ", function=" + UnidbgPointer.pointer(emulator, functionAddress));
}
});

日志系统

日志系统

Unidbg 中使用 Apache 的开源项目 log4jcommons-logging 处理日志。Unidbg 的绝大多数代码逻辑都提供了信息展示,但只在DEBUG日志等级下才做输出打印。Unidbg 基于模块去管理输出,想了解哪部分日志,就指定具体的类为 DEBUG 等级。

1
2
3
4
5
6
7
8
9
10
import org.apache.log4j.Level;
import org.apache.log4j.Logger;

public static void main(String[] args) {
NetWork nw = new NetWork();
Logger.getLogger(ARM32SyscallHandler.class).setLevel(Level.DEBUG);
Logger.getLogger(AndroidSyscallHandler.class).setLevel(Level.DEBUG);
String result = nw.callSign();
System.out.println("call s result:"+result);
}

除了常规日志,Unidbg 还有另一套日志输出,主要打印 JNI 、Syscall 调用相关的内容。它和常规日志的输出有重叠,但内容更详细一些。我们通过 vm.setVerbose 开启或关闭它。在一般情况下,我们都会开启这个日志。

虚拟机日志

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public WeiBo() {
emulator = AndroidEmulatorBuilder
.for32Bit()
.addBackendFactory(new Unicorn2Factory(true))
.setProcessName("com.weico.international")
.build();
Memory memory = emulator.getMemory();
memory.setLibraryResolver(new AndroidResolver(23));
vm = emulator.createDalvikVM(new File("unidbg-android/src/test/resources/weibo/sinaInternational.apk"));
vm.setJni(this);
// 设置是否打印以 JNI 为主的虚拟机调用细节
vm.setVerbose(false);
DalvikModule dm = vm.loadLibrary("utility", true);
WeiboSecurityUtils = vm.resolveClass("com/sina/weibo/security/WeiboSecurityUtils");
dm.callJNI_OnLoad(emulator);
}

龙哥Unidbg Hook 大全 - Silas

评论