iOS App Hook 杂谈-绝口不再提Hook
Hello大家好,上一篇我们聊到了《Android App Hook 杂谈》 里面介绍了我在工作中接触或者学习到的各种Hook的技术,按照国际惯例既然写了Android那我肯定不会抛弃iOS的小伙伴,那么我们今天就来看看iOS在开发中有哪些Hook可以去学习探索的呢,那么我会以我视角来带大家去看看这些常用的Hook技术。
首先声明,本人并非是做底层架构出身,也并非是从事App安全相关的工作,之所以对于Hook有这么多的了解只不过是兴趣使然,再加上本人有扎实的计算机底层的基础,对于一些Hook技术可以去弄明白他的缘由而已,另外这篇文章也为了我也是为了我对于Hook这个主题做一个自我的总结,以后可能都不会多花时间去涉及这一块的内容了,好的让我们愉快的开始吧。
本着该文是杂谈属性的又一篇“杰作”,熟悉我博客的小伙伴就知道这一篇肯定也是轻松愉快的文章,代码量肯定不大,大家只需放松心态即可,那么大家知道iOS主要是基于OC、Swift语言的开发的,熟悉OC语言的小伙伴都知道,OC本身就是一门带动态特性的静态语言,所以我们单从iOS的应用层就有很多的Hook技术可以用来玩的,
首先使用OC语言runtime的特性就很容易的写出一种简单的一般称作方法交换的Hook,如下:
#import "UIViewController+swizzling.h"
#import <objc/runtime.h>
@implementation UIViewController (swizzling)
+ (void)load {
// 通过class_getInstanceMethod()函数从当前对象中的method list获取method结构体,如果是类方法就使用class_getClassMethod()函数获取。
Method fromMethod = class_getInstanceMethod([self class], @selector(viewDidLoad));
Method toMethod = class_getInstanceMethod([self class], @selector(swizzlingViewDidLoad));
/**
* 我们在这里使用class_addMethod()函数对Method Swizzling做了一层验证,如果self没有实现被交换的方法,会导致失败。
* 而且self没有交换的方法实现,但是父类有这个方法,这样就会调用父类的方法,结果就不是我们想要的结果了。
* 所以我们在这里通过class_addMethod()的验证,如果self实现了这个方法,class_addMethod()函数将会返回NO,我们就可以对其进行交换了。
*/
if (!class_addMethod([self class], @selector(swizzlingViewDidLoad), method_getImplementation(toMethod), method_getTypeEncoding(toMethod))) {
method_exchangeImplementations(fromMethod, toMethod);
}
}
@end
使用的时候注意方法替换class_replaceMethod以及方法交换method_exchangeImplementations的区别即可
既然提到了runtime,那我就不得不提一下很早以前有一个大量使用了runtime特性去做热更新的一个框架那就是JSpatch。
JSpatch用iOS内置的JavaSriptCore.framework作为JavaScript的执行引擎,但是他并没有直接使用JavaScript-OC的互相调用,而是通过OC Runtime从JavaScript传递调用的类名和函数名到OC,再使用NSInvocation反射去进行动态的调用OC的方法。
这个在当年是一个很不错的热修复框架,但是好景不长最后被禁用了,但是这里顺便提一句很多人一知半解认为苹果公司禁用热更新所以禁用掉了了JSPatch,其实不然,苹果公司真正拒绝JSPatch的原因是JSPatch可以理解为所有的 Objective-C 的 API 进行了映射,允许开发者在 JS 端调用任意原生代码。这显然是极其危险的。假设这段代码是通过热更新技术下载执行的,如果在中间存在黑客,把这段代码动态替换掉,比如修改为获取用户通讯录并上传到黑客的服务器,就会造成重大的安全问题
最主要的是JSPatch可以任意对于所有的 Objective-C 的 API 进行了映射,而React Native不能,这就是为什么React Native能够活下来的原因;
下一个要介绍的就是基于Got的hook,在iOS里面可以找到到一个成熟的框架去使用(这点比Android要好),那就是大名鼎鼎的fish Hook,那么他是怎么工作的呢
首先我们要知道的是在C/C++语言里面模块间的数据访问和函数调用,都是用间接寻址,主模块将要访问动态库里的数据符号地址放在got(也称Non-Lazy Symbol Pointers)数据段,调用动态库的函数的地址放在la_symbol_ptr数据段。而数据段是可读写的,所以程序运行期间我们可以通过修改got(nl_symbol_ptr)和la_symbol_ptr数据段,来替换函数跟全局变量的地址。这个就是fishhook的原理。
说白了fishhook 的本质是遍历 image 中的非懒加载 nl_symbol_ptr和懒加载 la_symbol_ptr 数据段,将里面的函数地址替换成自定义的函数地址;
fish Hook涉及内容比较多,给大家推荐两篇文章 :
看完这两篇文章相信你对于fish Hook应该是可以掌握了
fish Hook在iOS中比Android要多一点点,但是他也并不是全能的,网络上很多的博客以及up主的视频都提到了主要他是用来Hook外部方法的,但是其实这种说法是不正确的,这里没有针对那些博主的以及up主,因为大家知道C/C++语言里面方法默认就是外部方法,难道都是可以被Got Hook的吗,显然是不可能的,熟悉fish Hook的都知道这种应该是在动态库里面Hook才会有效果的,所以应该是在动态库里面的强符号方法才可以被Hook,因为Got Hook这种方式本身就是只针对于动态库而言的。
现在fish Hook已经很底层了,那还有没有更底层的Hook呢,还真有那就是称之为终极Hook的inline Hook,目前据了解支持inline Hook的框架有Cydia Substrate,Dobby这两个框架,老实讲这种Hook的作用范围非常广,不限于iOS底层,只要是C/C++语言相关的他都可以Hook,而且几乎没有限制条件,那么他是怎么Hook的呢,先来看一张图
image.png它属于是运行时指令替换,假设目标方法内有ins0, ins1, ins2三条指令,首先将起始指令(实际上是前2条指令)替换为等长的跳转指令jump_ins,jump_ins负责跳转到hook方法执行,而hook操作后,往往还需要保留调用原方法的能力以保证功能可用性,所以hook方法内还有一个跳转指令来调回原方法继续执行(jump ins1),调回前需要先补充执行目标方法已被替换的原始指令(图中ins0),保证原方法完整性。综上,inline hook需要完成的工作就是图中绿色的部分,即跳转指令的替换、补充执行原指令、跳回原方法继续执行这三步。
这个原理也是比较复杂,具体给大家分享一个链接 https://www.jianshu.com/p/2684e251124d
好了已经介绍了很多种,下面再来给大家最后介绍一种Hook最为结束的开胃菜吧,那就是通过ptrace的Hook,这种方式比较少见一来是实现比较复杂,二来稳定性也不是最高的,而且需要root的权限去实现,所以用得很少,但是我们还是来看看他是怎么实现的吧
他的原理就是利用ptrace附加进程进行调试,利用ARM汇编底层修改替换寄存器来实现Hook,可以简单的分为以下步骤:
- 1.先找到被注入进程的pid
- 2.附加当前进程到被注入进程
- 3.保存原寄存器的值
- 4.找到需要Hook的系统调用函数
- 5.修改目标进程寄存器
- 6.执行目标函数调用
- 7.恢复寄存器的值
- 8.分离附加进程
原理也是比较复杂的,细节大家可以看看我这两篇博客:
已经讲得很到位了,绝对能帮助到大家了
介绍得差不多了,这篇博客也是把我这几年接触到的Hook基本上都写出来了,因为这些对于我来说在工作中都不是直接主要的技能,所以以后可能都不会再去写关于Hook的内容了,这个也是为什么副标题叫“绝口不再提”的原因,一开始是凭着兴趣入门摸索,一腔热血,没有任何人指导也坚持到了现在,今天也算是为了自己这么多年的做的总结吧
好了对于iOS的Hook我们就介绍到这里了,上面的这些Hook技术都是我在工作中积累的内容了,如果能帮助到也是喜欢Hook技术的你的话那么我很是欣慰了,如果你还喜欢的话请帮忙点赞加关注,你的一键三连是我持续写作的动力。
image.png今天是2023年的第二天了,送给大家一张萌兔的照片,祝大家2023兔年心想事成,天天开心,今天也是我家小的生日,不多说了,我要去给她过生日了,2023年大家后会有期😁···