12.5 阻止数据执行 307
1
2
3
4
5
13
6
7
8
9
10
11
12
现在的设备使用将硬件和软件相结合的方法来阻止数据执行。当前的 ARM 和 x86 处理器都
支持这个特性,不过各个平台在使用这个技术时的术语有些许差异。ARM 在 AMD64 系列处理
器(例如 Athlon 64 和 Opteron)中首先引入了对 NX(Never Execute)的硬件支持。此后,Intel
在奔腾 4 系列处理器中引入了 XD(eXecute Disable)。ARM 则从 ARMv6 架构起开始支持 XN
(eXecute Never)。HTC Dream(也就是众所周知的 G1 或 ADP1)就使用了这种处理器设计。
无论是 ARM 还是 x86 架构,操作系统内核都必须使用这一特性,将某些内存区域标记为不
应该被执行。如果程序试图执行这些内存区域,就会产生一个处理器执行错误,然后提交给操作
系统内核。内核处理这个错误的方法是向产生问题的进程发送一个信号,这通常会使其终止执行。
对一个程序而言,除非它包含没有设置可执行标志的
GNU_STACK 程序头,否则 Linux 内核
就会将其栈所在的内存标记为可执行。编译程序时,如果将
-znoexecstack 选项传给编译工具
链,编译器就会在生成的可执行文件中插入这个程序头。如果不存在
GNU_STACK 程序头,或者
存在但可执行标志被置上,那么栈就是可执行的。受此影响,所有其他可读的内存映射也都是可
执行的。
可以使用
execstack 或者 readelf 工具来判断某个二进制可执行文件中是否包含这样的
程序头。大部分 Linux 发行版上都有这两个工具,在 AOSP 仓库中也能找到。下面摘录了如何用
这两个工具判断栈的可执行状态:
dev:~/android $ execstack -q cat*
? cat-g1
- cat-gn-takju
X cat-gn-takju-CLEARED
dev:~/android $ readelf -a cat-g1 | grep GNU_STACK
dev:~/android $ readelf -a cat-gn-takju | grep GNU_STACK
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0
dev:~/android $ readelf -a cat-gn-takju-CLEARED | grep GNU_STACK
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RWE 0
代码倒数第 3 行的 RW 和倒数第 1 行的 RWE 加粗
除了这些工具,还可以通过 proc 文件系统中的 maps 文件来判断内存映射是否可执行。下面
摘录了在运行 Android 4.2.1 的 Galaxy Nexus 和运行 Android 2.2.2 的 Motorola Droid 上使用
cat
工具查看映射情况的结果:
shell@android:/ $ # on the Galaxy Nexus running Android 4.2.1
shell@android:/ $ cat /proc/self/maps | grep -E '(stack|heap)'
409e4000-409ec000 rw-p 00000000 00:00 0 [heap]
bebaf000-bebd0000 rw-p 00000000 00:00 0 [stack]
$ # on the Motorola Droid running Android 2.2.2
$ cat /proc/self/maps | grep -E '(stack|heap)'
0001c000-00022000 rwxp 00000000 00:00 0 [heap]
bea13000-bea14000 rwxp 00000000 00:00 0 [stack]
maps 文件中的每一行都包括一块内存区域的起止地址、权限、页偏移地址、设备主编号和