从逆向工程角度分析iOS中block在内存中的结构

  最近在了解逆向工程,其中看到了一篇关于逆向解析支付宝的文章,文章中提到了block在内存中的存储结构,以及根据汇编代码推断block在内存中的具体地址。在这里摘出来供大家更加深入的了解block。

block在内存中的结构

首先,我们先分析下运行时,block在内存中的存在形式。block在内存中是以一个结构体的形式存在的,大致的结构如下:

struct __block_impl {
  /**
  block在内存中也是类NSObject的结构体,
  结构体开始位置是一个isa指针
  */
  Class isa;
  
  /** 这两个变量暂时不关心 */
  int flags;
  int reserved;
  
  /**
  真正的函数指针!!
  */
  void (*invoke)(...);
  ...
}

说明下block中的isa指针,根据实际情况会有三种不同的取值,来表示不同类型的block:

  1. _NSConcreteStackBlock

    栈上的block,一般block创建时是在栈上分配了一个block结构体的空间,然后对其中的isa等变量赋值。

  2. _NSConcreteMallocBlock

    堆上的block,当block被加入到GCD或者被对象持有时,将栈上的block复制到堆上,此时复制得到的block类型变为了_NSConcreteMallocBlock。

  3. _NSConcreteGlobalBlock

    全局静态的block,当block不依赖于上下文环境,比如不持有block外的变量、只使用block内部的变量的时候,block的内存分配可以在编译期就完成,分配在全局的静态常量区。

第2种block在运行时才会出现,我们只关注1、3两种,下面就分析这两种isa指针和block符号地址之间的关联。

block isa指针和符号地址之间的关联

分析这部分需要用到IDA这个反汇编软件, 这里结合两个实际的小例子来说明:

1._NSConcreteStackBlock

假设我们的源代码是这样很简单的一个block:

@implementation ViewController
- (void)viewDidLoad {
    int t = 2;
    void (^ foo)() = ^(){
        NSLog(@"%d", t); //block 引用了外部的变量t
    };
    foo();
}
@end

编译完后,实际的汇编长这个样子:

实际运行时,block的构造过程是这样:

  1. 为block开辟栈空间
  2. 为block的isa指针赋值(一定会引用全局变量:_NSConcreteStackBlock
  3. 获取函数地址,赋值给函数指针

所以我们可以整理出这样一个特征:

重点来了!!! 

凡是代码里用到了栈上的block,一定会获取__NSConcreteStackBlock作为isa指针,同时会紧接着获取一个函数地址,那个函数地址就是block的函数地址。

结合下面这个图,仔细理解上面这句话
(这张图和上面那张图是同一个文件,不过裁掉了符号表)

利用这个特征,逆向分析时我们可以做如下推断:

在一个OC方法里发现引用了__NSConcreteStackBlock这个变量,那么在这附近,一定会出现一个函数地址,这个函数地址就是这个OC方法里的一个block。

比如上面图中,我们发现 viewDidLoad 里,引用了__NSConcreteStackBlock,同时紧接着加载了 sub_100049D4 的函数地址,那我们就可以认定sub_100049D4是viewDidLoad里的一个block, sub_100049D4函数的符号名应该是 viewDidLoad_block.

2. _NSConcreteGlobalBlock

全局的静态block,是那种不引用block外变量的block,他因为不引用外部变量,所以他可以在编译期就进行内存分配操作,也不用担心block的复制等等操作,他存在于可执行文件的常量区里。

不太理解的话,看个例子:

我们把源代码改成这样:

@implementation ViewController
- (void)viewDidLoad {
   
    void (^ foo)() = ^(){
        //block 不引用外部的变量
        NSLog(@"%d", 123);
    };
    foo();
}
@end

那么在编译后会变成这样:

那么借鉴上面的思路,在逆向分析的时候,我们可以这么推断

  1. 在静态常量区发现一个_NSConcreteGlobalBlock的引用
  2. 这个地方必然存在一个block的结构体数据
  3. 在这个结构体第16个字节的地方会出现一个值,这个值是一个block的函数地址

3. block 的嵌套结构

实际在使用中,可能会出现block内嵌block的情况:

- (void)viewDidLoad {
  dispatch_async(background_queue ,^{
    ...
    dispatch_async(main_queue, ^{
      ...     
    });
  });
}

所以这里block就出现了父子关系,如果我们将这些父子关系收集起来,就可以发现,这些关系会构成图论里的森林结构,这里可以简单用递归的深度优先搜索来处理,详细过程不再描述。


参考:iOS符号表恢复&逆向支付宝



猜你喜欢

转载自blog.csdn.net/u013602835/article/details/80418127