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-代码注入_第1张图片

只要Load Command中有对应库的LC_LOAD_DYLIBdyld就会去对应路径加载库

知道了这点,那么代码注入的过程就很明确了

  1. 确认目标App的MachO执行文件的Load Commands中有对应的LC_LOAD_DYLIB
  2. 将自己的代码写入动态库中

1.1.1 注入步骤

  1. 给自己的壳工程添加一个target LGHook Framework动态库

⚠️注意:这里的壳工程,用的是上篇文章10-应用重签名中的shell脚本重签名的模式。

11-代码注入_第2张图片
11-代码注入_第3张图片
  1. LGHook 添加LGInject类,并且重写+load方法
11-代码注入_第4张图片

如果LGHook被加载进内存,则会打印log

  1. run,查看Produces.app
11-代码注入_第5张图片
11-代码注入_第6张图片
11-代码注入_第7张图片

动态库LGHook已经在目标AppFramewroks中了,接着我们查看MachO

  1. 查看MachO文件
11-代码注入_第8张图片

Load Commands中并没有LGHookLC_LOAD_DYLIB,所以run起来,控制台中并没有打印日志。

1.1.2 yololib手动注入

这个时候就需要使用yololib工具修改MachO文件,将LGHook真正加入到目标App的Load Commands

  1. 拷贝yololib和目标App可执行文件同一目录(可单独新建一个文件夹)

然后执行命令

./yololib 目标可执行文件 要添加的Framework路径名称

例如

./yololib WeChat Frameworks/LGHook.framework/LGHook
11-代码注入_第9张图片

再查看可执行文件Load Commands

11-代码注入_第10张图片

此时就有LGHook了。

  1. 拿取原始.ipa包,解压,在Payload文件夹中找到.app,右键显示包内容,替换成上一步生成的二进制MachO文件,再回到Payload所在目录,输入以下指令生成.ipa
zip -ry xx.ipa Payload/

⚠️注意:这一步一定要用原始.ipa包,替换原始包里的二进制MachO文件。

  • 原始包微信8.0.2.ipa,将后缀改为.zip,解压
  • 右键显示包内容
11-代码注入_第11张图片
  • 替换上一步的WeChat Mach-O
11-代码注入_第12张图片
  • 返回到Payload所在目录,输入指令生成.ipa包
  1. 将上一步生成的.ipa包,放入APP文件夹中
11-代码注入_第13张图片

cmd+shift+k先清空XCode项目的缓存,再来run

解决

11-代码注入_第14张图片

修改LGHook.framework所支持的最低版本。

再次run

11-代码注入_第15张图片

大功告成!

小结

以上步骤大致为

  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-代码注入_第16张图片
11-代码注入_第17张图片

修改Base SDKiOS

11-代码注入_第18张图片

Code Signing Identity修改为iOS Developer

11-代码注入_第19张图片

build Phases中添加Copy Files,增加libLGDylibHook.dylib

11-代码注入_第20张图片
  1. LGDylibHook.m添加Hook代码
+ (void)load {
    NSLog(@"LGDylibHook Success ");
}
11-代码注入_第21张图片

此时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-代码注入_第22张图片
  1. 观察libLGDylibHook.dylib是否在Frameworks
11-代码注入_第23张图片

MachOLoad Commands

11-代码注入_第24张图片
  1. 运行

⚠️注意:需要先编译libLGDylibHook.dylib再编译壳工程

11-代码注入_第25张图片
小结

注入步骤

  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(@"LGDylib  Hook 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-代码注入_第26张图片

成功拦截!接下来,我们想在拦截到事件后,先执行自己的代码,然后恢复原有的流程,怎么做到呢?相信大家都能想到 Method Swizzle

Method Swizzle

在OC中,SELIMP 之间的关系,就好像一本书的目录SEL 是方法编号,就像标题一样。IMP是方法实现的真实地址,就像页码一样。他们是一一对应的关系。
Runtime提供了交换两个SELIMP对应关系的函数。

11-代码注入_第27张图片
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-代码注入_第28张图片

结合presponderpviews分析。不过这种方式一般比较麻烦

2.2.2静态分析

第二种就是静态分析了 使用class-dump工具导出头文件(⚠️swift文件不行)。

./class-dump -H WeChat -o ./Headers
11-代码注入_第29张图片

执行完成后,所有头文件都在Headers文件夹

11-代码注入_第30张图片

文件过多22335个,不推荐Xcode打开。

  • 搜索WCAccountMainLoginViewController,找到onNext
11-代码注入_第31张图片

onNext方法既没有参数也没有返回值,再看看有没其它的和密码相关的

11-代码注入_第32张图片

我们发现了_textFieldUserPwdItem不就是密码的输入框吗。

  1. 搜索WCAccountTextFieldItem
11-代码注入_第33张图片

没有找到WCUITextField相关的内容,接着找父类WCBaseTextFieldItem

11-代码注入_第34张图片

找到了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-代码注入_第35张图片

接下来,在这个过程中我们应该调用回原来的方法,让登录进行下去

- (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版本

你可能感兴趣的:(11-代码注入)