【iOS安全】Block开发与逆向
1. OC中的Block
1.1 Block的基本概念
- 在iOS开发中,Block是一种特殊的数据类型,类似于其他编程语言中的匿名函数。它可以封装一段代码,并且能够像普通变量一样传递、存储和执行。Block可以捕获并访问定义它时所在作用域的变量,这种变量捕获机制使得Block在异步编程、回调处理和事件响应等场景中非常有用。
1.2. Block的语法结构
- Block的语法结构如下:
^ 返回值类型 (参数列表) { // 代码块内容 };
- 例如,一个简单的Block,它接收两个整数参数并返回它们的和:
int (^sumBlock)(int, int) = ^int(int a, int b) { return a + b; };
- 在这个例子中,
sumBlock
是一个Block变量,^int(int a, int b)
定义了这个Block的返回值类型为int
,参数为两个int
类型的变量a
和b
,{ return a + b; }
是Block内部的代码,用于计算并返回a
和b
的和。
1.3. Block在异步编程中的应用(以网络请求为例)
- 在iOS开发中,进行网络请求时,通常会使用异步操作,因为网络请求可能需要花费一些时间才能完成。Block可以作为回调函数来处理网络请求的结果。
- 例如,使用
NSURLSession
进行网络请求:NSURLSession *session = [NSURLSession sharedSession]; NSURL *url = [NSURL URLWithString:@"https://example.com/api/data"]; NSURLSessionDataTask *task = [session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { if (!error) { // 解析数据并更新UI等操作 NSString *result = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; NSLog(@"请求成功,结果: %@", result); } else { NSLog(@"请求失败,错误: %@", error); } }]; [task resume];
- 在这个例子中,
completionHandler
是一个Block,它作为参数传递给dataTaskWithURL:completionHandler:
方法。当网络请求完成时,这个Block会被调用。Block内部可以访问data
(请求返回的数据)、response
(响应信息)和error
(错误信息),并根据这些信息进行相应的处理,如解析数据、更新用户界面(UI)等操作。
1.4. Block在事件处理中的应用(以按钮点击为例)
- 当处理用户界面(UI)中的按钮点击事件时,Block也非常有用。例如,使用
UIButton
的addTarget:action:forControlEvents:
方法来添加按钮点击事件的处理。 - 假设在一个视图控制器中有一个按钮,当点击按钮时,要执行一段特定的代码,可以使用Block来实现简洁的事件处理:
UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem]; [button setTitle:@"点击我" forState:UIControlStateNormal]; [button addTarget:self action:^(UIButton *sender) { NSLog(@"按钮被点击了"); } forControlEvents:UIControlEventTouchUpInside];
- 在这个例子中,
addTarget:action:forControlEvents:
方法的action
参数是一个Block。当按钮被点击(UIControlEventTouchUpInside
事件发生)时,这个Block会被执行,在Block内部可以编写需要执行的代码,如在这里只是简单地打印一条消息表示按钮被点击。这种方式相比于传统的@selector
(选择器)方法更加直观和灵活,特别是在处理简单的事件响应时,可以减少代码的复杂性。
1.5. Block在排序和遍历中的应用(以数组排序为例)
- 在对数组进行排序时,
NSArray
和NSMutableArray
提供了可以接受Block的排序方法。 - 例如,有一个包含整数的数组,要按照数字大小进行排序:
NSArray *numbers = @[@3, @1, @4, @1, @5, @9, @2, @6, @5, @3, @5]; NSArray *sortedNumbers = [numbers sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) { NSInteger num1 = [obj1 integerValue]; NSInteger num2 = [obj2 integerValue]; if (num1 < num2) { return NSComparisonResultLessThan; } else if (num1 > num2) { return NSComparisonResultGreaterThan; } else { return NSComparisonResultSame; } }]; NSLog(@"排序后的数组: %@", sortedNumbers);
- 在这个例子中,
sortedArrayUsingComparator:
方法接受一个Block作为参数。这个Block用于定义两个元素(obj1
和obj2
)的比较规则。通过比较两个元素对应的整数值,返回NSComparisonResultLessThan
、NSComparisonResultGreaterThan
或NSComparisonResultSame
来确定元素的顺序,从而实现数组的排序。这种方式允许开发者根据自己的需求灵活地定义排序规则,而不是局限于固定的排序方式。
2. Block逆向分析
转载自:https://blog.imjun.net/posts/restore-symbol-of-iOS-app/
因为这篇博客介绍的十分清楚、简洁,所以直接摘录其内容
2.1 Block在内存中的结构
block在内存中是以一个结构体的形式存在的,大致的结构如下:
struct __block_impl {
/**
block在内存中也是类NSObject的结构体,
结构体开始位置是一个isa指针
*/
Class isa;
/** 这两个变量暂时不关心 */
int flags;
int reserved;
/**
真正的函数指针!!
*/
void (*invoke)(...);
...
}
block中的isa指针,根据实际情况会有三种不同的取值,来表示不同类型的block:
-
_NSConcreteStackBlock
栈上的block,一般block创建时是在栈上分配了一个block结构体的空间,然后对其中的isa等变量赋值。 -
_NSConcreteMallocBlock
堆上的block,当block被加入到GCD或者被对象持有时,将栈上的block复制到堆上,此时复制得到的block类型变为了_NSConcreteMallocBlock。 -
_NSConcreteGlobalBlock
全局静态的block,当block不依赖于上下文环境,比如不持有block外的变量、只使用block内部的变量的时候,block的内存分配可以在编译期就完成,分配在全局的静态常量区。
第2种block在运行时才会出现,我们只关注1、3两种,下面就分析这两种isa指针和block符号地址之间的关联。
2.2 Block的isa指针
分析这部分需要用到IDA
1._NSConcreteStackBlock
假设我们的源代码是这样很简单的一个block:
编译完后,实际的汇编长这个样子:
实际运行时,block的构造过程是这样:
- 为block开辟栈空间
- 为block的isa指针赋值(一定会引用全局变量:_NSConcreteStackBlock)
- 获取函数地址,赋值给函数指针
所以我们可以整理出这样一个特征:
重点来了!!!
凡是代码里用到了栈上的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
1. (void)viewDidLoad {
void (^ foo)() = ^(){
//block 不引用外部的变量
NSLog(@"%d", 123);
};
foo();
}
@end
那么在编译后会变成这样:
那么借鉴上面的思路,在逆向分析的时候,我们可以这么推断
- 在静态常量区发现一个_NSConcreteGlobalBlock的引用
- 这个地方必然存在一个block的结构体数据
- 在这个结构体第16个字节的地方会出现一个值,这个值是一个block的函数地址