[llvm-dev] [RFC] Zeroing Caller Saved Regs

Stephen Checkoway via llvm-dev llvm-dev at lists.llvm.org
Wed Aug 12 21:28:57 PDT 2020

> On Aug 13, 2020, at 00:01, Bill Wendling <isanbard at gmail.com> wrote:
> On Wed, Aug 12, 2020 at 8:38 PM Stephen Checkoway <s at pahtak.org> wrote:
>>> On Aug 12, 2020, at 17:44, Bill Wendling via llvm-dev <llvm-dev at lists.llvm.org> wrote:
>>> My guess is that inserting zeroing instructions right before the "ret"
>>> instruction can disable some of the hacks we see with ROP:
>>>  `pop rdi ; ret` becomes `pop rdi ; xor rdi, rdi ; ret`
>> Three comments on this.
>> 1. The very first ROP paper [1] used only unintended instruction sequences. That is, none of the return instructions were placed there by the compiler, they appeared completely within other instructions.
>> 2. ROP doesn't require any return instructions [2]. It can be performed using call or jmp instructions.
> Sure, but the authors of the paper claim that it's incredibly
> difficult to have *only* COP / JOP gadgets. At some point you'll need
> to have an ROP gadget:
> "Usually, the gadgets of ROP end with a return instruction which we
> called conventional ROP attacks. Call Oriented Programming (COP) [8]
> and Jump-Oriented Programming (JOP) [9] are the variations of ROP
> attacks without returns [10]. The variations use gadgets that end with
> indirect call or jump instruction. However, performing ROP attacks
> without return instruction in reality is difficult for the reason that
> the gadgets of COP and JOP that can form a completed gadget chain are
> almost nonexistent. Actually, adversaries prefer to use combinational
> gadgets to evade current protection mechanisms."

That's not entirely wrong, but also not entirely correct. The key insight that makes return-oriented programming without returns work is that you only need a single "update-load-branch" sequence. If you can get the address of that into a register, then any sequence of instructions ending in an indirect jump through that register is usable. And it turns out there are plenty of those.

So the hard part is finding that one update-load-branch sequence. In my paper, I used pop edx; jmp edx and pointed out it can be done using other instructions like call (which is exactly what I believe the COP paper later did). I didn't find any pop/jmp sequences in libc, but they did exist in large libraries at the time like libxul and libphp5. Of course, my work was all on 32-bit x86 years ago. Modern x86-64 codegen may make finding them easier or harder, I haven't looked.

>> 3. As binaries get larger, the number of available instruction sequences from which one can build gadgets increases dramatically. If the goal is to make one system call like mprotect, you don't need very many at all. If want to get arbitrary computation using ROP and something like mprotect doesn't exist (e.g., on a Harvard architecture machine), you only need a few tens of kilobytes of code. I did it on the Z80 with 16 kB of code with a hardware interlock that forced instructions to be fetched from ROM [3].
>> There have been a bunch of defenses that purport to make attacks harder by decreasing the number of useful instruction sequences available to the attacker. They don't have a significant impact on attacks.
>> That's not to say that this couldn't be useful, but I'm skeptical it would defend against ROP, or even make a ROP attack much more difficult.
> This is why having variable length instructions sucks. :-)

One of several reasons. :) Alas, ROP works on fixed-length instructions too.

> I see your point. I was actually looking at the code we generate with
> the pop/xor if you start at different offsets in the code when your
> email came in.

There's a simple recursive algorithm using a trie in Shacham's original ROP paper to find all such sequences. I believe ROPgadget implemented it or something similar at some point [1].

1. https://github.com/JonathanSalwan/ROPgadget



Stephen Checkoway

More information about the llvm-dev mailing list