[Full-Disclosure] Fwd: Theo's presentation on exploit prevention
From: Bas Alberts (bas.alberts_at_immunitysec.com)
To: firstname.lastname@example.org Date: Wed, 15 Sep 2004 20:55:37 -0400
Some interesting comments on pro-active security appeared on the daily
dave just now.
Pasted for your convenience:
[Dailydave] Theo's presentation on exploit prevention
pageexec at freemail.hu pageexec at freemail.hu
Theo's presentation on exploit prevention or the siren  sang again.
lest we die in ignorance though, let's look at that song.
we're getting lured into the belief that something genuine is going to
happen here. we're about to learn how the OpenBSD project smartened up
finally and copied the intrusion prevention technologies that others had
developed in the previous few years and more! we're promised a much more
hostile system environment and backwards compatibility, no less.
one begins to suspect why the stupid greek sailors all died in the end.
here we learn in more detail about the noble goals of this effort. it's
not quite clear how one's supposed to not break behaviour that the apps
depend on while still prevent the exploits from making use of the same
behaviour... but let's not rush ahead so fast. we're going to follow
POSIX and still do nasty nasty things to those attackers - or so the
let's cut to the meat! buffer overflows. to add insult to injury, it's
the stack based ones. oh, not THEM again. we thought we'd been past that
for years now! while marveling at the beauty of the 1000th buffer overflow
depiction we keep wondering when the OpenBSD Team will eventually learn
about the clever attacker that is exploiting more than buffer overflows.
anyway, let's look at this old buddy again. we learn that the buffer is
ALWAYS at the same place. how nice. how secure. stuff like environment
strings, program arguments and whatnot surely have no role in the stack
pointer value. apparently not on OpenBSD. proactive insecurity, isn't it.
so what shall we do about it? shift the stack by a random amount! where,
how, how much? apparently on OpenBSD the top of the stack is what normal
mortals consider the bottom of it... honest to god mistake, we understand
where the real gap is. at the bottom. no pun intended.
and then look at those failures! all due to that unbelievable 15 bits of
randomness OpenBSD managed to cram into that space! and we learn that all
this costs us at most one physical page of RAM. what we don't learn is
that it also costs us 256 kB of virtual address space (or more precisely,
whatever our admin deemed acceptable for his sense of security), but we
have got plenty of that and as we'll see later, it's a drop in the water
only (we're sailors, don't miss the pun!) compared to what will follow
later. so, what is 15 bits of randomness worth really? at one attempt
a second, it's less than half a day on average (assuming we're actually
going for the full 15 bits and can't get away with less), for stuff that
forks only it's even guaranteed in that time. on localhost it's a matter
and you're wondering why other vendors haven't picked it up (one wonders
where OpenBSD picked it up from). maybe because they can count further
than 15. what about 24, or 32 or whatever the address space reasonably
allows? sounds better, doesn't it.
here we learn about the fantastic japanese stuff (great sailors of their
time) that many in the world, including OpenBSD, have blissfully ignored
for... a few years at least. but now they have rediscovered the precious,
and it's all theirs and theirs and... we get this stack overflow focus
again. the one ring that binds them all. and we forget about information
leaking bugs that render this and other randomization based approaches
pretty vulnerable to attack. we also forget about localhost where we get
unexpected help for this: the kernel (and the nice bugs in it). the best
use of SSP is in the kernel itself. having a single canary value sitting
like a duck for the uptime of the system is the best idea men, err, sirens
could come up with. having it change per process and syscall crosses only
the minds of mad sailors.
we can see that SSP found a few exploitable bugs. but where are the
advisories? surely not swept under the carpet, right?
W^X! no, we didn't want to curse at you, honest! this pearl is the OpenBSD
attempt at PaX a few years late (, ), and on the surface it actually
does what it promises: separate writable (not writeable) pages from
executable ones unless the program Wants Just That. problem is that as
any female reader can attest, a boy Wants Just That. including those
badass blackhats who dare to challenge the security of OpenBSD. and since
the OpenBSD Team made it the policy to obey the program's wishes, they
stand a chance to actually wish something very pleasant. no, not that
(that fish is thorny, not horny), just a r00t shell or the like. but
let's do things at their own pace.
we've got a bunch of messy diagrams here, no wonder no sailor has ever
found his way back from here. looks like the author got lost too and
confused things like ctors/dtors with the C++ language constructs
(__attribute__((constructor)) is not at all equivalent to C++ class
first we see that the black sheep in the family a.k.a. the stack (it
can't even identify its own bottom as we saw above) is executable. and
all that because someone put the signal trampoline there. so they
move it to its own page, adding a whole bunch of logic to the kernel
instead of figuring out that libc could very well host it in its own
.text, not unlike how any Linux system has been doing it for a few
then come other evil animals that also host writable and executable
pages. getting rid of them is a matter of splitting them up into their
own pages so they fall into either of the allowed categories. or change
between them. as the runtime linker or the application (read: attacker)
wish. not to mention that mprotect'ing the GOT/PLT on every lazily
resolved entry must surely be a huge performance benefit and clearly
superior to the PT_GNU_RELRO approach preferred by RedHat .
last but not least we get to hear about the sad story of i386 where
per-page non-executable rights are just an impossibility. they seriously
mean it. look at that monster address space layout. if you consider the
fragmentation and waste you realize that the 256 kB stack gap pales in
comparison. while not exactly the semantics one would expect under POSIX,
we'll happily forgive it because this implementation offers us a true
gem. the userland code segment comes in only two sizes, and while most
people learn early to not put all their eggs in one basket (both code
segment descriptors in the GDT), this old adage has apparently fallen
on the collective deaf ears of the OpenBSD Team. so what this allows our
hypothetical attacker to do is a simple return to a 'retf' instruction
that will further return to the injected shellcode in the all-executable
code segment... voila, proactive security at its best.
here we are told that i386 is not all that bad provided one uses its
64 bit cousin and learns to program in PAE mode. apparently the latter
is a serious challenge for the OpenBSD Team (read: they couldn't just
lift the code from FreeBSD). so the sirens are now trying to lure the
unsuspecting vendors into implementing yet another way of doing
non-executable pages, as if it wasn't already messy enough.
so what did W^X buy us? security! performance! compatibility! we! all!
believe! in! sirens! obviously something's wrong here. security concerns
were discussed above, performance numbers can be hardly argued without
actually having them... so let's see compatibility. nothing breaks. sure,
X didn't break either then. including its homegrown module loader that
every non-executable page implementation ran into over time. and there're
no OpenBSD specific defines for this reason in the X module loader code
either. 'cos nothing broke. honest. at least it's fixed now.
what about JIT engines like in Java? we are told that on split I-D cache
systems they would not have worked anyway without proper use of msync and
mprotect. weird, i386 has split caches yet it's never ever needed any of
these to generate code at runtime. obviously the problem is not the split
cache itself but that it's not coherent on every system (and some systems
offer simple userland accessible primitives for flushing the caches, no
need for expensive syscalls at all).
this one is a true gem as well: library order randomization. we are led
to believe that it's a worthwile effort. let's see... 'n' libraries can
be loaded in n! order, that's a nice exponential value in 'n' (one would
think of more than 'n' bits of extra randomness that an attacker has to
get right). question is whether that is its true security value as well
or not. considering that an attacker normally wants to use a single
library in a ret-to-libc style attack (libc... get it?), we can easily
conclude that for this purpose the load order will be 'right' once in
every 'n' attempt. that in turn means that all this gained is log(n)
bits of extra randomization... hardly worth a mention at all, not to
mention its cost in code complexity and performance impact in ld.so.
then we're shown the 'wee bit' of virtual address space that is wasted
in library base address randomization. heavily fragmented all 256 MB of
it. double that as the waste is mirrored above the executable limit as
well (most if not all libraries contain both code and data). that's
1/6th of the entire address space. a wee bit understated, indeed.
confused yet? rhetorical question but see, those dumb attackers really
are, no question about it. they're facing incredible amounts of entropy,
they can no longer execute their payload, and worst, they are stuck at
these classic buffer overflows! of course information leaking bugs are
unheard of in siren land, as are non-linear overflows. and the many
kinds of memory corruption bugs that the creative human mind comes up
good old mmap randomization (3+ years in PaX), except it doesn't only
depend on the presence of MAP_FIXED but also the address hint (non-0
hints above p_vmspace->vm_daddr won't be randomized).
there is hope! apparently someone did hear about heap overflows and
related exploits in the OpenBSD Team. a pity they haven't actually
delved in the fine details of it , else they would know that adding
randomization within a page gives only 2-8 bits of randomness, hardly
this is probably the most useless security feature one can ever come
up with. while making .rodata non-executable looks like good housekeeping
on the surface, it's pretty useless as a security measure. the readership
is challenged to find (or produce) a real-life bug that cannot be
exploited except when .rodata is executable. we have yet to see one.
we also learn the underlying concept of all these address space tweaks:
the noble goal of least privilege. except the security-challenged OpenBSD
Team doesn't know that memory protection rights come in pair: one set
defines the active permissions and another the attainable ones. restricting
the former only while leaving free reign over the latter means that it
is possible to circumvent the former. and it wasn't until 3.4 where one
could call mprotect() with random stack garbage as arguments and have
the kernel still accept the protection flags it knew about and ignore
besides the usual trashing of the quality of "Open Source" (one wonders
if it applies to OpenBSD as well then), we note that there is a reason
why ElectricFence is not used in production, it just kills performance
due to the heavy address space fragmentation. and this thing is at least
a decade old... does everything take this long to be 'discovered' in the
this is a cool hack! it's not quite clear though what advantage it has
over SSP given that they both detect the same kind of attacks. and we
are of course forever indebted for the reference to PaX , in those
hard days when the OpenBSD Team hadn't heard of us for another year or
two. seriously. they said so therefore it must be true.
least privilege again, this time of more mundane ones, not memory access
rights. of the two methods, privilege revocation does actually make sense
however privilege separation (not seperation) doesn't. for noone has a
bugfree kernel. especially not OpenBSD that wasn't written from scratch
by its maintainers who have often little idea what a given piece of code
does. examples like the improper use of the i386 GDT mentioned above and
other (sometimes not yet public) snafus clearly prove the point.
so what does a kernel bug do? a good one will allow the skilled attacker
to run his code with kernel privileges (say ring-0 on i386) and effectively
circumvent anything that the OpenBSD Team have dreamt up for protection.
in other words, it doesn't matter where you shift the buggy code, it can
already exploit any of the kernel bugs to gain whatever privileges the
attacker needs. put that into the second stage of a normal remote exploit
and you're back at square one, remote root, whatever way you look at it.
finally, we can see the difficulties facing the defenders:
- no clear concept let alone implementation against exploits of memory
corruption bugs (tree vs. forest problem), effectively you can never
be sure if a given bug is exploitable under these measures and what
kind of damage it can cause,
- performance and compatibility information is unreliable, you're still
best off by simply testing it yourself, that's especially important
for 3rd party apps that might break due to the unconventional address
space layout and memory protection semantics,
- exploitable bugs are fixed silently, how to learn to update then?
this concludes our odyssey and for the rest of you, happy sailing!
just don't forget the earplugs when the sirens begin to sing.
Full-Disclosure - We believe in it.