注意系统库的坑之load函数调用多次
source link: http://satanwoo.github.io/2017/11/02/load-twice/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
今天在群友逆向企业微信的时候,发现了一个比较有意思的现象了,发现对于NSObject
添加的load
的方法执行了两次,导致原本意图的Swizzle
出现了问题。
之前在个人的理解中,load
和initialize
函数有所不同,load
是在加载二进制程序的时候,将这些二进制程序中的类中包含的load
方法进行一一调用,调用过程中不会有调用父类的情况。而initialize
则不同,是在类第一次使用的过程中进行调用,同时也会有过程中调用父类的情况。
所以,今天一开始这个情况有点懵逼啊,来看看究竟是为啥。
准备工作(略)
- PP助手上下载一个企业微信
- 重签名 -> Build
写一个诸如下面这么简单的
NSObject Category
,并实现+(void)Load
方法@implementation NSObject (injectLocation) + (void)load { NSLog(@"我好弱"); } @end
按照我们对load
函数的理解,程序加载开始的时候,会通过libobjc
的call_load_methods
遍历逐一执行所有的load
方法,如下图印证:
一开始当我在使用iOS 10.3.3的设备进行测试的时候,这就是唯一一次调用,没有二次重入的状况。
于是我按照群友的提示换了iOS 11的设备,果不其然,iOS 11的企业微信在登录过程中,会再次调用我这个分类的load
方法,让我们一起来看看调用栈:
卧槽,又从WebThread
这个类里面进行了调用了load
,匪夷所思啊。
lldb调试下,结果如下:
frame #0: 0x0000000107a2558c libZXLQYWechatDylib.dylib`+[NSObject(self=SKUIMetricsAppLaunchEvent, _cmd="load") load] at TestCategory.m:15
frame #1: 0x0000000196767f9c StoreKitUI`+[SKUIMetricsAppLaunchEvent load] + 44
frame #2: 0x00000001807fa91c libobjc.A.dylib`call_load_methods + 184
frame #3: 0x00000001807fba84 libobjc.A.dylib`load_images + 76
frame #4: 0x00000001074e6170 dyld`dyld::notifySingle(dyld_image_states, ImageLoader const*, ImageLoader::InitializerTimingList*) + 488
frame #5: 0x00000001074f6ce8 dyld`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 348
frame #6: 0x00000001074f6c90 dyld`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 260
frame #7: 0x00000001074f6c90 dyld`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 260
frame #8: 0x00000001074f6c90 dyld`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 260
frame #9: 0x00000001074f5d40 dyld`ImageLoader::processInitializers(ImageLoader::LinkContext const&, unsigned int, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 136
frame #10: 0x00000001074f5dfc dyld`ImageLoader::runInitializers(ImageLoader::LinkContext const&, ImageLoader::InitializerTimingList&) + 84
frame #11: 0x00000001074e979c dyld`dyld::runInitializers(ImageLoader*) + 88
frame #12: 0x00000001074f0324 dyld`dlopen + 976
frame #13: 0x0000000180ccf4d4 libdyld.dylib`dlopen + 116
frame #14: 0x0000000189caec58 WebCore`initWebFilterEvaluator() + 36
从上述链路看起来:WebCore
通过dlopen
加载了/System/Library/PrivateFrameworks/StoreKitUI.framework/StoreKitUI
这个动态库,然后动态库加载完成后,执行了和主二进制一样的call_load_methods
过程。
逐一执行load
的过程中,会调用到这个类SKUIMetricsAppLaunchEvent
,然后这个类执行的汇编我们看看:
StoreKitUI`+[SKUIMetricsAppLaunchEvent load]:
-> 0x196767f70 <+0>: sub sp, sp, #0x20 ; =0x20
0x196767f74 <+4>: stp x29, x30, [sp, #0x10]
0x196767f78 <+8>: add x29, sp, #0x10 ; =0x10
0x196767f7c <+12>: str x0, [sp]
0x196767f80 <+16>: adrp x8, 108130
0x196767f84 <+20>: ldr x8, [x8, #0xff8]
0x196767f88 <+24>: str x8, [sp, #0x8]
0x196767f8c <+28>: adrp x8, 108114
0x196767f90 <+32>: ldr x1, [x8, #0xf70]
0x196767f94 <+36>: mov x0, sp
0x196767f98 <+40>: bl 0x1902ccaac
0x196767f9c <+44>: adrp x8, 111804
0x196767fa0 <+48>: ldr x8, [x8, #0x6e0]
0x196767fa4 <+52>: cmn x8, #0x1 ; =0x1
0x196767fa8 <+56>: b.ne 0x196767fb8 ; <+72>
0x196767fac <+60>: ldp x29, x30, [sp, #0x10]
0x196767fb0 <+64>: add sp, sp, #0x20 ; =0x20
0x196767fb4 <+68>: ret
0x196767fb8 <+72>: adrp x0, 111804
0x196767fbc <+76>: add x0, x0, #0x6e0 ; =0x6e0
0x196767fc0 <+80>: adrp x1, 93832
0x196767fc4 <+84>: add x1, x1, #0xf60 ; =0xf60
0x196767fc8 <+88>: bl 0x19684f598 ; symbol stub for: __copy_helper_block_.236
0x196767fcc <+92>: b 0x196767fac ; <+60>
看起来没有关键字stub for objc_msgSend
之类的关键字,那我们就重点关注几个跳转指令对应的地址。
排除掉 b 0x196767fac
和b.ne 0x196767fb8
,因为这两地址就属于本函数。
通过lldb一查询看看剩下的0x1902ccaac
是干啥的,卧槽,没结果。那干脆断这个地址试试,然后继续执行,得到如下结果:
0x1902ccaac: b 0x1886362ac
0x1902ccab0: b 0x188637ae8
0x1902ccab4: b 0x1886362e8
0x1902ccab8: b 0x1886365a4
0x1902ccabc: b 0x18863ada8
0x1902ccac0: b 0x1886365b4
0x1902ccac4: b 0x18863889c
0x1902ccac8: b 0x188636b2c
好吧,看起来这是运行时创建的桥(trampoline)。继续断0x1886362ac
,然后执行:
0x1886362ac: b 0x18080c620 ; objc_msgSendSuper2
0x1886362b0: b 0x180814250 ; objc_release
0x1886362b4: b 0x180814190 ; objc_retain
0x1886362b8: b 0x1808165f0 ; objc_retainAutorelease
0x1886362bc: b 0x180816558 ; objc_retainAutoreleaseReturnValue
0x1886362c0: b 0x180816588 ; objc_retainAutoreleasedReturnValue
0x1886362c4: b 0x180802fa8 ; class_addMethod
0x1886362c8: b 0x18080157c ; class_getInstanceMethod
哈哈,看到我们想要的代码了:
0x1886362ac: b 0x18080c620 ; objc_msgSendSuper2
从这段汇编不难看出,在+[SKUIMetricsAppLaunchEvent load]
方法里面,会调用[super load]
这样的代码。
为啥iOS 10上没有问题
在iOS 10上其实也有同样的问题,但是由于WebCore
不会主动把对应的StoreKitUI加载进来,所以也就没出触发这样的问题,但是如果我们主动通过dlopen
加载这个系统库,也一样有问题:
__attribute__((constructor)) void load_private()
{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
void *libHandleIMD = dlopen("/System/Library/PrivateFrameworks/StoreKitUI.framework/StoreKitUI", RTLD_LAZY);
NSLog(@"libHandleIMD is %p", libHandleIMD);
if (!libHandleIMD) {
printf("error is %s\n", dlerror());
}
});
}
对于在系统类上添加的load
方法,建议还是做是否是重入的判断或者保护,不然很可能出现与预期不相符的结果。
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK