GKLBB

当你经历了暴风雨,你也就成为了暴风雨

导航

应用安全 --- 安卓逆向 之 dobby框架

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

 第一步

Android ARM64 平台 libdobby.so 编译过程总结

1. 项目概述

Dobby 是一个轻量级、跨平台、多架构的 inline hook 框架,支持 Android、iOS、Linux、Windows 等主流操作系统。

2. 编译环境准备

  • 操作系统:Linux(推荐 Kali Linux 或 Ubuntu)

  • NDK 版本:Android NDK r25b

  • CMake 版本:≥ 3.10

  • 编译器:NDK 自带的 Clang

3. 关键配置文件

 
文件作用
CMakeLists.txt 主 CMake 构建配置
android_ndk_fixed.cmake Android NDK 工具链配置(修复版)
build_android_arm64.sh 自动化构建脚本

4. 完整编译步骤

步骤 1:设置环境变量

bash
export ANDROID_NDK_HOME=/path/to/ndk-r25b

步骤 2:准备构建脚本

bash
cd ~/Desktop/Dobby-Android-main
chmod +x build_android_arm64.sh

步骤 3:清理旧构建文件(重要)

bash
rm -rf CMakeFiles CMakeCache.txt Makefile cmake_install.cmake

步骤 4:运行构建脚本

bash
./build_android_arm64.sh

5. CMake 配置详情

构建脚本中实际执行的 CMake 命令:

bash
cmake -S . -B build_android_ndk_arm64 \
    -DCMAKE_TOOLCHAIN_FILE=android_ndk_fixed.cmake \
    -DCMAKE_BUILD_TYPE=Release \
    -G "Unix Makefiles"

6. 编译选项配置

 
选项说明
CMAKE_BUILD_TYPE Release 发布模式编译
DOBBY_GENERATE_SHARED ON 生成共享库
DOBBY_DEBUG OFF 关闭调试日志
NearBranch ON 启用近分支跳板
Plugin.SymbolResolver ON 启用符号解析器
Plugin.Android.BionicLinkerUtil ON 启用 Android Bionic 链接器工具

7. 编译产物

编译成功后,在 build_android_ndk_arm64/ 目录下生成:

 
文件类型说明
libdobby.so 共享库 Android ARM64 动态链接库
libdobby.a 静态库 Android ARM64 静态链接库

8. 技术要点

  • 目标架构:ARM64(arm64-v8a)

  • 最低 Android 平台版本:Android 24(Android 7.0)

  • C++ 标准:C++11

  • C++ STL:c++_shared

  • 链接库:链接 Android 日志库(-llog

9. 源文件构成

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

 

 

image

 

 

Dobby Hook框架实现原理深度分析

一、整体架构概览

text
┌─────────────────────────────────────────────────────┐
│                   Public API Layer                   │
│         DobbyHook / DobbyInstrument / DobbyDestroy  │
└──────────────────────┬──────────────────────────────┘
┌──────────────────────▼──────────────────────────────┐
│                  Interceptor                         │
│              (Hook Entry 管理中心)                    │
│           双向链表维护所有HookEntry                   │
└──────────────────────┬──────────────────────────────┘
┌──────────────────────▼──────────────────────────────┐
│               InterceptRouting                       │
│    FunctionInlineReplace / DynamicBinaryInstrument  │
│              (路由调度核心)                           │
└──────┬───────────────┬──────────────────────────────┘
       │               │
┌──────▼──────┐ ┌──────▼──────────────────────────────┐
│ Trampoline  │ │    ClosureTrampolineBridge            │
│  跳板生成   │ │  闭包跳板+寄存器上下文保存/恢复       │
└──────┬──────┘ └──────┬──────────────────────────────┘
       │               │
┌──────▼───────────────▼──────────────────────────────┐
│           InstructionRelocation                      │
│              指令重定位引擎                           │
└──────────────────────┬──────────────────────────────┘
┌──────────────────────▼──────────────────────────────┐
│    Assembler / CodeGen / CodeBuffer / MemoryArena    │
│           底层汇编/内存管理基础设施                   │
└─────────────────────────────────────────────────────┘

二、核心数据结构

2.1 HookEntry —— Hook的基本信息单元

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返回时执行原始逻辑

2.2 Interceptor —— 单例Hook管理器

C++
// 使用Linux内核风格的双向循环链表管理所有HookEntry
struct list_head hook_entry_list_;  // 链表头

// 核心操作
FindHookEntry(void *address)   // 按地址查找
AddHookEntry(HookEntry *entry) // 插入链表头部
RemoveHookEntry(void *address) // 从链表删除

三、DobbyHook核心流程

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为例)

这是整个框架最精密的部分。ARM64使用PC相对寻址,搬移指令时必须修正。

4.1 需要重定位的指令类型

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          │
│ 其他指令              │ 原样复制                            │
└──────────────────────┴────────────────────────────────────┘

4.2 条件跳转重定位原理

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)

4.3 重定位代码末尾追加回跳

C++
// 所有需要重定位的指令处理完后
CodeGen codegen(&turbo_assembler_);
codegen.LiteralLdrBranch(curr_orig_pc);  // 跳回原函数剩余部分

五、跳板(Trampoline)生成机制

5.1 ARM64普通跳板

根据源地址和目标地址的距离选择策略:

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字节

5.2 ARM64近跳板(NearBranchTrampoline插件)

当启用dobby_enable_near_branch_trampoline()时,只需4字节:

asm
B #offset   ; 单条B指令,范围±128MB

如果目标超出范围,则在±128MB内分配一个快速转发跳板(fast forward trampoline),形成二级跳转:

text
原函数 --[B #near]--> 快速转发跳板 --[Adrp+Add+Br]--> 目标地址

5.3 x64跳板

使用RIP相对间接跳转(6字节):

asm
FF 25 XX XX XX XX    ; JMP [RIP + offset]
                     ; offset指向存有绝对目标地址的内存位置

六、ClosureTrampolineBridge(闭包跳板桥)

这是DobbyInstrument(DBI模式)的核心,用于在不知道具体目标函数的情况下,将任意Hook点的上下文打包传递给用户回调。

6.1 整体结构

text
ClosureTrampolineEntry {
    void *address;        // 跳板代码地址
    void *carry_data;     // 携带的数据(HookEntry*)
    void *carry_handler;  // 处理函数(instrument_routing_dispatch)
}

6.2 ARM64完整执行流程

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)

6.3 RegisterContext内存布局(ARM64)

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结构体,用户回调可直接读写寄存器。


七、DBI路由处理链

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跳到重定位后的原始指令

八、内存管理子系统

8.1 MemoryArena(普通内存池)

text
page_chunks (LiteMutableArray)
    ├─► PageChunk_1 (kReadExecute, 4KB)
    │       page_cursor → 当前分配位置
    │       chunks[] → [CodeChunk1, CodeChunk2, ...]
    └─► PageChunk_2 (kReadWrite, 4KB)
            ...

分配流程:

  1. 遍历已有页面,找到同权限且有剩余空间的页
  2. 没有则mmap分配新页(4KB)
  3. 从页面的page_cursor处划分内存块

8.2 NearMemoryArena(近端内存池)

pos ± alloc_range范围内寻找可用内存,两种策略:

策略1:寻找空白页面(search_near_blank_page)

text
遍历进程内存布局 → 找相邻region之间的空洞 → mmap分配

策略2:寻找代码洞(search_near_blank_memory_chunk)

text
在已有可执行页面中 → 用memmem搜索连续零字节 → 直接写入
(适用于无法分配新页的限制环境)

8.3 CodeBuffer层次结构

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

九、代码修补(CodePatch)多平台实现

9.1 Linux/Android

C++
mprotect(page, size, PROT_READ|PROT_WRITE|PROT_EXEC)  // 改权限
memcpy(target, buffer, size)                            // 写入
mprotect(page, size, PROT_READ|PROT_EXEC)              // 恢复权限
ClearCache(start, end)                                  // 清指令缓存

9.2 macOS/iOS(关键)

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

9.3 ARM64缓存清除关键步骤

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"

十、汇编器Label系统

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)

十一、多架构跳板对比

架构普通跳板大小指令序列近跳板大小
ARM64 12B(近) / 16B(远) ADRP+ADD+BR / LDR+BR+data 4B (B #off)
ARM 8B LDR PC, [PC,-4] + data 4B (B #off)
x86 5B JMP rel32 -
x64 6B+8B JMP [RIP+0] + abs_addr -

十二、DobbyDestroy恢复机制

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;
}

十三、关键设计决策总结

设计点实现方案原因
Hook管理 单例+侵入式双向链表 轻量,无STL依赖
寄存器保存 动态汇编生成 灵活,支持运行时决策
指令重定位 逐条解码+重写 处理所有PC相关指令
iOS写保护绕过 mach_vm_remap 唯一合法绕过W^X的方式
近端内存 进程布局扫描+代码洞 最小化跳板大小
前向label 两阶段绑定+回填 支持不知道目标地址时先emit
跨平台 编译期宏隔离 零运行时开销

 

posted on 2026-04-13 07:47  GKLBB  阅读(35)  评论(0)    收藏  举报