汇编终章

一. OC的反汇编

创建空工程 001--OC方法的本质,在工程中创建Person类

// Person.h文件内容
#import 
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 
#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
汇编终章_第1张图片
image.png
  • 同样的方法拿到x1寄存器,拿到OC -> person方法
汇编终章_第2张图片
image.png
汇编终章_第3张图片
image.png

通过对外调试进行验证

汇编终章_第4张图片
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函数
汇编终章_第5张图片
截屏2021-04-17 上午11.52.19.png
汇编终章_第6张图片
截屏2021-04-17 上午11.59.47.png

为什么系统版本不同调用不同呢?
不同的系统版本在运行时是不同的,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
汇编终章_第7张图片
image.png
汇编终章_第8张图片
image.png
汇编终章_第9张图片
image.png

获取 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 
#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查看

汇编终章_第10张图片
image.png
汇编终章_第11张图片
image.png

也可以把可执行文件拖入 MachOView工具分析,这里可以看出来类对象Person在Data段

汇编终章_第12张图片
image.png

类方法@selector(person)也可以双击进去查看

汇编终章_第13张图片
image.png

MachOView工具中查看

image.png

三. Block的反汇编

3.1 Blokc反汇编一,不引用外部变量

// main.m文件内容如下
#import 
#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
};
汇编终章_第14张图片
image.png
汇编终章_第15张图片
截屏2021-04-17 下午4.28.54.png

下面找到可执行文件,使用Hopper工具查看汇编代码将会更加直观

  • 双击___block_literal_global如下图
截屏2021-04-17 下午4.38.59.png
  • 双击block的invoke地址如下图
汇编终章_第16张图片
image.png
  • 也可以点击block的descriptor地址查看,会发现___block_literal_global的实现与描述信息在一块,与block结构体源码很像

3.2 Blokc反汇编二,引用外部变量

// main.m文件内容如下
#import 
#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]
... 
汇编终章_第17张图片
image.png

通过静态分析,取到block的invoke汇编代码

汇编终章_第18张图片
image.png

使用Hopper工具来进行分析

  • 双击___main_block_invoke查看block的实现
汇编终章_第19张图片
image.png
  • 双击 ___block_descriptor_36_e5_v8�?0查看block的描述信息,会发现这一次block的实现与描述并不在一块

3.2 Blokc反汇编三

// main.m文件内容如下
#import 
#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打开,

  • 这个时候如果使用汇编代码分析的话就比较麻烦,此时我们可是查看流程来分析
汇编终章_第20张图片
image.png
  • 也可以使用伪代码查看将会直观一些
汇编终章_第21张图片
image.png

这里Hopper还原出的伪代码可读性不是很好,使用IDA反汇编,里面的伪代码会更加直观,IDA不仅可以做静态分析,还可以动态调试,只是IDA反汇编工具比较昂贵,这里使用Hopper稳定一些。

总结

经过一段时间的学习,逆向学习最重要的汇编篇章结束了。后面我们开始学习加解密篇章,欢迎大家一起探讨...

你可能感兴趣的:(汇编终章)