一、类方法 +load

  • iOS 有四种方法可方便的在 premain 阶段执行代码:
    • Objective C 类的 +load 方法;
    • C++ static initializer;
    • C/C++ attribute(constructor) functions;
    • 动态库中的上面三种方法。
  • 所有类的 +load 方法是在 main 函数之前、在主线程,以串行方式调用,因此任何一个 +load 方法的耗时大小将直接影响到 App 的启动耗时。
  • Objective C Runtime 如下:
/***********************************************************************
* call_class_loads
* Call all pending class +load methods.
* If new classes become loadable, +load is NOT called for them.
* Called only by call_load_methods().
**********************************************************************/
static void call_class_loads(void) {int i;// Detach current loadable list.struct loadable_class *classes = loadable_classes;int used = loadable_classes_used;loadable_classes = nil;loadable_classes_allocated = 0;loadable_classes_used = 0;// Call all +loads for the detached list.for (i = 0; i < used; i++) {Class cls = classes[i].cls;load_method_t load_method = (load_method_t)classes[i].method;if (!cls) continue; if (PrintLoading) {_objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());}(*load_method)(cls, SEL_load);}// Destroy the detached list.if (classes) free(classes);
}
  • 直接通过遍历 loadable_classes 全局变量,逐个调用。全局变量的定义如下:
// List of classes that need +load called (pending superclass +load)
// This list always has superclasses first because of the way it is constructed
static struct loadable_class *loadable_classes = nil;
static int loadable_classes_used = 0;
static int loadable_classes_allocated = 0;
  • 苹果的官方文档对 +load 的说明如下:
The order of initialization is as follows:
- All initializers in any framework you link to.
- All +load methods in your image.
- All C++ static initializers and C/C++ __attribute__(constructor) functions in your image.
- All initializers in frameworks that link to you.

二、运用 CaptainHook hook 类方法 +load

  • 由于 +load 方法调用时机已经很早,早于 C++ static initializer 等,但晚于 framework(动态库),那就可以把 hook 的代码写到动态库中,也就可以做到在主程序的 loadable_classes 全局变量初始化之前就把 +load hook 掉。
  • 创建一个动态库,使用 CaptainHook (只有一个头文件,使用也很简单):
#import "CaptainHook.h"CHDeclareClass(MyClass);
CHClassMethod0(void, MyClass, load){CFTimeInterval start = CFAbsoluteTimeGetCurrent();CHSuper0(MyClass,load);CFTimeInterval end = CFAbsoluteTimeGetCurrent();// output: end - start
}__attribute__((constructor)) static void entry(){NSLog(@"dylib loaded");CHLoadLateClass(MyClass);CHHook0(MyClass, load);
}
  • 把这个动态库链接到 App 主程序,就可以 hook 主程序中的 MyClass 类的 +load 方法。
  • 列出程序所有 +load 方法可以通过 Runtime 获取:
int numClasses;
Class * classes = NULL;classes = NULL;
numClasses = objc_getClassList(NULL, 0);if (numClasses > 0) {classes = (Class*)malloc(sizeof(Class) * numClasses);numClasses = objc_getClassList(classes, numClasses);for(int idx = 0; idx < numClasses; ++idx){Class cls = *(classes + idx);const char *className = object_getClassName(cls);Class metaCls = objc_getMetaClass(className);BOOL hasLoad = NO;unsigned int methodCount = 0;Method *methods = class_copyMethodList(metaCls, & methodCount);if(methods){for(int j = 0; j < methodCount; ++j){Method method = *(methods + j);SEL name = method_getName(method);NSString *methodName = NSStringFromSelector(name);if([methodName isEqualToString:@"load"]){hasLoad = YES;break;}}}if(hasLoad){NSLog(@"has load : %@", NSStringFromClass(cls));}else{//                NSLog(@"not has load : %@", NSStringFromClass(cls));}}free(classes);
}
  • 经过测试可以发现,如果一个类存在 Category,上面的方法只能 hook Category 中的 +load,多个 Category 也只能 hook 一个;并且 CaptainHook 方法需要先静态分析(使用 Hopper)来看到所有 +load 方法,或者使用 objc runtime 的方法获取所有包含 +load 方法的类名,非常麻烦,那么该怎么处理和改进呢?

三、Hook 所有 +load 方法(包括 Category)

① hook 目的

  • 假设 App 包含两个自动链接的动态库,文件如下:

  • 我们的目的就是 hook 这三个 MachO 文件中的所有 Objective C +load 方法,并统计出耗时,打印出来。

② 新增动态库

  • 为了让 Hook 代码加载的比这两个动态库早,需要新增一个动态库 LoadRuler.dylib,链接的顺序很重要,要把 LoadRuler 第一个链接(App 启动时也就会第一个加载,以及第一个执行 macho 中的 +load 方法):

③ 获取 App 的所有 MachO

  • 首先获取所有加载的 MachO 可以这样:
static void AppendAllImagePaths(std::vector<std::string> & image_paths){uint32_t imageCount = _dyld_image_count();for(uint32_t imageIndex = 0; imageIndex < imageCount; ++imageIndex){const char * path = _dyld_get_image_name(imageIndex);image_paths.push_back(std::string(path));}
}
  • 然后可以根据路径区分出 App 中的所有 MachO(动态库和可执行的主二进制文件):
static void AppendProductImagePaths(std::vector<std::string> & product_image_paths){NSString *mainBundlePath = [NSBundle mainBundle].bundlePath;std::vector<std::string> all_image_paths;AppendAllImagePaths(all_image_paths);for(auto path: all_image_paths){NSString *imagePath = [NSString stringWithUTF8String:path.c_str()];if([imagePath containsString:mainBundlePath] ||[imagePath containsString:@"Build/Products/"]){product_image_paths.push_back(path);}}
}
  • 其中 Build/Products/ 是为了适配开发模式,例如上图的工程配置下 FirstDylib 的目录是在:
/Users/everettjf/Library/Developer/Xcode/DerivedData/LoadCostSample-amfsvwltyimldeaxbquwejweulqd/Build/Products/Debug-iphonesimulator/FirstDylib.framework/FirstDylib
  • 为了把这种情况过滤出来,这里简单的通过 Build/Products 匹配下(没有用 DerivedData 是考虑到 DerivedData 目录在 Xcode 的设置中是可修改的)。

④ 获取所有类

unsigned int classCount = 0;
const char ** classNames = objc_copyClassNamesForImage(path.c_str(),&classCount);for(unsigned int classIndex = 0; classIndex < classCount; ++classIndex) {NSString *className = [NSString stringWithUTF8String:classNames[classIndex]];Class cls = object_getClass(NSClassFromString(className));
  • 关键代码如下:
@interface LoadRuler : NSObject
@end
@implementation LoadRuler+(void)LoadRulerSwizzledLoad0{LoadRulerBegin;[self LoadRulerSwizzledLoad0];LoadRulerEnd;
}+(void)LoadRulerSwizzledLoad1{LoadRulerBegin;[self LoadRulerSwizzledLoad1];LoadRulerEnd;
}
+(void)LoadRulerSwizzledLoad2{LoadRulerBegin;[self LoadRulerSwizzledLoad2];LoadRulerEnd;
}
+(void)LoadRulerSwizzledLoad3{LoadRulerBegin;[self LoadRulerSwizzledLoad3];LoadRulerEnd;
}
+(void)LoadRulerSwizzledLoad4{LoadRulerBegin;[self LoadRulerSwizzledLoad4];LoadRulerEnd;
}+(void)load{PrintAllImagePaths();SEL originalSelector = @selector(load);Class rulerClass = [LoadRuler class];std::vector<std::string> product_image_paths;AppendProductImagePaths(product_image_paths);for(auto path : product_image_paths){unsigned int classCount = 0;const char ** classNames = objc_copyClassNamesForImage(path.c_str(),&classCount);for(unsigned int classIndex = 0; classIndex < classCount; ++classIndex){NSString *className = [NSString stringWithUTF8String:classNames[classIndex]];Class cls = object_getClass(NSClassFromString(className));// 不要把自己hook了if(cls == [self class]){continue;}unsigned int methodCount = 0;Method * methods = class_copyMethodList(cls, &methodCount);NSUInteger currentLoadIndex = 0;for(unsigned int methodIndex = 0; methodIndex < methodCount; ++methodIndex){Method method = methods[methodIndex];std::string methodName(sel_getName(method_getName(method)));if(methodName == "load"){SEL swizzledSelector = NSSelectorFromString([NSString stringWithFormat:@"LoadRulerSwizzledLoad%@",@(currentLoadIndex)]);Method originalMethod = method;Method swizzledMethod = class_getClassMethod(rulerClass, swizzledSelector);BOOL addSuccess = class_addMethod(cls, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));// 添加成功,则说明不存在load。但动态添加的load,不会被调用。与load的调用方式有关if(!addSuccess){// 已经存在,则添加新的selectorBOOL didAddSuccess = class_addMethod(cls, swizzledSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));if(didAddSuccess){// 然后交换swizzledMethod = class_getClassMethod(cls, swizzledSelector);method_exchangeImplementations(originalMethod, swizzledMethod);}}++currentLoadIndex;}}}}
}@end

⑤ Category 的处理

  • 工程中 FirstLoader 的类及几个 Category 是如下这样:
@implementation FirstLoader+ (void)load{NSLog(@"first +load");usleep(1000 * 15);
}
@end@implementation FirstLoader (FirstCategory)+(void)load{NSLog(@"first category +load for FirstLoader");usleep(1000 * 45);
}@end@implementation  FirstLoader (SecondCategory)+ (void)load{NSLog(@"second category +load for FirstLoader");usleep(1000 * 55);
}@end
  • Hopper 中看到 Category 中的 +load,最终的符号没有体现出来:

  • 为了把一个类及对应 Category 中的所有 load 都 hook,上面的代码使用了 class_copyMethodList 或许所有类方法,然后逐个替换。为了代码实现的简单,可以创建 LoadRulerSwizzledLoad0、 LoadRulerSwizzledLoad1、 LoadRulerSwizzledLoad2、 LoadRulerSwizzledLoad3 等这样的方法,适配 N 个 Category 的情况。

四、完整示例

  • Objective C之Hook所有+load方法简单示例。

iOS逆向之深入解析如何Hook所有+load方法及Category的处理相关推荐

  1. iOS逆向之深入解析如何计算+load方法的耗时

    一.类方法 +load 在 pre-main 时期,objc 会向 dyld 注册一个 init 回调: void _objc_init(void) {static bool initialized ...

  2. iOS逆向之深入解析如何使用Theos开发插件

    一.Logos 语法 Logos 作为 Theos 开发组件的一部分,通过一组特殊的预处理指令,可以让编写函数钩子(hook)代码变得非常简单和清晰,Logos 是随着 Theos 发布的. %hoo ...

  3. iOS逆向之深入解析App签名的双向验证机制和原理

    一.非对称加密 通常说的签名就是数字签名,它是基于非对称加密算法实现的. 对称加密是通过同一份密钥加密和解密数据,而非对称加密则有两份密钥,分别是公钥和私钥,用公钥加密的数据,要用私钥才能解密,用私钥 ...

  4. iOS逆向之深入解析MachO文件

    MachO文件简介 一.什么是MachO文件? Mach-O其实是Mach Object文件格式的缩写,它是Mac以及iOS上一种用于可执行文件.目标代码.动态库的文件格式,类似于Windows上面的 ...

  5. IOS逆向笔记之HOOK实现(非越狱)

    HOOK是越狱的最终目标,目的是给应用添加功能如插件或者是更改应用的某个功能来满足我们的需求,如微信中添加抢红包插件.本文将以最近比较火的"快看"漫画为例子去除付费漫画中的收费弹窗 ...

  6. 2020年 IOS 逆向 反编译 注入修改游戏或APP的调用参数新手系列教程——使用theos tweak 注入hook修改游戏执行代码上传动态头像

    2020年 IOS 逆向 反编译 注入修改游戏或APP的调用参数新手系列教程--使用theos tweak 注入hook修改游戏执行代码上传动态头像 开篇 需求&最终效果 环境要求与即将使用的 ...

  7. iOS逆向之反HOOK的基本防护

    iOS逆向之Method Swizzle iOS逆向之fishHook原理探究 iOS逆向之fishHook怎么通过符号找字符串 学习完上面的文章后,深感fishhook之强大,既然fishhook能 ...

  8. iOS 逆向-非越狱手机Hook App

    引子 由于对iOS逆向分析很感兴趣,所以也花了很长一段时间学习了iOS逆向相关知识,并积累了一些相关经验, 这几天又到看雪论坛逛了一下,看看最近有没有什么大牛分享心得成果的!还真让我看到一篇有意思的文 ...

  9. 安卓逆向_24( 一 ) --- Hook 框架 frida( Hook Java层 和 so层) )

    From:Hook 神器家族的 Frida 工具使用详解:https://blog.csdn.net/FlyPigYe/article/details/90258758 详解 Hook 框架 frid ...

最新文章

  1. C语言基础(12)-输入和输出
  2. matlab 2012 vs2010混合编程
  3. 【干货分享】云服务平台的架构及优势(上)
  4. Oracle的sqlplus登录方式
  5. 【数据结构作业—02】双链表
  6. 谁说2021届秋招算法岗一定要灰飞烟灭啦?
  7. Linux下查看显卡PCIE速率x16x8x4及设定
  8. php正在尝试获取中非对象的属性_PHP7-2: 面向对象开发
  9. 你给客户报完价,客户就没消息了,什么原因呢?
  10. 程序员如何高性能排序多个文件?
  11. 阶段5 3.微服务项目【学成在线】_day02 CMS前端开发_25-前后端请求响应流程小结...
  12. eclipse如何设置自己喜欢的主题
  13. H264解码之读取本地H264文件
  14. vivo浏览器缓存视频如何拷贝到电脑上
  15. Postgresql的基本操作
  16. ENVI监督分类后背景值也被分成一种地物,解决方案和转移矩阵制作方法
  17. java开发手机app教程,看完必懂
  18. 小白鼠测试---VR头戴设备-暴风魔镜4
  19. OpenCV图像梯度——Scharr算子(cv2.Scharr())
  20. 《软件设计的哲学》读书总结

热门文章

  1. 如何通过DBLINK取REMOTE DB的DDL
  2. $Django 多表操作(增删改查,基于双下划线,对象的查询) 在Python脚本中调用Django环境...
  3. ElasticStack系列之八 _source 字段
  4. java按照字节切割字符串,解决汉字的问题
  5. ios NSComparator 三种枚举类型
  6. c#可移动不规则窗体
  7. Shell编程之变量赋值和引用
  8. 总结XX餐饮收银项目中的得与失
  9. 服务器多路径协议,多主机多路径分流传输协议研究与设计
  10. 用计算机进行资料检索工作是,用计算机进行资料检索工作是属于计算机应用中的什么...