日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

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

發布時間:2024/5/28 编程问答 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 iOS逆向之深入解析如何Hook所有+load方法及Category的处理 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一、類方法 +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方法簡單示例。
與50位技術專家面對面20年技術見證,附贈技術全景圖

總結

以上是生活随笔為你收集整理的iOS逆向之深入解析如何Hook所有+load方法及Category的处理的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。