一. OC的反汇编
创建空工程 001--OC方法的本质,在工程中创建Person类
// Person.h文件内容
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Person : NSObject
@property(nonatomic, copy) NSString * name;
@property(nonatomic, assign) int age;
+(instancetype)person;
@end
NS_ASSUME_NONNULL_END
// Person.m文件内容
#import "Person.h"
@implementation Person
+(instancetype)person {
return [[self alloc] init];
}
@end
// main.m文件内容如下
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
#import "Person.h"
int main(int argc, char * argv[]) {
Person *p = [Person person];
return 0;
}
// 运行工程,查看汇编代码
001--OC方法的本质`main:
0x102aaa164 <+0>: sub sp, sp, #0x30 ; =0x30
0x102aaa168 <+4>: stp x29, x30, [sp, #0x20]
0x102aaa16c <+8>: add x29, sp, #0x20 ; =0x20
// wzr指令 把0保存入栈
0x102aaa170 <+12>: stur wzr, [x29, #-0x4]
0x102aaa174 <+16>: stur w0, [x29, #-0x8]
0x102aaa178 <+20>: str x1, [sp, #0x10]
// 下面两行汇编,通过地址拿到一个变量,注意这里拿到的是一个指针
0x102aaa17c <+24>: adrp x8, 7
0x102aaa180 <+28>: add x8, x8, #0x540 ; =0x540
// 把x8值取出来给到x0,可以看出x8是一个指针,x0是id类型,也就是结构体指针
-> 0x102aaa184 <+32>: ldr x0, [x8]
// 下面三行汇编又是拿到一个指针,把值取出来给到x1,x1是sel类型
0x102aaa188 <+36>: adrp x8, 7
0x102aaa18c <+40>: add x8, x8, #0x530 ; =0x530
0x102aaa190 <+44>: ldr x1, [x8]
// objc_msgSend函数 第一个参数是id类型,第二个参数是sel类型,两个参数分别是上面的x0 x1
0x102aaa194 <+48>: bl 0x102aaa4e4 ; symbol stub for: objc_msgSend
// 执行到这里,创建的OC实例对象已经返回
0x102aaa198 <+52>: mov x29, x29
0x102aaa19c <+56>: bl 0x102aaa508 ; symbol stub for: objc_retainAutoreleasedReturnValue
// x8相当于取&p,作为objc_storeStrong第一个参数
0x102aaa1a0 <+60>: add x8, sp, #0x8 ; =0x8
// sp偏移8放的是p对象
0x102aaa1a4 <+64>: str x0, [sp, #0x8]
0x102aaa1a8 <+68>: stur wzr, [x29, #-0x4]
0x102aaa1ac <+72>: mov x0, x8
// 下面两行汇编可以看出x1为nil
0x102aaa1b0 <+76>: mov x8, #0x0
0x102aaa1b4 <+80>: mov x1, x8
// objc_storeStrong强引用函数,Person *p = [Person person];局部变量p就相当于有一个强引用在引用[Person person],这一次的objc_storeStrong不一定是让引用计数count+1,如果这里的代码对外引用count会+1,而[Person person]并没有对外引用
// 下面栈平衡会让实例对象销毁,objc_storeStrong函数既能让引用计数加一,也能让对象销毁
0x102aaa1b8 <+84>: bl 0x102aaa520 ; symbol stub for: objc_storeStrong
0x102aaa1bc <+88>: ldur w0, [x29, #-0x4]
0x102aaa1c0 <+92>: ldp x29, x30, [sp, #0x20]
0x102aaa1c4 <+96>: add sp, sp, #0x30 ; =0x30
0x102aaa1c8 <+100>: ret
通过静态调试来进行计算
- 通过读x0寄存器拿到类对象Person
- 同样的方法拿到x1寄存器,拿到OC -> person方法
通过对外调试进行验证
image.png小结
- 通过objc_msgSend汇编代码分析,就能还原出所有OC方法的调用,x1 ~ x8寄存器能还原方法的参数
- 注意如果有的方法是用C语言写的,不一定能还原出来,但是OC的方法就能还原
继续执行就跳入[Person person]方法内,这里发现只有objc_alloc_init方法,并没有objc_msgSend方法
001--OC方法的本质`+[Person person]:
0x102dbe068 <+0>: sub sp, sp, #0x20 ; =0x20
0x102dbe06c <+4>: stp x29, x30, [sp, #0x10]
0x102dbe070 <+8>: add x29, sp, #0x10 ; =0x10
0x102dbe074 <+12>: str x0, [sp, #0x8]
0x102dbe078 <+16>: str x1, [sp]
-> 0x102dbe07c <+20>: ldr x0, [sp, #0x8]
0x102dbe080 <+24>: bl 0x102dbe4c0 ; symbol stub for: objc_alloc_init
0x102dbe084 <+28>: ldp x29, x30, [sp, #0x10]
0x102dbe088 <+32>: add sp, sp, #0x20 ; =0x20
// 把创建好的对象返回
0x102dbe08c <+36>: b 0x102dbe4cc ; symbol stub for: objc_autoreleaseReturnValue
早期在iOS11系统还是会调用objc_msgSend方法,可以找一部iOS11系统的手机进行验证,如下图所示
- objc_alloc函数的返回值是x0寄存器,作为objc_msgSend函数的第一个参数,这时的x0已经是一个实例person对象
- objc_msgSend函数的第二个参数是下面取到的x1
- objc_msgSend函数实际上在调用init函数
为什么系统版本不同调用不同呢?
不同的系统版本在运行时是不同的,iOS11系统优化了alloc方法,alloc方法不再执行objc_msgSend方法,但是init依然执行objc_msgSend方法
小结
- 早期的系统执行alloc init方法( [[self alloc] init]; ) 相当于两次消息发送,每次都是调用objc_msgSend方法(可以使用iOS9系统的手机进行验证)
- iOS11系统对alloc 方法进行了优化,进行一次消息发送,先调用objc_alloc 再调用objc_msgSend
- iOS14系统对alloc init方法进行了优化,不用调用消息发送,只调用objc_alloc_init
获取 objc4-818.2,我们搜索objc_storeStrong能够看到源码
void
// 第一个参数 指向对象的指针,第二个参数 对象
// 第一个参数实际上是 &p,局部变量的指针 p的地址,第二个参数实际上是nil
objc_storeStrong(id *location, id obj)
{
// prev相当于上面的p对象
id prev = *location;
if (obj == prev) {
return;
}
objc_retain(obj);
*location = obj;
// 相当于release(p) 把堆内存释放
objc_release(prev);
}
// 函数目的是给 一个 strong修饰的对象进行retain+1,对老的对象进行release
// 这里实际上是这样调用的 objc_storeStrong(&p, nil),最终结果就是让p释放
二. 工具反汇编
修改main.m代码如下,编译工程 -> Products -> 选中001--OC方法的本质.app -> Show in Finder -> 显示包内容 -> 找到可执行文件 -> 使用Hopper工具打开 用Hopper工具来分析MachO文件
// main.m文件内容如下
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
#import "Person.h"
int main(int argc, char * argv[]) {
Person *p = [Person person];
p.name = @"hello";
p.age = 18;
return 0;
}
// 查看main函数汇编代码如下
_main:
0000000100006128 sub sp, sp, #0x30
000000010000612c stp x29, x30, [sp, #0x20]
0000000100006130 add x29, sp, #0x20
0000000100006134 stur wzr, [x29, #-0x4]
0000000100006138 stur w0, [x29, #-0x8]
000000010000613c str x1, [sp, #0x10]
// 取地址的指令,右边告诉了是Objc Class类型的引用,objc_cls_ref_Person中ref表示引用,objc_cls_ref_Person表示Person类对象
// 这里拿到的是静态区或常量区的东西
0000000100006140 adrp x8, #0x10000d000
0000000100006144 add x8, x8, #0x550 ; objc_cls_ref_Person
0000000100006148 ldr x0, x8
// 下面两行是个sel对象,表示类方法 person
000000010000614c adrp x8, #0x10000d000
0000000100006150 add x8, x8, #0x530 ; @selector(person)
0000000100006154 ldr x1, x8
// imp表示实现,stubs__objc_msgSend表示符号名字是 objc_msgSend, 这里表示调用的是一个函数,这个函数名字是objc_msgSend
0000000100006158 bl imp___stubs__objc_msgSend
000000010000615c mov x29, x29
0000000100006160 bl imp___stubs__objc_retainAutoreleasedReturnValue
0000000100006164 add x8, sp, #0x8
// x0是objc_msgSend方法的返回值,也就是p对象
0000000100006168 str x0, [sp, #0x8]
000000010000616c ldr x0, [sp, #0x8]
// 方法是setName
0000000100006170 adrp x9, #0x10000d000
0000000100006174 add x9, x9, #0x538 ; @selector(setName:)
0000000100006178 ldr x1, x9
// 参数是hello
000000010000617c adrp x2, #0x100008000
0000000100006180 add x2, x2, #0x28 ; @"hello"
0000000100006184 str x8, sp
// objc_msgSend方法第一个参数是p对象,第二个参数是setName,第三个参数是hello
0000000100006188 bl imp___stubs__objc_msgSend
000000010000618c ldr x0, [sp, #0x8]
0000000100006190 adrp x8, #0x10000d000
0000000100006194 add x8, x8, #0x540 ; @selector(setAge:)
0000000100006198 ldr x1, x8
000000010000619c movz w2, #0x12
00000001000061a0 bl imp___stubs__objc_msgSend
00000001000061a4 stur wzr, [x29, #-0x4]
00000001000061a8 ldr x0, sp
00000001000061ac movz x8, #0x0
00000001000061b0 mov x1, x8
00000001000061b4 bl imp___stubs__objc_storeStrong
00000001000061b8 ldur w0, [x29, #-0x4]
00000001000061bc ldp x29, x30, [sp, #0x20]
00000001000061c0 add sp, sp, #0x30
00000001000061c4 ret
; endp
编译器是怎么知道objc_cls_ref_Person是一个Person类对象呢?
这里与常量区有关,在macho中分析汇编代码的时候,通过地址就能找到对应的字符串,根据字符串类型就能判断出具体是什么,这就是反汇编工具能够还原名字的原因。
Hopper工具,MacOView工具都是根据特定格式读取macho文件,根据macho存放不同位置代表不同意义来进行还原
Hopper工具中双击objc_cls_ref_Person查看
image.png image.png也可以把可执行文件拖入 MachOView工具分析,这里可以看出来类对象Person在Data段
image.png类方法@selector(person)也可以双击进去查看
image.pngMachOView工具中查看
image.png三. Block的反汇编
3.1 Blokc反汇编一,不引用外部变量
// main.m文件内容如下
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char * argv[]) {
void(^block)(void) = ^() {
NSLog(@"block");
};
block();
return 0;
}
// 查看main函数汇编代码如下
001--OC方法的本质`main:
0x104886100 <+0>: sub sp, sp, #0x30 ; =0x30
0x104886104 <+4>: stp x29, x30, [sp, #0x20]
0x104886108 <+8>: add x29, sp, #0x20 ; =0x20
// 保存0入栈
0x10488610c <+12>: stur wzr, [x29, #-0x4]
// 保存w0入栈
0x104886110 <+16>: stur w0, [x29, #-0x8]
// 保存x1入栈
0x104886114 <+20>: str x1, [sp, #0x10]
// x0就是block,使用反汇编工具Hopper,可以直观看到这里是 ___block_literal_global
0x104886118 <+24>: adrp x0, 2
0x10488611c <+28>: add x0, x0, #0x28 ; =0x28
-> 0x104886120 <+32>: bl 0x1048864e4 ; symbol stub for: objc_retainBlock
0x104886124 <+36>: add x8, sp, #0x8 ; =0x8
...
// GlobalBlock的实现,libclosure-74-master 源码中可以查看,block是一个结构体
struct Block_layout {
void *isa;
volatile int32_t flags; // contains ref count
int32_t reserved;
BlockInvokeFunction invoke;
struct Block_descriptor_1 *descriptor;
// imported variables
};
image.png
截屏2021-04-17 下午4.28.54.png
下面找到可执行文件,使用Hopper工具查看汇编代码将会更加直观
- 双击___block_literal_global如下图
- 双击block的invoke地址如下图
- 也可以点击block的descriptor地址查看,会发现___block_literal_global的实现与描述信息在一块,与block结构体源码很像
3.2 Blokc反汇编二,引用外部变量
// main.m文件内容如下
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char * argv[]) {
int a = 10;
void(^block)(void) = ^() {
NSLog(@"block--%d",a);
};
block();
return 0;
}
// 查看main函数汇编代码如下
001--OC方法的本质`main:
0x1001860b4 <+0>: sub sp, sp, #0x60 ; =0x60
0x1001860b8 <+4>: stp x29, x30, [sp, #0x50]
0x1001860bc <+8>: add x29, sp, #0x50 ; =0x50
0x1001860c0 <+12>: stur wzr, [x29, #-0x4]
0x1001860c4 <+16>: stur w0, [x29, #-0x8]
0x1001860c8 <+20>: stur x1, [x29, #-0x10]
// w8的值为10
0x1001860cc <+24>: mov w8, #0xa
0x1001860d0 <+28>: stur w8, [x29, #-0x14]
// x9指向局部变量所以是一个指针
0x1001860d4 <+32>: add x9, sp, #0x8 ; =0x8
// 下面两行汇编就是block 的isa指针,可以看下图
0x1001860d8 <+36>: adrp x10, 2
0x1001860dc <+40>: ldr x10, [x10]
-> 0x1001860e0 <+44>: str x10, [sp, #0x8]
0x1001860e4 <+48>: mov w8, #-0x40000000
0x1001860e8 <+52>: str w8, [sp, #0x10]
0x1001860ec <+56>: str wzr, [sp, #0x14]
// 下面两行汇编就是block的invoke
0x1001860f0 <+60>: adrp x10, 0
0x1001860f4 <+64>: add x10, x10, #0x144 ; =0x144
0x1001860f8 <+68>: str x10, [x9, #0x10]
// 下面两行汇编就是block的descriptor
0x1001860fc <+72>: adrp x10, 2
0x100186100 <+76>: add x10, x10, #0x10 ; =0x10
0x100186104 <+80>: str x10, [x9, #0x18]
...
image.png
通过静态分析,取到block的invoke汇编代码
image.png使用Hopper工具来进行分析
- 双击___main_block_invoke查看block的实现
- 双击 ___block_descriptor_36_e5_v8�?0查看block的描述信息,会发现这一次block的实现与描述并不在一块
3.2 Blokc反汇编三
// main.m文件内容如下
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
void test3(){NSLog(@"123");}
void test2(){test3();}
void test1(){test2();}
void test(){
int a = 2;
if (a < 0) {
test1();
}else{
test2();
}
}
int main(int argc, char * argv[]) {
int a = 10;
void(^block)(void) = ^() {
NSLog(@"block--%d",a);
};
if (a > 0) {
test();
}else{
test1();
}
block();
return 0;
}
编译工程找到可执行文件,使用Hopper打开,
- 这个时候如果使用汇编代码分析的话就比较麻烦,此时我们可是查看流程来分析
- 也可以使用伪代码查看将会直观一些
这里Hopper还原出的伪代码可读性不是很好,使用IDA反汇编,里面的伪代码会更加直观,IDA不仅可以做静态分析,还可以动态调试,只是IDA反汇编工具比较昂贵,这里使用Hopper稳定一些。
总结
经过一段时间的学习,逆向学习最重要的汇编篇章结束了。后面我们开始学习加解密篇章,欢迎大家一起探讨...
网友评论