



























https://blog.csdn.net/qq_60933960/article/details/151935127
dobby框架本质就是so层的hook工具,通过编写c++代码替换原有的c++函数,与firda不同的是不用root并且是内部编译进入apk中的持久化的
分为两步,第一步编译库为so ,第二步 将so 拷贝进入你目标安卓项目
注意原仓库的无法编译为安卓so文件,我用第三方仓库编译so,在kali linux中编译通过
https://gitee.com/null_465_7266/dobby
第一步
Dobby 是一个轻量级、跨平台、多架构的 inline hook 框架,支持 Android、iOS、Linux、Windows 等主流操作系统。
操作系统:Linux(推荐 Kali Linux 或 Ubuntu)
NDK 版本:Android NDK r25b
CMake 版本:≥ 3.10
编译器:NDK 自带的 Clang
| 文件 | 作用 |
|---|---|
export ANDROID_NDK_HOME=/path/to/ndk-r25b
cd ~/Desktop/Dobby-Android-main
chmod +x build_android_arm64.sh
rm -rf CMakeFiles CMakeCache.txt Makefile cmake_install.cmake
./build_android_arm64.sh
构建脚本中实际执行的 CMake 命令:
cmake -S . -B build_android_ndk_arm64 \
-DCMAKE_TOOLCHAIN_FILE=android_ndk_fixed.cmake \
-DCMAKE_BUILD_TYPE=Release \
-G "Unix Makefiles"
| 选项 | 值 | 说明 |
|---|---|---|
编译成功后,在 build_android_ndk_arm64/ 目录下生成:
| 文件 | 类型 | 说明 |
|---|---|---|
目标架构:ARM64(arm64-v8a)
最低 Android 平台版本:Android 24(Android 7.0)
C++ 标准:C++11
C++ STL:c++_shared
链接库:链接 Android 日志库(-llog)
libdobby.so 包含以下核心模块:
核心架构支持:CPU 特性、寄存器管理
汇编器:ARM64 架构汇编器
代码生成:ARM64 代码生成器
内存管理:代码缓冲区、内存分配器
指令重定位:ARM64 指令重定位
拦截路由:函数拦截、内联 Hook
平台适配:Android 用户模式支持
内置插件:SymbolResolver 等
总结:按照上述步骤即可完成 Android ARM64 平台的 libdobby.so 编译。
第二步
我创建一个demo
https://gitee.com/null_465_7266/dobbyandroidproject
我这里也有将两步合并为一步的代码
https://gitee.com/null_465_7266/dobbydemo
这是上面代码精简只保留arm64的代码,方便分析原理
https://gitee.com/null_465_7266/dobbyandroid
text
┌─────────────────────────────────────────────────────┐
│ Public API Layer │
│ DobbyHook / DobbyInstrument / DobbyDestroy │
└──────────────────────┬──────────────────────────────┘
│
┌──────────────────────▼──────────────────────────────┐
│ Interceptor │
│ (Hook Entry 管理中心) │
│ 双向链表维护所有HookEntry │
└──────────────────────┬──────────────────────────────┘
│
┌──────────────────────▼──────────────────────────────┐
│ InterceptRouting │
│ FunctionInlineReplace / DynamicBinaryInstrument │
│ (路由调度核心) │
└──────┬───────────────┬──────────────────────────────┘
│ │
┌──────▼──────┐ ┌──────▼──────────────────────────────┐
│ Trampoline │ │ ClosureTrampolineBridge │
│ 跳板生成 │ │ 闭包跳板+寄存器上下文保存/恢复 │
└──────┬──────┘ └──────┬──────────────────────────────┘
│ │
┌──────▼───────────────▼──────────────────────────────┐
│ InstructionRelocation │
│ 指令重定位引擎 │
└──────────────────────┬──────────────────────────────┘
│
┌──────────────────────▼──────────────────────────────┐
│ Assembler / CodeGen / CodeBuffer / MemoryArena │
│ 底层汇编/内存管理基础设施 │
└─────────────────────────────────────────────────────┘
C
typedef struct {
int id;
int type; // kFunctionWrapper / kFunctionInlineHook / kDynamicBinaryInstrument
union {
void *target_address;
void *function_address;
void *instruction_address;
};
void *route; // 指向对应的InterceptRouting子类实例
union {
void *relocated_origin_instructions; // 重定位后的原始指令地址
void *relocated_origin_function;
};
AssemblyCodeChunkBuffer origin_chunk_; // 保存被覆盖的原始字节
} HookEntry;
关键设计:
origin_chunk_保存原始指令字节,用于DobbyDestroy时恢复relocated_origin_instructions指向重定位后的代码,供Hook返回时执行原始逻辑C++
// 使用Linux内核风格的双向循环链表管理所有HookEntry
struct list_head hook_entry_list_; // 链表头
// 核心操作
FindHookEntry(void *address) // 按地址查找
AddHookEntry(HookEntry *entry) // 插入链表头部
RemoveHookEntry(void *address) // 从链表删除
以DobbyHook(address, replace_call, origin_call)为例,完整执行流程:
text
DobbyHook()
│
├─1─► 创建 HookEntry
│ type = kFunctionInlineHook
│ function_address = address
│
├─2─► 创建 FunctionInlineReplaceRouting
│
├─3─► route->Prepare() [空实现,留给子类扩展]
│
├─4─► route->DispatchRouting()
│ │
│ ├─► BuildReplaceRouting()
│ │ SetTrampolineTarget(replace_call)
│ │ GenerateTrampolineBuffer(target, replace_call)
│ │ │
│ │ └─► 生成跳转到replace_call的机器码
│ │ (arm64: adrp+add+br 或 ldr+br)
│ │ (x64: jmp [rip+offset])
│ │
│ └─► GenerateRelocatedCode(tramp_size)
│ │
│ ├─► 读取target处原始指令(tramp_size字节)
│ ├─► 调用 GenRelocateCodeAndBranch()
│ │ 重定位PC相关指令 + 末尾追加跳回原函数的branch
│ └─► 保存原始字节到 origin_chunk_
│
├─5─► Interceptor::AddHookEntry(entry)
│
├─6─► *origin_call = entry->relocated_origin_function
│
└─7─► route->Commit() => route->Active()
CodePatch(target_address, trampoline_buffer, size)
[实际写入跳转指令,劫持控制流]
这是整个框架最精密的部分。ARM64使用PC相对寻址,搬移指令时必须修正。
text
┌──────────────────────┬────────────────────────────────────┐
│ 指令类型 │ 重定位策略 │
├──────────────────────┼────────────────────────────────────┤
│ LDR (literal) │ Mov(TMP, abs_addr) + LDR [TMP, 0] │
│ ADR / ADRP │ Mov(Xrd, runtime_address) │
│ B / BL │ Ldr(TMP, label) + BR/BLR TMP │
│ TBZ / TBNZ │ 反转条件 + 跳过 + Ldr + BR │
│ CBZ / CBNZ │ 反转条件 + 跳过 + Ldr + BR │
│ B.cond │ 反转条件 + 跳过 + Ldr + BR │
│ 其他指令 │ 原样复制 │
└──────────────────────┴────────────────────────────────────┘
以CBZ X0, #far_target为例,原始范围±1MB,重定位后需跳到任意地址:
asm
; 原始代码(在原地址)
CBZ X0, #far_target ; 如果X0==0,跳到far_target
; 重定位后代码(在新地址)
CBNZ X0, #skip ; 条件取反:如果X0!=0,跳过branch块
LDR X17, =far_target ; 加载绝对地址到临时寄存器
BR X17 ; 无条件跳转
skip:
; ... 继续执行下一条重定位指令
关键细节:条件取反(op ^ 1),固定偏移为4 * 3 = 12字节(branch_instr + ldr + br)
C++
// 所有需要重定位的指令处理完后
CodeGen codegen(&turbo_assembler_);
codegen.LiteralLdrBranch(curr_orig_pc); // 跳回原函数剩余部分
根据源地址和目标地址的距离选择策略:
text
距离 < 128MB (adrp范围):
ADRP X17, to_PAGE - from_PAGE ; 4字节
ADD X17, X17, to_PAGEOFF ; 4字节
BR X17 ; 4字节
共12字节
距离 >= 128MB:
LDR X17, #8 ; 4字节,从后面的label加载
BR X17 ; 4字节
.quad target_addr ; 8字节
共16字节
当启用dobby_enable_near_branch_trampoline()时,只需4字节:
asm
B #offset ; 单条B指令,范围±128MB
如果目标超出范围,则在±128MB内分配一个快速转发跳板(fast forward trampoline),形成二级跳转:
text
原函数 --[B #near]--> 快速转发跳板 --[Adrp+Add+Br]--> 目标地址
使用RIP相对间接跳转(6字节):
asm
FF 25 XX XX XX XX ; JMP [RIP + offset]
; offset指向存有绝对目标地址的内存位置
这是DobbyInstrument(DBI模式)的核心,用于在不知道具体目标函数的情况下,将任意Hook点的上下文打包传递给用户回调。
text
ClosureTrampolineEntry {
void *address; // 跳板代码地址
void *carry_data; // 携带的数据(HookEntry*)
void *carry_handler; // 处理函数(instrument_routing_dispatch)
}
text
原函数入口
│
▼
[ClosureTrampoline 代码]
│ SUB SP, SP, #16 ; 分配栈空间
│ STR X30, [SP, #8] ; 保存LR
│ LDR X17, =entry ; 加载ClosureTrampolineEntry*
│ STR X17, [SP, #0] ; 压栈(作为closure_bridge的参数)
│ LDR X17, =closure_bridge ; 加载closure_bridge地址
│ BLR X17 ; 调用closure_bridge(执行后X17=next_hop)
│ LDR X30, [SP, #8] ; 恢复LR
│ ADD SP, SP, #16 ; 释放栈
│ BR X17 ; 跳转到next_hop(重定位后的原始指令)
▼
[closure_bridge 代码](动态生成)
│ ; 保存完整寄存器上下文
│ SUB SP, SP, #(8*16) ; 为q0-q7分配空间
│ STP Q6, Q7, [SP, #96]
│ ... (保存所有浮点寄存器)
│ SUB SP, SP, #(30*8) ; 为x1-x30分配空间
│ STP X29, X30, [SP, #224]
│ ... (保存所有通用寄存器)
│ SUB SP, SP, #16
│ STR X0, [SP, #8] ; 保存X0
│ ; 计算原始SP
│ ADD X17, SP, #(2*8 + 2*8 + 30*8 + 8*16)
│ SUB SP, SP, #16
│ STR X17, [SP, #8] ; 保存原始SP
│
│ MOV X0, SP ; arg1 = RegisterContext*
│ LDR X1, [SP, #REGISTER_CONTEXT_SIZE] ; arg2 = ClosureTrampolineEntry*
│ CALL intercept_routing_common_bridge_handler
│
│ ; 恢复寄存器
│ ADD SP, SP, #16 ; 释放SP占位
│ LDR X0, [SP, #8]
│ ADD SP, SP, #16 ; 释放X0占位
│ LDP X1, X2, [SP], #16 ; 恢复x1-x30
│ ... (恢复所有寄存器)
│ LDP Q0, Q1, [SP], #32 ; 恢复浮点寄存器
│ ...
│ RET ; 返回ClosureTrampoline(X17已是next_hop)
text
高地址
┌──────────────────┐ ← 原始SP
│ 原始SP(8字节) │
│ dummy(8字节) │
├──────────────────┤
│ x0(8字节) │
│ dummy(8字节) │
├──────────────────┤
│ x1...x28(29*8)│
├──────────────────┤
│ fp=x29(8字节) │
│ lr=x30(8字节) │
├──────────────────┤
│ q0...q7(8*16) │
低地址(当前SP)
对应dobby.h中的RegisterContext结构体,用户回调可直接读写寄存器。
text
instrument_routing_dispatch(ctx, closure_trampoline_entry)
│
├─► entry = closure_trampoline_entry->carry_data (HookEntry*)
│
└─► instrument_call_forward_handler(ctx, entry)
│
├─► route = entry->route (DynamicBinaryInstrumentRouting*)
│
├─► 调用用户的 handler(ctx, &entry_info)
│ 用户可在此读写所有寄存器
│
└─► set_routing_bridge_next_hop(ctx, entry->relocated_origin_instructions)
// 设置 ctx->general.x[17] = relocated_addr
// closure_bridge结束后BR X17跳到重定位后的原始指令
text
page_chunks (LiteMutableArray)
│
├─► PageChunk_1 (kReadExecute, 4KB)
│ page_cursor → 当前分配位置
│ chunks[] → [CodeChunk1, CodeChunk2, ...]
│
└─► PageChunk_2 (kReadWrite, 4KB)
...
分配流程:
mmap分配新页(4KB)page_cursor处划分内存块在pos ± alloc_range范围内寻找可用内存,两种策略:
策略1:寻找空白页面(search_near_blank_page)
text
遍历进程内存布局 → 找相邻region之间的空洞 → mmap分配
策略2:寻找代码洞(search_near_blank_memory_chunk)
text
在已有可执行页面中 → 用memmem搜索连续零字节 → 直接写入
(适用于无法分配新页的限制环境)
text
LiteMutableBuffer(动态扩容缓冲区)
└─► CodeBufferBase(添加Emit8/16/32/64)
├─► CodeBuffer(ARM) : +EmitARMInst/EmitThumb1/2Inst
├─► CodeBuffer(ARM64) : +EmitInst/Emit64/FixBindLabel
├─► CodeBuffer(x86) : +Emit32/FixBindLabel
└─► CodeBuffer(x64) : +Emit32/Emit64/FixBindLabel
C++
mprotect(page, size, PROT_READ|PROT_WRITE|PROT_EXEC) // 改权限
memcpy(target, buffer, size) // 写入
mprotect(page, size, PROT_READ|PROT_EXEC) // 恢复权限
ClearCache(start, end) // 清指令缓存
iOS不允许直接修改可执行页,使用mach_vm_remap技巧:
text
1. mmap 分配新匿名页(dummy_page)
2. memcpy 原始页内容到 dummy_page
3. 在 dummy_page 上 patch 目标字节
4. mprotect dummy_page 为 READ|EXEC
5. mach_vm_remap(self_port, &target_page, ..., dummy_page, ...)
→ 将 dummy_page 重映射覆盖到 target_page 的虚拟地址
→ 绕过 W^X 限制
6. munmap dummy_page
7. ClearCache
C
// 1. 清D-cache(数据缓存到统一点)
for (addr = start; addr < end; addr += dcache_line_size)
"dc cvau, addr"
// 2. 数据同步屏障
"dsb ish"
// 3. 清I-cache(指令缓存)
for (addr = start; addr < end; addr += icache_line_size)
"ic ivau, addr"
// 4. 指令同步屏障
"isb sy"
Dobby实现了一个两阶段label绑定系统:
text
Label状态机:
unused (pos_=0)
│ link_to(pos)
▼
linked (pos_>0) ←─── link_to(pos) 形成链表
│ bind_to(pos)
▼
bound (pos_<0)
PseudoLabel扩展了Label,记录所有引用该label的指令位置:
C++
// 前向引用场景:
Ldr(X17, &label) // 此时label未绑定,记录当前位置到instructions_[]
...
PseudoBind(&label) // 绑定label
EmitInt64(data) // 发射实际数据
│
└─► link_confused_instructions()
// 回填之前所有LDR指令的偏移量
encoded = inst32 & 0xFF00001F
encoded |= LeftShift((offset >> 2), 19, 5)
FixBindLabel(position, encoded)
| 架构 | 普通跳板大小 | 指令序列 | 近跳板大小 |
|---|---|---|---|
C++
PUBLIC int DobbyDestroy(void *address) {
HookEntry *entry = Interceptor::SharedInstance()->FindHookEntry(address);
if (entry) {
uint8_t *buffer = entry->origin_chunk_.chunk_buffer; // 原始字节
uint32_t size = entry->origin_chunk_.chunk.length; // 原始大小
// ARM需要去掉Thumb标记位(最低bit)
#if TARGET_ARCH_ARM
address = (void*)((addr_t)address - 1);
#endif
CodePatch(address, buffer, size); // 恢复原始指令
Interceptor::SharedInstance()->RemoveHookEntry(address);
return RT_SUCCESS;
}
return RT_FAILED;
}
| 设计点 | 实现方案 | 原因 |
|---|---|---|
免责声明 本文档所有内容仅供安全研究、学术交流与技术学习使用,严禁用于任何未经授权的逆向破解、网络攻击、隐私窃取、恶意软件开发及其他违反《中华人民共和国网络安全法》《数据安全法》等法律法规的行为,使用者应确保已获得目标软件权利人的合法授权并自行承担因使用本文档内容所产生的一切法律责任与后果,作者不对任何直接或间接损害承担任何责任,继续阅读即视为您已知悉并同意上述全部条款。
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。