11-代码注入

前言 上篇文章10-应用重签名,我们利用CodeSign终端指令Shell脚本2种方式,成功实现了对微信app的重签名,已经能够查看微信的登录注册页面UI层级,接下来,我们想做些自己的事情,例如注入自己的代码,修改微信注册登录的流程。
一、代码注入 一般修改原始的程序,是利用代码注入的方式,注入代码就会选择利用Framework 或者Dylib等第三方库的方式注入。
1.1 FrameWork注入
我们知道,重签名app后自己的壳工程的代码就被替换掉了(替换的是MachO二级制执行文件),那么原有的工程代码并不会执行。iOS系统是通过dyld(iOS系统提供的动态链接器)加载MachO可执行文件,而加载的过程,首先就是读取MachO的Load Commands(其中包括_PAGEZERO、_TEXT、_DATA、_LINKEDIT等)
App的执行过程,除了执行自己的工程代码外,还依赖一些系统基础库(例如UIKit,Foudation等)和第三方的库(.framwork.a库等),而这些库最终也是一个MachO可执行文件,分析MachO文件会发现Frameworks中的库在Load Commands中以LC_LOAD_DYLIB存在,路径是Frameworks下对应库
11-代码注入
文章图片

只要Load Command中有对应库的LC_LOAD_DYLIBdyld就会去对应路径加载库
知道了这点,那么代码注入的过程就很明确了
  1. 确认目标App的MachO执行文件的Load Commands中有对应的LC_LOAD_DYLIB
  2. 将自己的代码写入动态库中
1.1.1 注入步骤
  1. 给自己的壳工程添加一个target LGHook Framework动态库
??注意:这里的壳工程,用的是上篇文章10-应用重签名中的shell脚本重签名的模式。
11-代码注入
文章图片
11-代码注入
文章图片
  1. LGHook 添加LGInject类,并且重写+load方法
11-代码注入
文章图片
如果LGHook被加载进内存,则会打印log
  1. run,查看Produces.app
11-代码注入
文章图片
11-代码注入
文章图片
11-代码注入
文章图片
动态库LGHook已经在目标AppFramewroks中了,接着我们查看MachO
  1. 查看MachO文件
11-代码注入
文章图片
Load Commands中并没有LGHookLC_LOAD_DYLIB,所以run起来,控制台中并没有打印日志。
1.1.2 yololib手动注入 这个时候就需要使用yololib工具修改MachO文件,将LGHook真正加入到目标App的Load Commands
  1. 拷贝yololib和目标App可执行文件同一目录(可单独新建一个文件夹)
11-代码注入
文章图片
然后执行命令
./yololib 目标可执行文件 要添加的Framework路径名称
例如
./yololib WeChat Frameworks/LGHook.framework/LGHook

11-代码注入
文章图片
再查看可执行文件Load Commands
11-代码注入
文章图片
此时就有LGHook了。
  1. 拿取原始.ipa包,解压,在Payload文件夹中找到.app,右键显示包内容,替换成上一步生成的二进制MachO文件,再回到Payload所在目录,输入以下指令生成.ipa
zip -ry xx.ipa Payload/

??注意:这一步一定要用原始.ipa包,替换原始包里的二进制MachO文件。
  • 原始包微信8.0.2.ipa,将后缀改为.zip,解压
    11-代码注入
    文章图片
11-代码注入
文章图片
  • 右键显示包内容
11-代码注入
文章图片
  • 替换上一步的WeChat Mach-O
11-代码注入
文章图片
  • 返回到Payload所在目录,输入指令生成.ipa包
  1. 将上一步生成的.ipa包,放入APP文件夹中
11-代码注入
文章图片
cmd+shift+k先清空XCode项目的缓存,再来run
解决
11-代码注入
文章图片
修改LGHook.framework所支持的最低版本。
再次run
11-代码注入
文章图片
大功告成!
小结 以上步骤大致为
  1. 新建壳工程WeChat,配置重签名脚本
  2. 通过Xcode新建LGHook.framwork(注意改支持的版本),真机运行,将库安装进入APP包
  3. 通过yololib,对WeChatMachO注入LGHook.framwork库路径。命令 $./yololib MachO文件路径 库路径
  4. 使用原始微信.ipa包,将第3步生成的MachO,替换原始包里面的MachO,再次打包生成.ipa
  5. 壳工程的APP文件夹中,替换第4步生成的.ipa包,直接运行即可。
所有的Framwork加载都是由DYLD加载进入内存被执行的。注入成功的库路径会写入到MachO文件的LC_LOAD_DYLIB中。
1.2 Dylib注入
除了Framwork注入外,还能用Dylib注入。原理和Framwork相同,过程和Framwork差不多。
  1. 创建dylib库。这里选择了macOS,为了是库为动态库
11-代码注入
文章图片
11-代码注入
文章图片
修改Base SDKiOS
11-代码注入
文章图片
Code Signing Identity修改为iOS Developer
11-代码注入
文章图片
build Phases中添加Copy Files,增加libLGDylibHook.dylib
11-代码注入
文章图片
11-代码注入
文章图片
  1. LGDylibHook.m添加Hook代码
+ (void)load { NSLog(@"LGDylibHook Success "); }

11-代码注入
文章图片
此时run,仍然只是Frameworks有libLGDylibHook.dylib库,MachOLoad Commands仍然没有LC_LOAD_DYLIB
1.2.1 yololib自动注入
  1. 直接在appResign.sh脚本中添加yololib修改MachO脚本的指令
#yololib修改MachO文件 #./yololib "$TARGET_APP_PATH/$APP_BINARY" "Frameworks/LGHook.framework/LGHook" ./yololib "$TARGET_APP_PATH/$APP_BINARY" "Frameworks/libLGDylibHook.dylib"

yololib库放在工程目录中
11-代码注入
文章图片
  1. 观察libLGDylibHook.dylib是否在Frameworks
11-代码注入
文章图片
MachOLoad Commands
11-代码注入
文章图片
  1. 运行
??注意:需要先编译libLGDylibHook.dylib再编译壳工程
11-代码注入
文章图片
小结 注入步骤
  1. 通过Xcode新建dylib库(注意:dylib属于MacOS,所以需要修改属性
    • Base SDKiOS
    • Code Signing Identity修改为iOS Developer
  2. 添加Target依赖,让Xcode将自定义Dylib文件打包进入APP
  3. 利用yololib进行注入
二、示例演示 接下来,我们实战一下,针对微信的注册登录页面,代码HOOK注册和登录的流程。
2.1 注册分析
首先是注册,我们查看注册按钮的UI层级
上图,打印地址可知,发现注册按钮的TargetWCAccountLoginControlLogicactiononFirstViewRegister
然后我们直接hook这个这个方法就可以拦截了注册事件
#import "LGDylibHook.h" #import @implementation LGDylibHook+ (void)load { NSLog(@"LGDylibHook success"); //拦截微信注册事件 Method oldMethod = class_getInstanceMethod(objc_getClass("WCAccountLoginControlLogic"), @selector(onFirstViewRegister)); Method newMethod = class_getInstanceMethod(self, @selector(hook_onFirstViewRegister)); method_exchangeImplementations(oldMethod, newMethod); }- (void)hook_onFirstViewRegister { NSLog(@"WeChat click login"); }@end

【11-代码注入】运行看看
11-代码注入
文章图片
成功拦截!接下来,我们想在拦截到事件后,先执行自己的代码,然后恢复原有的流程,怎么做到呢?相信大家都能想到 Method Swizzle。
Method Swizzle 在OC中,SELIMP 之间的关系,就好像一本书的目录SEL 是方法编号,就像标题一样。IMP是方法实现的真实地址,就像页码一样。他们是一一对应的关系。
Runtime提供了交换两个SELIMP对应关系的函数。
11-代码注入
文章图片
OBJC_EXPORT void method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

通过这个函数交换两个SELIMP对应关系的技术称之为Method Swizzle(方法欺骗)
2.2 登录调试分析
接下来我们调试下【登录】功能
上图可知,view debug分析可以得到TargetWCAccountMainLoginViewControlleractiononNext。同理,【登录】事件可以拦截拿到,那么pwd怎么获取呢?
view debug可以看到pwd控件是WCUITextField并且能看到对应的text(即密码)。假如我们不借助view debug,怎么去获取密码呢?
2.2.1动态分析 第一种方式就是动态分析 可以通过响应链一步步分析控件层级响应关系
11-代码注入
文章图片
结合presponderpviews分析。不过这种方式一般比较麻烦
2.2.2静态分析 第二种就是静态分析了 使用class-dump工具导出头文件(??swift文件不行)。
./class-dump -H WeChat -o ./Headers

11-代码注入
文章图片
执行完成后,所有头文件都在Headers文件夹
11-代码注入
文章图片
文件过多22335个,不推荐Xcode打开。
  • 搜索WCAccountMainLoginViewController,找到onNext
11-代码注入
文章图片
onNext方法既没有参数也没有返回值,再看看有没其它的和密码相关的
11-代码注入
文章图片
我们发现了_textFieldUserPwdItem不就是密码的输入框吗。
  1. 搜索WCAccountTextFieldItem
11-代码注入
文章图片
没有找到WCUITextField相关的内容,接着找父类WCBaseTextFieldItem
11-代码注入
文章图片
找到了WCUITextField类型的m_textField成员变量。这个不就是输入账号密码的控件。
  1. 调试验证
接下来我们来验证下,是否是输入密码的控件? KVC控制台打印查看
相关指令
(lldb) po [(WCAccountMainLoginViewController *)0x117813000 valueForKey:@"_textFieldUserPwdItem"] (lldb) po [(WCAccountTextFieldItem *)0x2830f9ec0 valueForKey:@"m_textField"] ; layer = >(lldb) po [(WCUITextField *)0x1120a4400 text] qwer1234

果然找到了对应的和想要的密码
2.3 登录代码注入
??注意:别用自己的常用账号去尝试,有可能被封号
最后,也是重点,注入自己的代码
+ (void)load { NSLog(@"LGDylib Hook success"); //拦截微信登录事件 Method oldMethod = class_getInstanceMethod(objc_getClass("WCAccountMainLoginViewController"), @selector(onNext)); Method newMethod = class_getInstanceMethod(self, @selector(hook_onNext)); method_exchangeImplementations(oldMethod, newMethod); }- (void)hook_onNext { NSLog(@"WeChat click login"); //获取密码 UITextField *textField = (UITextField *)[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"]; NSString *pwd = [textField text]; NSLog(@"password: %@",pwd); }

11-代码注入
文章图片
接下来,在这个过程中我们应该调用回原来的方法,让登录进行下去
- (void)hook_onNext { NSLog(@"WeChat click login"); //获取密码 UITextField *textField = (UITextField *)[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"]; NSString *pwd = [textField text]; NSLog(@"password: %@",pwd); [self hook_onNext]; }

运行
报错:找不到方法!WCAccountMainLoginViewController中不存在hook_onNext方法。因为
一般情况下我们进行方法交换分类中进行,现在不是在分类中.
那有没有解决方案呢?当然有,且有3种
class_addMethod方式 利用AddMethod方式,让原始方法可以被调用,不至于因为找不到SEL而崩溃
//1.class_addMethod 方式 + (void)load { //拦截微信登录事件 Class cls = objc_getClass("WCAccountMainLoginViewController"); Method onNext = class_getInstanceMethod(cls, @selector(onNext)); //给WC添加新方法 class_addMethod(cls, @selector(new_onNext), new_onNext, "v@:"); //交换 method_exchangeImplementations(onNext, class_getInstanceMethod(cls, @selector(new_onNext))); }//相当于imp void new_onNext(id self, SEL _cmd) { //获取密码 UITextField *textField = (UITextField *)[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"]; NSString *pwd = [textField text]; NSLog(@"password: %@",pwd); [self performSelector:@selector(new_onNext)]; }

class_replaceMethod方式 利用class_replaceMethod,直接给原始的方法替换IMP
//2.class_replaceMethod 方式 + (void)load { //拦截微信登录事件 Class cls = objc_getClass("WCAccountMainLoginViewController"); //替换 origin_onNext = class_replaceMethod(cls, @selector(onNext), new_onNext, "v@:"); }//原始imp IMP (*origin_onNext)(id self, SEL _cmd); //相当于imp void new_onNext(id self, SEL _cmd) { //获取密码 UITextField *textField = (UITextField *)[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"]; NSString *pwd = [textField text]; NSLog(@"password: %@",pwd); origin_onNext(self,_cmd); }

method_setImplementation方式 利用method_setImplementation,直接重新赋值原始的IMP
//3.method_setImplementation 方式 + (void)load { //拦截微信登录事件 Class cls = objc_getClass("WCAccountMainLoginViewController"); //获取method Method onNext = class_getInstanceMethod(cls,@selector(onNext)); //get origin_onNext = method_getImplementation(onNext); //set method_setImplementation(onNext, new_onNext); }//原始imp IMP (*origin_onNext)(id self, SEL _cmd); //相当于imp void new_onNext(id self, SEL _cmd) { //获取密码 UITextField *textField = (UITextField *)[[self valueForKey:@"_textFieldUserPwdItem"] valueForKey:@"m_textField"]; NSString *pwd = [textField text]; NSLog(@"password: %@",pwd); origin_onNext(self,_cmd); }

以上3种方式都可以成功回到原来的登录流程。大家可以自行验证。
Demo(包含微信.ipa原始包) XFResignHook
??注意:微信ipa包体过大,无法上传gitHub,如果有需要,请留言发邮箱或私信我。
总结
  • 代码注入:Framework(推荐)/ dylib
    • 创建Framework/dylib
    • yololib修改Macho Load Commands
      • ./yololib 要修改的可执行文件 要添加的Framework/dylib路径&名称
  • 调试分析
    • 动态调试:view debug调试控件层级结合presponderpviews
    • 静态分析:通过class-dump导出头文件分析代码逻辑
  • 代码Hook
    • + load方法中hook对应类的对应方法
    • hook方法中调用回原来的方法
      1. class_addMethod
      2. class_replaceMethod
      3. method_setImplementation
相关链接 yololib
class-dump官网
class-dump github
class-dump 兼容Swift版本

    推荐阅读