【转载】Unidbg Hook 大全- SeeFlowerX
本文由 简悦
SimpRead 转码, 原文地址 blog.seeflower.dev
Be patient.
本文章转载自龙哥,已获许可,持续更新中。
本文总结了
样例前往百度云下载:
链接:https://pan.baidu.com/s/1ZRPtQrx4QAPEQhrpq6gbgg 提取码:6666
更多

一、基础知识
1. 获取SO 基地址
Ⅰfrida 获取基地址
var baseAddr = Module.findBaseAddress("libnative-lib.so");
Ⅱ Unidbg 获取基地址
DalvikModule dm = vm.loadLibrary("libnative-lib.so", true);
module = dm.getModule();
System.out.println("baseAddr:"+module.base);
加载了多个
Module yourModule = emulator.getMemory().findModule("yourModuleName");
System.out.println("baseAddr:"+yourModule.base);
如果只主动加载一个
public interface Memory extends IO, Loader, StackMemory {
long STACK_BASE = 0xc0000000L;
int STACK_SIZE_OF_PAGE = 256;
long MMAP_BASE = 0x40000000L;
UnidbgPointer allocateStack(int size);
UnidbgPointer pointer(long address);
void setStackPoint(long sp);
2. 获取函数地址
Ⅰ Frida 获取导出函数地址
Module.findExportByName("libc.so", "strcmp")
Ⅱ Unidbg 获取导出函数地址
DalvikModule dm = vm.loadLibrary("libnative-lib.so", true);
module = dm.getModule();
int address = (int) module.findSymbolByName("funcNmae").getAddress();
Ⅲ Frida 获取非导出函数地址
var soAddr = Module.findBaseAddress("libnative-lib.so");
var FuncAddr = soAddr.add(0x1768 + 1);
Ⅳ Unidbg 获取非导出函数地址
DalvikModule dm = vm.loadLibrary("libnative-lib.so", true);
module = dm.getModule();
int offset = 0x1768;
int address = (int) (module.base + offset);
3.Unidbg Hook 大盘点
Unidbg 内置的第三方Hook 框架,包括xHook/Whale/HookZz Unicorn Hook 以及Unidbg 基于它封装的Console Debugger
第一类是
第二类是当
该怎么选择
private static AndroidEmulator createARMEmulator() {
return AndroidEmulatorBuilder.for32Bit()
.addBackendFactory(new DynarmicFactory(true))
.build();
}
private static AndroidEmulator createARMEmulator() {
return AndroidEmulatorBuilder.for32Bit()
.build();
}
使用
我个人认为有三点优势
HookZz 或者xHook 等方案,都可以基于其Hook 实现原理进行检测,但Unicorn 原生Hook 不容易被检测。Unicorn Hook 没有局限,其他方案局限性较大。比如Inline Hook 方案不能Hook 短函数,或者两个相邻的地址;PLT Hook 不能Hook Sub_xxx 子函数。- 第三方
inline Hook 框架和原生Hook 方案同时使用时会摩擦出BUG 的火花,事实上,单使用Unicorn 的某些Hook 功能都有BUG 。所以说,统一用原生Hook 会少一些BUG ,少一些麻烦。
总结如下
Ⅰ 以模拟执行为目的
使用第三方
Ⅱ 以算法还原为目的
使用
4. 本篇的基础代码
即模拟执行
package com.tutorial;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.Module;
import com.github.unidbg.arm.HookStatus;
import com.github.unidbg.arm.backend.Backend;
import com.github.unidbg.arm.backend.CodeHook;
import com.github.unidbg.arm.context.RegisterContext;
import com.github.unidbg.debugger.BreakPointCallback;
import com.github.unidbg.hook.HookContext;
import com.github.unidbg.hook.ReplaceCallback;
import com.github.unidbg.hook.hookzz.*;
import com.github.unidbg.hook.whale.IWhale;
import com.github.unidbg.hook.whale.Whale;
import com.github.unidbg.hook.xhook.IxHook;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.XHookImpl;
import com.github.unidbg.linux.android.dvm.DalvikModule;
import com.github.unidbg.linux.android.dvm.DvmClass;
import com.github.unidbg.linux.android.dvm.DvmObject;
import com.github.unidbg.linux.android.dvm.VM;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.utils.Inspector;
import com.sun.jna.Pointer;
import unicorn.ArmConst;
import unicorn.Unicorn;
import java.io.File;
public class hookInUnidbg {
private final AndroidEmulator emulator;
private final VM vm;
private final Module module;
hookInUnidbg() {
emulator = AndroidEmulatorBuilder.for32Bit().build();
final Memory memory = emulator.getMemory();
memory.setLibraryResolver(new AndroidResolver(23));
vm = emulator.createDalvikVM(new File("unidbg-android/src/test/resources/tutorial/hookinunidbg.apk"));
DalvikModule dm = vm.loadLibrary("hookinunidbg", true);
module = dm.getModule();
dm.callJNI_OnLoad(emulator);
}
public void call(){
DvmClass dvmClass = vm.resolveClass("com/example/hookinunidbg/MainActivity");
String methodSign = "call()V";
DvmObject<?> dvmObject = dvmClass.newObject(null);
dvmObject.callJniMethodObject(emulator, methodSign);
}
public static void main(String[] args) {
hookInUnidbg mydemo = new hookInUnidbg();
mydemo.call();
}
}
运行时有一些日志输出,为正常逻辑。
二、Hook 函数
unsigned int
base64_encode(const unsigned char *in, unsigned int inlen, char *out);
参数解释如下
char *out:一块
buffer 的首地址,用来存放转码后的内容。char *in:原字符串的首地址,指向原字符串内容。
int inlen:原字符串长度。
返回值:正常情况下返回转换后字符串的实际长度。
本节的任务就是打印
1.Frida
function main(){
var base_addr = Module.findBaseAddress("libhookinunidbg.so");
if (base_addr){
var func_addr = Module.findExportByName("libhookinunidbg.so", "base64_encode");
console.log("hook base64_encode function")
Interceptor.attach(func_addr,{
onEnter: function (args) {
console.log("\n input:")
this.buffer = args[2];
var length = args[1];
console.log(hexdump(args[0],{length: length.toUInt32()}))
console.log("\n")
},
onLeave: function () {
console.log(" output:")
console.log(this.buffer.readCString());
}
})
}
}
setImmediate(main);
2.Console Debugger
emulator.attach().addBreakPoint(module.findSymbolByName("base64_encode").getAddress());
需要重申和强调几个概念
- 运行到对应地址时触发断点,类似于
GDB 调试或者IDA 调试,时机为目标指令执行前。 - 断点不具有函数的种种概念,需要从
ARM 汇编指令的角度去理解函数。 Console Debugger 用于辅助算法分析,快速分析、确认某个函数的功能。在Unicorn 引擎下才可以用。
针对第二条做补充
根据
ARM ATPCS 调用约定,当参数个数小于等于4 个的时候,子程序间通过R0~R3 来传递参数(即R0-R3 代表参数1 - 参数4 ) ,如果参数个数大于4 个,余下的参数通过sp 所指向的数据栈进行参数传递。而函数的返回值总是通过R0 传递回来。
以目标函数为例,函数调用前,调用方把三个参数依次放在

立即数可以直接查看,比如此处的参数
Frida hexdump 时,左侧基地址从当前地址开始,而Unidbg 从0 开始。Unidbg 给出了所打印数据块的md5 值,方便对比两块数据块内容是否一致,而且Unidbg 展示数据的Hex String ,方便在大量日志中搜索。
c: continue
n: step over
bt: back trace
st hex: search stack
shw hex: search writable heap
shr hex: search readable heap
shx hex: search executable heap
nb: break at next block
s|si: step into
s[decimal]: execute specified amount instruction
s(blx): execute util BLX mnemonic, low performance
m(op) [size]: show memory, default size is 0x70, size may hex or decimal
mr0-mr7, mfp, mip, msp [size]: show memory of specified register
m(address) [size]: show memory of specified address, address must start with 0x
wr0-wr7, wfp, wip, wsp <value>: write specified register
wb(address), ws(address), wi(address) <value>: write (byte, short, integer) memory of specified address, address must start with 0x
wx(address) <hex>: write bytes to memory at specified address, address must start with 0x
b(address): add temporarily breakpoint, address must start with 0x, can be module offset
b: add breakpoint of register PC
r: remove breakpoint of register PC
blr: add temporarily breakpoint of register LR
p (assembly): patch assembly at PC address
where: show java stack trace
trace [begin end]: Set trace instructions
traceRead [begin end]: Set trace memory read
traceWrite [begin end]: Set trace memory write
vm: view loaded modules
vbs: view breakpoints
d|dis: show disassemble
d(0x): show disassemble at specify address
stop: stop emulation
run [arg]: run test
cc size: convert asm from 0x400008a0 - 0x400008a0 + size bytes to c function
在console.log(hexdump(args[0],{length: args[1].toUInt32()}))
来表示 打印参数
mr0 5
>-----------------------------------------------------------------------------<
[23:41:37 891]r0=RX@0x400022e0[libhookinunidbg.so]0x22e0, md5=f5704182e75d12316f5b729e89a499df, hex=6c696c6163
size: 5
0000: 6C 69 6C 61 63 lilac
^-----------------------------------------------------------------------------^
目前
至此实现了
整体逻辑如下
- 在目标函数的地址处下断点
- 运行到断点处,进入
Console Debugger 交互调试 mxx 系列查看参数blr 在函数返回处下断点c 使程序继续运行,到返回值处断下- 查看此时的
buffer
需要注意的是,在
这样我们就实现了
public void HookByConsoleDebugger(){
emulator.attach().addBreakPoint(module.findSymbolByName("base64_encode").getAddress(), new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
RegisterContext context = emulator.getContext();
Pointer input = context.getPointerArg(0);
int length = context.getIntArg(1);
Pointer buffer = context.getPointerArg(2);
Inspector.inspect(input.getByteArray(0, length), "base64 input");
emulator.attach().addBreakPoint(context.getLRPointer().peer, new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
String result = buffer.getString(0);
System.out.println("base64 result:"+result);
return true;
}
});
return true;
}
});
}
3. 第三方Hook 框架
如下目标函数均在
ⅠxHook
public void HookByXhook(){
IxHook xHook = XHookImpl.getInstance(emulator);
xHook.register("libhookinunidbg.so", "base64_encode", new ReplaceCallback() {
@Override
public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) {
Pointer input = context.getPointerArg(0);
int length = context.getIntArg(1);
Pointer buffer = context.getPointerArg(2);
Inspector.inspect(input.getByteArray(0, length), "base64 input");
context.push(buffer);
return HookStatus.RET(emulator, originFunction);
}
@Override
public void postCall(Emulator<?> emulator, HookContext context) {
Pointer buffer = context.pop();
System.out.println("base64 result:"+buffer.getString(0));
}
}, true);
xHook.refresh();
}
Ⅱ HookZz
public void HookByHookZz(){
IHookZz hookZz = HookZz.getInstance(emulator);
hookZz.enable_arm_arm64_b_branch();
hookZz.wrap(module.findSymbolByName("base64_encode"), new WrapCallback<HookZzArm32RegisterContext>() {
@Override
public void preCall(Emulator<?> emulator, HookZzArm32RegisterContext context, HookEntryInfo info) {
Pointer input = context.getPointerArg(0);
int length = context.getIntArg(1);
Pointer buffer = context.getPointerArg(2);
Inspector.inspect(input.getByteArray(0, length), "base64 input");
context.push(buffer);
}
@Override
public void postCall(Emulator<?> emulator, HookZzArm32RegisterContext context, HookEntryInfo info) {
Pointer buffer = context.pop();
System.out.println("base64 result:"+buffer.getString(0));
}
});
hookZz.disable_arm_arm64_b_branch();
}
IHookZz hookZz = HookZz.getInstance(emulator);
hookZz.instrument(module.base + 0x978 + 1, new InstrumentCallback<RegisterContext>() {
@Override
public void dbiCall(Emulator<?> emulator, RegisterContext ctx, HookEntryInfo info) {
System.out.println(ctx.getIntArg(0));
}
});
Ⅲ Whale
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);
}
4.Unicorn Hook
如果想对某个函数进行集中的、高强度的、同时又灵活的调试,
找到目标函数的代码范围

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){
int r0 = registerContext.getIntByReg(ArmConst.UC_ARM_REG_R0);
System.out.println("0x97C 处 r0:"+Integer.toHexString(r0));
}
if(address == module.base + 0x97C + 2){
int r2 = registerContext.getIntByReg(ArmConst.UC_ARM_REG_R2);
System.out.println("0x97C +2 处 r2:"+Integer.toHexString(r2));
}
if(address == module.base + 0x97C + 4){
int r4 = registerContext.getIntByReg(ArmConst.UC_ARM_REG_R4);
System.out.println("0x97C +4 处 r4:"+Integer.toHexString(r4));
}
}
@Override
public void onAttach(Unicorn.UnHook unHook) {
}
@Override
public void detach() {
}
}, start, end, null);
}
三、Replace 参数和返回值
1. 替换参数
需求:如果入参为
ⅠFrida
function main(){
var base_addr = Module.findBaseAddress("libhookinunidbg.so");
if (base_addr){
var func_addr = Module.findExportByName("libhookinunidbg.so", "base64_encode");
console.log("hook base64_encode function")
var fakeinput = "hello world"
var fakeinputPtr = Memory.allocUtf8String(fakeinput);
Interceptor.attach(func_addr,{
onEnter: function (args) {
args[0] = fakeinputPtr;
args[1] = ptr(fakeinput.length);
this.buffer = args[2];
},
onLeave: function () {
console.log(" output:")
console.log(this.buffer.readCString());
}
})
}
}
setImmediate(main);
Ⅱ Console Debugger
快速打击、快速验证的
① 下断点,运行代码后进入
emulator.attach().addBreakPoint(module.findSymbolByName("base64_encode").getAddress());
② 通过命令修改参数
wx0x40002403 68656c6c6f20776f726c64
>-----------------------------------------------------------------------------<
[14:06:46 165]RX@0x40002403[libhookinunidbg.so]0x2403, md5=5eb63bbbe01eeed093cb22bb8f5acdc3, hex=68656c6c6f20776f726c64
size: 11
0000: 68 65 6C 6C 6F 20 77 6F 72 6C 64 hello world
^-----------------------------------------------------------------------------^
wr1 11
>>> r1=0xb
wr0-wr7, wfp, wip, wsp <value>: write specified register
wb(address), ws(address), wi(address) <value>: write (byte, short, integer) memory of specified address, address must start with 0x
wx(address) <hex>: write bytes to memory at specified address, address must start with 0x
但这其实并不方便,还是做持久化比较舒服。
public void ReplaceArgByConsoleDebugger(){
emulator.attach().addBreakPoint(module.findSymbolByName("base64_encode").getAddress(), new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
RegisterContext context = emulator.getContext();
String fakeInput = "hello world";
int length = fakeInput.length();
emulator.getBackend().reg_write(ArmConst.UC_ARM_REG_R1, length);
MemoryBlock fakeInputBlock = emulator.getMemory().malloc(length, true);
fakeInputBlock.getPointer().write(fakeInput.getBytes(StandardCharsets.UTF_8));
emulator.getBackend().reg_write(ArmConst.UC_ARM_REG_R0, fakeInputBlock.getPointer().peer);
Pointer buffer = context.getPointerArg(2);
emulator.attach().addBreakPoint(context.getLRPointer().peer, new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
String result = buffer.getString(0);
System.out.println("base64 result:"+result);
return true;
}
});
return true;
}
});
}
Ⅲ 第三方Hook 框架
变来变去的只有外壳。
① xHook
public void ReplaceArgByXhook(){
IxHook xHook = XHookImpl.getInstance(emulator);
xHook.register("libhookinunidbg.so", "base64_encode", new ReplaceCallback() {
@Override
public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) {
String fakeInput = "hello world";
int length = fakeInput.length();
emulator.getBackend().reg_write(ArmConst.UC_ARM_REG_R1, length);
MemoryBlock fakeInputBlock = emulator.getMemory().malloc(length, true);
fakeInputBlock.getPointer().write(fakeInput.getBytes(StandardCharsets.UTF_8));
emulator.getBackend().reg_write(ArmConst.UC_ARM_REG_R0, fakeInputBlock.getPointer().peer);
Pointer buffer = context.getPointerArg(2);
context.push(buffer);
return HookStatus.RET(emulator, originFunction);
}
@Override
public void postCall(Emulator<?> emulator, HookContext context) {
Pointer buffer = context.pop();
System.out.println("base64 result:"+buffer.getString(0));
}
}, true);
xHook.refresh();
}
② HookZz
public void ReplaceArgByHookZz(){
IHookZz hookZz = HookZz.getInstance(emulator);
hookZz.enable_arm_arm64_b_branch();
hookZz.wrap(module.findSymbolByName("base64_encode"), new WrapCallback<HookZzArm32RegisterContext>() {
@Override
public void preCall(Emulator<?> emulator, HookZzArm32RegisterContext context, HookEntryInfo info) {
Pointer input = context.getPointerArg(0);
String fakeInput = "hello world";
input.setString(0, fakeInput);
context.setR1(fakeInput.length());
Pointer buffer = context.getPointerArg(2);
context.push(buffer);
}
@Override
public void postCall(Emulator<?> emulator, HookZzArm32RegisterContext context, HookEntryInfo info) {
Pointer buffer = context.pop();
System.out.println("base64 result:"+buffer.getString(0));
}
});
hookZz.disable_arm_arm64_b_branch();
}
因为可以用
2. 修改返回值
修改返回值的逻辑和替换参数并没什么区别,但它可以引出第四节,所以还是仔细讲一下。
在
extern "C"
JNIEXPORT void JNICALL
Java_com_example_hookinunidbg_MainActivity_call(JNIEnv *env, jobject thiz) {
int verifyret = verifyApkSign();
if(verifyret == 1){
LOGE("APK sign verify failed!");
} else{
LOGE("APK sign verify success!");
}
testBase64();
}
extern "C" int verifyApkSign(){
LOGE("verify apk sign");
return 1;
};
ⅠFrida
function main(){
var base_addr = Module.findBaseAddress("libhookinunidbg.so");
if (base_addr){
var func_addr = Module.findExportByName("libhookinunidbg.so", "verifyApkSign");
console.log("hook verifyApkSign function")
Interceptor.attach(func_addr,{
onEnter: function (args) {
},
onLeave: function (retval) {
retval.replace(0);
}
})
}
}
setImmediate(main);
Ⅱ Console Debugger
public void ReplaceRetByConsoleDebugger(){
emulator.attach().addBreakPoint(module.findSymbolByName("verifyApkSign").getAddress(), new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
RegisterContext context = emulator.getContext();
emulator.attach().addBreakPoint(context.getLRPointer().peer, new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
emulator.getBackend().reg_write(ArmConst.UC_ARM_REG_R0, 0);
return true;
}
});
return true;
}
});
}
我们的
四、替换函数
1.Frida
const verifyApkSignPtr = Module.findExportByName("libhookinunidbg.so", "verifyApkSign");
Interceptor.replace(verifyApkSignPtr, new NativeCallback(() => {
console.log("replace verifyApkSign Function")
return 0;
}, 'void', []));
2. 第三方Hook 框架
这里只演示
public void ReplaceFuncByHookZz(){
HookZz hook = HookZz.getInstance(emulator);
hook.replace(module.findSymbolByName("verifyApkSign").getAddress(), new ReplaceCallback() {
@Override
public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) {
emulator.getBackend().reg_write(Unicorn.UC_ARM_REG_R0,0);
return HookStatus.RET(emulator,context.getLR());
}
});
}
R0 赋值为0 LR 赋值给PC ,这意味着函数一行不执行就返回了,又因为R0 赋值0 所以返回值为0 。
3.Console Debugger
public void ReplaceFuncByConsoleDebugger(){
emulator.attach().addBreakPoint(module.findSymbolByName("verifyApkSign").getAddress(), new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
System.out.println("替换函数 verifyApkSign");
RegisterContext registerContext = emulator.getContext();
emulator.getBackend().reg_write(ArmConst.UC_ARM_REG_PC, registerContext.getLRPointer().peer);
emulator.getBackend().reg_write(ArmConst.UC_ARM_REG_R0, 0);
return true;
}
});
}
非常清晰易懂。
五、Call 函数
分析具体算法时,常需要对其进行主动调用,进行更灵活和细致的分析
// TODO
1.Frida
2.Unidbg
六、Patch 与内存检索
1.Patch
patch 二进制文件- 在内存里
patch


需要注意的是,本文只讨论了
ⅠFrida
① 方法一
var str_name_so = "libhookinunidbg.so";
var n_addr_func_offset = 0x8CA;
var n_addr_so = Module.findBaseAddress(str_name_so);
var n_addr_assemble = n_addr_so.add(n_addr_func_offset);
Memory.protect(n_addr_assemble, 4, 'rwx');
n_addr_assemble.writeByteArray([0x00, 0x20, 0x00, 0xBF]);
但这并不是最佳实践,因为相较于
- 是否存在多线程操纵目标地址处的内存?是否有冲突
arm 的缓存刷新机制
所以
② 方法二
var str_name_so = "libhookinunidbg.so";
var n_addr_func_offset = 0x8CA;
var n_addr_so = Module.findBaseAddress(str_name_so);
var n_addr_assemble = n_addr_so.add(n_addr_func_offset);
Memory.patchCode(n_addr_assemble, 4, function () {
var cw = new ThumbWriter(n_addr_assemble);
cw.putInstruction(0x2000)
cw.putInstruction(0xBF00);
cw.flush();
console.log(hexdump(n_addr_assemble))
});
Ⅱ Unidbg
① 方法一
public void Patch1(){
int patchCode = 0xBF002000;
emulator.getMemory().pointer(module.base + 0x8CA).setInt(0,patchCode);
}
② 方法二
public void Patch2(){
byte[] patchCode = {0x00, 0x20, 0x00, (byte) 0xBF};
emulator.getBackend().mem_write(module.base + 0x8CA, patchCode);
}
③ 方法三
public void Patch3(){
try (Keystone keystone = new Keystone(KeystoneArchitecture.Arm, KeystoneMode.ArmThumb)) {
KeystoneEncoded encoded = keystone.assemble("movs r0,0;nop");
byte[] patchCode = encoded.getMachineCode();
emulator.getMemory().pointer(module.base + 0x8CA).write(0, patchCode, 0, patchCode.length);
}
}
2. 内存检索
假设
搜索特征片段依据需求,可能是搜索函数开头十字节,也可能是搜索目标地址上下字节或者其他。

ⅠFrida
function searchAndPatch() {
var module = Process.findModuleByName("libhookinunidbg.so");
var pattern = "80 b5 6f 46 84 b0 03 90 02 91"
var matches = Memory.scanSync(module.base, module.size, pattern);
console.log(matches.length)
if (matches.length !== 0)
{
var n_addr_assemble = matches[0].address.add(10);
Memory.patchCode(n_addr_assemble, 4, function () {
var cw = new ThumbWriter(n_addr_assemble);
cw.putInstruction(0x2000)
cw.putInstruction(0xBF00);
cw.flush();
console.log(hexdump(n_addr_assemble))
});
}
}
setImmediate(searchAndPatch);
Ⅱ Unidbg
public void SearchAndPatch(){
byte[] patterns = {(byte) 0x80, (byte) 0xb5,0x6f,0x46, (byte) 0x84, (byte) 0xb0,0x03, (byte) 0x90,0x02, (byte) 0x91};
Collection<Pointer> pointers = searchMemory(module.base, module.base+module.size, patterns);
if(pointers.size() > 0){
try (Keystone keystone = new Keystone(KeystoneArchitecture.Arm, KeystoneMode.ArmThumb)) {
KeystoneEncoded encoded = keystone.assemble("movs r0,0;nop");
byte[] patchCode = encoded.getMachineCode();
((ArrayList<Pointer>) pointers).get(0).write(10, patchCode, 0, patchCode.length);
}
}
}
private Collection<Pointer> searchMemory(long start, long end, byte[] data) {
List<Pointer> pointers = new ArrayList<>();
for (long i = start, m = end - data.length; i < m; i++) {
byte[] oneByte = emulator.getBackend().mem_read(i, 1);
if (data[0] != oneByte[0]) {
continue;
}
if (Arrays.equals(data, emulator.getBackend().mem_read(i, data.length))) {
pointers.add(UnidbgPointer.pointer(emulator, i));
i += (data.length - 1);
}
}
return pointers;
}
值得一提的是,本节的内容也可用 LIEF
七、Hook 时机过晚问题
上文中,
var android_dlopen_ext = Module.findExportByName(null, "android_dlopen_ext");
if (android_dlopen_ext != null) {
Interceptor.attach(android_dlopen_ext, {
onEnter: function (args) {
this.hook = false;
var soName = args[0].readCString();
if (soName.indexOf("libhookinunidbg.so") !== -1) {
this.hook = true;
}
},
onLeave: function (retval) {
if (this.hook) {
this.hook = false;
}
}
});
}
但如果
在
以我们的
extern "C" void _init(void) {
char str1[15];
char str2[15];
int ret;
strcpy(str1, "abcdef");
strcpy(str2, "ABCDEF");
ret = strcmp(str1, str2);
if(ret < 0)
{
LOGI("str1 小于 str2");
}
else if(ret > 0)
{
LOGI("str1 大于 str2");
}
else
{
LOGI("str1 等于 str2");
}
}
当前显示
1. 提前加载libc
提前加载
package com.tutorial;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.Module;
import com.github.unidbg.arm.context.RegisterContext;
import com.github.unidbg.debugger.BreakPointCallback;
import com.github.unidbg.hook.hookzz.*;
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.memory.Memory;
import unicorn.ArmConst;
import java.io.File;
public class hookInUnidbg {
private final AndroidEmulator emulator;
private final VM vm;
private final Module module;
private final Module moduleLibc;
hookInUnidbg() {
emulator = AndroidEmulatorBuilder.for32Bit().build();
final Memory memory = emulator.getMemory();
memory.setLibraryResolver(new AndroidResolver(23));
vm = emulator.createDalvikVM(new File("unidbg-android/src/test/resources/tutorial/hookinunidbg.apk"));
DalvikModule dmLibc = vm.loadLibrary(new File("unidbg-android/src/main/resources/android/sdk23/lib/libc.so"), true);
moduleLibc = dmLibc.getModule();
hookStrcmpByUnicorn();
DalvikModule dm = vm.loadLibrary("hookinunidbg", true);
module = dm.getModule();
dm.callJNI_OnLoad(emulator);
}
public void call(){
DvmClass dvmClass = vm.resolveClass("com/example/hookinunidbg/MainActivity");
String methodSign = "call()V";
DvmObject<?> dvmObject = dvmClass.newObject(null);
dvmObject.callJniMethodObject(emulator, methodSign);
}
public static void main(String[] args) {
hookInUnidbg mydemo = new hookInUnidbg();
mydemo.call();
}
public void hookStrcmpByUnicorn(){
emulator.attach().addBreakPoint(moduleLibc.findSymbolByName("strcmp").getAddress(), new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
RegisterContext registerContext = emulator.getContext();
String arg1 = registerContext.getPointerArg(0).getString(0);
emulator.attach().addBreakPoint(registerContext.getLRPointer().peer, new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
if(arg1.equals("abcdef")){
emulator.getBackend().reg_write(ArmConst.UC_ARM_REG_R0, -1);
}
return true;
}
});
return true;
}
});
}
public void hookStrcmpByHookZz(){
IHookZz hookZz = HookZz.getInstance(emulator);
hookZz.enable_arm_arm64_b_branch();
hookZz.wrap(moduleLibc.findSymbolByName("strcmp"), new WrapCallback<HookZzArm32RegisterContext>() {
String arg1;
@Override
public void preCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
arg1 = ctx.getPointerArg(0).getString(0);
}
@Override
public void postCall(Emulator<?> emulator, HookZzArm32RegisterContext ctx, HookEntryInfo info) {
if(arg1.equals("abcdef")){
ctx.setR0(-1);
}
}
});
hookZz.disable_arm_arm64_b_branch();
}
}
但如果想

2. 固定地址下断点
这是最常用也最方便的方式,但只有
通过 vm.loadLibrary
加载的第一个用户
package com.tutorial;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.Module;
import com.github.unidbg.arm.context.RegisterContext;
import com.github.unidbg.debugger.BreakPointCallback;
import com.github.unidbg.hook.hookzz.*;
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.memory.Memory;
import unicorn.ArmConst;
import java.io.File;
public class hookInUnidbg {
private final AndroidEmulator emulator;
private final VM vm;
private final Module module;
private Module moduleLibc;
hookInUnidbg() {
emulator = AndroidEmulatorBuilder.for32Bit().build();
final Memory memory = emulator.getMemory();
memory.setLibraryResolver(new AndroidResolver(23));
vm = emulator.createDalvikVM(new File("unidbg-android/src/test/resources/tutorial/hookinunidbg.apk"));
emulator.attach().addBreakPoint(0x40000000 + 0x978);
DalvikModule dm = vm.loadLibrary("hookinunidbg", true);
module = dm.getModule();
dm.callJNI_OnLoad(emulator);
}
public void call(){
DvmClass dvmClass = vm.resolveClass("com/example/hookinunidbg/MainActivity");
String methodSign = "call()V";
DvmObject<?> dvmObject = dvmClass.newObject(null);
dvmObject.callJniMethodObject(emulator, methodSign);
}
public static void main(String[] args) {
hookInUnidbg mydemo = new hookInUnidbg();
mydemo.call();
}
}

如果加载了多个用户
3. 使用Unidbg 提供的模块监听器
实现自己的模块监听器
package com.tutorial;
import com.github.unidbg.Emulator;
import com.github.unidbg.Module;
import com.github.unidbg.ModuleListener;
import com.github.unidbg.arm.context.RegisterContext;
import com.github.unidbg.hook.hookzz.HookEntryInfo;
import com.github.unidbg.hook.hookzz.HookZz;
import com.github.unidbg.hook.hookzz.InstrumentCallback;
public class MyModuleListener implements ModuleListener {
private HookZz hook;
@Override
public void onLoaded(Emulator<?> emulator, Module module) {
if(module.name.equals("libc.so")){
hook = HookZz.getInstance(emulator);
}
if(module.name.equals("libhookinunidbg.so")){
hook.instrument(module.base + 0x978 + 1, new InstrumentCallback<RegisterContext>() {
@Override
public void dbiCall(Emulator<?> emulator, RegisterContext ctx, HookEntryInfo info) {
System.out.println(ctx.getIntArg(0));
}
});
}
}
}
通过memory.addModuleListener
绑定。
package com.tutorial;
import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Module;
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.memory.Memory;
import java.io.File;
public class hookInUnidbg{
private final AndroidEmulator emulator;
private final VM vm;
hookInUnidbg() {
emulator = AndroidEmulatorBuilder.for32Bit().build();
final Memory memory = emulator.getMemory();
memory.addModuleListener(new MyModuleListener());
memory.setLibraryResolver(new AndroidResolver(23));
vm = emulator.createDalvikVM(new File("unidbg-android/src/test/resources/tutorial/hookinunidbg.apk"));
DalvikModule dm = vm.loadLibrary("hookinunidbg", true);
Module module = dm.getModule();
dm.callJNI_OnLoad(emulator);
}
public void call(){
DvmClass dvmClass = vm.resolveClass("com/example/hookinunidbg/MainActivity");
String methodSign = "call()V";
DvmObject<?> dvmObject = dvmClass.newObject(null);
dvmObject.callJniMethodObject(emulator, methodSign);
}
public static void main(String[] args) {
hookInUnidbg mydemo = new hookInUnidbg();
mydemo.call();
}
}
每种方法都有对应使用场景,按需使用。除此之外也可以修改
八、条件断点
在算法分析时,条件断点可以减少干扰信息。以
1. 限定于某SO
ⅠFrida
Interceptor.attach(
Module.findExportByName("libc.so", "strcmp"), {
onEnter: function(args) {
var moduleName = Process.getModuleByAddress(this.returnAddress).name;
console.log("strcmp arg1:"+args[0].readCString())
console.log("call from :"+moduleName)
},
onLeave: function(ret) {
}
}
);
Ⅱ Unidbg
public void hookstrcmp(){
long address = module.findSymbolByName("strcmp").getAddress();
emulator.attach().addBreakPoint(address, new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
RegisterContext registerContext = emulator.getContext();
String arg1 = registerContext.getPointerArg(0).getString(0);
String moduleName = emulator.getMemory().findModuleByAddress(registerContext.getLRPointer().peer).name;
if(moduleName.equals("libhookinunidbg.so")){
System.out.println("strcmp arg1:"+arg1);
}
return true;
}
});
}
2. 限定于某函数
比如某个函数在
ⅠFrida
var show = false;
Interceptor.attach(
Module.findExportByName("libc.so", "strcmp"), {
onEnter: function(args) {
if(show){
console.log("strcmp arg1:"+args[0].readCString())
}
},
onLeave: function(ret) {
}
}
);
Interceptor.attach(
Module.findExportByName("libhookinunidbg.so", "targetfunction"),{
onEnter: function(args) {
show = this;
},
onLeave: function(ret) {
show = false;
}
}
)
Ⅱ Unidbg
public void hookstrcmp(){
emulator.attach().addBreakPoint(module.findSymbolByName("targetfunction").getAddress(), new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
RegisterContext registerContext = emulator.getContext();
show = true;
emulator.attach().addBreakPoint(registerContext.getLRPointer().peer, new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
show = false;
return true;
}
});
return true;
}
});
emulator.attach().addBreakPoint(module.findSymbolByName("strcmp").getAddress(), new BreakPointCallback() {
@Override
public boolean onHit(Emulator<?> emulator, long address) {
RegisterContext registerContext = emulator.getContext();
String arg1 = registerContext.getPointerArg(0).getString(0);
if(show){
System.out.println("strcmp arg1:"+arg1);
}
return true;
}
});
}
3. 限定于某处

比如上图,只关注
另一个办法是
emulator.attach().addBreakPoint(module, 0xA00);
emulator.attach().addBreakPoint(module, 0xA04);
一定要掌握这些知识,并做到灵活变通。在实战中,诸如 “
九、系统调用拦截——以时间为例
这里说的系统调用拦截,并不是要对系统调用进行
有两个问题需要解释
-
为什么要修改系统调用?
Unidbg 中部分系统调用没实现或者没实现好,以及有时候想要固定其输出,比如获取时间的系统调用,这些需求需要我们修复或修改Unidbg 中系统调用的实现。 -
为什么不直接修改
Unidbg 源码1 是灵活性较差,2 是我们的实现或修改并不是完美的,直接改Unidbg 源码是对运行环境的污染,影响其他项目。
在分析算法时,输入不变的前提下,如果输出在不停变化,会干扰算法分析,这种情况的一大来源是时间戳参与了运算。在
1.Frida
hook time
var time = Module.findExportByName(null, "time");
if (time != null) {
Interceptor.attach(time, {
onEnter: function (args) {
},
onLeave: function (retval) {
retval.replace(100);
}
})
}
hook gettimeofday
function hook_gettimeofday() {
var addr_gettimeofday = Module.findExportByName(null, "gettimeofday");
var gettimeofday = new NativeFunction(addr_gettimeofday, "int", ["pointer", "pointer"]);
Interceptor.replace(addr_gettimeofday, new NativeCallback(function (ptr_tz, ptr_tzp) {
var result = gettimeofday(ptr_tz, ptr_tzp);
if (result == 0) {
console.log("hook gettimeofday:", ptr_tz, ptr_tzp, result);
var t = new Int32Array(ArrayBuffer.wrap(ptr_tz, 8));
t[0] = 0xAAAA;
t[1] = 0xBBBB;
console.log(hexdump(ptr_tz));
}
return result;
}, "int", ["pointer", "pointer"]));
}
但
2.Unidbg
首先实现时间相关的系统调用处理器,其中的
package com.tutorial;
import com.github.unidbg.Emulator;
import com.github.unidbg.linux.ARM32SyscallHandler;
import com.github.unidbg.memory.SvcMemory;
import com.github.unidbg.pointer.UnidbgPointer;
import com.github.unidbg.unix.struct.TimeVal32;
import com.github.unidbg.unix.struct.TimeZone;
import com.sun.jna.Pointer;
import unicorn.ArmConst;
import java.util.Calendar;
public class TimeSyscallHandler extends ARM32SyscallHandler {
public TimeSyscallHandler(SvcMemory svcMemory) {
super(svcMemory);
}
@Override
protected boolean handleUnknownSyscall(Emulator emulator, int NR) {
switch (NR) {
case 78:
mygettimeofday(emulator);
return true;
case 263:
myclock_gettime(emulator);
return true;
}
return super.handleUnknownSyscall(emulator, NR);
}
private void mygettimeofday(Emulator<?> emulator) {
Pointer tv = UnidbgPointer.register(emulator, ArmConst.UC_ARM_REG_R0);
Pointer tz = UnidbgPointer.register(emulator, ArmConst.UC_ARM_REG_R1);
emulator.getBackend().reg_write(ArmConst.UC_ARM_REG_R0, mygettimeofday(tv, tz));
};
private int mygettimeofday(Pointer tv, Pointer tz) {
long currentTimeMillis = System.currentTimeMillis();
long tv_sec = currentTimeMillis / 1000;
long tv_usec = (currentTimeMillis % 1000) * 1000;
TimeVal32 timeVal = new TimeVal32(tv);
timeVal.tv_sec = (int) tv_sec;
timeVal.tv_usec = (int) tv_usec;
timeVal.pack();
if (tz != null) {
Calendar calendar = Calendar.getInstance();
int tz_minuteswest = -(calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET)) / (60 * 1000);
TimeZone timeZone = new TimeZone(tz);
timeZone.tz_minuteswest = tz_minuteswest;
timeZone.tz_dsttime = 0;
timeZone.pack();
}
return 0;
}
private static final int CLOCK_REALTIME = 0;
private static final int CLOCK_MONOTONIC = 1;
private static final int CLOCK_THREAD_CPUTIME_ID = 3;
private static final int CLOCK_MONOTONIC_RAW = 4;
private static final int CLOCK_MONOTONIC_COARSE = 6;
private static final int CLOCK_BOOTTIME = 7;
private final long nanoTime = System.nanoTime();
private int myclock_gettime(Emulator<?> emulator) {
int clk_id = emulator.getBackend().reg_read(ArmConst.UC_ARM_REG_R0).intValue();
Pointer tp = UnidbgPointer.register(emulator, ArmConst.UC_ARM_REG_R1);
long offset = clk_id == CLOCK_REALTIME ? System.currentTimeMillis() * 1000000L : System.nanoTime() - nanoTime;
long tv_sec = offset / 1000000000L;
long tv_nsec = offset % 1000000000L;
switch (clk_id) {
case CLOCK_REALTIME:
case CLOCK_MONOTONIC:
case CLOCK_MONOTONIC_RAW:
case CLOCK_MONOTONIC_COARSE:
case CLOCK_BOOTTIME:
tp.setInt(0, (int) tv_sec);
tp.setInt(4, (int) tv_nsec);
return 0;
case CLOCK_THREAD_CPUTIME_ID:
tp.setInt(0, 0);
tp.setInt(4, 1);
return 0;
}
throw new UnsupportedOperationException("clk_id=" + clk_id);
}
}
在自己的模拟器上使用它,原来模拟器创建是这么一句
emulator = AndroidEmulatorBuilder.for32Bit().build();
修改如下
AndroidEmulatorBuilder builder = new AndroidEmulatorBuilder(false) {
public AndroidEmulator build() {
return new AndroidARMEmulator(processName, rootDir,
backendFactories) {
@Override
protected UnixSyscallHandler<AndroidFileIO>
createSyscallHandler(SvcMemory svcMemory) {
return new TimeSyscallHandler(svcMemory);
}
};
}
};
emulator = builder.build();
十、Hook 检测
Unidbg 自身的多个重大弱点没有解决,比如多线程和信号机制尚未实现。Unidbg 普及率和推广度还不高。
所以本节专注于
1. 检测第三方Hook 框架
基于其
ⅠInline Hook
以我熟悉的
- 函数开头前几个字节是否被篡改
- 函数体是否完整未被修改,常使用
crc32 校验,为什么不用md5 或其他哈希函数?因为crc32 极快,性能影响小,碰撞率又在可接受的范围内
Ⅱ Got Hook
相关项目:
2. 检测Unicorn Based Hook
除此之外,
十一、Unidbg Trace 四件套
基于
在
1.Instruction tracing
令追踪包括两部分
- 记录每条指令的执行,打印地址、机器码、汇编等信息
- 打印每条指令相关的寄存器值
TraceHook traceCode();
TraceHook traceCode(long begin, long end);
TraceHook traceCode(long begin, long end, TraceCodeListener listener);

增加
2.Function Tracing
指令

在

- 如何获得一个
SO 的全部函数列表,就像IDA 一样 - 如何
Hook 函数 - 如何获得调用层级关系,形成树结构
关于问题
def getFunctionList():
functionlist = ""
minLength = 10
maxAddress = ida_ida.inf_get_max_ea()
for func in idautils.Functions(0, maxAddress):
if len(list(idautils.FuncItems(func))) > minLength:
functionName = str(idaapi.ida_funcs.get_func_name(func))
oneFunction = hex(func) + "!" + functionName + "\t\n"
functionlist += oneFunction
return functionlist
脚本获取了函数以及对应函数名列表,同时通过
- 过短的函数可能导致
Frida Hook 失败(inline hook 原理所致) - 过短的函数可能是工具函数,调用次数多,但价值不大,让调用图变得臃肿不堪
完整的
import os
import time
import ida_ida
import ida_nalt
import idaapi
import idautils
from idaapi import plugin_t
from idaapi import PLUGIN_PROC
from idaapi import PLUGIN_OK
def getFunctionList():
functionlist = ""
minLength = 10
maxAddress = ida_ida.inf_get_max_ea()
for func in idautils.Functions(0, maxAddress):
if len(list(idautils.FuncItems(func))) > minLength:
functionName = str(idaapi.ida_funcs.get_func_name(func))
oneFunction = hex(func) + "!" + functionName + "\t\n"
functionlist += oneFunction
return functionlist
def getSoPathAndName():
fullpath = ida_nalt.get_input_file_path()
filepath, filename = os.path.split(fullpath)
return filepath, filename
class getFunctions(plugin_t):
flags = PLUGIN_PROC
comment = "getFunctions"
help = ""
wanted_name = "getFunctions"
wanted_hotkey = ""
def init(self):
print("getFunctions(v0.1) plugin has been loaded.")
return PLUGIN_OK
def run(self, arg):
so_path, so_name = getSoPathAndName()
functionlist = getFunctionList()
script_name = so_name.split(".")[0] + "_functionlist_" + str(int(time.time())) + ".txt"
save_path = os.path.join(so_path, script_name)
with open(save_path, "w", encoding="utf-8") as F:
F.write(functionlist)
F.close()
print(f"location: {save_path}")
def term(self):
pass
def PLUGIN_ENTRY():
return getFunctions()
关于问题
关于问题
Interceptor.attach(Module.getExportByName(null, 'read'), {
onEnter(args) {
console.log('Context information:');
console.log('Context : ' + JSON.stringify(this.context));
console.log('Return : ' + this.returnAddress);
console.log('ThreadId : ' + this.threadId);
console.log('Depth : ' + this.depth);
console.log('Errornr : ' + this.err);
this.fd = args[0].toInt32();
this.buf = args[1];
this.count = args[2].toInt32();
},
onLeave(result) {
console.log('----------')
const numBytes = result.toInt32();
if (numBytes > 0) {
console.log(hexdump(this.buf, { length: numBytes, ansi: true }));
}
console.log('Result : ' + numBytes);
}
})
这三个问题能在
首先问题一,只是一个获取函数列表的插件,与使用
接下来就是问题三,栈回溯这块,
src/main/java/com/github/unidbg/unwind/Unwinder.java
public final int depth(){
int count = 0;
Frame frame = null;
while((frame = unw_step(emulator, frame)) != null) {
if(frame.isFinish()){
return count;
}
count++;
}
return count;
}
接下来三步骤合一,组装代码
PrintStream traceStream = null;
try {
String traceFile = "unidbg-android/src/test/resources/app/traceFunctions.txt";
traceStream = new PrintStream(new FileOutputStream(traceFile), true);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
final PrintStream finalTraceStream = traceStream;
emulator.getBackend().hook_add_new(new BlockHook() {
@Override
public void hookBlock(Backend backend, long address, int size, Object user) {
if(size>8){
Capstone.CsInsn[] insns = emulator.disassemble(address, 4, 0);
if(insns[0].mnemonic.equals("push")){
int level = emulator.getUnwinder().depth();
assert finalTraceStream != null;
for(int i = 0 ; i < level ; i++){
finalTraceStream.print(" | ");
}
finalTraceStream.println(" "+"sub_"+Integer.toHexString((int) (address-module.base))+" ");
}
}
}
@Override
public void onAttach(Unicorn.UnHook unHook) {
}
@Override
public void detach() {
}
}, module.base, module.base+module.size, 0);
可以发现代码非常的简洁优雅,效果也不错

3.Unidbg-FindKey
4.Unidbg-Findcrypt
Findcrypt 处理不了加壳SO Findcrypt 中说存在某种加密,但SO 中并不一定用,我们的目标函数更不一定用。- 从
Findcrypt 提示的常数不一定能找到对应函数,静态交叉分析有局限
// TODO
十二、固定随机数
// TODO
十三、杂项
无需
// TODO