本文主要介绍如何利用OC Runtime的特性,让OC野指针对象主动抛出自己的信息,秒杀某些全系统栈Crash。
陈其锋,腾讯SNG即通产品部音视频技术中心软件工程师,主要负责iOS平台音视频功能开发,热衷于移动开发,以及各类APP体验。
(注:本文由于涉及一些技术比较猥琐,可能会引起处女座同学的不适,如果有任何疑问欢迎一起讨论。另外,本文只讨论Arm 32位情况)
为什么错误地址是0x55555561?
为了解答这个问题,我们可以先看看Crash栈,就会发现这些Crash都是在objc_msgSend上。我们知道Obj-C的对象方法调用是通过objc_msgSend进行的,我们通过野指针访问一个对象的方法也一样,其实是通过objc_msgSend给已经释放的对象发了一条消息。
而objc_msgSend的函数签名是这样:
id objc_msgSend(id self, SEL op, …)
我们再来看看objc_msgSend的代码:
libobjc.A.dylib`objc_msgSend:
0x2f879f40 <+0>: cbz r0, 0x2f879f7e ; <+62>
0x2f879f42 <+2>: ldr.w r9, [r0]
0x2f879f46 <+6>: ldrh.w r12, [r9, #0xc]
0x2f879f4a <+10>: ldr.w r9, [r9, #0x8]
0x2f879f4e <+14>: and.w r12, r12, r1
0x2f879f52 <+18>: add.w r9, r9, r12, lsl #3
0x2f879f56 <+22>: ldr.w r12, [r9]
0x2f879f5a <+26>: teq.w r12, r1
0x2f879f5e <+30>: bne 0x2f879f66 ; <+38>
0x2f879f60 <+32>: ldr.w r12, [r9, #0x4]
0x2f879f64 <+36>: bx r12
0x2f879f66 <+38>: cmp.w r12, #0x1
0x2f879f6a <+42>: blo 0x2f879f78 ; <+56>
0x2f879f6c <+44>: it eq
0x2f879f6e <+46>: ldreq.w r9, [r9, #0x4]
0x2f879f72 <+50>: ldr r12, [r9, #8]!
0x2f879f76 <+54>: b 0x2f879f5a ; <+26>
0x2f879f78 <+56>: ldr.w r9, [r0]
0x2f879f7c <+60>: b 0x2f87a1c0 ; _objc_msgSend_uncached
0x2f879f7e <+62>: mov.w r1, #0x0
0x2f879f82 <+66>: bx lr
我们可以结合Obj-C类的内存布局再来解读一下上面的汇编代码(节选于Obj-C类的源代码):
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache;
uintptr_t data_NEVER_USE; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() {
return (class_rw_t *)(data_NEVER_USE & ~CLASS_FAST_FLAG_MASK);
}
void setData(class_rw_t *newData) {
uintptr_t flags = (uintptr_t)data_NEVER_USE & CLASS_FAST_FLAG_MASK;
data_NEVER_USE = (uintptr_t)newData | flags;
}
……..
struct cache_t {
struct bucket_t *buckets;
mask_t shiftmask;
mask_t occupied;
……..
struct bucket_t {
cache_key_t key;
IMP imp;
……
typedef uintptr_t cache_key_t;
根据苹果的函数调用约定,objc_msgSend被调用的时候,寄存器对应关系:r0是对象本身self,r1是sel,r2和r3是参数。根据objc_class的声明,我们可以知道:
0x2f879f40 <+0>: cbz r0, 0x2f879f7e //如果self为0就跳转到0x2f879f7e。给nil发消息的话就什么都不做
0x2f879f42 <+2>: ldr.w r9, [r0] //取对象的类到r9
0x2f879f46 <+6>: ldrh.w r12, [r9, #0xc] //取类的偏移#0xc的数据到r12,也就是shiftmask的值
0x2f879f4a <+10>: ldr.w r9, [r9, #0x8] //取类的偏移#0x8的成员到r9,也即是cache
0x2f879f4e <+14>: and.w r12, r12, r1 //r1和shiftmask与,放到r12,r1是参数一,也就是sel,用来计算sel的index
有理想在的地方,地狱就是天堂