前一篇介紹過(guò)isa的優(yōu)化方式以及從被優(yōu)化過(guò)的isa中獲取真正的struct objc_class指針。然而我們對(duì)知識(shí)的渴望,并不允許自己僅僅只是知道它、了解它而已,還想進(jìn)一步分析struct objc_class結(jié)構(gòu)體,以及用它來(lái)做點(diǎn)什么。
接下來(lái),從isa指針中到底有哪些信息和獲取到這些信息可以用來(lái)做什么兩個(gè)方面,來(lái)進(jìn)一步揭開(kāi)isa的面紗。
isa指針中到底有哪些信息?
既然已經(jīng)可以從一個(gè)實(shí)例對(duì)象中獲取到isa指針,那么我們就直接用這個(gè)指針來(lái)struct objc_class中的數(shù)據(jù)。
要解析指針指向的地址中存放的數(shù)據(jù),肯定需要知道struct objc_class的定義(中間有很多方法直接忽略掉,只看其數(shù)據(jù)結(jié)構(gòu)):
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
...
}
因?yàn)橹羔樥加玫膬?nèi)存空間是8字節(jié),所以通過(guò)看其結(jié)構(gòu)體的構(gòu)成,可以知道前16個(gè)字節(jié)里存放的是struct objc_class *類型的指針ISA和superclass。
這兩個(gè)指針,實(shí)際上存儲(chǔ)的是類對(duì)象的父類以及元類的指針。父類和元類的解析,因?yàn)轭愋鸵粯?,所以和自身的解析是一樣的?br>
第17個(gè)字節(jié)開(kāi)始,存放的是cache_t類型的數(shù)據(jù),但是這個(gè)數(shù)據(jù)并沒(méi)有使用指針,所以它里面的數(shù)據(jù),會(huì)有序并字節(jié)對(duì)齊的存放在第17個(gè)字節(jié)開(kāi)始的位置??梢酝ㄟ^(guò)cache_t結(jié)構(gòu)體來(lái)查看其數(shù)據(jù)類型:
struct cache_t {
struct bucket_t *_buckets;
mask_t _mask;
mask_t _occupied;
}
#if __LP64__
typedef uint32_t mask_t; // x86_64 & arm64 asm are less efficient with 16-bits
struct cache_t里面有一個(gè)指針,占8字節(jié)內(nèi)存;有兩個(gè)mask_t類型(即uint32_t類型)的數(shù)據(jù),每個(gè)占4字節(jié)的內(nèi)存,所以cache_t cache一共占16個(gè)字節(jié)的內(nèi)存。
這個(gè)字段里,存放的是方法緩存相關(guān)的信息。
最后一個(gè)結(jié)構(gòu)體class_data_bits_t bits:
struct class_data_bits_t {
// Values are the FAST_ flags above.
uintptr_t bits;
}
里面就一個(gè)占8字節(jié)的數(shù)據(jù)。
從這個(gè)結(jié)構(gòu)體的名字可以看出,類的主要信息都存儲(chǔ)在這個(gè)字段里。這個(gè)字段里存儲(chǔ)的是class_rw_t類型的指針,但是也不是直接將指針的值存進(jìn)去的,和被優(yōu)化的isa一樣被優(yōu)化過(guò)的,所以不能直接取出來(lái)用。那么要怎么獲取到這個(gè)指針呢?
在源碼中往下看struct class_data_bits_t這個(gè)結(jié)構(gòu)體,會(huì)發(fā)現(xiàn)有一個(gè)data()方法,返回一個(gè)class_rw_t*:
// data pointer
#define FAST_DATA_MASK 0x00007ffffffffff8UL
class_rw_t* data() {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
所以其實(shí)我們也可以將獲取的bits的值,和0x00007ffffffffff8UL進(jìn)行&運(yùn)算,得到class_rw_t*指針。
寫(xiě)個(gè)簡(jiǎn)單的代碼,可以走一下這個(gè)順序,驗(yàn)證一下:
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
appDelegateClassName = NSStringFromClass([AppDelegate class]);
CustomClass *c = [CustomClass new];
c.i = 10;
c.obj = [NSObject new];
c.str = @"i love code";
NSLog(@"c:%p",c);
//1、通過(guò)實(shí)例對(duì)象`c`,獲取 isa 指針
uintptr_t c_isa = (*(uintptr_t *)c) & 0x0000000FFFFFFFF8ULL;
//2、獲取 `class_data_bits_t bits` 的值
uintptr_t c_bits = *(uintptr_t *)((unsigned long long)c_isa + 4*8);
//3、獲取 `class_rw_t *`指針
uintptr_t rw_t = c_bits & 0x00007ffffffffff8UL;
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
雖說(shuō)是驗(yàn)證,但是取到這個(gè)值,也不知道是不是正確的,反正值是取到了!
要想驗(yàn)證取的值是否正確,還得看看class_rw_t *里面的數(shù)據(jù),是否是這個(gè)類的信息。
所以還是要跟進(jìn)去看這個(gè)結(jié)構(gòu)體:
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
Class firstSubclass;
Class nextSiblingClass;
char *demangledName;
還是按照剛才的思路,挨個(gè)字段去解析:
- flags
- version
- ro
const 修飾的class_ro_t *指針,儲(chǔ)了當(dāng)前類在編譯期就已經(jīng)確定的屬性、方法以及遵循的協(xié)議,不可修改。 - methods
實(shí)例方法列表(元類中存儲(chǔ)的是類方法列表),是一個(gè)指向method_t的二級(jí)指針。 - properties
屬性列表。 - protocols
協(xié)議列表
這里主要關(guān)注ro、methods、properties以及protocols字段。
先看一下ro的類型class_ro_t的結(jié)構(gòu):
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif
const uint8_t * ivarLayout;
const char * name;
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
method_list_t *baseMethods() const {
return baseMethodList;
}
};
剛剛的代碼中,我們以及獲取到了class_rw_t指針的值,我們可以通過(guò)讀取class_rw_t中ro字段的值,然后獲取class_ro_t里面name,來(lái)判斷我們獲取的指針是否正確:
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
appDelegateClassName = NSStringFromClass([AppDelegate class]);
CustomClass *c = [CustomClass new];
c.i = 10;
c.obj = [NSObject new];
c.str = @"i love code";
NSLog(@"c:%p",c);
//1、通過(guò)實(shí)例對(duì)象`c`,獲取 isa 指針
uintptr_t c_isa = (*(uintptr_t *)c) & 0x0000000FFFFFFFF8ULL;
//2、獲取 `class_data_bits_t bits` 的值
uintptr_t c_bits = *(uintptr_t *)((unsigned long long)c_isa + 4*8);
//3、獲取 `class_rw_t *`指針
uintptr_t rw_t = c_bits & 0x00007ffffffffff8UL;
//4、獲取 `class_rw_t` 中 ro 指針
uintptr_t ro = *(uintptr_t *)((unsigned long long)rw_t+4+4);
//5、獲取 `class_ro_t` 中的 `name`
const char *name = (const char *)(*(uintptr_t *)((unsigned long long)ro + 4 +4 + 4 +4 + 8));
/*這里下斷點(diǎn)*/ return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
2019-11-25 00:00:30.956322+0800 IvarDemo[11902:3499502] c:0x1740261c0
(lldb) p/s name
(const char *) $0 = "CustomClass" "CustomClass"
(lldb)
看log,可以看出,取出的值都是正確的。
既然name可以取出,那么其他的字段,也是可以取出的,例如在 8byte~12byte 存儲(chǔ)的instanceSize:
//6、獲取 `class_ro_t` 中的 `instanceSize`
uint32_t instanceSize = *(uint32_t *)((unsigned long long)ro + 4 +4);
/*
使用 class_getInstanceSize() 函數(shù)驗(yàn)證
*/
(lldb) p/d instanceSize
(uint32_t) $0 = 32
Printing description of c:
<CustomClass: 0x17002f340>
(lldb) p/d (uint32_t)class_getInstanceSize([c class])
(uint32_t) $3 = 32
(lldb)
其他字段就不一一獲取。
回過(guò)頭去看看class_rw_t中的methods字段。
還是先看其定義:
class method_array_t :
public list_array_tt<method_t, method_list_t>
{
typedef list_array_tt<method_t, method_list_t> Super;
public:
method_list_t **beginCategoryMethodLists() {
return beginLists();
}
method_list_t **endCategoryMethodLists(Class cls);
method_array_t duplicate() {
return Super::duplicate<method_array_t>();
}
};
method_array_t是一個(gè)C++類,且其繼承自模板類list_array_tt,所以我們還得了解一下list_array_tt這個(gè)模板類:
template <typename Element, typename List>
class list_array_tt {
...
private:
union {
List* list;
uintptr_t arrayAndFlag;
};
...
}
從結(jié)構(gòu)中可以看出,method_array_t類只占8字節(jié)的內(nèi)存空間,成員用union聯(lián)合,說(shuō)明list和arrayAndFlag公用8字節(jié)的空間。
我們知道List在類method_array_t中是method_array_t,但是既然定義成聯(lián)合,那么肯定不是簡(jiǎn)單的將method_array_t指針存入這8個(gè)字節(jié)的,所以現(xiàn)在問(wèn)題就變成了:怎么獲取真正的method_array_t指針的值。
觀察這個(gè)模板類,發(fā)現(xiàn)里面有一個(gè)List** beginLists()方法:
List** beginLists() {
if (hasArray()) {
return array()->lists;
} else {
return &list;
}
}
bool hasArray() const {
return arrayAndFlag & 1;
}
array_t *array() {
return (array_t *)(arrayAndFlag & ~1);
}
以上三個(gè)的函數(shù)告訴了咱們?cè)趺赐ㄟ^(guò)arrayAndFlag獲取真正的List**:
1、判斷arrayAndFlag最后一位是否為1;
2、通過(guò)判斷結(jié)果,分別獲取List**;
- 判斷正確:
array()->lists; - 否則:
&list;
接下來(lái),我們也可以書(shū)寫(xiě)代碼,嘗試著獲取List**:
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
appDelegateClassName = NSStringFromClass([AppDelegate class]);
CustomClass *c = [CustomClass new];
c.i = 10;
c.obj = [NSObject new];
c.str = @"i love code";
NSLog(@"c:%p",c);
//1、通過(guò)實(shí)例對(duì)象`c`,獲取 isa 指針
uintptr_t c_isa = (*(uintptr_t *)c) & 0x0000000FFFFFFFF8ULL;
//2、獲取 `class_data_bits_t bits` 的值
uintptr_t c_bits = *(uintptr_t *)((unsigned long long)c_isa + 4*8);
//3、獲取 `class_rw_t *`指針
uintptr_t rw_t = c_bits & 0x00007ffffffffff8UL;
//4、獲取 `class_rw_t` 中 ro 指針
uintptr_t ro = *(uintptr_t *)((unsigned long long)rw_t+4+4);
//5、獲取 `class_ro_t` 中的 `name`
const char *name = (const char *)(*(uintptr_t *)((unsigned long long)ro + 4 +4 + 4 +4 + 8));
//6、獲取 `class_ro_t` 中的 `instanceSize`
uint32_t instanceSize = *(uint32_t *)((unsigned long long)ro + 4 +4);
//7、獲取 `class_rw_t` 中 `method`
uintptr_t method = *((uintptr_t *)(((unsigned long long)rw_t)+4+4+8));
/*注:因?yàn)閌method`成員沒(méi)有用指針修飾,所以`method`的值即為
union
{List *list;
uintptr_t arrayAndFlag;
}
的值。
*/
//8、獲取 `arrayAndFlag`的值
uintptr_t arrayAndFlag = method;
//9、//獲取 `List **`
uintptr_t *list;
//判斷 `arrayAndFlag` 標(biāo)志位
if (arrayAndFlag & 1) {
//獲取 `array_t *`
uintptr_t array_t = arrayAndFlag & ~1;
list = &(*((uintptr_t *)((unsigned long long)array_t+8)));
}else {
//獲取 `List **`
list = &arrayAndFlag;
}
/*這里下斷點(diǎn)*/ return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
雖然到這里,已經(jīng)獲取到了List **(也就是method_list_t**),但是我們要找的方法列表,還只是初見(jiàn)端倪,并沒(méi)有完全浮出水面。所以我們還是要進(jìn)一步分析一下method_list_t:
struct method_list_t : entsize_list_tt<method_t, method_list_t, 0x3> {
bool isFixedUp() const;
void setFixedUp();
uint32_t indexOfMethod(const method_t *meth) const {
uint32_t i =
(uint32_t)(((uintptr_t)meth - (uintptr_t)this) / entsize());
assert(i < count);
return i;
}
};
template <typename Element, typename List, uint32_t FlagMask>
struct entsize_list_tt {
uint32_t entsizeAndFlags;
uint32_t count;
Element first;
...
}
method_list_t結(jié)構(gòu)體繼承自模板結(jié)構(gòu)體entsize_list_tt,所以method_list_t的成員變量有3個(gè):
uint32_t entsizeAndFlags;
uint32_t count;
Element first;
我們要關(guān)注的,是count和first;count中存儲(chǔ)的是方法個(gè)數(shù),而first則是方法列表的其實(shí)地址,也就是方法數(shù)組的第一個(gè)元素。(至于第一個(gè)entsizeAndFlags,存儲(chǔ)的是數(shù)組中每個(gè)元素的大小。)
既然可以看到其內(nèi)存布局,也就意味著我們可以獲取到數(shù)據(jù):
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
appDelegateClassName = NSStringFromClass([AppDelegate class]);
CustomClass *c = [CustomClass new];
NSLog(@"c:%p",c);
//1、通過(guò)實(shí)例對(duì)象`c`,獲取 isa 指針
uintptr_t c_isa = (*(uintptr_t *)c) & 0x0000000FFFFFFFF8ULL;
//2、獲取 `class_data_bits_t bits` 的值
uintptr_t c_bits = *(uintptr_t *)((unsigned long long)c_isa + 4*8);
//3、獲取 `class_rw_t *`指針
uintptr_t rw_t = c_bits & 0x00007ffffffffff8UL;
//4、獲取 `class_rw_t` 中 ro 指針
uintptr_t ro = *(uintptr_t *)((unsigned long long)rw_t+4+4);
//5、獲取 `class_ro_t` 中的 `name`
const char *name = (const char *)(*(uintptr_t *)((unsigned long long)ro + 4 +4 + 4 +4 + 8));
//6、獲取 `class_ro_t` 中的 `instanceSize`
uint32_t instanceSize = *(uint32_t *)((unsigned long long)ro + 4 +4);
//7、獲取 `class_rw_t` 中 `method`
uintptr_t method = *((uintptr_t *)(((unsigned long long)rw_t)+4+4+8));
/*注:因?yàn)閌method`成員沒(méi)有用指針修飾,所以`method`的值即為
union
{List *list;
uintptr_t arrayAndFlag;
}
的值。
*/
//8、獲取 `arrayAndFlag`的值
uintptr_t arrayAndFlag = method;
//9、//獲取 `List **`
uintptr_t *list;
//判斷 `arrayAndFlag` 標(biāo)志位
if (arrayAndFlag & 1) {
//獲取 `array_t *`
uintptr_t array_t = arrayAndFlag & ~1;
list = &(*((uintptr_t *)((unsigned long long)array_t+8)));
}else {
//獲取 `List **`
list = &arrayAndFlag;
}
//10、獲取`method_list_t` 中的 `count`
//獲取`method_list_t *`指針
uintptr_t method_list_t = *list;
//獲取`count`
uint32_t count = *(uint32_t *)((unsigned long long)method_list_t + 4);
//11、獲取 `method_list_t`中的`first`
/* 這里為了更好的獲取方法信息,定義結(jié)構(gòu)體struct method_t */
struct sl_method_t {
SEL name;
const char *types;
IMP imp;
};
struct sl_method_t first =*((struct sl_method_t *)((unsigned long long)method_list_t + 4 + 4));
//12、打印所有方法的信息
//獲取數(shù)組中每個(gè)元素的大小
uint32_t entsizeAndFlags = *(uint32_t *)((unsigned long long)method_list_t);
/*
uint32_t entsize() const {
return entsizeAndFlags & ~FlagMask;
}
*/
uint32_t entsize = entsizeAndFlags & ~0x3;
for (int i=0; i<count; i++) {
struct sl_method_t method = *((struct sl_method_t *)((unsigned long long)method_list_t + 4 + 4 + i*entsize));
printf("method_name:%s \t method_types:%s \t method_imp:0x%llx",method.name,method.types,(unsigned long long)(method.imp));
printf("\n");
}
/*這里下斷點(diǎn)*/ return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
method_name:setStr: method_types:v24@0:8@16 method_imp:0x1000a8120
method_name:setObj: method_types:v24@0:8@16 method_imp:0x1000a8174
method_name:.cxx_destruct method_types:v16@0:8 method_imp:0x1000a81b4
method_name:str method_types:@16@0:8 method_imp:0x1000a80e8
method_name:setI: method_types:v20@0:8i16 method_imp:0x1000a80c4
method_name:i method_types:i16@0:8 method_imp:0x1000a80a8
method_name:obj method_types:@16@0:8 method_imp:0x1000a8158
(lldb)
到這里,就意味著struct class_rw_t 中的method_array_t methods已經(jīng)完全被解析出來(lái)了。
那么同樣的,其他幾個(gè)字段的內(nèi)容,也是可以獲取到的。(例如:property_array_t properties; protocol_array_t protocols)。這里就不一一讀取。
我們接下來(lái)的問(wèn)題是:
獲取到這些信息可以用來(lái)做什么?
最簡(jiǎn)單的,我們既然可以獲取到類中每個(gè)方法的IMP,那么我們是否可以替換掉其指針,到達(dá)Hook的效果呢?
簡(jiǎn)單修改一下代碼:
NSString * sl_str(id self,SEL sel){
return @"hooked!!!";
}
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
appDelegateClassName = NSStringFromClass([AppDelegate class]);
CustomClass *c = [CustomClass new];
NSLog(@"c:%p",c);
//1、通過(guò)實(shí)例對(duì)象`c`,獲取 isa 指針
uintptr_t c_isa = (*(uintptr_t *)c) & 0x0000000FFFFFFFF8ULL;
//2、獲取 `class_data_bits_t bits` 的值
uintptr_t c_bits = *(uintptr_t *)((unsigned long long)c_isa + 4*8);
//3、獲取 `class_rw_t *`指針
uintptr_t rw_t = c_bits & 0x00007ffffffffff8UL;
//4、獲取 `class_rw_t` 中 ro 指針
uintptr_t ro = *(uintptr_t *)((unsigned long long)rw_t+4+4);
//5、獲取 `class_ro_t` 中的 `name`
const char *name = (const char *)(*(uintptr_t *)((unsigned long long)ro + 4 +4 + 4 +4 + 8));
//6、獲取 `class_ro_t` 中的 `instanceSize`
uint32_t instanceSize = *(uint32_t *)((unsigned long long)ro + 4 +4);
//7、獲取 `class_rw_t` 中 `method`
uintptr_t method = *((uintptr_t *)(((unsigned long long)rw_t)+4+4+8));
/*注:因?yàn)閌method`成員沒(méi)有用指針修飾,所以`method`的值即為
union
{List *list;
uintptr_t arrayAndFlag;
}
的值。
*/
//8、獲取 `arrayAndFlag`的值
uintptr_t arrayAndFlag = method;
//9、//獲取 `List **`
uintptr_t *list;
//判斷 `arrayAndFlag` 標(biāo)志位
if (arrayAndFlag & 1) {
//獲取 `array_t *`
uintptr_t array_t = arrayAndFlag & ~1;
list = &(*((uintptr_t *)((unsigned long long)array_t+8)));
}else {
//獲取 `List **`
list = &arrayAndFlag;
}
//10、獲取`method_list_t` 中的 `count`
//獲取`method_list_t *`指針
uintptr_t method_list_t = *list;
//獲取`count`
uint32_t count = *(uint32_t *)((unsigned long long)method_list_t + 4);
//11、獲取 `method_list_t`中的`first`
/* 這里為了更好的獲取方法信息,定義結(jié)構(gòu)體struct method_t */
struct sl_method_t {
SEL name;
const char *types;
IMP imp;
};
struct sl_method_t first =*((struct sl_method_t *)((unsigned long long)method_list_t + 4 + 4));
//12、打印所有方法的信息
//獲取數(shù)組中每個(gè)元素的大小
uint32_t entsizeAndFlags = *(uint32_t *)((unsigned long long)method_list_t);
/*
uint32_t entsize() const {
return entsizeAndFlags & ~FlagMask;
}
*/
uint32_t entsize = entsizeAndFlags & ~0x3;
for (int i=0; i<count; i++) {
struct sl_method_t method = *((struct sl_method_t *)((unsigned long long)method_list_t + 4 + 4 + i*entsize));
printf("method_name:%s \t method_types:%s \t method_imp:0x%llx",method.name,method.types,(unsigned long long)(method.imp));
/*Hook `str` 方法*/
if (!strcmp((char *)(method.name), "str")) {
//修改IMP的值
*(uintptr_t *)((unsigned long long)method_list_t + 4 + 4 + i*entsize+8+8) = (uintptr_t)&sl_str;
}
printf("\n");
}
//調(diào)用,檢查是否被hook
NSString *str = [c str];
/*這里下斷點(diǎn)*/ return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
method_name:setStr: method_types:v24@0:8@16 method_imp:0x1000880fc
method_name:setObj: method_types:v24@0:8@16 method_imp:0x100088150
method_name:.cxx_destruct method_types:v16@0:8 method_imp:0x100088190
method_name:str method_types:@16@0:8 method_imp:0x1000880c4
method_name:setI: method_types:v20@0:8i16 method_imp:0x1000880a0
method_name:i method_types:i16@0:8 method_imp:0x100088084
method_name:obj method_types:@16@0:8 method_imp:0x100088134
(lldb) po str
hooked!!!
(lldb)
看到最后的hooked!!!說(shuō)明,hook成功了!
當(dāng)然,完整的hook,并不是單純的替換函數(shù)指針即可,還需要考慮很多問(wèn)題,這里只是簡(jiǎn)單的測(cè)試一下可行性。