ROP gadgets removal
As exposed in Is Less Really More? Why Reducing Code Reuse Gadget Counts via Software Debloating Doesn’t Necessarily Lead to Better Security by Michael D. Brown and Santosh Pande, removing ROP gadgets isn’t really a good metric to measure ROP resistance, if at all.
Nonetheless, Theo de Raadt said the following in 2017, about RETGUARD:
For those who know about polymorphism and pop/jmp or JOP, we believe once standard-RET is solved those concerns become easier to address seperately in the future. In any case a substantial reduction of gadgets is powerful.
This is likely why Todd Mortimer, the author of the aforementioned RETGUARD
, gave a
talk at EuroBSD 2018,
detailing his approach, which is more or less an implementation of a
subset/superset of the G-Free: Defeating Return-Oriented Programming through
Gadget-less
Binaries
paper from 2010.
On slide 13, the following is stated:
We don’t need to get to zero gadgets
❖ Just remove enough to make building useful ROP chains hard / impossible
❖ Use ROP tool output to measure progress
Unfortunately, since OpenBSD doesn’t implement PAX_MPROTECT
, an attacker usually needs only a couple of
gadgets to run the usual read; write; mprotect; jmp
payload, with a gadget to
dispatch syscalls, usually than a
dozen on Linux, and a bit more on Windows.
Misaligned ret
gadgets are removed via using two different means:
- Changing the register selection order in clang: this has no performances impact, and reduce the number of gadgets by 6%.
- Replace instructions that contain a
ret
by other ones (xchg A, B; mov B, α; xchg B, A
instead ofmov A, α
.): negligible performance overhead, reduce the number of uniques gadgets by 5% according to Mortimer’s slides (slide 28) in 2018, but by 60% in 2019 (slide 51).
In February 2019,
this mitigation was improved, by prefixing gadgets with a ret
instruction
in their immediates or in their encoding, by a TRAPSLED (jmp A; int3; int3; … int3; A:
) to prevent usage of misaligned gadgets.
This reduce the number of gadgets of 11%, leaving around 66,750 remaining one.
A quick look at Exodus Intel’s reports, metasploit modules, Project Zero blog posts or exploits, spender’s exploits, phoenhex' writeups, ZDI’s blogposts, or even at exploit-db highlight that not a single exploit would have been made significantly harder to write, or less stable, should the number of gadgets be reduced by 11%.
But Theo de Raadt was convinced, in August 2018, that this was still a good idea:
In any case a substantial reduction of gadgets is powerful.
On arm64, since there are no misaligned instructions (except in
Thumb and
Thumb2
mode of course), every ret
instruction is covered by RETGUARD,
but fear not, there are still plenty of JOP gadgets:
$ ROPgadget --binary ~/bsd --filter brk | grep Unique
Unique gadgets found: 12891
$
Speaking of misaligned instructions, a better way to reduce this number would be to make use of the unaligned access performance counter and make it trap with a threshold of one: OpenBSD controls its toolchain entirely, so there shouldn’t be any unaligned jumps. It would reduces the number of unaligned gadgets by somewhere between 0/8 and 7/8, and be way cheaper on every level.
There are of course more
instructions/gadgets
that aren’t covered by RETGUARD
.
GCC used to have --mmitigate-rop
,
but it was removed because:
This option is fairly ineffective, and in the light of CET, nobody seems interested to improve it. Deprecate the option, so it won’t lure developers to the land of false security.
The ROP tools used to find gadgets are ropper,
ROPGadget and pwntools, which is ok-ish, except that a metric used in the slides is
to ask ROPGadget.py to build a complete ropchain, and since it fails to do so
on a kernel with less rop gadgets, the mitigation is effective. This
affirmation is, given how
basic
ROPGadget’s heuristics are, pretty amusing. Moreover, what would be the point
of building an execve
ROP-chain in kernel-land‽
Even if they changed their goal, with the objective of removing every single ROP gadget, and they magically achieved it, there would still be JOP, (P)COP, COOP, … that aren’t significantly harder to use instead.
In 2023, De Raadt sent a snarky email
on openbsd-tech, about OpenSSL having 0xC3
in some assembly routines:
Far be it from me to suggest that the security experts over there in OpenSSL land are unaware of modern exploitation methods! Very far from that, very very far.
So apparently Joan Daemen and Vincent Rijmen
are playing the long game with shellcode/gadgets in s-boxes!
Albeit to be fair, those s-boxes should be in the data-segment, but it’s hardly
security-relevant. Moreover, OpenBSD also has/had
a bunch of data in .text
segment, so nothing to brag about.
Anyway, removing ROP gadgets the way OpenBSD is doing it doesn’t add a large amount of complexity, doesn’t harm performances nor debuggability, so why not, but it doesn’t make exploitation significantly harder, at all.