标签: DFI
Abstract
用于防止内存错误
WIT在编译时刻用过程间指向性分析[23]计算CFG和程序中可能被每个指令写的一系列objects。然后装备代码防止不在静态分析所得到的集合里的指令修改objects,并且保证间接控制跳转符合CFG。为了提升精确度,WIT在原始的程序objects间插入小的guards。我们描述了一个实现,降低了空间和时间损耗,并且可以实用因为它不用进行修改的编译C和C++程序,没有误报。平均时间损耗7%(CPU密集型benchmarks)。
WIT使用指向性分析对每一个object和写操作赋一个color,所有可以被一条指令进行写操作的objects具有相同的color,装备代码在运行时记录 object colors并且检查指令写到正确的color。存储单元的color记录在一个color表中,当objects被分配和释放时color表会相应更新。写检查在表中查阅正在被写的存储单元的color并且检查是否和写指令的color一致。如此确保了写完整性。
WIT也会给间接跳转指令和可能间接跳转的函数的入口点一个color,由此所有可能被同一条指令call的函数有相同的color。WIT装备代码在color表中记录函数colors并且检查间接跳转指令。间接跳转在表中查找目标地址的color,并且检查是否与间接跳转指令的color匹配。这些检查和写检查确保了CFI。CFI防止攻击者绕过我们的检查并且提供了第二道防线来防御写检查没有探测到的攻击。
优化:
1. 静态分析找到不违反写完整性的访问和只有安全访问的objects,我们只装备不安全的写操作,并且对所有安全的objects赋给同样的颜色。这会减少写检查的数目和color表的损耗。降低了需要用来表示color的bit数。我们所有的实验中,一个字节足够表示颜色。
2. color表使用紧凑表示,可以有效率的查找。color表用一个字节表示一个8字节的存储,降低了12.5%的空间损耗。
3. Third, we reduce the cost of updating color table entries on function calls. Since most local variables are safe, we only update entries for guards and unsafe variables on function entry and we reset these entries to the color of safe objects on function exit.
2. Overview
WIT有compile-time和runtime两个部件。
// Example vulnerable code: simplified Web server with a buffer overflow vulnerability.
1: char cgiCommand[1024];
2: char cgiDir[1024];
3:
4: void ProcessCGIRequest(char* msg, int sz) {
5: int i=0;
6: while (i < sz) {
7: cgiCommand[i] = msg[i];
8: i++;
9: }
10:
11: ExecuteRequest(cgiDir, cgiCommand);
12: }
lines 5-9 存在缓冲区溢出漏洞,如果message过长,攻击者可以重写cgiDir(里面是CGI commond调用的可执行文件的路径)来运行任意可执行程序(例如shell)。这是一个non-control-data attack,不违反CFI。
使用指向性分析[23]计算程序中每条指令可能修改的obiects,上例中,分析计算出 the set {i} for the instructions at lines 5 and 8, and the set {cgiCommand} for the instruction at line 7.
为了降低运行时的损耗,提出写安全检查来计算安全的指令和objects。不违反写完整性的指令为安全指令,如果所有的能修改object的指令都是安全的,那么这个object是安全的。上例中, instructions 5 and 8 are safe because they can only modify i and, therefore, i is safe. ProcessCGIRequest的参数也是安全的。instruction 7 is not safe because it may modify objects other than cgiCommand depending on i’s value.
对所有的安全指令和objects的color设为0。上例中, 变量 msg, sz, and i and instructions 5 and 8 are assigned color 0 because they are safe. We assign color 3 to variable cgiCommand and instruction 7, and color 4 to variable cgiDir.
为了降低漏报率(由于指向性分析的不精确导致),在原始程序的不安全的objects之间插入小得guards。 Guard objects have color 0 or 1。这些color永远不能赋给不安全的指令以确保WIT能检测到重写guards或者安全的objects。
函数的color集合与objects,guards的color集合不相交。这可以防止不安全的指令重写代码并且防止代码区域外的控制转移。
WIT加了额外的编译阶段来装备以实现写完整性和控制流完整性。有四种类型的instrumention:to insert guards, to maintain the color table, to check writes, and to check indirect calls. Guards 8个字节大小. 上例中, add guards just before cgiCommand, between cgiCommand and cgiDir, and just after cgiDir。
当一个object被分配时,把它存储位置的color设为object的color。上例中, WIT adds instrumentation at the beginning of main to set the color of the storage locations occupied by cgiCommand to 3, the color of the storage for cgiDir to 4, and the color of the storage for the guards around them to 0。
为了降低更新color表的损耗,初始化所有存储单元的color表为0,当安全的objects被分配时不更新color表。Instead, we only update the colors for locations corresponding to unsafe objects on function entry. On function exit, we reset color table entries that we updated on function entry to 0. Therefore, there is no instrumentation to update the color table on function entry or exit for ProcessCGIRequest.
上例中, WIT adds write checks only before instruction 7 to check if the location being written has color 3. It does not add write checks before lines 5 and 8 because these instructions are safe。
WIT的防御依赖于指向性分析的精度,比如两个相同color的object可以相互赋值。
WIT可以防御上述例子。The write check before line 7 fails and raises an exception if an attacker attempts to overflow cgiCommand. When i is 1024, the color of the location being written is 0 (which is the color of the guard) rather than 3 (which is the color of cgiCommand). Even without guards, WIT would be able to detect this attack because the colors of cgiCommand and cgiDir are different。
3. Static analysis
We implemented the points-to and the write safety analysis using the Phoenix compiler framework [30]. These analysis operate on Phoenix’s medium level intermediate representation (MIR), which enables them to be applied to different languages and target architectures。
i = ASSIGN 0
$L6: t273 = COMPARE(LT) _i, _sz
CONDITIONALBRANCH(True) t273, $L8, $L7
$L8: t278 = ADD _msg, _i
t276 = ADD &_cgiCommand, _i
[t276] = ASSIGN [t278]
_i = ADD _i, 1
GOTO $L6
$L7: CALL &_ExecuteRequest,&_cgiDir,&_cgiCommand
Figure 2. Example vulnerable code in mediumlevel intermediate representation (MIR).
We use an inter-procedural points-to analysis due to Andersen [8] that is flow and context insensitive but scales to large programs. It computes a points-to set for each pointer,which is the set of logical objects the pointer may refer to. The logical objects are local and global variables and dynamically allocated objects (for example, allocated with malloc). We use a single logical object to represent all objects that are dynamically allocated at the same point in the program but we do cloning of simple allocation wrappers to improve analysis precision. Our implementation is similar to the one described in [23] but it is field-insensitive rather
than field-based (i.e., it does not distinguish between the different fields in a structure, union, or class). We use Phoenix to compile each source file to MIR and write points-to constraints to a file. The analysis reads the constraints file, computes the points-to sets, and stores them in a file.
In addition, the write safety analysis runs a simple intra-procedural pointer-range analysis to compute writes through pointers that are always in bounds. The instructions that perform these writes are marked safe.
While making the global pass over all source files to collect constraints for the points-to analysis, we also run the write safety analysis. We write unsafe pointers to a file. A pointer is unsafe if it is dereferenced for writing by an unsafe instruction.
We use an iterative process to compute color sets, which include objects and unsafe pointer dereferences that must be assigned the same color because they may alias each other.
WIT uses a similar algorithm to assign colors to functions that may be called indirectly.
4. Instrumentation
4.1 Color table
We implemented WIT for 32-bit x86 machines running Windows.We used several Phoenix plugins [30] to generate WIT’s instrumentation.
WIT maintains a color table that maps memory addresses to colors. The color table must cover the whole user virtual address space and it is accessed often by write and indirect call checks.
To keep the color table small, we divide the virtual memory of the instrumented program into aligned eight-byte slots. The color table is implemented as an array with an eight-bit color identifier for each of these slots.Therefore,it introduces a space overhead of only 12.5%.
We are able to record a single color for each eight-byte slot because we generate code such that no two objects with distinct colors ever share the same slot. It is easy to enforce this requirement for heap objects because they are eight-byte aligned and for functions because they are 16-byte aligned. But since the stack and data sections are only four-byte aligned in 32-bit x86 architectures,we cannot currently force eight byte alignment of objects in these sections without introducing runtime overhead.
Instead,we force unsafe objects and guard objects in the stack and data sections to be four-byte aligned and we insert a four-byte aligned pad after unsafe objects. For an unsafe object of size s, the pad is eight-bytes long if �s/4� is even and four-bytes long if �s/4� is odd. We set �s/8� color table entries to the color of the unsafe object when the pad is four-bytes long and �s/8�+1 when the pad is eight-bytes long. We should be able to reduce the space overhead when targeting 64-bit x86 architectures because the stack and data sections are eight-byte aligned in these architectures.
Since our points-to analysis does not distinguish between different fields in objects and between different elements in arrays,we always assign the same color to all the elements of an array and to all the fields of an object. Therefore, it is not necessary to change the layout of arrays and objects,which is important for backwards compatibility.
We only require eight bits to represent colors because the write safety analysis is very effective at reducing the number of objects that we must assign colors to. However,it is possible that more bits will be required to represent colors in very large programs. If this ever happens, there are several things we can do. For example, we can increase the size of color table entries to 16-bits and increase memory slot sizes to 16-bytes, or use 8-bit color identifiers at the expense of worse coverage.
The color table can be accessed efficiently. Since there are 2 GB of virtual address space available for the user in Windows XP and Windows Vista, we allocate 256 MB of virtual address space for the color table 2. We rely on the operating system to allocate physical pages for the color table on demand when they are first accessed. The base of the color table is currently at address 40000000h. So to compute the address of the color table entry for a storage location,we take the address of the storage location, shift it right by three, and add 40000000h.
To protect the color table from being overwritten by an attacker, we read-protect the pages in the table that contain the entries for the virtual address range occupied by the table itself. With the base of the table at 40000000h,we protect the pages in the address range 48000000h to 4A000000h (color table原地址:256MB,为40000000h-50000000h)to prevent reads and writes. Since we add checks before unsafe writes and control-flow integrity ensures that the attacker cannot bypass these checks, the attacker cannot overwrite the color table because the write check would trigger a read fault on the protected address range. This technique was first described in [44].
4.2 Inserting guards
The guards are eight-bytes long to match the size of the slots that we record colors for in the color table. The instrumentation to insert these guards is different for the stack, heap, and global data sections.
To insert guards in the stack, we replace the compiler phase that lays out local variables in a stack frame by our implementation. We segregate safe local variables from unsafe ones to reduce the space overhead. First, we allocate contiguous storage for the safe local variables. Then we allocate storage for the guards, pads, and unsafe local variables. This allows us to insert only n+1 guards and pads for n unsafe local variables: the guard that prevents overflows of a variable prevents underflows of the next variable.
In the rare case where a function argument is written by an unsafe instruction, we cannot easily insert guards and pads around it. Therefore, we copy the argument to a local variable and rewrite the instructions to refer to the copy.This local variable is marked unsafe and we insert guards and pads around it.
We mark all heap-allocated objects as unsafe but we do not insert pads or guards around them. The standard heap allocator in Windows Vista, Windows XP SP2, and Windows 2003 inserts an eight-byte header before each allocated object. We use this header as a guard by simply setting its color to 1 in the color table.
We add guards and pads between all variables in the .data section and .bss sections but not in the read-only data section (.rdata).
We plan to implement an optimization that avoids the need for most guards by laying out stack and global objects such that adjacent objects have different colors.