一般修改原始程序,会利用代码注入的方式,注入代码就会选择利用FrameWorkdylib等三方库的方式注入。

注入原理

当运行重签名的App时,想让它触发当前项目中的代码,例如:写在ViewController的中代码,这是不可能的。因为项目中的App在安装时会被重签名App替换掉,它们根本不是一个MachO文件。

代码注入原理:

使用MachOView分析可执行文件

wx8.0.2.ipa中导出WeChat可执行文件,拖入MachOView,可能会出现没有权限的错误提示
iOS逆向实战--015:代码注入 - 图1

解决方法,使用MachOView菜单中的File -> Open...
iOS逆向实战--015:代码注入 - 图2

选择WeChat可执行文件
iOS逆向实战--015:代码注入 - 图3

成功解析WeChat中的内容
iOS逆向实战--015:代码注入 - 图4

dyld加载可执行文件时,先读取MachO中的Header,获取MachO类型。然后读取Load Commands,通过读取__PAGEZERO__TEXT__DATA__LINKEDIT等段,可以得到MachO的大小,代码段和数据段的位置,告知dyld应该如何将MachO加载到内存中。当dyld读取代码段时,通过读取LC_MAIN找到程序入口。所以读取Load Commands几乎可以读取到MachO的所有信息
iOS逆向实战--015:代码注入 - 图5

dyld除了加载MachO,还要加载UIkitFoundation等系统库,以及Frameworks目录下的动态库
iOS逆向实战--015:代码注入 - 图6

Load Commands中,列出MachO所依赖的所有系统库以及三方库
iOS逆向实战--015:代码注入 - 图7

所以dyld会加载Load Commands中包含的所有库。如果将注入的代码包装成一个动态库,将其插入到Load Commands中,理论上动态库可以被加载,注入的代码也可以被执行

Framework注入

Framwork注入流程:

  • 通过Xcode新建Framwork,将库安装进入App
  • 通过yololib注入Framwork库路径
  1. ./yololib MachO文件路径 库路径
  • 所有的Framwork加载都是由dyld加载进入内存被执行的
  • 注入成功的库路径会写入到MachO文件的LC_LOAD_DYLIB字段中

案例1:

使用Framework注入代码

打开自动重签名WeChat项目,创建Framework动态库
iOS逆向实战--015:代码注入 - 图8

命名HOOK
iOS逆向实战--015:代码注入 - 图9

动态库中创建Class,命名Inject
iOS逆向实战--015:代码注入 - 图10

打开Inject.m文件,写入以下代码:

```

import “Inject.h”

@implementation Inject

+(void)load{ NSLog(@”\n\n\n\n\n🍺🍺🍺🍺🍺\n\n\n\n\n”); }

@end

  1. > 编译项目,在`App`包中的`Frameworks`目录,可以找到`HOOK.framework`动态库<br />
  2. ![](https://upload-images.jianshu.io/upload_images/9297953-b43901228abab3cb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  3. > 此时`HOOK`动态库中的代码还不能被执行,因为`MachO``Load Commands`中,还没有插入`HOOK`动态库的`LC_LOAD_DYLIB`字段
  4. > 使用`yololib``MachO`注入动态库
  5. > `MachO`文件,拷贝到`yololib`文件的同级目录<br />
  6. ![](https://upload-images.jianshu.io/upload_images/9297953-aaeb69f460407d32.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  7. > 注入动态库
  8. >

./yololib WeChat Frameworks/HOOK.framework/HOOK

2021-04-22 17:14:52.339 yololib[22774:33058817] dylib path @executable_path/Frameworks/HOOK.framework/HOOK 2021-04-22 17:14:52.340 yololib[22774:33058817] dylib path @executable_path/Frameworks/HOOK.framework/HOOK Reading binary: WeChat

2021-04-22 17:14:52.341 yololib[22774:33058817] Thin 64bit binary! 2021-04-22 17:14:52.341 yololib[22774:33058817] dylib size wow 72 2021-04-22 17:14:52.341 yololib[22774:33058817] mach.ncmds 124 2021-04-22 17:14:52.341 yololib[22774:33058817] mach.ncmds 125 2021-04-22 17:14:52.341 yololib[22774:33058817] Patching mach_header.. 2021-04-22 17:14:52.399 yololib[22774:33058817] Attaching dylib..

2021-04-22 17:14:52.399 yololib[22774:33058817] size 71 2021-04-22 17:14:52.399 yololib[22774:33058817] complete!

  1. > 查看`MachO``Load Commands`,成功将`HOOK`动态库插入到最后,路径是注入时设置的`参数2`<br />
  2. ![](https://upload-images.jianshu.io/upload_images/9297953-ecb75be597d038b5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  3. > 解压缩`wx8.0.2.ipa`,将`MachO`文件替换,然后重新打包`ipa`<br />
  4. ![](https://upload-images.jianshu.io/upload_images/9297953-c3cfe743b19a159c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  5. > 将重新打包的`wx8.0.2.ipa`,拷贝到项目中`APP`目录<br />
  6. ![](https://upload-images.jianshu.io/upload_images/9297953-1696cfb9ff3d2b2c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  7. > 真机运行项目,`App`安装成功,正常运行,同时打印注入代码<br />
  8. ![](https://upload-images.jianshu.io/upload_images/9297953-cdfc3b5543037a8d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  9. #####dylib注入
  10. > `dylib`注入流程:
  11. > - 通过`Xcode`新建`dylib`库(注意:`dylib`属于`macOS`,所以需要修改属性)
  12. > - 添加`Target`依赖,让`Xcode`将自定义`dylib`文件打包进入`App`
  13. > - 利用`yololib`进行注入
  14. > 案例1
  15. > 使用`dylib`注入代码
  16. > 打开自动重签名`WeChat`项目,创建`dylib`动态库<br />
  17. ![](https://upload-images.jianshu.io/upload_images/9297953-2bf712f89b0a7393.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  18. > 命名`HOOK`<br />
  19. ![](https://upload-images.jianshu.io/upload_images/9297953-4a6973573c1a748f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  20. > `Build Settings`中,将`Base SDK`设置项修改为`iOS`<br />
  21. ![](https://upload-images.jianshu.io/upload_images/9297953-d8c971e729168af3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  22. > `Code Signing Identity`设置项修改为`iOS Developer`<br />
  23. ![](https://upload-images.jianshu.io/upload_images/9297953-a91c910e157342db.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  24. > 切换到`App``Target`,选择`Build Phases`,点击`+`,选择`New Copy Files Phase`<br />
  25. ![](https://upload-images.jianshu.io/upload_images/9297953-12ae4957c7542e3f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  26. > `Copy Files``Destination`中,选择`Frameworks`<br />
  27. ![](https://upload-images.jianshu.io/upload_images/9297953-4ab968f90977723d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  28. > 点击`+`,选择`libHOOK.dylib`<br />
  29. ![](https://upload-images.jianshu.io/upload_images/9297953-e7ab1d1841a06b10.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  30. > 打开`HOOK.m`文件,写入以下代码
  31. >

import “HOOK.h”

@implementation HOOK

+(void)load{ NSLog(@”\n\n\n\n\n🍺🍺 dylib 🍺🍺\n\n\n\n\n”); }

@end

  1. > 使用`脚本注入`动态库:将`yololib`文件,拷贝到项目根目录<br />
  2. ![](https://upload-images.jianshu.io/upload_images/9297953-b6047cfab4d26533.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  3. > 打开`rsign.sh`文件,加入以下代码:
  4. >

./yololib “$TARGET_APP_PATH/$APP_BINARY” “Frameworks/libHOOK.dylib”

  1. > 真机运行项目,`App`安装成功,正常运行,同时打印注入代码<br />
  2. ![](https://upload-images.jianshu.io/upload_images/9297953-76bea0edc0a1f0a6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  3. #####Method Swizzle
  4. 利用`OC``Runtime`特性,动态改变`SEL`(方法编号)和`IMP`(方法实现)的对应关系,达到`OC`方法调用流程改变的目的。主要用于`OC`方法。
  5. > `OC`中,`SEL``IMP`之间的关系,就好像一本书的“`目录`”<br />
  6. ![](https://upload-images.jianshu.io/upload_images/9297953-e9a74f414ee0886b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  7. > - `SEL`是方法编号,就像“`标题`”一样
  8. > - `IMP`是方法实现的真实地址,就像“`页码`”一样
  9. > - 它们是一一对应的关系
  10. > `Runtime`提供了交换两个`SEL``IMP`对应关系的函数
  11. >

OBJC_EXPORT void method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

  1. > 通过这个函数交换两个`SEL``IMP`对应关系的技术,我们称之为`Method Swizzle`(方法欺骗)<br />
  2. ![](https://upload-images.jianshu.io/upload_images/9297953-a5240e9310c9bd30.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  3. > 多种`HOOK`方式
  4. > - `class_addMethod`方式:让原始方法可以被调用,不至于因为找不到`SEL`而崩溃
  5. > - `class_replaceMethod`方式:直接给原始的方法替换`IMP`
  6. > - `method_setImplementation`方式:直接重新赋值新的`IMP`
  7. #####窃取登录密码
  8. > 案例1
  9. > 点击登录按钮,输出用户输入的密码
  10. > 延用`Framwork`代码注入的案例
  11. > 打开`rsign.sh`文件,加入以下代码:
  12. >

./yololib “$TARGET_APP_PATH/$APP_BINARY” “Frameworks/HOOK.framework/HOOK”

  1. > 真机运行项目,进入登录页,使用`Debug View Hierarchy`动态调试
  2. > 找到登录按钮的`Target``Action`<br />
  3. ![](https://upload-images.jianshu.io/upload_images/9297953-1167e2e4de4f6271.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  4. > - `Target``WCAccountMainLoginViewController`
  5. > - `Action``onNext`
  6. > 想要打印密码框的内容,必须先找到密码框的位置,然后通过控件的属性拿到内容
  7. > 【方式一】:动态调试
  8. > 找到密码框的位置,找到存储内容的`text`属性<br />
  9. ![](https://upload-images.jianshu.io/upload_images/9297953-4bfacb04778ab215.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  10. > - 可以通过`响应链条`,根据左侧树状图结构,对`ViewController.view.subviews`进行递归遍历,找到符合条件的`WCUITextField`为止<br />
  11. ![](https://upload-images.jianshu.io/upload_images/9297953-ac50de0f5b668137.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  12. > 使用动态调试,虽然也能找到密码框的位置,但寻找的过程太过繁琐,不推荐使用
  13. > 【方式二】:静态分析
  14. > 目前通过动态分析,可以确定登录按钮和密码框都在`WCAccountMainLoginViewController`
  15. > 使用`class-dump`工具,通过`MachO`导出`OC`中所有类和方法列表以及成员变量
  16. > `MachO`文件,拷贝到`class-dump`文件的同级目录<br />
  17. ![](https://upload-images.jianshu.io/upload_images/9297953-a2c43f2d4648b092.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  18. > 导出`OC`中所有类和方法列表
  19. >

./class-dump -H WeChat -o ./headers/

2021-04-25 13:51:24.070 class-dump[31659:33427860] Warning: Parsing instance variable type failed, ready_ 2021-04-25 13:51:26.308 class-dump[31659:33427860] Warning: Parsing instance variable type failed, underlying 2021-04-25 13:51:26.308 class-dump[31659:33427860] Warning: Parsing instance variable type failed, enable … 2021-04-25 13:52:09.288 class-dump[31659:33427860] Warning: Parsing method types failed, getKeyExtensionList: 2021-04-25 13:52:09.292 class-dump[31659:33427860] Warning: Parsing method types failed, getKeyExtensionList: 2021-04-25 13:52:09.292 class-dump[31659:33427860] Warning: Parsing method types failed, getExtensionListForSelector:

  1. > 打开`headers`目录,列出所有导出的文件列表,找到`WCAccountMainLoginViewController`文件<br />
  2. ![](https://upload-images.jianshu.io/upload_images/9297953-4f2c6dca97b3ba63.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  3. > 打开`WCAccountMainLoginViewController`文件,没有找到`WCUITextField`控件,但发现一个`WCAccountTextFieldItem`类型控件,名称为`_textFieldUserPwdItem`<br />
  4. ![](https://upload-images.jianshu.io/upload_images/9297953-b612a1ea9142c690.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  5. > 打开`WCAccountTextFieldItem`文件,还是没有找到`WCUITextField`控件,但它继承自`WCBaseTextFieldItem`类,我们要找的控件很有可能在父类中<br />
  6. ![](https://upload-images.jianshu.io/upload_images/9297953-e75520170e22cae4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  7. > 打开`WCBaseTextFieldItem`文件,找到`WCUITextField`控件<br />
  8. ![](https://upload-images.jianshu.io/upload_images/9297953-6a0b721370c2728c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  9. > 使用代码之前,可以先结合`动态调试`,验证能否顺利获取到用户密码<br />
  10. ![](https://upload-images.jianshu.io/upload_images/9297953-6ce7259be28de4ed.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  11. > 通过`动态调试`,成功获取密码框里的内容
  12. > 使用代码,获取用户密码
  13. > 打开`Inject`文件,写入以下代码:
  14. >

import “Inject.h”

import

import

@implementation Inject

+(void)load{ Method wx_onNext = class_getInstanceMethod(objc_getClass(“WCAccountMainLoginViewController”), @selector(onNext)); Method my_onNext = class_getInstanceMethod(self, @selector(hook_onNext)); method_exchangeImplementations(wx_onNext, my_onNext); }

-(void)hook_onNext{ UITextField txtPwd = (UITextField )[[self valueForKey:@”_textFieldUserPwdItem”] valueForKey:@”m_textField”]; NSLog(@”密码:%@”, txtPwd.text); }

@end

  1. > 真机运行项目,点击登录按钮
  2. >

密码:123456

  1. > 成功输出用户密码
  2. > 案例2
  3. > 窃取密码后,希望可以调用原始的代码逻辑
  4. > 日常开发中,方法交互一般写在当前类的分类中。由于交互后`hook_onNext`指向了`onNext``IMP`,所以代码中直接调用`hook_onNext`即可
  5. > 打开`Inject`文件,修改`hook_onNext`方法:
  6. >

-(void)hook_onNext{ UITextField txtPwd = (UITextField )[[self valueForKey:@”_textFieldUserPwdItem”] valueForKey:@”m_textField”]; NSLog(@”密码:%@”, txtPwd.text);

[self hook_onNext]; }

  1. > 但是,`HOOK`动态库中的`Inject`文件,和`WCAccountMainLoginViewController`类没有任何关联。虽然`hook_onNext`指向`onNext``IMP`,但是调用`objc_msgSend`函数时,给`VC`发送`hook_onNext`,程序会因为找不到`SEL`而崩溃<br />
  2. ![](https://upload-images.jianshu.io/upload_images/9297953-3aad84c48335d268.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  3. > 下面介绍三种方式,都可以解决上述问题
  4. > 【方式一】:`class_addMethod`
  5. > 既然`VC`中没有`hook_onNext`方法,可以使用`class_addMethod`函数给`VC`添加一个`hook_onNext`,调用时不会因为找不到`SEL`而崩溃
  6. > 打开`Inject`文件,写入以下代码:
  7. >

import “Inject.h”

import

import

@implementation Inject

+(void)load{ Method wx_onNext = class_getInstanceMethod(objc_getClass(“WCAccountMainLoginViewController”), @selector(onNext));

class_addMethod(objc_getClass(“WCAccountMainLoginViewController”), @selector(hook_onNext), hook_onNext, @”v@:”); Method my_onNext = class_getInstanceMethod(objc_getClass(“WCAccountMainLoginViewController”), @selector(hook_onNext));

method_exchangeImplementations(wx_onNext, my_onNext); }

void hook_onNext(id self, SEL _cmd){ UITextField txtPwd = (UITextField )[[self valueForKey:@”_textFieldUserPwdItem”] valueForKey:@”m_textField”]; NSLog(@”密码:%@”, txtPwd.text);

[self performSelector:@selector(hook_onNext)]; }

@end

  1. > - 之前的`hook_onNext`方法,改为函数实现,增加`OC`方法的两个隐式参数
  2. > - 使用`class_addMethod`函数,给`VC`增加`hook_onNext`方法,函数名即是`IMP`
  3. > - `VC`下的`hook_onNext``onNext`进行方法交互
  4. > - `hook_onNext`函数中,如果直接调用函数,调用的不是替换后的`IMP`,而是`hook_onNext``IMP`,这样会形成递归。所以想调用原始方法,要使用`objc_msgSend``performSelector`方式调用
  5. > 真机运行项目,点击登录按钮,窃取密码后,调用原始的代码逻辑
  6. >

密码:123456

  1. > ![](https://upload-images.jianshu.io/upload_images/9297953-a72de88eb14dce61.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  2. > 【方式二】:`class_replaceMethod`
  3. > `VC``onNext`方法的`IMP`替换
  4. > 打开`Inject`文件,写入以下代码:
  5. >

import “Inject.h”

import

import

@implementation Inject

IMP (*oldImp)(id self, SEL _cmd);

+(void)load{ oldImp = class_replaceMethod(objc_getClass(“WCAccountMainLoginViewController”), @selector(onNext), hook_onNext, @”v@:”); }

void hook_onNext(id self, SEL _cmd){ UITextField txtPwd = (UITextField )[[self valueForKey:@”_textFieldUserPwdItem”] valueForKey:@”m_textField”]; NSLog(@”密码:%@”, txtPwd.text);

oldImp(self, _cmd); }

@end

  1. > - 声明`IMP`类型的`oldImp`函数指针
  2. > - 使用`class_replaceMethod`函数,直接将`onNext``IMP`替换为`hook_onNext``IMP`,将返回的原始`IMP`赋值给`oldImp`
  3. > - `hook_onNext`函数中,想调用原始方法,直接调用`oldImp`并传入隐式参数即可
  4. > 【方式三】:`method_setImplementation`
  5. > 获取原始的`IMP`,重新赋值新`IMP`
  6. > 打开`Inject`文件,写入以下代码:
  7. >

import “Inject.h”

import

import

@implementation Inject

IMP (*oldImp)(id self, SEL _cmd);

+(void)load{ Method wx_onNext = class_getInstanceMethod(objc_getClass(“WCAccountMainLoginViewController”), @selector(onNext)); oldImp = method_getImplementation(wx_onNext);

method_setImplementation(wx_onNext, hook_onNext); }

void hook_onNext(id self, SEL _cmd){ UITextField txtPwd = (UITextField )[[self valueForKey:@”_textFieldUserPwdItem”] valueForKey:@”m_textField”]; NSLog(@”密码:%@”, txtPwd.text);

oldImp(self, _cmd); }

@end ```

  • 使用method_getImplementation函数,获取onNextIMP,赋值给oldImp
  • 使用method_setImplementation函数,直接赋值hook_onNextIMP
总结

注入原理:

  • Xcode将注入的动态库打包进App包中
  • MachOLoad Commands中,包含注入动态库的LC_LOAD_DYLIB字段
  • DYLD加载注入的动态库

注入方式:

  • Framework注入
  • dylib注入

Framwork注入流程:

  • 通过Xcode新建Framwork,将库安装进入App
  • 通过yololib注入Framwork库路径

dylib注入流程:

  • 通过Xcode新建dylib库(注意:dylib属于macOS,所以需要修改属性)
  • 添加Target依赖,让Xcode将自定义dylib文件打包进入App
  • 利用yololib进行注入

案例,窃取登录密码:

  • 分析思路
  • 动态调试,界面入手
  • 静态分析,使用class-dump,通过MachO导出OC的类和方法列表

Method Swizzle

  • 使用method_exchangeImplementations交互SELIMP对应关系,此方式存在隐患,如果不在同一个类,无法调用原始方法,因为找不到SEL而崩溃
  • 使用class_addMethod添加方法,不至于因为找不到SEL而崩溃。过程比较复杂,不推荐使用
  • 使用class_replaceMethod替换SELIMP
  • 使用method_getImplementationmethod_setImplementation配合,直接重新赋值新的IMP。逻辑清晰,大部分HOOK框架使用此方式,推荐使用