了解运行在基于 Power™/Cell Broadband Engine™ Architecture 处理器的服务器中的 Linux® 缓冲区溢出漏洞。当进程尝试将数据储存到固定长度的缓冲区的范围之外时,将出现缓冲区溢出。当出现这种情况时,可能会导致出现各种异常的系统行为,并且某些行为可能会对系统安全性造成威胁。本系列文章的第 1 部分将简要介绍缓冲区溢出及 Power 和 Cell/B.E.™ 架构,然后说明如何更改目标系统中的进程执行流程以及如何在 32 位和 64 位模式中重写局部变量(第 2 部分将介绍如何在 32 位和 64 位模式中重写函数指针并阐述如何通过 shell、网络和套接字代码样例组装组件)。
在本文中,运行在基于 Power/Cell Broadband Engine Architecture 处理器的服务器上的 Linux 中的所有缓冲区溢出漏洞示例都是在运行 Red Hat Enterprise Linux 4 Update 7 的 IBM BladeCenter® JS22 Express 服务器、IBM BladeCenter QS21 服务器和 Sony Playstation 3 中开发和执行的。
回顾缓冲区溢出问题
现在让我们快速回顾一下缓冲区溢出问题。当进程尝试将数据储存到固定长度的缓冲区的范围之外时,就会出现缓冲区溢出。结果导致额外的数据重写邻近的内存位置。重写的数据可以包括其他缓冲区、变量、程序流数据等。重写此数据会导致出现诸如异常程序行为、内存访问异常、由各种崩溃引起的程序终止、错误的返回结果或者最严重的系统完整性问题:安全性违背。
缓冲区溢出导致许多软件出现漏洞,并因此为恶意开发提供了可乘之机。C/C++ 系统特别容易出现溢出问题,这类系统没有提供内置保护来停止对某块内存数据的访问或重写,而且也不自动检查写到内置缓冲区数组中的数据是否位于该数组的边界范围内。这就是为什么您应当始终支持执行边界检查的系统(检查可由您或者编译器和运行时执行)的原因。
要了解关于缓冲区溢出及如何避免缓冲区溢出的更多信息,请阅读 developerWorks 文章 “安全编程:防止缓冲区溢出 -- 防止如今最常见的程序缺陷”。
Power Architecture
在 20 世纪 90 年代初,IBM 开始开发 POWER(Performance Optimization With Enhanced RISC)Architecture,并引入到 RISC System/6000 产品系列中。1991 年,Apple、IBM 和 Motorola(称为 AIM 联盟)开始协作开发 PowerPC® Architecture,扩展该架构的适用性。1997 年,Motorola 和 IBM 启动了另一项协作,旨在针对嵌入式系统优化 PowerPC。2004 年年底,Power.org 联盟启动,该联盟的目标是开发社区规范并为开发工具提供支持,这些开发工具有助于实现以 Power Architecture 为中心的集成及增强实现。
Power Architecture 是 Power Architecture Advisory Council 所维护的 Power Instruction Set Architecture(Power ISA)定义的开放架构,这将提供实现之间的兼容性并且让任何人都可以设计、创建与 Power Architecture 兼容的处理器。Xbox 360 处理器和 Cell Broadband Engine 处理器都是优秀的示例。
遵循 Power Architecture 的处理器实现有四类基本指令:
- 分支指令
- 使用定点寄存器的定点指令和其他指令
- 浮点指令和十进制浮点指令
- 矢量指令
定点指令作用于字节、半字、字和双字操作数。浮点指令作用于单精度和双精度浮点操作数。矢量指令作用于标量的矢量及标量,其中标量范围为字节、半字、字和四字。处理器使用长度为四个字节并且字与字对齐的指令。它规定在存储设备与一组 32 位通用寄存器(General Purpose Register,GPR)之间存取和储存的字节、半字、字和双字操作数。它规定在存储设备与一组 32 位浮点寄存器(Floating-Point Register,FPR)之间存取和储存的字和双字操作数。它还规定在存储设备与一组 32 位矢量寄存器(Vector Register,VR)之间存取和储存的字节、半字、字和四字操作数。
- 条件寄存器(Condition Register,CR)是反映特定操作结果并提供测试(及分支)机制的 32 位寄存器。
- 链接寄存器(Link Register,LR)是 64 位寄存器。它可用于为 Branch Conditional to Link Register 指令提供分支目标地址,并且它将保存 Branch 指令后面的返回地址。
- 计数寄存器(Count Register,CTR)是 64 位寄存器。它可用于保存可以在执行 Branch 指令期间递减的循环计数。
- 机器状态寄存器(Machine State Register,MSR)是 64 位寄存器。此寄存器将定义处理器的状态。第 64 位将定义处理器是 32 位模式还是 64 位模式(0 或 1)。
处理器将提供两种执行模式:64 位和 32 位模式。在两种模式下,设定 64 位寄存器的指令将影响所有 64 位。计算模式将控制有效地址的解析方法、状态位的设置方法、Branch 指令设置链接寄存器的方法,以及 Branch Conditional 指令测试计数寄存器的方法。几乎所有指令都可用于两种模式。在两种模式下,有效地址计算将使用所有 64 位相关寄存器(GPR、LR、CTR 等)并生成 64 位结果。但是,在 32 位模式下,为了解决存储寻址问题,将忽略高阶 32 位有效地址。
所有指令的长度都是四个字节并且字和字之间对齐。因此,只要将指令地址提供给处理器(如 Branch 指令),就会忽略低阶的两位。同样,只要处理器利用指令地址,低阶的两位就为零。
位 0:5 总是指定操作码。许多指令还有扩展操作码。指令的其他位包含其他指令格式的一个或多个字段。
在程序执行 Storage Access 或 Branch 指令或者在存取下一条顺序指令时,它将使用处理器计算出的有效地址引用存储设备。存储设备中的字节是从 0 开始依次编号的。每个编号都是相应字节的地址。存储器存取的字节顺序(Big-Endian 或 Little-Endian)是由操作系统指定的。
Cell Broadband Engine Architecture(CBEA)
Cell Broadband Engine(Cell/B.E.)处理器是遵循 Cell Broadband Engine Architecture(CBEA)的新型微处理器系列的第一个实现。CBEA 是扩展 64 位 Power Architecture 的架构。CBEA 和 Cell/B.E. 处理器是 Sony、Toshiba 与 IBM(称为 STI,正式启动于 2001 年初)之间协作的成果。
虽然 Cell/B.E. 处理器最初针对的是具有丰富媒体的消费性电子设备(例如游戏控制台及高清电视)中的应用程序,但是该架构在设计时主要是为了显著提升处理器性能。人们期望这些性能提升可以支持商业领域及科学领域中的各种应用程序。
Cell/B.E. 处理器最与众不同的特性是,在所有处理器元素共享内存的情况下,其功能可以分为两类:Power Processor Element(PPE)和 Synergistic Processor Element(SPE)。处理器有一个 PPE 和八个 SPE。
第一类处理器元素 PPE 包含 64 位 Power Architecture 内核。它遵循 64 位 Power Architecture 并且可以运行 32 位和 64 位操作系统及应用程序。
第二类处理器元素 SPE 最适于运行计算密集型 SIMD 应用程序;它不适合运行操作系统。SPE 是独立的处理器元素,每个 SPE 都运行自己的独立应用程序或线程。每个 SPE 都可以完全访问连续的共享内存,包括内存映射的 I/O 空间。
PPE 与 SPE 之间有相互依赖关系。SPE 依赖于 PPE 才可以运行操作系统,并且在许多情况下,还可以运行应用程序的顶层线程控制。PPE 需要依赖 SPE 来提供主要的应用程序性能。
SPE 与 PPE 之间最显著的差别在于访问内存的方法。PPE 用载入和存储指令访问主存储器(有效地址空间),可以在主存储器与内容可以缓存的私有寄存器文件之间移动数据。
SPE 用直接内存访问(DMA)命令访问主存储器,可以在主存储器与称为本地库或本地存储(LS)的私有本地内存之间移动数据和指令。SPE 的存取指令及载入和存储指令将访问其私有 LS ,而不访问共享主存储器;LS 没有关联缓存。这种三级存储结构(寄存器文件、LS、主存储器)以及在 LS 与主存储器之间使用的异步 DMA 传输,与传统的架构和编程模型截然不同,因为它将在传输数据和指令(提供计算并存储主存储器计算结果)的同时执行计算。
控制缓冲区溢出
现在我们将查看以下内容:
- 更改进程执行流程
- 在 32 位模式下重写局部变量
- 在 64 位模式下重写局部变量
(并且在第 2 部分中,我们将介绍如何在 32 位和 64 位模式下重写函数指针并提供一些示例代码)。
更改进程执行流程
类似于 x86/x86_64 架构,可以通过以下操作更改 Power/CBEA 中给定进程的执行流程:
- 重写靠近缓冲区(保存给定进??的虚拟地址空间)的局部变量以更改应用程序的行为。
- 重写栈框架中经过保存的返回指令指针。从调用函数处返回指针后,将在指定的返回指令指针处恢复执行。
- 重写随后执行的函数指针或异常处理程序。
在 32 位模式下重写局部变量
本节将讨论如何通过重写局部变量更改给定进程的执行流程。
以下示例容易出现基于堆的缓冲区溢出:
清单 1. example1.c(容易出现基于堆的缓冲区溢出)
#include <stdio.h> #include <stdlib.h> #include <string.h> struct mystruct { unsigned char buffer[16]; unsigned long cookie; }; int main(int argc, char **argv) { struct mystruct *s; if ((s = malloc(sizeof(struct mystruct))) == NULL) { perror("malloc"); exit(EXIT_FAILURE); } s->cookie = 0; if (argc > 1) strcpy(s->buffer, argv[1]); if (s->cookie == 0x42424242) { printf("Congratulations! You won a cookie!\n"); exit(EXIT_SUCCESS); } printf("Hello world!\n"); exit(EXIT_SUCCESS); } |
在使用 strcpy 函数将用户提供的数据复制到先前分配的 struct mystruct 的 buffer 成员中时,清单 1 不验证用户提供的数据,造成堆中缓冲区溢出。正常执行该示例将把 “Hello world!” 字符串写到 stdout 中。
清单 2. 编译并执行清单 1
$ gcc -Wall -o example1 example1.c $ ./example1 Hello world! $ |
图 1 表示堆分段中的 struct mystruct 及其成员。
图 1. struct mystruct
Lesser Greater addresses addresses struct mystruct buffer cookie [ ][ ] Bottom of Top of heap heap |
通过重写 struct mystruct 的 cookie 成员,可以更改进程的执行流程,struct mystruct 位于保存 0x42424242 值(ASCII 字符集中的 BBBB)的缓冲区之后 。
清单 3. 重写 cookie 成员
$ ./example1 AAAAAAAAAAAAAAAABBBB Congratulations! You won a cookie! $ |
图 2 表示溢出后的堆分段中的 struct mystruct 及其成员。
图 2. 溢出后的 struct mystruct
Lesser Greater addresses addresses struct mystruct buffer cookie [AAAAAAAAAAAAAAAA][BBBB] Bottom of Top of heap heap |
在 64 位模式下重写局部变量
现在让我们讨论一下,如何通过在 64 位模式下重写局部变量更改给定进程的执行流程。在 C 语言中,在 32 位与 64 位模式之间只更改长型和指针数据类型。无论是处于 32 位模式下还是处于 64 位模式下,都应当使用长型变量执行所有指针算法。指针赋值只应在其他指针或长型变量之间执行。
以下示例容易出现基于堆的缓冲区溢出:
清单 4. example2.c(容易出现基于堆的缓冲区溢出)
#include <stdio.h> #include <stdlib.h> #include <string.h> struct mystruct { unsigned char buffer[16]; unsigned long cookie; }; int main(int argc, char **argv) { struct mystruct *s; if ((s = malloc(sizeof(struct mystruct))) == NULL) { perror("malloc"); exit(EXIT_FAILURE); } s->cookie = 0; if (argc > 1) strcpy(s->buffer, argv[1]); if (s->cookie == 0x4242424242424242) { printf("Congratulations! You won a cookie!\n"); exit(EXIT_SUCCESS); } printf("Hello world!\n"); exit(EXIT_SUCCESS); } |
通过重写 struct mystruct 的 cookie 成员,可以更改进程的执行流程,struct mystruct 位于值为 0x4242424242424242(ASCII 字符集中的 BBBBBBBB)的缓冲区之后。
清单 5. 重写 cookie 成员
$ gcc -Wall -m64 -o example2 example2.c $ ./example2 AAAAAAAAAAAAAAAABBBBBBBB Congratulations! You won a cookie! $ |
图 3 表示溢出后的堆分段中的 struct mystruct 及其成员。
图 3. 溢出后的 struct mystruct
Lesser Greater addresses addresses struct mystruct buffer cookie [AAAAAAAAAAAAAAAA][BBBBBBBB] Bottom of Top of heap heap |
结束语
我们在本文中仅仅触及了一些浅显的内容。在第 2 部分中,我将介绍如何重写函数指针并介绍组件组装和一些有趣的 shell、网络、套接字代码示例。(责任编辑:A6)