iOS Hook在IDA中显示为sub_xxx的函数
基础
1. Mach-O文件组成部分
Header、Load commands、Raw segment date(常见的一些段__PAGEZERO空指针陷阱段、_TEXT程序代码段、__DATA程序数据段、__LINKEDIT:链接器使用段等);
2. Mach-O文件的加载 dyld
Mach-O文件被dyld进行加载的;dyld(the dynamic link editor)是 Apple 的动态链接器,系统 kernel 做好启动程序的初始准备后,交给 dyld 负责;
3. ASLR
也就是地址空间布局随机化,它会让 Mach-O 文件每次加载的时候是随机地址;
4. PIC
位置代码独立,位置和代码无关,假如我们要调用外部函数,首先会在映射表中增加一个间接指针,指向外部的函数,dyld会动态的去绑定,将指针指向外部的函数地址;
为什么不使用fishhook?
1. fishhook原理
dyld通过更新Mach-O二进制文件__DATA段的特定部分中的指针来绑定lazy 和 non-lazy 的符号。fishhook通过传递给rebind_symbols的每个符号名称,确定某一个符号(外部函数的符号)在 __DATA 段中的位置,保存原符号对应的函数指针,将原有符号的函数指针指向内部函数,实现重新绑定符号,从而实现了对C函数的Hook。其中*复杂的部分就是从二进制文件中寻找某个符号的位置,具体可查看fishhook部分;
2. 不使用的原因
(1)fishhook是通过绑定lazy 和 non-lazy 的符号,将指向系统方法(外部函数)的符号重新进行绑定指向内部的函数,从而实现了系统方法与自己定义的方法进行了交换。也就导致了C的自定义的函数无法修改,只能修改 Mach-O 外部的函数
(2)fishhook通过传递给rebind_symbols的每个符号名称去查找相应的符号位置的,但是我们要hook的sub_xxx不能确定函数的具体名称
_dyld_register_func_for_add_image函数
官方定义:
* The following functions allow you to install callbacks which will be called
* by dyld whenever an image is loaded or unloaded. During a call to _dyld_register_func_for_add_image()
* the callback func is called for every existing image. Later, it is called as each new image
* is loaded and bound (but initializers not yet run). The callback registered with
* _dyld_register_func_for_remove_image() is called after any terminators in an image are run
* and before the image is un-memory-mapped.
1. 以下函数当dyld装载镜像并绑定的时候会被调用,为每个镜像加载时的回调函数
void _dyld_register_func_for_add_image(void (*func)(const struct mach_header* mh, intptr_t vmaddr_slide))
2. 以下函数当dyld移除镜像以及绑定的时候会被调用
void _dyld_register_func_for_remove_image(void (*func)(const struct mach_header* mh, intptr_t vmaddr_slide))
Dl_info
Dl_info结构体用于存储一些镜像信息,比如路径,基地址,它的相关定义在#include <dlfcn.h>文件中;
通过dladdr函数,填充Mach-O Header以及Dl_info结构体,获取Dl_info的一些信息,获取Header偏移地址即ASLR偏移量;
CydiaSubstrate
CydiaSubstrate是大多数tweaks工程的基础,由MobileHooker,MobileLoader,Safe Mode组成;
其中MobileHooker经常被用来替换系统函数的调用,它主要有两个函数:
MSHookMessageEx用来hook Objective-C函数;
MSHookFunction用来hook C/C++函数;
MSHookFunction
MSHookFunction的三个参数作用分别为:替换的原函数,替换函数,以及MobileHooker保持的原函数。
Hook原理:
1. 修改目标函数前N字节,跳转到自定义函数入口;
2. 备份目标函数前N个字节,跳转回目标函数。
__attribute__((constructor))
要想让一个被加载的动态库在加载后自动运行某一段代码有好几种方法:
1. 建立OC某个类的分类,并在类的+load方法中添加代码;
2. 在动态库中定义带有__attribute__((constructor))声明的函数,并在函数内添加特定的代码。
hook sub_xxx函数的原理:
1. 计算公式:
模块偏移后的地址 = 模块偏移前的基地址 + ASLR偏移量
注:ida中所看到的地址都是模块偏移前的基地址
2. 原理
在ida中找到函数地址即模块偏移前的基地址,然后在hook代码中计算ASLR偏移量,然后相加,使用MSHookFunction函数进行hook;
说明:看到有些帖子上说此处需要((模块偏移前的基地址 + ASLR偏移量)| 0x1),亲测了一下arm64上是不需要的,猜测可能是架构的原因吧,具体没做太深入的研究,后期会补上,如果有遇到我的方式不成功的也可尝试一下此方法。
示例解析(github源码):
说明:因为我自己模拟没显示sub_xxx的函数,但是情况和sub_xxx一致的,这点可忽略;
1. 测试的原始函数
2. ida函数sub_xxx,如果想要hook它
3. 查找sub_xxx对应的内存地址
4. 编写hook代码
这里使用的是使用MonkeyDev新建的工程,仅列出了部分函数代码,具体请下载源代码查看:
//保存模块偏移基地址的值
static void _register_func_for_add_image(const struct mach_header *header, intptr_t slide) {
Dl_info image_info;
int result = dladdr(header, &image_info);
if (result == 0) {
NSLog(@”load mach_header failed”);
return;
}
//获取当前的可执行文件路径
NSString *execName = [[[NSBundle mainBundle] infoDictionary] objectForKey:@”CFBundleExecutable”];
NSString *execPath = [[[NSBundle mainBundle] bundlePath] stringByAppendingFormat:@”/%@”, execName];
if (strcmp([execPath UTF8String], image_info.dli_fname) == 0) {
g_slide = slide;
}
}
//hook后会来到这里
void hook_testMethod(void) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@”hook了我” message:@”message” delegate:nil cancelButtonTitle:nil otherButtonTitles:@”other”, nil];
[alert show];
}
static void __attribute__((constructor)) __init__() {
//注册添加镜像回调
_dyld_register_func_for_add_image(_register_func_for_add_image);
//通过 模块偏移前的基地址 + ASLR偏移量 找到函数真正的地址进行hook
MSHookFunction((void *)(0x100006934+g_slide), (void *)hook_testMethod, (void **)&orig_testMethod);
}
5. hook前效果图
6. hook后效果图