本文梳理了 iOS 或 OS X 中可以用于 hook 的框架及其使用, 对于 C/C++ 方法, 进行了私有和系统方法的区分阐述, 本文仅针对 hook 框架做讨论, 对于实验中用到的注入, 签名等不作阐述.
0x01 背景: 要 hook 的代码, 以下是测试 demo
本文我们会对一个编写的测试 Mac App 进行 hook, 其中 Mac App 的主要代码如下:
- #import "ViewController.h"
- int cfunc(int x,int y,int z) {
- return x + y + z;
- }
- @implementation ViewController
- - (IBAction)button:(id)sender {
- [self ocFunc];
- NSLog(@"%@", [NSString stringWithFormat:@"%zu",_length]);
- }
- -(void)ocFunc{
- NSLog(@"haha");
- _length = cfunc(1, 2, 3);
- }
- - (void)viewDidLoad {
- [super viewDidLoad];
- _length = cfunc(1, 2, 3);
- // Do any additional setup after loading the view.
- }
接下来要对 cfunc 和 ocFunc 进行 hook.
0x02 CydiaSubstrate Hook
首先要来的自然是大名鼎鼎的 Jay Freeman(saurik)写的 CydiaSubstrate,iOS7 越狱之前名为 MobileSubstrate(简称为 MS 或 MS 框架).
MobileHooker 组件主要提供了 MSHookMessageEx 和 MSHookFunction 两个函数针对不同语言的 inline hook 功能, 其中 MSHookMessageEx 负责用来 hook Objective-C 函数, MSHookFunction 负责用来 hook C/C++ 函数.
Objective-C 函数的 hook
原理: MSHookMessageEx 对于 ObjC 函数采用的也是 method swizzle 的方法, 主要是 Objetive-C 的 runtime 机制, 可以在 ObjC 方法时动态采用 class_replaceMethod 等 runtime 函数替换其实现.
void MSHookMessageEx(Class _class, SEL message, IMP hook, IMP *old);
其中第一个参数_class 为要 Hook 的 Objective-C 函数的类名; 第二个参数 message 为要 Hook 的 Objective-C 函数的 message; 第三个参数 hook 为 hook 后新的对应该 message 的执行逻辑, 即替换后的函数地址; 第四个参数 old 为对应该 message 的原函数的地址, 若无需调用原函数则该参数可以设为 NULL.
对 ocFunc 进行 hook 的动态库代码如下:
- @class ViewController;
- static void (*origin_ViewController_ocFunc)(id self,SEL _cmd);
- static void new_ViewController_ocFunc(id self,SEL _cmd){
- NSLog(@"oc func hook");
- origin_ViewController_ocFunc(self,_cmd);
- }
- static void __attribute__((constructor)) initialize(void) {
- // 初始化方法里进行替换
- MSHookMessageEx(objc_getClass("ViewController"), @selector(ocFunc), (IMP)&new_ViewController_ocFunc, (IMP *)&origin_ViewController_ocFunc);
- }
可以成功 hook
C/C++ 方法 (私有或系统) 的 hook
原理: MSHookFunction 对于 C 函数是在函数的开头修改了汇编指令, 使其跳转到新的实现, 执行完成后再返回执行原指令.
void MSHookFunction(void *symbol, void *hook, void **old);
其中第一个参数为所要 Hook 的函数地址, 值得注意的是该地址不一定限于函数头, 也可以是函数内部的任一代码地址; 第二个参数为 Hook 后要替换的函数地址; 第三个参数为指向 Hook 地址的指针, 用来保存被 Hook 函数替换掉的汇编指令方便执行完自己的代码逻辑后能够继续执行原函数的逻辑, 若不需要调用原函数, 则此处可以设为 "NULL".
私有方法
话不多说, 上代码, 来 hook 我们自己写的 C 方法
- static int (*orig_cfunc)(int x,int y,int z);
- int new_cfunc(int x,int y,int z){
- NSLog(@"c func hook");
- return orig_cfunc(x,y,z);
- }
- static void __attribute__((constructor)) initialize(void) {
- MSHookFunction(MSFindSymbol(NULL,"_cfunc"), (void *)&new_cfunc, (void **)&orig_cfunc);
- }
注意这里在调用 **MSFindSymbol** 时传入的是 **'_cfunc'**
另外注意:
新方法的函数地址这里也可以使用 new_cfunc, 因为 new_cfunc 和 & new_cfunc 地址是一样的, new_cfunc 是函数的首地址, 它的类型是 void (),&new_cfunc 表示一个指向函数 new_cfunc 这个对象的地址, 它的类型是 void (*)(), 因此 new_cfunc 和 & new_cfunc 所代表的地址值是一样的, 但类型不一样
第三个参数必须使用 & orig_cfunc, 因为这里要用的是函数地址, 用来保存被 Hook 函数替换掉的汇编指令方便执行完自己的代码逻辑后能够继续执行原函数的逻辑, 使用 orig_cfunc 是无效的.
系统方法
以上是 hook 私有的 c 方法, 当然也可以 hook 系统的方法, 比如官网的示例
- void *(*oldConnect)(int, const sockaddr *, socklen_t);
- void *newConnect(
- int socket, const sockaddr *address, socklen_t length) {
- if (address->sa_family == AF_INET) {
- sockaddr_in *address_in = address;
- if (address_in->sin_port == htons(6667)) {
- sockaddr_in copy = *address_in;
- address_in->sin_port = htons(7001);
- return oldConnect(socket, ©, length);
- }
- }
- return oldConnect(socket, address, length);
- }
- MSHookFunction(&connect, &newConnect, &oldConnect);
- 0x03 fishhook
https://GitHub.com/Facebook/fishhook 是 Facebook 提供的一个动态修改链接 mach-O 文件的工具, 可以用来 hook C/C++ 方法.
不能 hook 私有方法
这里也尝试对 cfunc 进行了 hook, 发现并未生效, 代码如下:
- static int (*orig_cfunc)(int x,int y,int z);
- int new_cfunc(int x,int y,int z){
- NSLog(@"c func hook");
- return (*orig_cfunc)(x,y,z);
- }
- static void __attribute__((constructor)) initialize(void) {
- rebind_symbols((struct rebinding[1]){{"cfunc", new_cfunc, (void *)&orig_cfunc}}, 1);
- }
结合 fishhook 的原理, 可以知道 fishhook 是不能 hook 私有 C 方法的.
hook 系统方法
但是 hook 系统的方法还是很好使的, 代码如下:
- static void (*orig_NSLog)(NSString *format, ...);
- void(new_NSLog)(NSString *format, ...) {
- va_list args;
- if(format) {
- va_start(args, format);
- NSString *message = [[NSString alloc] initWithFormat:format arguments:args];
- orig_NSLog(@"hook:%@", message);
- va_end(args);
- }
- }
- static void __attribute__((constructor)) initialize(void) {
- rebind_symbols((struct rebinding[1]){{"NSLog", new_NSLog, (void *)&orig_NSLog}}, 1);
- }
- 0x04 method swizzle
method swizzle 的原理主要是 Objetive-C 的 runtime 机制, 可以在 ObjC 方法时动态采用 class_replaceMethod 等 runtime 函数替换其实现.
由于是基于 runtime 的, 所以 C/C++ 方法是不生效的, 仅针对 Objective-C 方法有效, swift 中不是基于 OC 的对象也不会生效.
话不多说, 直接上一个 hook viewWillAppear 方法的例子
- #import <objc/runtime.h>
- @implementation UIViewController (Swizzle)
- +(void)load{
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- Class class = [self class];
- // Class class = object_getClass((id)self);
- NSLog(@"%@",class);
- SEL originalSelector = @selector(viewWillAppear:);
- SEL swizzledSelector = @selector(my_viewWillAppear:);
- Method originalMethod = class_getInstanceMethod(class, originalSelector);
- Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
- BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
- if (didAddMethod) {
- class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
- }
- else {
- method_exchangeImplementations(originalMethod, swizzledMethod);
- } });
- }
- -(void)my_viewWillAppear:(BOOL)animated
- {
- [self my_viewWillAppear:animated];
- NSLog(@"%@",[self class]);
- }
- @end
目前就针对这三种进行了实验和梳理, 后续如有遇到其它再进行补充, 也欢迎了解其他框架的大佬进行补充和指正.
参考
- https://www.jianshu.com/p/3479f9632a6f
- https://www.jianshu.com/p/98c97a32da29
来源: https://www.qcloud.com/developer/article/1339855