Starting in clang 3.7 they've introduced a new argument -fsanitize=cfi
which aims to protect indirect calls from overwrites.
All the code and binaries I used can be downloaded here
First, I thought I would look at how CFI applied to simple C structs with function pointers. After fighting with the compiler to get it to stop optimizing my code, (i.e. call <puts>
instead of call rcx
because clang
realized that rcx
was always going to be puts(3)
) I got it calling things from memory. However, there was no CFI protection on the call. I played around with this a bit (interestingly, clang
will optimize use of un-initialized memory to the ud2
instruction) but was unable to get any CFI protection in place.
Reading a bit of the clang manual, it talked a lot about C++ virtual
methods, so I thought I would look at those. I also played around with non virtual
calls, those were replaced with static calls as you'd expect and were fairly uninteresting. I wrote some C++ for the second time in a decade:
class obj {
public:
virtual void fp() { printf("[-] ?\n"); }
};
class thing : public obj {
public:
void fp() { printf("[-] :(\n"); }
};
// ...
thing *t;
t = new thing;
printf("[+] t @ %llx\n", t); // This is important, not sure why yet
t->fp();
This generates the following assembly:
mov rdi,QWORD PTR [rsp+0x8]
mov rax,QWORD PTR [rdi]
lea rcx,[rip+0x1eb4] # 2cc0 <_ZTV5thing+0x10>
cmp rax,rcx
jne e34 <main+0x244>
call QWORD PTR [rax]
# ...
ud2 # 0xe34 <main+0x244>
To understand what's going on here, here's a brief (slightly simplified) diagram of how C++ objects are commonly laid out in memory:
Heap or Stack: Heap: .data.rel.ro.local: .text:
thing object thing vtable printf
ptr to obj -> +0x0: ptr to vtable -> +0x0: fp -> [printf code]
Normally when exploiting vtables you aim for one of the first two locations in memory (.text and .data.rel.ro.local are read-only). Either we want to overwrite the pointer to the object (i.e. type confusion), the vtable pointer with a pointer to our own malicious vtable. So, how does the above assembly protect these objects?
In the assembly above [rsp+0x8]
is our stack based pointer to the object. We dereference that to get the object itself in rdi
. We then dereference rdi
to the address of the vtable, which we compare against a known offset from rip
. This is basically checking the first and second link in the above chain. If this check fails, we execute a ud2
instruction, which will crash the program.
A friend helpfully pointed out my examples were too trivial, and indeed after making things slightly more complex I found some different behavior. Let's just add a function to take advantage of the polymorphism in our objects.
void __attribute__ ((noinline)) doit(obj *o) {
o->fp();
}
// ...
obj *;
thing *t;
o = new obj;
t = new thing;
doit(o);
doit(t);
In the prior example the compiler knew the type of every object at the time we're accessing it's function pointers. However, in this case there are two valid vtables for the given object (obj
's and thing
's). The assembly now looks like this:
mov rax,QWORD PTR [rdi] # t
lea rcx,[rip+0x1382] # 1cf0 <_ZTV5thing+0x10>
mov rdx,rax
sub rdx,rcx
rol rdx,0x3b
cmp rdx,0x2
jae 982 <_Z4doitP3obj+0x22>
call QWORD PTR [rax]
# ...
ud2 # 0x982 <_Z4doitP3obj+0x22>
After playing around with what I believe is happening is the vtables are being laid out in memory such that:
+0xce0 <_ZTV5thing>: 0x0000000000000000 0x0000555555555d40
+0xcf0 <_ZTV5thing+16>: 0x00005555555549f0 0x0000000000000000
+0xd00 <_ZTV3obj>: 0x0000000000000000 0x0000555555555d30
+0xd10 <_ZTV3obj+16>: 0x0000555555554a10 0x0000000000000000
(vtables at +16)
By putting the pointers within a fixed, known-at-compile-time range from leaves of the object hierarchy, CFI can check that the vtables being passed are one of the expected, allowed options. This is somewhat corroborated by the fact that if we add object types to our hierarchy, the guards change. Specifically, the constant 0x2
in cmp rdx,0x2
gets incremented with every object we add. This means that presuming you can overflow the vtable pointer (the second link in the diagram above) you should only be able to redirect it to another vtable within the object hierarchy (and same with type confusion). I haven't played with it enough to see what happens if we have huge vtables or more complicated object hierarchies.
-fsanitize=cfi
does at least two things currently. First, it protects the initial object pointers and the object -> vtable pointers with a guard. In the case of where we have multiple types in a function, it restricts the vtables to one within the object hierarchy (and possibly a subset of that?). In the case of leaf objects, it restricts it to the specific vtable.