Enforcing Forward-Edge Control-Flow Integrity in GCC & LLVM

Introduction

This week I find that Codeblue CTF takes VTV as  a pwn challenge. So I decide to take a note on this paper presenting in USENIX 2014. And I will give a write-up on the pwn challenge some time later. In this paper, the author mainly proposes two CFI mechanism Virtual Table Verification (VTV) for gcc and Indirect Function Call Check (IFCC) for LLVM. Both CFI aim to verify the validity of indirect forward edge target.

VTV

Overview of VTV

To prevent attacks that hijack virtual calls through bogus vtables, VTV verifies the validity, at each call site, of the vtable pointer being used for the virtual call, before allowing the call to execute.

In particular, it verifies that the vtable pointer about to be used is correct for the call site, i.e., that it points either to the vtable for the static type of the object, or to a vtable for one of its descendant classes. VTV does this by rewriting the IR code for making the virtual call: a verification call is inserted after getting the vtable pointer value out of the object (ensuring the value cannot be attacked between its verification and its use) and before dereferencing the vtable pointer.

In addition to inserting verification calls at each call site, the compiler collects class hierarchy and vtable information during compilation, and uses it to generate function calls into libvtv, which will (at runtime) build the complete sets of valid vtable pointers for each polymorphic class in the program.

To keep track of static types of objects and to find sets of vtable pointers, VTV creates a special set of variables called vtable-map variables, one for each polymorphic class. At runtime, a vtable-map variable will point to the set of valid vtable pointers for its associated class. When VTV inserts a verification call, it passes in the appropriate vtable-map variable for the static type of the object, which points to the set to use for verification.

Vtable-map variables and vtable-pointer sets need to be read only to avoid introducing new vectors for attack. However, they must be writable when they are first initialized and whenever a dynamic library is loaded, since the dynamic library may need to add to the vtable-pointer sets. So, we need to be able to find all our data quickly. To keep track of the vtable-pointer sets, we wrote our own memory allocation scheme based on mmap. VTV uses this scheme when creating the sets.

20171119001

Some details in VTV

VTV encounter some false positive cases (verification process takes valid target as invalid) during its implementation. One case is when some files that define or extend classes are instrumented with vtable verification, and other files that define or extend part of the class hierarchy are not. A similar case is when libraries or plugins that pass objects in and out, if one is instrumented and the other is not.

The authors encountered this problem in ChromeOS with the Chrome browser. There are two third-party libraries which are built without VTV and are distributed to ChromeOS as stripped, obfuscated binaries.

To deal with the mixed-code problem in general, VTV was designed and written with a replaceable failure function. This function gets called if the verification function fails to find a vtable pointer in a valid set. To replace the default failure function, a programmer writes a replacement function, compiles it, and links it into the final binary.

IFCC

Overview of IFCC

Indirect Function-Call Checks (IFCC) is a CFI transformation implemented over LLVM 3.4. It operates on LLVM IR during link-time optimization (LTO). IFCC does not depend on the details of C++ or other high-level languages; instead, it protects indirect calls by generating jump tables for indirect-call targets and adding code at indirect-call sites to transform function pointers, ensuring that they point to a jump-table entry. Any function pointer that does not point into the appropriate table is considered a CFI violation and will be forced into the right table by IFCC. IFCC collects function-pointers into jump tables based on function-pointer sets, like VTV’s vtable-pointer sets, with one table per set.

IFCC forces all indirect-calls to go through its jump tables. This significantly reduces the set of possible indirectcall targets, and severely limits attacker options, preventing attacks that do not jump to a function entry point of the right type.

IFCC rewrites IR for functions that have their address taken; we call these address-taken functions. The main transformation replaces the address of each such function with the address of the corresponding entry in a jump table. Additionally, indirect calls are replaced with a sequence of instructions that use a mask and a base address to check the function pointer against the function-pointer set corresponding to the call site.

IFCC generates a new symbol @f_JT and defines it in the IR as an external function. It finds each instance where the program uses the address of @f and makes it use the address of @f_JT instead. It also creates a jump table of the form.

20171119002

Some details in IFCC

Like VTV, IFCC suffers from false positives due to external code. In particular, any function that was not defined or declared during link-time optimization will trigger a CFI violation. This can happen for several reasons. First, JIT code (like JavaScript engines) can generate functions dynamically. Second, some external functions (like dlsym or sigaction) can return external function pointers. Finally, some functions can be passed to external libraries and can be called there with external function pointers; this is common in graphics libraries like gtk.

To handle function pointers returned by external code, we added a fixed-size set of special functions to the beginning of each table. These functions perform indirect jumps through function pointers stored in an array. IFCC rewrites all calls to external functions (including dlsym) that return function pointers and inserts a function call that takes the pointer and writes it to the array if it is not already present.

To handle functions passed to external functions, IFCC must find all cases in which functions are passed to external code and must rewrite the functions to not test their function pointers against the jump tables generated by IFCC. We added a flag to the IFCC plugin that takes the name of a function to skip in rewriting.

 

Reference

[1]https://static.googleusercontent.com/media/research.google.com/en//pubs/archive/42808.pdf

[2]https://pdfs.semanticscholar.org/1f54/aae53621d7a84c55d79abd19d9f2f4df76a1.pdf

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.