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 (2019) by Michael D. Brown and Santosh Pande, removing ROP gadgets isn’t really a good metric to measure ROP resistance, if at all. The is corroborated by ROP Gadget Prevalence and Survival under Compiler-based Binary Diversification Schemes (2016):
We show that a small population of diversified variants is sufficient to eliminate 90-99% of ROP gadgets across a collection of real-world applications. Finally, we observe that the number of remaining gadgets may still be sufficient for an attacker to mount an effective attack regardless of the presence of software diversity.
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
retby other ones (xchg A, B; mov B, α; xchg B, Ainstead ofmov A, α.): negligible performance overhead, reduce the number of unique gadgets by 5% according to Mortimer’s slides (slide 28) in 2018, but by 60% in 2019 (slide 51).
Interestingly, in 2026, Jenna Esposito, Aaila Arif, Raul Cortinas, and Brian Robert Callahan, from the Department of Computer Science and Software Engineering from Monmouth University published A Final Return for OpenBSD Anti-Return- Oriented Programming Mitigations as well as Porting and Evaluating Return-Oriented Programming Defenses Implemented by the OpenBSD Operating System. In those papers, they replicated Mortimer’s clang-based gadget removal implementation to FreeBSD, and found some discrepancies:
- Changing the register selection order has noticeable binary size impact, about 0.5% increase, and remove only around 0.5% unique gadget reduction, not 6% as claimed by Mortimer.
- The instruction replacement pass only reduces unique gadgets by around 3.5%, nowhere near 60% or even the originally claimed 5%.
- Amusingly, these two techniques are not simply additive, a case was found that when they were combined, the results worsened.
- The instruction replacement pass doesn’t have a “negligible” performance impact, as when ported from LLVM to GCC, it added a 3% runtime performance.
out that the amount of unique kernel gadgets was reduced by less than 1% (and not 6%) with a binary size increase between 1% and 2%, higher than Mortimer’s claim of 0.15%. They also measured a performance impact a little less than 3%, which isn’t negligible,
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.