生活随笔
收集整理的這篇文章主要介紹了
iOS逆向之深入解析如何Hook所有+load方法及Category的处理
小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
一、類方法 +load
iOS 有四種方法可方便的在 premain 階段執(zhí)行代碼: C/C++ attribute (constructor) functions; 動(dòng)態(tài)庫(kù)中的上面三種方法。 所有類的 +load 方法是在 main 函數(shù)之前、在主線程,以串行方式調(diào)用,因此任何一個(gè) +load 方法的耗時(shí)大小將直接影響到 App 的啟動(dòng)耗時(shí)。 Objective C Runtime 如下:
static void call_class_loads ( void ) { int i
; struct loadable_class
* classes
= loadable_classes
; int used
= loadable_classes_used
; loadable_classes
= nil
; loadable_classes_allocated
= 0 ; loadable_classes_used
= 0 ; 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
) ; } if ( classes
) free ( classes
) ;
}
直接通過遍歷 loadable_classes 全局變量,逐個(gè)調(diào)用。全局變量的定義如下:
static struct loadable_class
* loadable_classes
= nil
;
static int loadable_classes_used
= 0 ;
static int loadable_classes_allocated
= 0 ;
蘋果的官方文檔對(duì) +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
.
二、運(yùn)用 CaptainHook hook 類方法 +load
由于 +load 方法調(diào)用時(shí)機(jī)已經(jīng)很早,早于 C++ static initializer 等,但晚于 framework(動(dòng)態(tài)庫(kù)),那就可以把 hook 的代碼寫到動(dòng)態(tài)庫(kù)中,也就可以做到在主程序的 loadable_classes 全局變量初始化之前就把 +load hook 掉。 創(chuàng)建一個(gè)動(dòng)態(tài)庫(kù),使用 CaptainHook (只有一個(gè)頭文件,使用也很簡(jiǎn)單):
# import "CaptainHook.h" CHDeclareClass ( MyClass
) ;
CHClassMethod0 ( void , MyClass
, load
) { CFTimeInterval start
= CFAbsoluteTimeGetCurrent ( ) ; CHSuper0 ( MyClass
, load
) ; CFTimeInterval end
= CFAbsoluteTimeGetCurrent ( ) ;
} __attribute__ ( ( constructor
) ) static void entry ( ) { NSLog ( @"dylib loaded" ) ; CHLoadLateClass ( MyClass
) ; CHHook0 ( MyClass
, load
) ;
}
把這個(gè)動(dòng)態(tài)庫(kù)鏈接到 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 {
} } free ( classes
) ;
}
經(jīng)過測(cè)試可以發(fā)現(xiàn),如果一個(gè)類存在 Category,上面的方法只能 hook Category 中的 +load,多個(gè) Category 也只能 hook 一個(gè);并且 CaptainHook 方法需要先靜態(tài)分析(使用 Hopper)來看到所有 +load 方法,或者使用 objc runtime 的方法獲取所有包含 +load 方法的類名,非常麻煩,那么該怎么處理和改進(jìn)呢?
三、Hook 所有 +load 方法(包括 Category)
① hook 目的
假設(shè) App 包含兩個(gè)自動(dòng)鏈接的動(dòng)態(tài)庫(kù),文件如下:
我們的目的就是 hook 這三個(gè) MachO 文件中的所有 Objective C +load 方法,并統(tǒng)計(jì)出耗時(shí),打印出來。
② 新增動(dòng)態(tài)庫(kù)
為了讓 Hook 代碼加載的比這兩個(gè)動(dòng)態(tài)庫(kù)早,需要新增一個(gè)動(dòng)態(tài)庫(kù) LoadRuler.dylib,鏈接的順序很重要,要把 LoadRuler 第一個(gè)鏈接(App 啟動(dòng)時(shí)也就會(huì)第一個(gè)加載,以及第一個(gè)執(zhí)行 macho 中的 +load 方法):
③ 獲取 App 的所有 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
) ) ; }
}
然后可以根據(jù)路徑區(qū)分出 App 中的所有 MachO(動(dòng)態(tài)庫(kù)和可執(zhí)行的主二進(jìn)制文件):
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/ 是為了適配開發(fā)模式,例如上圖的工程配置下 FirstDylib 的目錄是在:
/ Users
/ everettjf
/ Library
/ Developer
/ Xcode
/ DerivedData
/ LoadCostSample
- amfsvwltyimldeaxbquwejweulqd
/ Build
/ Products
/ Debug
- iphonesimulator
/ FirstDylib
. framework
/ FirstDylib
為了把這種情況過濾出來,這里簡(jiǎn)單的通過 Build/Products 匹配下(沒有用 DerivedData 是考慮到 DerivedData 目錄在 Xcode 的設(shè)置中是可修改的)。
④ 獲取所有類
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
) ) ; 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
) ) ; if ( ! addSuccess
) { BOOL 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 的類及幾個(gè) 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,最終的符號(hào)沒有體現(xiàn)出來:
為了把一個(gè)類及對(duì)應(yīng) Category 中的所有 load 都 hook,上面的代碼使用了 class_copyMethodList 或許所有類方法,然后逐個(gè)替換。為了代碼實(shí)現(xiàn)的簡(jiǎn)單,可以創(chuàng)建 LoadRulerSwizzledLoad0、 LoadRulerSwizzledLoad1、 LoadRulerSwizzledLoad2、 LoadRulerSwizzledLoad3 等這樣的方法,適配 N 個(gè) Category 的情況。
四、完整示例
Objective C之Hook所有+load方法簡(jiǎn)單示例。
與50位技術(shù)專家面對(duì)面 20年技術(shù)見證,附贈(zèng)技術(shù)全景圖
總結(jié)
以上是生活随笔 為你收集整理的iOS逆向之深入解析如何Hook所有+load方法及Category的处理 的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔 推薦給好友。