Dobby
是一个全平台
的InlineHook框架
,详情可查看官方文档
编译Dobby
- 将代码clone下来,可以将clone的代码存在自建的目录下。
git clone https://github.com/jmpews/Dobby.git --depth=1
从下载的
Dobby源码
可以看出,Dobby是跨平台框架
,所以项⽬并不是⼀个Xcode⼯程,需要使⽤cmake
将⼯程编译成为Xcode⼯程。进行
Dobby
目录,创建一个新的目录用于存放编译后的文件。
cd Dobby && mkdir build_for_ios_arm64 && cd build_for_ios_arm64
- 使用
cmake
进行编译
cmake .. -G Xcode \
-DCMAKE_TOOLCHAIN_FILE=cmake/ios.toolchain.cmake \
-DPLATFORM=OS64 -DARCHS="arm64" -DCMAKE_SYSTEM_PROCESSOR=arm64 \
-DENABLE_BITCODE=0 -DENABLE_ARC=0 -DENABLE_VISIBILITY=1 -DDEPLOYMENT_TARGET=9.3 \
-DDynamicBinaryInstrument=ON -DNearBranch=ON -DPlugin.SymbolResolver=ON -DPlugin.Darwin.HideLibrary=ON -DPlugin.Darwin.ObjectiveC=ON
编译成功之后显示如下:
编译完成之后,会生成一个Xcode工程,如下图所示:
-
打开
Dobby.xcodeproj
,Xcode工程文件,编译工程生成Framework
。注意,编译Framework时,需要开启bitcode
。
-
编译完成后,⽣成
DobbyX.framework
。
接下来就可以使用
DobbyX.framework
进行HOOK了。
使用Dobby
- 创建工程
InlineDemo
,添加DobbyX.framework
。
注意
DobbyX.framework
拷⻉问题。
将Framework⾸次
拖⼊⼯程,Xcode不会⾃动帮你拷⻉。运⾏时会发现Framework没有打包进⼊App包,造成DYLD
加载时找不到库的错误。
- 选择
Xcode
中的Build Phases
,点击+
,选择New Copy Files Phase
。
- 在
Copy Files
中,将Destination
选择Frameworks
。
- 点击
+
,选择DobbyX.framework
,点击Add
- 添加将要被HOOK的静态函数
int sum(int a,int b){
return a + b;
}
- 定义函数指针,⽤于保存被替换函数的地址
static int (*sum_p)(int a,int b);
- 定义新函数,⽤此函数替换将要HOOK的函数,该函数的返回值及参数必须⼀致
int mySum(int a,int b) {
NSLog(@"Sum:%d,",sum_p(a,b));
return a - b;
}
- 在viewDidLoad方法中,调用
DobbyHook
进行函数的Hook
Dobby
的核心的函数int DobbyHook(void *address, void *replace_call, void **origin_call);
参数说明:
- address:需要HOOK的函数地址
- replace_call:新函数地址
- origin_call:保留原始函数的指针的地址
- (void)viewDidLoad {
[super viewDidLoad];
DobbyHook((void *)sum, mySum, (void *)&sum_p);
}
- 在touchesBegan中,调用sum函数
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(@"Sum:%d",sum(10, 20));
}
- HOOK成功
[*] ================================
[*] Dobby
[*] ================================
[*] dobby in debug log mode, disable with cmake flag "-DDOBBY_DEBUG=OFF"
[*] [DobbyHook] Initialize at 0x1025ddd4c
[*] [trampoline] Generate trampoline buffer 0x1025ddd4c -> 0x1025ddd6c
[*] [insn relocate] origin 0x1025ddd4c - 12
[*] [insn relocate] relocated 0x104bd4000 - 32
[*] [intercept routing] Active patch 0x1025ddd4c
2021-05-21 10:01:37.712129+0800 InlineDemo[3741:3357493] Hook成功了,原函数结果:30
2021-05-21 10:01:37.712525+0800 InlineDemo[3741:3357493] Sum:-10
Dobby原理分析
1、在调用DobbyHook
处打个断点,查看还未HOOK之前,sum
函数的汇编。
我们知道,
DobbyHook
函数的第一个参数就是sum
函数的地址,第二个参数是mySum
函数的地址,因此,可以直接从x0、x1寄存器获取sum
函数和mySum
函数的汇编。
(lldb) register read x0
x0 = 0x0000000100731d4c InlineDemo`sum at ViewController.m:18
(lldb) dis -s 0x0000000100731d4c
InlineDemo`sum:
0x100731d4c <+0>: sub sp, sp, #0x10 ; =0x10
0x100731d50 <+4>: str w0, [sp, #0xc]
0x100731d54 <+8>: str w1, [sp, #0x8]
0x100731d58 <+12>: ldr w8, [sp, #0xc]
0x100731d5c <+16>: ldr w9, [sp, #0x8]
0x100731d60 <+20>: add w0, w8, w9
0x100731d64 <+24>: add sp, sp, #0x10 ; =0x10
0x100731d68 <+28>: ret
(lldb) register read x1
x1 = 0x0000000100731d6c InlineDemo`mySum at ViewController.m:24
(lldb) dis -s 0x0000000100731d6c
InlineDemo`mySum:
0x100731d6c <+0>: sub sp, sp, #0x30 ; =0x30
0x100731d70 <+4>: stp x29, x30, [sp, #0x20]
0x100731d74 <+8>: add x29, sp, #0x20 ; =0x20
0x100731d78 <+12>: stur w0, [x29, #-0x4]
0x100731d7c <+16>: stur w1, [x29, #-0x8]
0x100731d80 <+20>: adrp x8, 8
0x100731d84 <+24>: add x8, x8, #0x660 ; =0x660
0x100731d88 <+28>: ldr x8, [x8]
2、在调用sum
函数处断点停下,查看汇编如下:
InlineDemo`-[ViewController touchesBegan:withEvent:]:
0x100731e94 <+84>: mov w0, #0xa
0x100731e98 <+88>: mov w1, #0x14
-> 0x100731e9c <+92>: bl 0x100731d4c ; sum at ViewController.m:18
0x100731ea0 <+96>: adrp x8, 3
0x100731ea4 <+100>: add x8, x8, #0x28 ; =0x28
分析:
- 将0xa(10)赋值给w0。
- 将0x14(20)赋值给w1。
- 跳转至0x100731d4c地址。(从第一步可以知道,该地址是
sum
函数的地址)
3、跳转至0x100731d4c地址,查看汇编
InlineDemo`sum:
-> 0x100731d4c <+0>: adrp x17, 0
0x100731d50 <+4>: add x17, x17, #0xd6c ; =0xd6c
0x100731d54 <+8>: br x17
0x100731d58 <+12>: ldr w8, [sp, #0xc]
0x100731d5c <+16>: ldr w9, [sp, #0x8]
0x100731d60 <+20>: add w0, w8, w9
0x100731d64 <+24>: add sp, sp, #0x10 ; =0x10
0x100731d68 <+28>: ret
分析:
此处得到的sum
函数汇编与之前的汇编存在差异,具体表现为前面三行
不同。
在DobbyHook之前
,sum
函数汇编的前三行为:
- 拉伸栈空间
- 将
sum
函数的参数存入栈空间。
在DobbyHook之后
,sum
函数汇编的前三行为:
- 获取一个地址至x17寄存器
- 将地址偏移至第0页的首地址:0x100731000
- 将0x100731000 + 0xd6c = 0x0000000100731d6c
- 跳转至x17寄存器的值,以下是直接查看x17寄存器的内容
(lldb) register read x17
x17 = 0x0000000100731d6c InlineDemo`mySum at ViewController.m:24
(lldb) dis -s 0x0000000100731d6c
InlineDemo`mySum:
0x100731d6c <+0>: sub sp, sp, #0x30 ; =0x30
0x100731d70 <+4>: stp x29, x30, [sp, #0x20]
0x100731d74 <+8>: add x29, sp, #0x20 ; =0x20
0x100731d78 <+12>: stur w0, [x29, #-0x4]
0x100731d7c <+16>: stur w1, [x29, #-0x8]
0x100731d80 <+20>: adrp x8, 8
0x100731d84 <+24>: add x8, x8, #0x660 ; =0x660
0x100731d88 <+28>: ldr x8, [x8]
4、跳转至x17之后,从汇编及函数地址可以看出,就是跳转至mySum
函数执行。由于在sum
函数的汇编中,只是进行了函数跳转,并未修改寄存器的值,寄存器x30
仍然保存了sum
函数调用完成之后需要回归的地址。因此,当mySum
函数执行完ret
之后,回到-[ViewController touchesBegan:withEvent:]:
函数中执行。
InlineDemo`mySum:
-> 0x100731d6c <+0>: sub sp, sp, #0x30 ; =0x30
0x100731d70 <+4>: stp x29, x30, [sp, #0x20]
0x100731d74 <+8>: add x29, sp, #0x20 ; =0x20
0x100731d78 <+12>: stur w0, [x29, #-0x4]
0x100731d7c <+16>: stur w1, [x29, #-0x8]
0x100731d80 <+20>: adrp x8, 8
0x100731d84 <+24>: add x8, x8, #0x660 ; =0x660
0x100731d88 <+28>: ldr x8, [x8]
0x100731d8c <+32>: ldur w0, [x29, #-0x4]
0x100731d90 <+36>: ldur w1, [x29, #-0x8]
0x100731d94 <+40>: blr x8
0x100731d98 <+44>: adrp x8, 3
0x100731d9c <+48>: add x8, x8, #0x8 ; =0x8
0x100731da0 <+52>: stur w0, [x29, #-0xc]
0x100731da4 <+56>: mov x0, x8
0x100731da8 <+60>: mov x8, sp
0x100731dac <+64>: ldur w9, [x29, #-0xc]
0x100731db0 <+68>: mov x2, x9
0x100731db4 <+72>: str x2, [x8]
0x100731db8 <+76>: bl 0x1007324b0 ; symbol stub for: NSLog
0x100731dbc <+80>: ldur w9, [x29, #-0x4]
0x100731dc0 <+84>: ldur w10, [x29, #-0x8]
0x100731dc4 <+88>: subs w0, w9, w10
0x100731dc8 <+92>: ldp x29, x30, [sp, #0x20]
0x100731dcc <+96>: add sp, sp, #0x30 ; =0x30
0x100731dd0 <+100>: ret
(lldb) register read x30
lr = 0x0000000100731ea0 InlineDemo`-[ViewController touchesBegan:withEvent:] + 96 at ViewController.m
5、在mySum
函数中,有通过orgin_sum
调用原函数,接下来我们从汇编来看看是如何进行跳转的。
【第1步】获取orgin_sum
函数指针
0x100731d80 <+20>: adrp x8, 8
0x100731d84 <+24>: add x8, x8, #0x660 ; =0x660
(lldb) register read x8
x8 = 0x0000000100739660 InlineDemo`orgin_sum
【第2步】取出orgin_sum
函数指针的内容
0x100731d88 <+28>: ldr x8, [x8]
(lldb) register read x8
x8 = 0x0000000100e84000
【第3步】跳转至x8寄存器中的地址处开始执行
-> 0x100e84000: sub sp, sp, #0x10 ; =0x10
0x100e84004: str w0, [sp, #0xc]
0x100e84008: str w1, [sp, #0x8]
0x100e8400c: ldr x17, #0x8
0x100e84010: br x17
0x100e84014: .long 0x00731d58 ; unknown opcode
0x100e84018: udf #0x1
0x100e8401c: udf #0x0
分析:
以上代码的主要工作是:
拉伸栈空间
- 将
函数参数
存入栈空间 - 通过
当前地址偏移0x8
,并获取其内容至x17寄存器
//1. 当前地址偏移0x8
(lldb) p/x 0x100e8400c+0x8
(long) $2 = 0x0000000100e84014
//2. 获取0x0000000100e84014地址中的内容
(lldb) x/2g 0x0000000100e84014
0x100e84014: 0x0000000100731d58 0x0000000000000000
//3. 将0x0000000100731d58地址存入x17寄存器
(lldb) register read x17
x17 = 0x0000000100731d58 InlineDemo`sum + 12 at ViewController.m:19:12
- 跳转至
x17寄存器的地址
处执行
6、从x17寄存器
的值可以看出,这个地址其实就是sum
函数的第四行
汇编,即此处又回到sum
函数执行。
7、sum
函数执行完成之后,会恢复栈平衡。
总结
Dobby原理:运行时对
目标函数
的汇编代码
进行前三行
替换,注意:这修改的是内存中MachO的代码段__TEXT
。-
当使用
Dobby
进行HOOK,此时有两种情况:-
若不调用原函数
,则原函数的汇编不执行。 -
若需要调用原函数
,由于原函数的汇编前三行(即拉伸栈空间)
被替换了,此时需要重新拉伸栈空间
,并通过偏移跳转至原函数汇编的第四行
开始执行。
-
扩展
在逆向开发中,应用会剥离符号表
,我们无法获得符号名称
,在调用DobbyHook
函数时,其入参就需要从地址
着手进行HOOK。
由于应用每次启动时,ASLR偏移地址
都不一样,所以不能直接HOOK Mach-O文件中的地址,而是先找到函数在Mach-O中的偏移地址
,加上PAGEZERO(0x100000000)
,再加上本次启动的ASLR偏移地址
。
接下来以Dobby
示例中sum
函数为例,尝试从地址进行HOOK。
1、从Mach-O
中的__TEXT段
,找到sum函数
的汇编代码,取出其偏移值(0x5d0c)。偏移值 = 实际运行的内存地址 - ASLR
2、修改ViewController.m的代码如下:
static uintptr_t sumP = 0x5d0c + 0x100000000;
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
sumP += _dyld_get_image_vmaddr_slide(0);
DobbyHook((void *)sumP, mySum, (void *)&orgin_sum);
}
3、运行程序,可以看到成功HOOK了sum
函数
注意:
在以上HOOK过程中,需要注意,当代码被修改
时,__TEXT段的偏移地址
也会发生变化,因此,在获取sum
函数汇编代码在Mach-O中的偏移后,代码不能再发生变化
,若代码发生了变化,则sum
函数汇编代码的偏移值将是一个不正确的值。
以上是在自己的测试案例中进行函数HOOK,因此会涉及代码修改,从而导致地址被修改,但在逆向开发中,分析第三方应用,不会出现这种问题。