Bypassing Pointer Guard in Linux's glibc
Dr Silvio Cesare
SummaryPointer guard is an exploit mitigation in glibc that applies to stored pointers and especially stored function pointers. A number of library calls can register function pointers that get executed later on. An example of this is registering an exit handler with atexit(). Stored function pointers are scrambled or mangled by XORing them with a secret in the thread data (fs:0x30) and applying a bitwise rotation. This mitigates control-flow hijacking by an attacker who would otherwise be able to overwrite the stored function pointer with a location of their choosing. In this blog post, I'll present a bypass for pointer guard in multithreaded applications where an attacker knows the libc base address and has an arbitrary read.
IntroductionPointer guard is documented in glibc reference materials https://sourceware.org/glibc/wiki/PointerEncryption. The mitigation provides a set of macros that mangle and demangle pointers. The API to use is PTR_MANGLE and PTR_DEMANGLE. For example, if an application wants to store a function pointer in *stored_ptr, they could use the following:
*stored_ptr = PTR_MANGLE(ptr)
And to demangle it:
ptr = PTR_DEMANGLE(*stored_ptr);
The pointer mangling works by XORing the pointer with an internal 64-bit secret, then performing a bitwise left rotatation of 0x11 bits (on x86-64). Demangling is the reverse.
Related WorkAfter I tweeted the requirements for this attack, I was linked to http://binholic.blogspot.com/2017/05/notes-on-abusing-exit-handlers.html. This is similar attack to the one I present with some specific differences. Interested readers are advised to review it.
The AttackThe attack is essentially a known-plaintext attack against the mangling operation. If we know the original pointer and its mangled version, we can recover the 64-bit secret.
How do we get known plaintexts? The related work linked earlier shows 1 way to identify known plaintext. I will present another approach.
Let's grep -rw PTR_MANGLE glibc/ --include '*.c' and examine each reference. I can quickly see an interesting use:
In thread initialization, we can see a function pointer table at a fixed address (__libc_pthread_functions).
If we examine what the first entry of this function pointer table, we can see that it points to __pthread_attr_destroy.
This is enough to defeat pointer guard if we know the library base from an ASLR leak. This is shown in the following pseudo code.
x = __libc_pthread_functions;
secret = rotr64(x, 0x11) ^ &__pthread_attr_destroy;
There is something else we can try. Is there a possibility that there is a mangled function pointer where the function pointer is equal to 0 or perhaps -1 or another fixed constant?
I write some test code to recover the cookie in a multithreaded application, and then i take the results of:
In GDB using the GEF debugging plugin, I use pattern-search to find any such memory in the address space that has stored one of these mangled pointers with known plaintexts (pointers).
I find one.
__libc_pthread_functions in my particular application has a mangled NULL pointer.
To defeat pointer guard then after program initialization, given the address of __libc_pthread_functions, is:
secret = rotr64(__libc_pthread_functions, 0x11);
From this point, an attacker can safely and correctly mangle their own pointers.