Eyes Above The Waves

Robert O'Callahan. Christian. Repatriate Kiwi. Hacker.

Wednesday 27 March 2013

Mitigating Control-Flow Exploits With x86 ISA Extensions

A lot of exploit styles (e.g. "return-oriented programming") rely on jumping or calling into code that was never meant to be executed "stand-alone" --- i.e., jumping to an instruction that was only ever supposed to be executed by falling through from the previous instruction or via a branch within its function. On x86 this includes destination "instructions" that are actually part of another multi-byte instruction. It seems to me these exploits could be made much harder by somehow marking the start of "valid" branch/call/return targets and faulting when control is transferred to other instructions inappropriately.

Here's a more specific proposal for x86:

  • Add a new flag bit for code pages to enable "destination checking" on a per-page basis.
  • An indirect control transfer is any return instruction that pops EIP off the stack, or any jump or call instruction whose operand is not a constant. When an indirect control transfer jumps to a page marked for destination checking, and the instruction at EIP is not 0x90 (NOP), fault.
  • A toolchain would take advantage of this feature by marking code pages for destination checking, avoiding use of 0x90 for regular NOPs, and placing a 0x90 at every function entry point and after every call instruction. For bonus points, avoid generating 0x90 bytes inside other instructions.

Obviously there are a lot of ways to tweak this. For example "PUSH EBP" is very often the first instruction of a function, so you could whitelist that instruction as a valid function entry point. You could avoid having to place a NOP after a direct CALL instruction by checking if the instruction at EIP-5 is 0x9A (direct CALL). You could make false returns even harder by extending that special case to require the address we're returning from to be "close to" the destination of the direct CALL instruction.

This is obvious enough that I presume someone's worked on it already.


Josh Cogliati
Interesting idea. I think that for maximum security it would be worth it to be able to specify the list of valid come-from addresses. For many functions this would be a fairly small list. I do wonder, could you get the same amount of security from having separate data and call return stacks? In that case, data could only ever overwrite data. I personally think for high security applications (chemical plant control system for example), it might make sense to only use Harvard architecture ( http://en.wikipedia.org/wiki/Harvard_architecture ) where the instructions are stored in EPROM and make the architecture so there is no programatic way to ever get an address into the stack from ram or instructions from ram. Sometimes you do not want a universal Turing machine.
Unfortunately, x86 instructions are variable length, have no alignment constraints, and contain immediate values, which means no matter what byte sequence you choose for your landing pad instruction, somebody will be able to find it inside other instructions.
Josh Cogliati
Anonymous, that does mean that a single byte landing sequence can't be used very well, but a four byte sequence has a fairly good chance (since there are 2^32 different combinations) of being unique and only used as the landing sequence.
Evan Jones
The intent reminds me of Google Native Client's sandbox, which can verify that the code does not jump or reference data outside of a given section of memory. Their verifier requires that jmp targets be static, and be aligned on a 32-byte boundary which is part of ensuring that the code can't escape the sandbox. http://www.stanford.edu/class/cs240/readings/nacl_paper.pdf