生活随笔
收集整理的这篇文章主要介绍了
iOS逆向之深入解析如何Hook所有+load方法及Category的处理
小编觉得挺不错的,现在分享给大家,帮大家做个参考.
一、类方法 +load
iOS 有四种方法可方便的在 premain 阶段执行代码: C/C++ attribute (constructor) functions; 所有类的 +load 方法是在 main 函数之前、在主线程,以串行方式调用,因此任何一个 +load 方法的耗时大小将直接影响到 App 的启动耗时。 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 全局变量,逐个调用。全局变量的定义如下:
static struct loadable_class
* loadable_classes
= nil
;
static int loadable_classes_used
= 0 ;
static int loadable_classes_allocated
= 0 ;
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 ( ) ;
} __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 {
} } 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
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
) ) ; 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 的类及几个 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的处理 的全部内容,希望文章能够帮你解决所遇到的问题。
如果觉得生活随笔 网站内容还不错,欢迎将生活随笔 推荐给好友。