前言
上一个章节汇编与函数我们初探了函数的汇编以及常用的汇编指令和寄存器,我觉得这个基础知识对于我们探索逆向是非常重要的,磨刀不误砍柴工就是这个道理。下面我们接着探索一下OC对象的汇编作为整个汇编章节的终结。
指针
我们知道对象的创建是在堆内存中开辟空间,然后返回一个堆内存地址,这个堆内存地址用一个变量接收存放在栈中,我们称这个变量为指针,所谓指针即在栈中存放地址的变量。用下面这个示例理解一下
int* a;
int b = 10;
a = &b;
NSLog(@"%p-%d-%p",a,*a,&b);
输出如下:
0x16b4cb804-10-0x16b4cb808
定义一个指针变量a和基本数据类型b,b的地址赋值给我a,指针a本来就是一个存放地址的变量,这个变量存放的地址值是0x16b4cb804即b的地址,0x16b4cb804地址处存的值是10。指针a作为变量在内存中也是有地址的,即存在地址0x16b4cb808存放这指针变量a。不理解的人会觉得绕口,只要记住指针就是一个存放地址的变量,它的本质就是地址。下面看这个例子
int* c=20;
NSLog(@"%p-%d",c,*c);
此时会奔溃,控制台输出一下
指针变量c本质上存的是地址,我们定义为20即0x14,所以打印c存放的地址没问题,但是*c取地址0x14所对应的值奔溃了,因为内存中不存在这个0x14地址。
指针的运算
指针的宽度:它所指向的数据类型的宽度指针的自增自减:是按照指向的数据类型(指针的宽度)来运算的
还是用示例理解这两句话
//示例1
int * a ;
a=(int *)100;
a++;
printf(“%d\n”,a);
//示例2
int ** c;
c=(int **)100;
c++;
printf(“%d\n”,c);
//示例3
int arr[5] = {1,2,3,4,5};
int * d = arr;
for (int i = 0; i < 5; i++) {
printf("%d",*(d++));
}
输出
104
108
12345
指针a指向是基本数据类型int,int数据类型宽度是4字节,所以a++即100+4。注意这里的100是地址值,地址本质就是数字,如果*a取地址对应值的话会像上面那样奔溃的,因为内存中找不到该地址,只是我们定义的时候是可以的。指针c指向指针类型int*,指针类型的数据宽度是8字节,所以c++即100+8。指针本身是8个字节,如cha**数据宽度8个字节,cha*是数据宽度是1个字节数组arr比较特殊,数组名本身就是指向首地址的指针即arr=&arr[0]。
对象反汇编
了解了指针的基础知识下面进入正题,创建一个对象Person,看一下arm64真机汇编。
@interface Person : NSObject
@property(nonatomic, copy) NSString * name;
@property(nonatomic, assign) int age;
+(instancetype)person;
@implementation Person
+ (instancetype)person{
return [[self alloc] init];
}
int main(int argc, char * argv[]) {
Person * p = [Person person]
p.name = @"隔壁老王";
p.age = 18;
}
断点在Person * p = [Person person]处看汇编
分析:
[Person person]方法的本质是消息发送即objc_msgSend,消息发送两个隐藏参数为id和SEL,也就是x0、x1寄存器,所以我们先静态分析一下x0和x1存的是什么,最后lldb读一下寄存器x0和x1验证一下。
- 第10行
ldr x0,[x8]:取x8寄存器地址处的值存进x0,说明x8是一个地址。而x8是通过adrp和add的组合指令获取的,在汇编与函数中我们分析了这个指令,它是获取全局变量或常量的指令,这里简单描述运算逻辑,先将pc寄存器地址即0x102032028加上后面的0x7000,然后将结果低3位清0,再加上0x560,最后的结果0x102039560即为x8的地址值,通过X命令查看x8地址指针的内存情况,ios是小端模式,从右往左读16位(8个字节,寄存器就是8个字节)即为x8的值,po这个值就是类对象"person"。这也说明了寄存器只是存放着指向这个对象的指针地址,通过寻址找到内存中的对象空间。 - x1寄存器SEl的读取也跟x0一样就不再分析了,只是需要
强转成SEL。 - 最后
lldb动态查看寄存器x0和x1跟我们静态分析的结果一致。
objc_storeStrong
main函数调用栈最后会调用一个objc_storeStrong符号,字面意思是一个存储强引用的符号,OC中初始化一个对象就是对这个对象的一次强引用,我们先看下此时的x0和x1。
po x0是一个地址而不是一个对象,说明此时x0是一个指向对象的地址指针,x1是nil。源码看下objc_storeStrong符号
void objc_storeStrong(id *location, id obj)
{
//location是指向对象的指针,obj是对象
//获取location指针的值也就是对象
id prev = *location;
if (obj == prev) {
return;
}
objc_retain(obj);
*location = obj;
objc_release(prev);
}
分析
- 就以上面例子为例分析,
x0我们知道是一个指向对象的指针即为location,x1是nil即为obj。此时*location!=obj,那么会objc_retain(nil), *location =nil,objc_release(nil),此时会把location指向的对象释放掉,也就是说函数的最后会释放person所占用的内存。
block反汇编
我们破解block本质是要破解block的实现invoke,所以我们需要知道invoke汇编后是什么,先写个block看下汇编
int a = 10;
void(^block)(void) = ^() {
NSLog(@"block--%d",a);
};
block();
可以看到
x0存放的是block对象,invoke实现地址就是block isa偏移16个字节。这个要结合block源码结构看才能理解,block源码结构如下
struct Block_layout {
void *isa;//block isa 8个字节
volatile int32_t flags; //4个字节
int32_t reserved; //4个字节
BlockInvokeFunction invoke;//invoke实现
struct Block_descriptor_1 *descriptor;//描述
};
block本质上是一个结构体对象看出isa偏移16个字节就是invoke实现地址。通过反汇编工具Hopper看一下这个可执行文件
可以看到Hopper反汇编工具直接帮我们翻译好了,反汇编工具今天就浅尝辄止,其实今天主要还是探索的汇编,只是结合oc对象去看探索汇编而已,相比于看帖子不如自己操作一下,感受大不同。
总结
读万卷书,不如行万里路,看帖子不如自己动手试一试。