一、inlinehook概述
inlineHook
(内联钩子):所谓InlineHook
就是直接修改目标函数的头部代码。让它跳转到我们自定义的函数里面执行我们的代码,从而达到Hook
的目的。这种Hook
技术一般用在静态语言的HOOK
上面。
Inline Hook
就是在运行的流程中插入跳转指令来抢夺运行流程的一个方法。大体分为三步:
- 将原函数的前
N
个字节搬运到Hook
函数的前N
个字节; - 然后将原函数的前
N
个字节填充跳转到Hook
函数的跳转指令; - 在
Hook
函数末尾几个字节填充跳转回原函数+N
的跳转指令;
Dobby
(原名:HOOKZz
)。是一个全平台的inlineHook
(内联钩子)框架,用起来就和fishhook
一样。Dobby
是通过插入 __zDATA
段和__zTEXT
段到 Mach-O
中。
-
__zDATA
用来记录Hook
信息(Hook
数量、每个Hook
方法的地址)、每个Hook
方法的信息(函数地址、跳转指令地址、写Hook
函数的接口地址)、每个Hook
的接口(指针)。 -
__zText
用来记录每个Hook
函数的跳转指令。
Dobby
通过 mmap
把整个 Mach-O
文件映射到用户的内存空间,写入完成保存本地。所以 Dobby
并不是在原 Mach-O
上进行操作,而是重新生成并替换。
Doddy
二、 编译Dobby
首先clone
工程
#depth用于指定克隆深度,为1即表示只克隆最近一次commit.
git clone https://github.com/jmpews/Dobby.git --depth=1
由于Dobby
是跨平台的,所以项目并不是一个Xcode
工程,要使用cmake
将这个工程编译成为Xcode
工程。进入Dobby
目录,创建一个文件夹,然后cmake
编译工程:
cd Dobby && mkdir build_for_ios_arm64 && cd build_for_ios_arm64
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
工程(这里在build_for_ios_arm64
目录中 )。接下来编译Xcode
工程生成我们的Framework
。
新版本的Dobby
多了很多功能,这里我们只编译DpbbyX
或者dobby
,区别就是一个是.Framework
一个是.dylib
:
到这里
Dobby
库就已经准备好了。
和上一篇文章一样如果提示需要账户直接添加
CODE_SIGNING_ALLOWED
配置。
三、导入 DobbyX.Framework / libdobby.dylib
新建一个工程DoddyDemo
,导入DobbyX.Framework
:
问题处理
-
bitcode
问题'/Users/zaizai/HookTest/DoddyDemo/DobbyX.framework/DobbyX' does not contain bitcode. You must rebuild it with bitcode enabled (Xcode setting ENABLE_BITCODE), obtain an updated library from the vendor, or disable bitcode for this target. file '/Users/zaizai/HookTest/DoddyDemo/DobbyX.framework/DobbyX' for architecture arm64
两个方案:
1.生成Framework
/dylib
的时候设置支持bitcode
2.项目关闭bitcode
。
拷贝
Framework
:dyld: Library not loaded: @rpath/DobbyX.framework/DobbyX Referenced from: /private/var/containers/Bundle/Application/BFDFC7CA-7045-4392-881D-1BFEA22E6BFA/DoddyDemo.app/DoddyDemo Reason: image not found
这里就是DobbyX
在Macho
文件中已经有了,但是在Frameworks
目录中没有。Xcode
不会帮我们自动Copy
,需要添加Copy
:
处理完成后运行工程输出以为内容表示成功:
[*] ================================
[*] Dobby
[*] ================================
[*] dobby in debug log mode, disable with cmake flag "-DDOBBY_DEBUG=OFF"
四、 DobbyDemo
dobby
最核心的函数DobbyHook
定义如下:
// replace function
int DobbyHook(void *address, void *replace_call, void **origin_call);
-
address
:需要HOOK
的函数地址。 -
replace_call
:新函数地址。 -
origin_call
:保留原始函数的指针的地址。
从函数的参数就能看出来和fishhook
很相似。使用下试试:
#import <DobbyX/dobby.h>
+ (void)load {
//Hook sum
DobbyHook(sum, HP_sum, (void *)&sum_p);
}
//要Hook的函数
int sum(int a, int b){
return a + b;
}
//新函数
int HP_sum(int a,int b) {
NSLog(@"before hook: %d + %d = %d",a,b,sum_p(a,b));
return a - b;
}
//原函数指针
static int(*sum_p)(int a,int b);
调用:
NSLog(@"after hook: %d + %d = %d",10,20,sum(10, 20));
输出:
DoddyDemo[9679:6241363] before hook: 10 + 20 = 30
DoddyDemo[9679:6241363] after hook: 10 + 20 = -10
这里也就Hook
了自定义C
函数,弥补了fishhook
不能hook
自定义c
函数的问题。
那么外部函数可以Hook
么?Hook
下NSLog
试下:
//新函数
void HP_NSLog(NSString *format, ...) {
NSLog_p([format stringByAppendingString:@"\nHook Success"]);
}
//原函数指针
static void(*NSLog_p)(NSString *format, ...);
+ (void)load {
DobbyHook(NSLog, HP_NSLog, (void *)&NSLog_p);
}
调用:
NSLog(@"HotpotCat");
输出:
HotpotCat
Hook Success
可以看到也是可以的,由于是直接插入跳转指令来执行自己的代码肯定都可以。
那么Dobby
是怎么做的呢?以sum
函数为例,分别在Hook
前后在sum
函数中打断点看下汇编代码:
//before hook
DoddyDemo`sum:
0x104ab9c3c <+0>: sub sp, sp, #0x10 ; =0x10
0x104ab9c40 <+4>: str w0, [sp, #0xc]
0x104ab9c44 <+8>: str w1, [sp, #0x8]
-> 0x104ab9c48 <+12>: ldr w8, [sp, #0xc]
0x104ab9c4c <+16>: ldr w9, [sp, #0x8]
0x104ab9c50 <+20>: add w0, w8, w9
0x104ab9c54 <+24>: add sp, sp, #0x10 ; =0x10
0x104ab9c58 <+28>: ret
//after hook
DoddyDemo`sum:
0x104ab9c3c <+0>: adrp x17, 0
0x104ab9c40 <+4>: add x17, x17, #0xc5c ; =0xc5c
0x104ab9c44 <+8>: br x17
-> 0x104ab9c48 <+12>: ldr w8, [sp, #0xc]
0x104ab9c4c <+16>: ldr w9, [sp, #0x8]
0x104ab9c50 <+20>: add w0, w8, w9
0x104ab9c54 <+24>: add sp, sp, #0x10 ; =0x10
0x104ab9c58 <+28>: ret
通过汇编对比可以看到前面3
行发生了变化(⚠️第二个sum
调用没有拉伸栈空间,这里也就确实修改后汇编的行数是不变的,栈平衡放到了后面的函数中),打断点查看x17
的内容:
可以看到在
sum
的头部跳转到了HP_sum
中执行HP_sum
函数。这也就验证了inlinehook
是hook
函数的头部进行跳转。在这里br x17
后面的代码就不执行了,只有在HP_sum
中调用sum_p
的时候才执行原来的代码。
继续进入HP_sum
:
进入blr x9
:
可以看到这里拉伸了栈空间,也就是说只有调用原来的函数才开辟空间,在
sum
的br x17
后面做栈回收。这样也就栈平衡了。
继续往下调用,最后会分别回到HP_sum->sum
也就是执行sum
后面的一段汇编:
继续进入:
验证确实回到了
sum
中继续执行。⚠️修改的
__Text
段实际上是替换。
五、 地址替换符号
在正常的逆向开发中,我们一般情况下拿到的都是地址,所以DobbyHook
的第一个参数应该是一个地址。那么怎么将符号替换成地址呢?还是以sum
函数为例。
在这里我们能算出函数的偏移值为:
0x102dc5c70 - 0x0000000102dc0000 = 0x5C70
,查看MachO
也确实是sum
函数。那么在代码中直接通过
偏移值 + ASLR
就能得到函数执行时候的地址了。(一般我们并不知道这个地址是sum
,这里只是演示。正常逆向是要分析代码逻辑找地址然后尝试的)。修改
sum
函数hook
为地址代码如下:
#import <DobbyX/dobby.h>
#import <mach-o/dyld.h>
//sum 函数地址 偏移值: PAGEZERO(0x100000000) + offset(0x5C70),这里用 uintptr_t 类型是为了方便计算。这里只是64位,暂不考虑32位。如果最低版本从`iOS11`开始,就不用考虑32位的情况了。
static uintptr_t sum_address_offset = 0x100005C70;
+ (void)load {
//地址hook
//获取ASLR,相当于rebase
uintptr_t slide = _dyld_get_image_vmaddr_slide(0);
uintptr_t sum_address = sum_address_offset + slide;
NSLog(@"sum_address_offset:%p\nslide:%p\nsum_address:%p",(void *)sum_address_offset,(void *)slide,(void *)sum_address);
DobbyHook((void *)sum_address, HP_sum, (void *)&sum_p);
}
这个时候运行并没有hook
成功:
sum_address_offset:0x100005c70
slide:0x4150000
sum_address:0x104155c70
hook: 10 + 20 = 30
因为我们在主工程添加了代码,MachO
变了,所以对应的获取的到的0x5C70
也发生了改变:
这里也就说明了一些
app
的插件在app
更新后就不能用的原因。这里有两种方式处理偏移值改变的情况。重新获取偏移值,或者将Hook
代码放入动态库中。重新获取计算偏移值发现是
0x5D08
,直接将sum_address_offset
修改:
static uintptr_t sum_address_offset = 0x100005D08;
再次运行工程发现Hook
成功了。
sum_address_offset:0x100005d08
slide:0x2570000
sum_address:0x102575d08
before hook: 10 + 20 = 30
hook: 10 + 20 = -10
⚠️这里有个问题是
sum_address_offset
的值变了,为什么偏移值没有变。这里是因为这块改动很小,数据大小是没有变化的,不会影响到偏移值的变化。当然最好的方案当然是放入动态库中。
六、 Dobby注入
正常情况写分析其它App
我们是要注入自己的动态库的,写个AppDemo
试下下整个流程。
AppDemo
的主页面有如下代码:
int sum(int a,int b) {
return a + b;
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"sum result: %d",sum(10, 20));
}
计算下偏移值为0x5E54
。编译生成AppDemo.app
。新建一个Hook
工程HookAppDemo
。利用HookAppDemo
工程对AppDemo.app
重签名和注入。
将DobbyX.framework
和AppDemo.app
拷贝到HookAppDemo
工程的目录。
目录结构如下:
HPHook
是自定义注入库。
这里HPHook
依赖DobbyX
有两种方式:
-
DobbyX
加入到主工程:
然后在HPHook
Target
配置:
这个时候两个库都已经在Frameworks
中了,并且注入成功了。
-
DobbyX
加入到HPHook
中:
配置HPHook
或者主工程
Copy Files
(任意一个都可以):
- 配置
HPHook
则主工程和Framework
MachO
分别如下:
- 配置主工程
MachO
分别如下:
原理一样,层级不同。
七、 Hook自定义函数
HPHook中写Hook代码
#import "HPInject.h"
#import <DobbyX/dobby.h>
#import <mach-o/dyld.h>
@implementation HPInject
//sum 函数地址 偏移值: PAGEZERO(0x100000000) + offset(0x5E54),这里用 uintptr_t 类型是为了方便计算。
static uintptr_t sum_address_offset = 0x100005E54;
+ (void)load {
//获取ASLR,相当于rebase
uintptr_t slide = _dyld_get_image_vmaddr_slide(0);
uintptr_t sum_address = sum_address_offset + slide;
NSLog(@"sum_address_offset:%p\nslide:%p\nsum_address:%p",(void *)sum_address_offset,(void *)slide,(void *)sum_address);
DobbyHook((void *)sum_address, HP_sum, (void *)&sum_p);
}
//要Hook的函数
int sum(int a, int b){
return a + b;
}
//新函数
int HP_sum(int a,int b) {
NSLog(@"Hook Success");
return sum_p(a,b);
}
//原函数指针
static int(*sum_p)(int a,int b);
@end
输出:
Hook Success
sum result: 30
这个时候就Hook
成功了。
八、Hook Swift
还是同样的逻辑,将AppDemo
中sum
改写为swift
实现,创建一个Swift AppDemo
,代码如下:
func sum(a: Int, b: Int) -> Int {
return a + b
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print(sum(a: 10, b: 20))
}
同样的方式计算偏移值0x6650
在HookAppDemo
中用SwiftAppDemo.app
替换AppDemo.app
,修改sum_address_offset
值。运行工程:
Hook Success
30
这个时候Swift
代码也Hook
成功了。
inlinehook(Dobby
)运行时Hook
:对目标函数的汇编代码进行修改,修改的是内存中的MachO
的代码段(强制替换)。
修改目标函数后,函数栈平衡放到调用原函数的地方。这么做为了确认是否需要调用原始的方法,需要的话会找时机拉伸栈后返回执行后面的代码。