代码注入

上一篇咱们已经讲到使用shell脚原本重签并调试别人的APP,那么咱们又是重签又是附加调试别人的APP是为了啥呢?是吃的太饱了吗,固然不是...咱们接下来的任务就是代码注入git

iOSAPP执行哪些代码?

在开始代码注入以前,咱们先了解一下一个iOS的APP在运行的时候,究竟会执行哪些代码,以及咱们从哪里入手注入代码程序员

  1. MachO(APP的二进制文件,咱们写的全部代码都会在这里,后面的文章会介绍)
  2. Framework(能够是咱们写的,也能够是第三方的代码)
  3. 系统库(系统提供的)

其中系统库,在非越狱手机上咱们改不了;MachO咱们能够修改,可是比较麻烦,须要会写汇编;而Framework是咱们最容易入手的,咱们本身写一个就好了...github

那么如今的问题就是咱们写的Framework别人的APP怎么会执行呢?shell

MachO文件里面有一个Load Commands的部分,DYLD(the dynamic link editor是苹果的动态连接器,后面的文章也会介绍)会读取这个Load Commands里面的内容,并加载到内存中,若是咱们能把咱们本身的Framework插入到别人APP的MachO的Load Commands里,那么咱们的代码就天然的被执行了api

那么如今的问题就变成了如何把咱们写的Framework插入到别人App的MachO文件里?微信

yololib这是一个终端命令行工具,就是用来把咱们写的Framework插入到MachO文件里,代码很少就200来行,感兴趣的能够看看源码,用法是yololib 参数1 参数2其中参数1是MachO文件,参数2是咱们的Framework相对于MachO文件的路径;为了方便咱们使用这个工具,建议把它放到系统的usr/local/bin目录下,记得给它提高可执行权限(在终端chmod +x yololib),这样咱们任意打开一个终端均可以使用这个命令了,也方便咱们使用脚原本操做;好了,如今完事具有,只欠咱们动手操做了...还记得上一篇文章讲到的内容吗,使用shell脚本重签APP,咱们能够接着来,也能够新建一个项目来执行后面的代码注入,但前提依然是完成了shell脚本重签APP的步骤;我这里新建一个项目来演示后面的代码注入markdown

代码注入的步骤

新建Framework

在完成了上一篇shell脚本重签的步骤以后,咱们先来新建一个Framework,把咱们的Framework准备好 image.png Framework的名字就叫FrankyHook(这个名字无所谓,可是你要记住,由于在使用yololib的时候要用到),在里面新建一个类CodeInject(这个叫什么也无所谓,继承NSObject就够了,主要是须要load方法),并在load方法里面打印点内容 image.png函数

在脚本中添加代码

记得红框部分的Framework名字,是你本身建立的...(要是照着个人抄的的当我没说 image.png 这里面有一个小细节,就是你的脚本运行必定要在嵌入Frameworks以前,否则每次脚本运行都把你的Framework给删掉了...(上面刚用yololib把你写的Framework路径加到MachO里面,这里脚本放在最后的话立刻就把你的Framework给干掉了,会报image not found运行不起来的) image.png 实不相瞒,作了四年多的iOS开发,也是到今天才知道这个地方竟然能够拖动...工具

command + R看控制台输出

image.png

怎么样,是否是发现咱们已经能够在WeChat里面执行咱们的代码了?接下来咱们来实现两个小小的需求oop

须要具有的一些知识点

接下来的内容须要对Objective-C的rumtime有必定的了解,了解的同窗这部分能够直接跳过,看下一部分
Method Swizzle方法交换
runtime提供了一些api来让咱们实现方法交换,如今假设有这么一段代码: image.png 使用NSURL初始化URL的时候,若是URL中含有中文,那么初始化就会失败,返回nil;这个问题作过iOS开发的同窗应该都遇到过,解决的办法就是对URL进行一次百分号编码,如今假设一种情景,你刚入职一家新公司,发现了这个问题,而后在工程里面搜索了一下,发现个人天啦,处处都是这种URL中夹着中文,而没有进行编码的状况,那么这个时候,你是选择一个一个的去修改呢,仍是会想其余更好的办法?

使用方法交换就是更好的办法,新建一个NSURL的分类,在分类的load方法中,实现咱们的方法交换 image.png 一个OC方法,咱们能够分为两个部分,一个是方法名SEL,一个是方法的实现IMP,正常状况下,一个方法的名字对应着它的实现,而有时候咱们经过runtime来交换方法的实现,就如上面的load方法里面的代码,默认状况下方法one和方法two的实现都是指向他们本身的IMP的,经过method_exchangeImplementations()函数交换以后,方法one的实现就指向了方法two的实现,而方法two的实现就指向了方法one的实现,当代码调用URLWithString:的时候,就会来到咱们的HK_URLWithString:方法,当代码调用HK_URLWithString:的时候,就会执行URLWithString:方法;这样原来工程里的全部URLWithString:方法,都会执行到咱们的代码逻辑,首先调用一次原始的初始化URL的方法,看可否成功生成url,若是为nil,就表示咱们可能须要对字符串str进行一下编码,咱们再用编码事后的str初始化url,这样就不用浪费时间精力去一个一个的去修改工程里的代码了

拦截微信的注册点击

使用Debug View Hierarchy查看微信的登陆注册页面的视图层次结构,找到注册的按钮,查看按钮的target和action image.png 咱们知道了注册按钮点击的时候,会调用WCAccountLoginControlLogiconFirstViewRegister方法,并且咱们也已经能够在咱们的Framework中执行咱们的注入代码了,那么接下来咱们如何实现拦截微信的注册按钮点击呢?固然可使用Method Swizzle来实现

在开始写代码实现方法交换以前,还有一个小小的问题,虽然咱们根据经验能够知道这个onFirstViewRegister应该是个对象方法,可能没有返回值,也没有参数,但这些都只是根据咱们经验的猜想...我们程序员仍是应该靠谱一点,那么怎么验证咱们的猜想呢?

Class-dumpyololib同样,也是一个终端命令行工具,一样也能够放到usr/local/bin目录下能够全局使用,它的做用是能够把MachO文件里的头文件信息所有导出来,咱们能够cd到工程编译生成的APP包里面,使用如下命令
class-dump -H WeChat -o ./headers/
将它的头文件都导到一个headers的文件夹内,这个过程须要一点时间,耐心等待一下 image.png 看了下这个文件挺大的,导完以后能够把它剪切到工程根目录下,这样给咱们手机也能省点空间,也确实彻底不必放在APP包里面 image.png 能够看到这里面有15074个项目,微信的头文件还真很多呢...这么大的文件夹若是咱们用Xcode打开搜索的话,必定会十分的痛苦,这里推荐使用sublime...更轻量一点,找起头文件来也更快,搜索一番发现如图所示 image.png 这样就确保了这个onFirstViewRegister方法是无返回值无参数的,能够开始编写代码实现拦截了

如今咱们能够来到咱们Framework的CodeInject.m文件写点代码 image.png

代码就这么点,如今command + R运行起来以后,再次点击微信的注册按钮试试看? image.png

窃取用户的帐号密码

上面的需求仅仅只是破坏了功能,原来的功能都无法使用了;接下来这个需求让用户在神不知鬼不觉的状况下,帐号密码就被窃取了;那么咱们在何时,能拿到用户的帐号和密码呢,固然是点击登陆按钮的时候,因此你懂的,使用viewDebug查看登陆按钮 image.png 登陆按钮点击的target:WCAccountMainLoginViewController,action:onNext;

查看刚刚class-dump出来的头文件发现这个onNext方法是没有参数的,那么咱们须要的帐号密码在哪里呢?哈哈,作过开发的同窗是否是发现这两个名字是否是莫名的熟悉

WCAccountTextFieldItem *_textFieldUserNameItem;
WCAccountTextFieldItem *_textFieldUserPwdItem;
复制代码

image.png

变量名是挺熟悉的,可是这个WCAccountTextFieldItem咱们不知道是个什么,怎么办?头文件都在手上了,还问怎么办?接着搜啊... image.png

WCAccountTextFieldItem里面貌似没啥东西,那就看它父类WCBaseTextFieldItem image.pngWCBaseTextFieldItem类里面发现了一个WXUITextField的东西,跟咱们熟悉的UITextField很像了,咱们猜想可能就是这个m_textField存放着咱们想要的东西,先不着急写代码

如今咱们能够再次使用viewDebug工具调试查看一下咱们的帐号和密码在哪里,而且使用lldb来动态调试验证咱们的猜想 image.png 首先控制器的地址,能够由登陆按钮的target获取到;获取到控制器以后,再使用KVC大法获取成员变量_textFieldUserPwdItem的地址(密码都能获取到了,帐号也是同样的操做)并打印它;获取到_textFieldUserPwdItem的地址以后,再次使用KVC大法获取它的成员变量m_textField的地址并打印这个对象,好家伙,明文密码不就在这儿了吗!!!

接下来咱们经过代码来实现需求: image.png 这代码看起来跟拦截微信注册的代码差不太多,那么咱们command + R运行看看结果 image.png WTF?帐号密码确实是都获取到了,可是APP缺崩溃了?为何会发生崩溃,崩溃信息是咱们常常可以遇到的经典报错unrecognized selector sent to instance 0x10b15b400,说是控制器WCAccountMainLoginViewController没法识别FK_onNext这个方法;

咱们仔细思考一下,方法交换为何通常推荐写在想要交换方法的类所在的分类里面?由于在想要交换方法的类的分类当中,咱们会新增一个方法,用来实现咱们的逻辑,也正好是由于在分类中,因此当前类也添加了咱们新增的方法,这样交换下来就不会出现找不到方法的错误;而上面的代码,咱们的本意也是但愿在WCAccountMainLoginViewController里面新增一个方法处理咱们的逻辑,并交换onNext方法,但如今的问题是咱们在CodeInject这个类的load方法中,那么有什么办法能够解决这个问题呢?

动态添加方法

rumtime的api提供了运行时动态添加方法的能力
BOOL class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types)
参数1: 给哪一个类添加方法
参数2: 方法的名字
参数3: 方法的实现
参数4: 方法的参数和返回值描述,用一些特定的符号表示,也能够不写
参数5: BOOL值,表示是否添加成功

那么借用这个api咱们能想到什么解决办法呢,给WCAccountMainLoginViewController控制器添加咱们的FK_onNext方法,再让FK_onNextonNext方法交换,最终的代码以下 image.png 再次command + R运行发现,既能成功获取到用户输入的帐号密码,又能成功的去调用微信的登陆逻辑了

动态替换方法

如今除了添加新方法的方式,咱们还有其余的办法吗?固然有(强大的runtime)
rumtime的api还提供了运行时替换方法的能力
IMP _Nullable class_replaceMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types)
参数1: 替换哪一个类
参数2: 方法的名字
参数3: 方法的实现
参数4: 方法的参数和返回值描述,用一些特定的符号表示,也能够不写
返回值: IMP,原始的方法实现

若是使用这个方式,那么咱们应该是将原始的onNext方法替换成咱们的FK_onNext方法,那么咱们如何去调用微信原始的onNext方法呢,在替换以前将原始的onNext方法的实现IMP记录下来,而后在咱们的FK_onNext方法中使用这个记录的IMP来实现对原始onNext的调用,具体代码以下图: image.png 对比方法交换的代码实现,咱们发现方法替换的方式须要多一个变量originalIMP,用来记录原始的onNext方法的实现,并且还会报个警告,虽然少了一点点代码,但看起来也不是那么好理解...固然,这里使用方法替换也只是为了学习一下runtime提供的api,感觉一下rumtime的强大,具体使用哪一个方式就看我的的喜爱罢了

动态获取方法实现和设置方法实现

那么,还有更骚的操做吗?固然有...
IMP _Nonnull method_getImplementation(Method _Nonnull m)
获取方法m的实现IMP
IMP _Nonnull method_setImplementation(Method _Nonnull m, IMP _Nonnull imp)
设置方法m的实现IMP
这个原理其实跟替换差不太多,首先获取原始onNext的实现并记录,而后设置新的IMP(咱们写的FK_onNext)给WCAccountMainLoginViewControlleronNext方法,具体代码以下: image.png

好了,到此咱们为了实现窃取用户的帐号密码已经使用了三种runtime提供的方式...到这里就真的没了

下一篇文章开始介绍咱们最近一直提到的MachO文件,到底什么是MachO文件,它包含了什么东西,干什么用的...