现代操作系统的内存分配以页为单位进行管理,而页通过段进行管理,组成了段页式内存管理。对于一个典型的进程来说,它的内存空间是由哪些部分组成的?每个部分又被安置在空间的什么位置?
抽象内存布局
代码段
CPU 运行一个程序,实质就是在顺序执行该程序的机器码。一个程序的机器码会被组织到同一个地方,这个地方就是代码段。
- 可执行指令
- 在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。
数据段
另外,程序在运行过程中必然要操作数据。这其中,对于有初值的变量,它的初始值会存放在程序的二进制文件中,而且,这些数据部分也会被装载到内存中,即程序的数据段。
- 数据段存放的是程序中已经初始化且不为 0 的全局变量和静态变量。
.data 可读/写 .rodata 只读
BSS段
对于未初始化的全局变量和静态变量,因为编译器知道它们的初始值都是 0,因此便不需要再在程序的二进制映像中存放这么多 0 了,只需要记录他们的大小即可,这便是BSS 段。
数据段和 BSS段里存放的数据也只能是部分数据,主要是全局变量和静态变量,但程序在运行过程中,仍然需要记录大量的临时变量,以及运行时生成的变量,这里就需要新的内存区域了,即程序的堆空间跟栈空间。与代码段以及数据段不同的是,堆和栈并不是从磁盘中加载,它们都是由程序在运行的过程中申请,在程序运行结束后释放。
堆空间
堆是向⾼地址扩展的数据结构,是不连续的内存区域。由代码分配和释放,手动申请空间,如果不释放,程序结束时,可能会由操作系统回收 。
- 优点:
灵活⽅便,数据适应⾯⼴泛 - 缺点:
效率有⼀定降低,容易产生碎片。
栈空间
栈是向低地址扩展的数据结构,由编译器⾃动分配并释放,存放函数的参数值,局部变量等。栈是系统数据结构,对应线程/进程是唯⼀的。
- 优点:
快速⾼效,不会产生碎片 - 缺点:
有限制,数据不灵活。(先进后出)
总的来说,一个程序想要运行起来所需要的几块基本内存区域:代码段、数据段、BSS 段、堆空间和栈空间。下面就是内存布局的示意图:
基本内存区域.png这是程序运行起来所需要的最小功能集,除了上面所讲的基本内存区域外,现代应用程序中还会包含其他的一些内存区域,主要有以下几类:
- 存放加载的共享库的内存空间
如果一个进程依赖共享库,那对应的,该共享库的代码段、数据段、BSS 段也需要被加载到这个进程的地址空间中。 - 共享内存段
我们可以通过系统调用映射一块匿名区域作为共享内存,用来进行进程间通信。 - 内存映射文件
我们也可以将磁盘的文件映射到内存中,用来进行文件编辑或者是类似共享内存的方式进行进程通信。
这样我们就初步了解了一个进程内存中需要哪些区域。
Section 与 Segment
对于磁盘的程序,每一个单元结构称为 Section。对于内存镜像,每一个单元结构称为 Segment。往往多个 Section 会对应一个 Segment,例如 .text
、.rodata
、等一些只读的 Section,会被映射到内存的一个只读 / 执行的 Segment 里;而 .data
、.bss
等一些可读写的 Section,则会被映射到内存的一个具有读写权限的 Segment 里。
总的来说,Section 主要是指在磁盘中的程序段,而 Segment 则用来指代内存中的程序段,Segment 是将具有相同权限属性的 Section 集合在一起,系统为它们分配的一块内存空间。
下图展示了部分 Mach-O 文件结构,也展示了在 iOS 中 Section 和 Segment 的关系。
ASLR:内存空间布局随机化
Mach-O 文件利用两种空间描述,来表达自己在 Mach-O 文件和虚拟内存中不同空间的分配方式。每个 App 都有自己独立的虚拟内存,这个虚拟内存只存在与自己的 Mach-O 文件的 load commands
的描述中。当 App 执行时,会被系统映射到实际的物理内存中。程序一旦编译完成,函数就安静的放在了 __TEXT
段, 全局变量就安静的放在了 __DATA
段上。等用户点击后,才被加载到内存。
在 iOS 逆向中,通过 Hopper 工具反汇编可以看到函数的虚拟内存地址,但是 Hopper 中展示的内存地址是没有 ASLR 的。想要得到函数的真正函数地址,还需要得到当前 Mach-O 文件在内存中的 ASLR 偏移值。当 App 被加载到内时,系统会自动进行 ASLR ,在 __PAGEZERO
段的上面随机多出一段空间作为偏移,使得 Mach-O 文件的整个虚拟内存向下整体(包括堆,栈,共享库映射等线性布局)偏移。从而可以让生成的函数内存地址不断变动。这样可以提高黑客的破解难道。
那如何得到当前 Mach-O 文件的 ASLR 偏移值呢?
通过 lldb 调试器的 Mach-O 文件列表查询命令,-o
查询所有使用的 Mach-O 文件包括 dyld 链接编辑器、App 的 Mach-O 文件、dylib 库,得到的第一个结果就是所需要的 ASLR 偏移值。
image list -o -f
APP 的进程内存布局
一般来说,进程的内存布局是相似的。在iOS App 中是通过 Mach-O 文件从而在虚拟内存中逻辑结构和布局的。关于 Mach-O 可以查看了解 Mach-O 文件。Mach-O 文件中不同的内容段 __TEXT
、__DATA
等在虚拟内存中的内存分布是用 VM Address 和 VM Size 来描述的;在 Mach-O 本地文件中的空间分布是用 File Offset 和 File Size 来描述的
通过代码输出变量及常量的地址
int a = 10; //已初始化全局变量
int b; //未初始化全局变量
const int testConst = 20; //const 常量
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
static int c = 20; //已初始化静态变量
static int d; //未初始化静态变量
int e; //未初始化局部变量
int f = 20; //已初始化局部变量
NSString *str = @"123";//字符串常量
NSObject *obj = [[NSObject alloc] init];//通过alloc动态分配(实例对象)
NSLog(@"\n&a=%p\n&b=%p\n&c=%p\n&d=%p\n&e=%p\n&f=%p\nstr=%p\nobj=%p\n&testConst=%p",
&a, &b, &c, &d, &e, &f, str, obj,&testConst);
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
通过输出地址以及 image list -o -f
查看到的 dylib 和 dyld 地址信息,得到下面 iOS 内存布局的示意图:
网友评论