
Return to abort() – Using code introspection to prevent stack-smashing - cjd
https://github.com/cjdelisle/return-to-abort
======
ebeip90
This isn't very useful, and has some pretty obvious design flaws.

Given any routine which ends with the very common pattern:

    
    
        return foo();
    

Which is assembled to the very common sequence:

    
    
        call foo
        leave
        ret
    

This project will instrument it to now look like:

    
    
        call foo
        jmp $+2
        .byte DE, AD
        leave
        ret
    

Whatever return address I hijack, I can now just point it at this __valid
__return site, and begin my ROP stack as per normal.

What's especially great is that the project _guarantees_ this pattern for us.
Now, every function has a path that looks like:

    
    
        call __stack_chk_fail
        jmp $+2
        .byte DE, AD
        < function frame cleanup >
        ret
    

This is effectively a no-op for security.

I cleaned up the author's code, added a sane makefile, and an example exploit
here: [https://github.com/zachriggle/return-to-
abort](https://github.com/zachriggle/return-to-abort)

(Pull Request: [https://github.com/cjdelisle/return-to-
abort/pull/1](https://github.com/cjdelisle/return-to-abort/pull/1))

------
tptacek
If you get to instrument codegen to insert countermeasures, you can do more
interesting things than this; for instance, you can do return address
protection and explicitly cookie functions and their returns.

Also, check out FSan:

[http://www.pcc.me.uk/~peter/acad/usenix14.pdf](http://www.pcc.me.uk/~peter/acad/usenix14.pdf)

------
comex
For a similar approach, look at grsecurity’s RAP:

[https://pax.grsecurity.net/docs/PaXTeam-H2HC15-RAP-RIP-
ROP.p...](https://pax.grsecurity.net/docs/PaXTeam-H2HC15-RAP-RIP-ROP.pdf)

(Not that you’ll be able to actually use RAP, as they’re only releasing it to
commercial customers, but the description of it is worth checking out.)

------
loeg
So this limits the number of gadget options you have for ROP, but doesn't
eliminate ROP entirely, right? It maybe increases the difficulty of ROP, if
you can't find enough/sufficient gadgets that happen to start after function
call sites. Not a silver bullet, anyway.

~~~
cjd
Well, it means that modulo hash collisions, a function can only return to one
of the places which calls that function, so in the really tragic case (for
example) that someone called a vulnerable function and then immediately after
called system() with a stack variable as the arg, the attacker can just return
there and make the arg point to "bash". But in general the whole business of
knitting together assembly instructions in executable memory would pretty much
be gone. Edit: typo, clarity

~~~
loeg
Is it really limited to only call sites of that function, or to all call
sites? I can't tell if their return cookie is shared throughout the binary or
unique to callees.

~~~
cjd
One approach is to assign a random 2 byte number to each function and all
callers to that function must follow the call with those 2 bytes (with a jmp 2
so it doesn't try to execute them). Unfortunately this would require the
linker to get involved because we're not going to know these cookies at
compile time.

Another approach is to take a hash of the types of the args and the return
value (pointers obviously being opaque). This way we know the cookie value for
any given function at compile time and we can stay out of the linker. However,
in this case function a(int, char) can return to the call sight of function
b(int, char) because to the code they're identical.

~~~
Ded7xSEoPKYNsDd
The problem with per-function cookies are dynamic calls. The only feasible
options I can think of is are either a) a secondary cookie that is allowed
from all functions or b) a shadow stack with the cookies.

------
smegel
Why isn't the return address stored in a register instead of on the stack to
avoid this in the first place?

~~~
mnem
It has to get put on the stack at some point so you can call more than 1
function deep. So why not always put it on the stack so that you don't waste a
valuable register?

~~~
pm215
The answer to "why not always put it on the stack" is "because a lot of
functions are leaf functions and so always writing it to the stack is making
every function pay the memory access hit rather than just the ones that need
it". RISC-ish architectures tend to have enough registers that dedicating one
to a link pointer isn't a big deal (and once you do spill it to the stack you
can use the link register as a temporary register anyway).

Some very early CPU architectures didn't actually support either putting the
return address in a register or on the stack. For instance, on the PDP-8
([https://en.wikipedia.org/wiki/PDP-8#Subroutines](https://en.wikipedia.org/wiki/PDP-8#Subroutines))
the JMS instruction writes the return address to the first word of the
subroutine it's about to call (and the actual subroutine entry point is just
after that), which meant it didn't conveniently support recursion. It wasn't
alone in that either -- I think that it just wasn't quite appreciated how
important recursion/reentrancy was back in the early 60s when these ISAs were
designed.

~~~
tptacek
Sure, and SPARC has register windows, but also still has control-flow
integrity attacks; overflows are just as bad there.

