6

G-Free: defeating return-oriented programming through gadget-less binaries

This paper describes what seems like a really cool technique to prevent ROP attacks if the source is availible. They use an assembly preprocessor getween gcc and the assembler to remove or protect all possible free branches (returns and indirect jumps). They claim to have <3% (1% average) speed/performance impact.

They modify instructions or add no-ops to change alignment so that unintended/unaligned branch instructions are eliminated. To protect legitimate returns and jumps, they encrypt the return address with a random runtime key in the function prologue and decrypt it by XORing it just before the return. This is supposed to prevent the return satement from executing successfully unless the entry point was at the beginning of the function itself.

My question is how will return address encryption prevent ROP gadgets from being used? The key/cookie is stored on the stack just above the return address, so what is to prevent the attacker from modifying the gadget chain on the stack to also include a key 0x00000000 after each return address?

The paper seems to have got 73 citations and no one has mentioned anything like this as far as I can see so I must be missing something. Can someone throw some light on how the protection code for legitimate jumps and returns work?

(I know that this is a long question requiring the reading of a long paper, but I hope that at the very least whoever reads this finds the paper as cool as I did)


Update

This is apparently the same (or very similar) technique used in StackGuard and ProPolice. Can anybody tell me how they fare against memory disclosure vulnerabilities? I can't seem to figure out for sure. What I do find either mentions them in passing or just trumpets their virtues.

4

Return address encryption prevents ROP gadgets from being used because the attacker won't be able to predict the value of the key, so the attacker won't know what to write on the stack to cause the return instruction at the end of one gadget to transfer control to the next gadget.

Remember how ROP attacks work. The attack involves executing a sequence of ROP gadgets. The attacker has to arrange memory so that these gadgets will be executed in the desired sequence. In a ROP attack, the attacker does this by overwriting the stack to contain a bunch of fake stack frames, where each stack frame contains whatever stack data will be needed/expected by a single gadget. Each ROP gadget ends in a RET (return) instruction, so the corresponding fake stack frame contains a saved return address that will be used by the RET instruction: when you execute the gadget, when it gets to the RET instruction, the RET instruction will pop off the "return address" on the stack and jump to it. So, the attacker arranges the contents of the stack so it has one fake "return address" for each gadget, and the fake "return address" has a value that is the address of the first instruction of the next gadget to execute.

In a standard ROP attack, this works, because the attacker knows the address of each gadget and can write those addresses onto the stack.

With G-Free's return address encryption, this attack no longer works. The attacker can't store the address of the gadget onto the stack; instead, the attacker needs to store an encrypted return address (namely, the encryption of the address of the next gadget). Without knowing the encryption key, the attacker won't know what to write.

So, without knowing the encryption key, ROP attacks don't work, because the attacker can't chain the gadgets together.

P.S. I agree with you. G-Free is indeed awesome. I wish I had a compiler flag that let me compile a program with G-Free; it is a very cool defense, with modest performance impact.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.