IOS 之FishHook原理及例子

一、HOOK概述

    HOOK,中文为“钩子”或“挂钩”,在ios逆向中是指改变程序的运行流程的一种技术,通过hook可以让别人的程序执行自己的代码逻辑,在逆向中经常使用。所以就来看看HOOK的原理吧!

上图很常见的微信抢红包,hook原理就相当于程序本来收到红包消息用户应该点击红包之后点击“抢”,才能领红包,而通过HOOK既可以执行自己的代码,用户不需要点击自动执行抢红包代码,这就是HOOK常见的应用。

1.ios中HOOK技术的几种方式 

1、Method Swizzle

    利用OC的Runtime特性,动态改变SEL(方法编号)和IMP(方法的实现)的对应关系,达到OC方法调用流程改变的目的,主要用在OC方法中。

2、fishhook

    他是facebook提供的一个动态修改链接mach-o文件的工具。利用MachO文件加载原理,通过修改懒加载和非懒加载两个表指针达到C函数HOOK的目的。

3、Cydia Substrate

    Cydia Substrate 原名为 Mobile Substrate,主要针对OC方法、C函数以及函数地址进行hook操作,他可以用于iOS,也可用于android 。官网:www.cydiasubstrate.com

 

Method Swizzle :

    在OC中,SEL和IMP的关系好比标题和页码、接口和实现之间的关系,一个是标题,一个是对应的实现。Runtime提供了交换两个SEL和IMP对应关系的函数:method_exchangeImplememtations(Method m1,Method m2) 这个函数交换两个SEL和IMP之间的关系的技术,叫Method Swizzle(方法欺骗)。

例子:

//
//  ViewController.m
//  Demo
//
//  Created by yrl on 2019/7/26.
//  Copyright © 2019 apple. All rights reserved.
//#import "ViewController.h"
#import <objc/runtime.h>@interface ViewController ()@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];NSURL *url = [NSURL URLWithString:@"https://study.163.com/courses-search?keyword=逻辑教育"];//出现中文应进行编码转换,否则会为空NSLog(@"%@",url);}@end

运行结果: 

 

 我们可以用stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]转码

利用OC的Runtime进行方法交换,例子:

//
//  NSURL+HK_URL.m
//  Demo
//
//  Created by yrl on 2019/7/26.
//  Copyright © 2019 apple. All rights reserved.
//#import "NSURL+HK_URL.h"
#import <objc/runtime.h>@implementation NSURL (HK_URL)
+(void)load{//类方法Method URLWithStr = class_getClassMethod(self, @selector(URLWithString:));//系统的Method HK_URLWithStr = class_getClassMethod(self, @selector(HK_URLWithString:));//自己的//changemethod_exchangeImplementations(URLWithStr, HK_URLWithStr);
}+(nullable instancetype)HK_URLWithString:(NSString *)URLString{NSURL * url = [NSURL HK_URLWithString:URLString];//应用HK_URLWithString系,URLWithString已经交换了,会造成死递归统if(url == nil){//如果为空则进行编码转换URLString = [URLString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];}url = [NSURL HK_URLWithString:URLString];//将URLString赋值给UK_URLWithStringreturn url;
}@end

实现了HOOK 了NSURL类的URLWithString方法,在类方法load中交换了系统的URLWithString和自己定义的HK_URLWithString,在调用URLWithString时会调用HK_URLWithString,实现HOOK,将中文编码,再运行程序:

可看到中文已经被编码了,说明我们已经改变了程序的执行流程。 

Cydia Substrate

很强大的一个框架 

1.MoblieHooker

    他有一系列的宏和函数,底层调用的objc的runtime和fishhook来替换系统或者目标应用,其中有两个函数:

MSHookMessageEx 主要用于Objective-C方法 void MSHookMessageEx(Class class ,SEL selector ,IMP replacement ,IMP result)第一个参数是哪个类,第二个参数是哪个编号(方法),第三个新方法的实现,第四个参数旧方法的实现

MSHookFunction 主要用于C/C++函数 void MSHookFunction(voidfunction, void * replacement, void ** p_original) Logos语法的%HOOK就是对这个函数做了一层封装。

2.MobileLoader

    用于加载第三方dylib在运行的应用程序中。启动MobileLoader会根据规则把指定的第三方动态库加载进去,第三方动态库也就是我们写的破解程序。

3.Safe Mode

   破解程序本质是dylib,寄生在别人的进程里,系统进程一旦出错,可能导致整个系统崩溃,Cydia Substrate提供安全模式,在安全模式下,所有的第三方dylib都会被禁止,便查错与修复。

FishHook

    不是一个框架,是一个工具,工具代码可以在github上下载:https:/github.com/facebook/fishhook.git,勾不住自己定义的函数,只能勾系统函数。

其中只有一个.c和.h文件,代码不长,有兴趣可以看看源码,代码有很多关于MachO文件的东西,先了解一下MachO的API

fishhook.h就俩函数一个结构体:

例子:点击屏幕触发NSLog函数转到myNSLog

将fishhook.c   fishhokk.h拖进项目

//
//  ViewController.m
//  fishhook
//
//  Created by yrl on 2019/7/26.
//  Copyright © 2019 apple. All rights reserved.
//#import "ViewController.h"
#import "fishhook.h"@interface ViewController ()@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];NSLog(@"123");//NSLog属于懒加载,调用才能看到//实现交换//定义rebinding结构体struct rebinding nslog;nslog.name = "NSLog";nslog.replacement = myNSLog;nslog.replaced = (void **)&sys_nslog;//定义结构体数组struct rebinding rebs[1] = {nslog};/* 用来重新绑定符号* arg1 存放rebinding结构体的数组* arg2 数组长度*/rebind_symbols(rebs, 1);//两个参数 结构体数组、数组长度printf("修改完毕!!");
}
//-------------更改系统NSLOG函数-------------
//函数指针,用来保存原来的函数地址
static void(*sys_nslog)(NSString *format,...);
//定义一个新的函数
void myNSLog(NSString *format, ...){format = [format stringByAppendingString:@"\n勾上了!!"];sys_nslog(format);
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{//屏幕触发事件NSLog(@"点击屏幕!!");
}@end

运行函数,点击屏幕:

二、FishHook的原理探究

    仿照上面的工程再写一个代码,这回我们用来勾自己定义的newFunc()函数,当点击屏幕时,func(str)应该被newFunc HOOK,转而执行newFunc函数逻辑:

//
//  ViewController.m
//  fishhook2
//
//  Created by yrl on 2019/7/26.
//  Copyright © 2019 apple. All rights reserved.
//#import "ViewController.h"
#import "fishhook.h"
@interface ViewController ()@end@implementation ViewControllervoid func(const char *str){NSLog(@"%s",str);
}- (void)viewDidLoad {[super viewDidLoad];//交换rebind_symbols((struct rebinding[1]){{"func",newRunc,(void **)&funcP}}, 1);}
//原始指针
static void (*funcP)(const char * str);
//新方法
void newRunc(const char * str){NSLog(@"勾住了");funcP(str);
}-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{func("hello");
}@end

运行后发现并没有按照预想的来:

FishHook原理:

git上有介绍(英文):

dyld binds lazy and non-lazy symbols by updating pointers in particular sections of the __DATA segment of a Mach-O binary. fishhook re-binds these symbols by determining the locations to update for each of the symbol names passed to rebind_symbols and then writing out the corresponding replacements.

For a given image, the __DATA segment may contain two sections that are relevant for dynamic symbol bindings: __nl_symbol_ptr and __la_symbol_ptr__nl_symbol_ptr is an array of pointers to non-lazily bound data (these are bound at the time a library is loaded) and __la_symbol_ptr is an array of pointers to imported functions that is generally filled by a routine called dyld_stub_binder during the first call to that symbol (it's also possible to tell dyld to bind these at launch). In order to find the name of the symbol that corresponds to a particular location in one of these sections, we have to jump through several layers of indirection. For the two relevant sections, the section headers (struct sections from <mach-o/loader.h>) provide an offset (in the reserved1 field) into what is known as the indirect symbol table. The indirect symbol table, which is located in the __LINKEDIT segment of the binary, is just an array of indexes into the symbol table (also in __LINKEDIT) whose order is identical to that of the pointers in the non-lazy and lazy symbol sections. So, given struct section nl_symbol_ptr, the corresponding index in the symbol table of the first address in that section is indirect_symbol_table[nl_symbol_ptr->reserved1]. The symbol table itself is an array of struct nlists (see <mach-o/nlist.h>), and each nlist contains an index into the string table in __LINKEDITwhich where the actual symbol names are stored. So, for each pointer __nl_symbol_ptr and __la_symbol_ptr, we are able to find the corresponding symbol and then the corresponding string to compare against the requested symbol names, and if there is a match, we replace the pointer in the section with the replacement.

可执行文件MachO怎么进入内存的?是通过DYLD(动态链接器)加载进内存的,通过lldb(调试工具)的image list可以看到加载MachO文件的同时还加载了哪些库,期间涉及到了ASLR(地址空间布局随机化),每次加载的内存地址的偏移都不一样。

拿上面的例子说,自己定义的func函数和NSLog函数在内存中的地址不一样,位置也不一样,func在MachO文件中,NSLog在系统动态库(dylib共享的动态链接库)中,那么func里面调用NSLog要找到NSLog的地址,MachO文件怎么找?怎么知道的?在程序启动之前都不知道,不同手机的NSLog地址也是不一样的,这就是DYLD的工作了,如图:

所有的可执行文件都是由它加载的,dyld加载了MachO,一旦加载了MachO文件,苹果采用了一种技术叫PIC(位置独立代码),当程序MachO调用其外部的函数的时候,比如说NSLog,他就会在DATA段创建一个指针,八个字节,用来放外部函数的地址,在调用之前这里是什么是不知道的,当func调用系统动态库中NSLog时就通过dyld链接dylib找到NSLog函数的真实地址,写入到DATA段,dyld就会绑定一个MachO里面的函数(符号),也就是写到data段里的符号,所以这就是为什么fishhook的交换函数叫rebind_sybomls(绑定符号),也就是说这个函数只适用于系统的动态连接库中的函数(系统C级别函数),因为只有系统函数在MachO调用的时候会有符号写入data段,所以这就是为什么去HOOK自己定义的函数不起作用,因为自己定义的函数MachO运行的时候不需要去通过dyld获取函数地址,自然就HOOK不到。

具体验证:上一个fishhook例子

    将例子build一下,提取fishhook.app里面的可执行文件,用MachOview打开,看看其二进制文件格式:我们就看DATA段

可以看到NSLog的地址0x100003028(现在这个值没用,运行的时候dyld才给它赋值) 偏移0x3028,运行时保存NSLog的真实地址,而这个地址在MachO文件偏移的0x3028处,我们找一下,调试在此处下断点:

通过lldb查看 输入image list

我们查看NSLog文件地址(MachO文件地址记得后面加偏移才是NSLog地址):x 0x10b729000+0x3028

这个是NSLog地址(小端序)0x010bac20de,查看此处的汇编代码:dis -s 0x010bac20de

往下走一步ni 再查看原来NSLog的地址:

 此处变为myNSLog了,可见,hook成功。

通过符号找字符

在MachO文件找字符,在Lazy Symbol Pointers表中NSLog在第一位,与之对应的有一个表Dynamic Symbol Table下的Indirect Symbols表

对比一下两个表一一对应,Indirect Symbols指向了下一个Symbols表的第0x79个表项:

 

 他中的数据指向了String Table 的index下标:

 位置在0x4f1c+0x9b =0x4fb7,找到这个位置:

对应的就是NSLog的字符,每一个字符都是以“_”以“.”结束。

这个过程就是开头那段英文所说的意思,官方给的图:

Visual explanation

Lazy Symbol和Indirect Symbol Table对应,Lazy Symbol的0x1061处的表项指向了 Indirect Symbol Table的对应0x1061处的表项,而Indirect Symbol Table0x1061处指向的是Symbol Table的16343处,Symbol Table的16343处指向的是String Table的70026处的字符,可以看到字符为"_close.",他用的是close()函数,而我们用的是NSLog函数。

后记:

    MachO文件很重要,在做防护,检测app是否被修改,被注入,是否在越狱环境中,都需要不断检测MachO文件的字段是否被修改,如果MachO被修改说明app被修改了,那么app就会做出相应的防护措施,比如微信扫脸支付等功能禁用等,所以我们要了解MachO文件。

Published by

风君子

独自遨游何稽首 揭天掀地慰生平

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注