您的位置:首页 > 移动开发 > IOS开发

iOS逆向之OC反汇编(下)

2021-05-10 16:14 2296 查看

本文主要理解OC对象反汇编,以及block常见类型的反汇编



OC反汇编

创建一个Person类,并在main函数中初始化一个Person对象

@interface Person : NSObject@property(nonatomic, copy) NSString *name;@property(nonatomic, assign) int age;

+ (instancetype)person;@end@implementation Person+ (instancetype)person{    return [[self alloc] init];
}@end<!--main.m中-->int main(int argc, char * argv[]) {

    Person *p = [Person person];    return 0;
}
  • 运行,查看其汇编代码


首先作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS开发公众号:编程大鑫,不管你是小白还是大牛都欢迎入驻 ,让我们一起进步,共同发展!

1、静态调试

通过adrp+add获取地址,分别读取x0,x1

  • 读取x0,读出来是Person:

    x 0x100c68eb0
     + 
    po 0x0100c68f98


  • 读取x1,读取出来是person方法:

    x 0x100c68e88
     + 
    p (SEL)0x01c019aef5


2、动态调试
通过一步一步执行汇编,来验证x0、x1是否如静态调试的结果一致?


通过调试发现,是一致的,其实这里的

x0、x1
就是 
objc_msgSend
的隐藏参数
(id self,SEL _cmd)

下面我们继续调试汇编

  • 点击step into,直接进入

    [Person person]
    方法(注意:这里不同iOS版本,多看到的汇编代码是有所区别的)

    从这里看到ios13.4系统的alloc、init并不会走objc_msgSend


  • ios11版本中,可以看到objc_msgSend,其本质是在调用init方法


    动态调试进行验证,结果如下所示,是一致的


    查看此时的x0,已经是一个实例对象,因为alloc开辟了内存,已经分配了空间,具体的内部实现可以查看iOS-底层原理


    疑问:为什么版本不同,调用不一样呢?

  • 在不同的版本下,系统在运行时是不一样的。因为系统对alloc 、init进行了优化

  • 接着往下看,点击step out 跳出[Person person],此时返回值在x0中


  • 执行到

    bl ... objc_storeStrong
    ,objc_storeStrong是OC中用strong修饰的对象底层都是调用这个函数,详情可以看iOS-底层原理
    疑问:我们此时并没有使用strong修饰?:此时的局部变量p在此时就相当于一个强引用,是默认的。且这个方法执行完成后,相当于销毁p


    查看此时的

    x0、x1
    ,相当于
    objc_storeStrong(&p,nil)
    ,将nil进行retain,将nil等于p(即 p=nil),p进行释放


  • 查看

    objc_storeStrong
    源码

  • 目的:对一个strong修饰的对象进行retain +1,对一个老的对象进行release

      为什么是指针? 因为函数是值传递,而函数内部需要修改p的值
    /*
     - id *location 指向对象的指针  本质上是 &p(即局部变量地址)
     - id obj 对象
     目的:对一个strong修饰的对象进行retain +1,对一个老的对象进行release
     为什么是指针? 因为函数是值传递,而函数内部需要修改p的值
     */voidobjc_storeStrong(id *location, id obj)
    {    //prev 相当于p ,因为location是 &p
        id prev = *location;    //第二个参数 == 第一个参数,直接return
        if (obj == prev) {        return;
        }    //retain+1
        objc_retain(obj);    //修改p的值,指向第二个对象
        *location = obj;    //释放老对象
        objc_release(prev);
    }

    相当于

    Person *p = p1;
    p = p2;//此时p1释放,p2retain+1

    所以以上汇编中的

    objc_storeStrong(&p,nil)
    的实现代码如下

    objc_storeStrong(&p,nil){    id prev = p;    if nil == p{        return;
        }
        objc_retain(nil);
        p = nil;//指针指向nil
        objc_release(p);//释放堆空间}
    • 下面来进行动态验证,发现

      Person对象指向nil


    [[self alloc] init] 优化过程

    • 在最初的版本(iOS9)中,相当于两次消息发送 

      objc_msgSend

    • iOS11版本 是一次消息发送 

      objc_alloc + objc_msgSend

    • iOS13.5.1以上版本,已经没有objc_msgSend,而是

      objc_alloc_init


    以上是LLDB动态调试

    Person *p = [Person person]; //objc_msgSend x0,x1

    通过工具看复杂的OC代码

    在上述OC代码的基础上增加一些代码,然后再来静态分析

    int main(int argc, char * argv[]) {
    
        Person *p = [Person person]; //objc_msgSend x0,x1
        p.name = @"CJL";
        p.age = 18;    return 0;
    }
    • CMD + B 编译程序,生成mach-o文件,并找到该文件

    • 通过Hopper反汇编mach-o文件,main函数的分析如下


    • 双击

      objc_cls_ref_Person
      ,查看p的地址,是
      000000010000ce88
      ,是在Data段


      通过

      MachOView
      打开mach-o分析,查找
      000000010000ce88
      ,与Hopper中的显示是一致的


    • 双击

      @selector(person)
      ,查看person方法的反汇编


      双击

      0x10000cc68


      双击

      “person”
      ,地址为 0x10000752a


      在mach中查找

      0x10000752a
      ,所有方法的name都在
      CString


    Block反汇编

    定义一个block

    int main(int argc, char * argv[]) {    void(^block)(void) = ^(){
            NSLog(@"block");
        };
        block();    return 0;
    }

    反汇编分析block的目的是想快速定位block的

    invoke
    ,因为invoke中是实现代码,以下是block的汇编代码


    • 查看

      x0
      是什么?:是一个
      __block_literal_global
      ,是一个
      全局静态block
      (即block
      不引用block外部变量
      ,在
      编译时期
      就可以
      确定内存
      的分配等操作,存在于可执行文件的
      常量区
      ),其他详情也可查看iOS-底层原理


      以下是源码中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};

    然后动态调试查看block的内存结构


    • 是否可以通过hopper查看 adrp + add 是一个block?
      答案是可以的


      双击

      ___block_literal_global


    • 双击

      0x0000000100006838
      ,查看
      invoke


    • 双击

      0x0000000100008008
      ,查看descriptor,和Block的源码结构类似


    如果block引用了外部变量呢?

    定义一个block,其中block引用了外部变量,查看此时的汇编代码

    int main(int argc, char * argv[]) {    int a = 10;    void(^block)(void) = ^(){
            NSLog(@"block -- %d", a);
        };
        block();    return 0;
    }

    1、lldb调试

    • 以下是代码的汇编


    • 验证是否是block的isa指针


      adrp x10, 2
       获取指针地址
    • ldr x10, [x10]
      :取值
  • 查看此时block的内存,找到invoke(由于invoke是代码实现,所以需要由

    dis -s
    (将代码的汇编打印出来)查看)


  • 2、静态分析

    • 通过hopper静态分析如下,以下是main函数的反汇编


    • 双击

      ___main_block_invoke
      ,跳转至invoke的具体实现(并没有在main函数中,是单独的实现)


    • 双击

      ___block_descriptor_36_e5_v8�?0l
      ,是一个单独的描述


    总结

    首先作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS开发公众号:编程大鑫,不管你是小白还是大牛都欢迎入驻 ,让我们一起进步,共同发展!

    • [[self alloc] init] 优化过程

      在最初的版本(iOS9)中,相当于两次消息发送 

      objc_msgSend

    • iOS11版本 是一次消息发送 

      objc_alloc + objc_msgSend

    • iOS13.5.1以上版本,已经没有objc_msgSend,而是

      objc_alloc_init

  • 反汇编分析方式:

      通过

      LLDB
      动态调试

    • 通过

      Hopper + MachOView
       静态分析


  • 内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
    标签: