Hacker News new | past | comments | ask | show | jobs | submit login
Attacking hardened Linux systems with kernel JIT spraying (mainisusuallyafunction.blogspot.com)
84 points by Symmetry on Nov 19, 2012 | hide | past | favorite | 17 comments



Fun! If you're interested in Linux kernel security, you should read the whole post, rather than jumping to conclusions about what it means right now.

SMEP is a processor feature that will disallow execution of instructions from pages marked as belonging to userland when the CPU is executing in CPL0 (kernel mode). SMEP makes life annoying for memory corruption exploit authors because it prevents them from using the userland memory they already control to hold shellcode.

BPF is part of the network monitoring code in the Linux (and BSD) kernel; it's what tcpdump expressions compile down to. BPF is a trivial two-register VM that implements matching on packet fields to select which packets will be shuttled (at great expense) into userland for pcap programs to look at. Modern Linux kernels have BPF implementations that compile down to native code.

JIT-spraying is a technique Dion Blazakis invented (and my friend Chris Rohlf, formerly on the Matasano team, spent several years working with). It notices that systems implementing JITs often give attackers control over executable code, even though the system is being careful not to allow data to be treated as instructions. JIT-spray attacks give a victim JIT code that when compiled will help abet an exploit; it's "sprayed" because the attacker does this repeatedly in order to fill the victim's memory with malicious code to make it easier to jump to during an exploit.

The coolest trick in this post is how they've accomplished the "spraying"; because there's a limit on open files and only one BPF filter can be attached to one, they need a way around resource limits. So they use explicit file descriptor passing, which is an obscure Unix feature that allows unrelated processes to send and receive open files over Unix domain sockets using sendmsg(2). By sending sockets with malicious BPF code attached, the technique forces the kernel to keep those BPF filters resident, while allowing the exploit code to close its own copy, freeing up space for a new socket.

The point of this post isn't "OMG Linux is doomed"; it's that it's difficult to blunt memory corruption attacks with simple countermeasures. 15 years ago, researchers thought they'd killed memory corruption code execution off for good with stack cookies; the years that followed have been a story of new countermeasures introduced, hailed as a single bullet, bypassed by clever attacks, and then fit into a patchwork of defenses that have made exploits progressively harder, but not close to impossible, to build.


But at least hardware vendors (Intel in this case) are giving software (Linux) more tools to protect themselves. Unfortunately, just like with the great hardware security provided by IBM for the PS3, software bugs mitigate many of these hardware protections!


Luckily pax anticipated that attack when releasing the KERNEXEC patch and fixed it a while ago.


i remember reading something i guess in the grsec forums about it (by "paxteam" afair). i cannot find it right now, care to send a link? the only thing i found is this post[1], which gives a very nice introduction to smap/smep.

[1]: https://forums.grsecurity.net/viewtopic.php?f=7&t=3046


As taken from spenders twitter, here are the relevant parts of the patch:

http://grsecurity.net/~spender/jit_prot.diff


I hardly think that this exploits hardened Linux systems since the JIT it exploits it is currently disabled by default. Maybe it should be titled "Attacking softened Linux systems...".


I think you're missing the point. The kernel module he exploits is also something that no sane sysadmin would choose to load. The technique "JIT spraying" targets JITs, however the broader methodology is "finding places to put stuff in memory without the NX bit set", the JIT used here is just one example. A relatively simple example. There are other things that do this too.

It's much like a "hello world" tutorial. You choose simple goals, use simplified but demonstrative non core parts, and show the overall method/technique/code/whatever in an easily digestible manner.

Further, I think a bigger takeaway than even the technique here is the good use of many different parts, each innocuous on its own, but in combination a path to owning the system. Its a clever combination of side-effects and primary effects, but with goals never considered by the original authors.


That is fine take away and I agree with it and really your whole comment. Except for the missing the point part. The title was inflamatory, link-bait and mostly inaccurate.


Can I just say that putting a JIT into a kernel is a really bad idea? That putting /any/ executable code in a trusted environment that isn't utterly static, cryptographically signed, and well armored, is just going to end badly, over and over again?

We have enough trouble with user-space code generation, and with return-oriented-programming. Actual code generation at the driver level seems utterly wrong.


That's the interesting thing about a post like this; it starts to form an argument about a principle like "no dynamic native code generation in the kernel", which clearly isn't an accepted principle today.

I'm not sure what I think about that principle, because native code generation is exotic today, but probably won't be 10 years from now.

But either way: your statement is exactly the point the post is trying to make. It's not "Linux is insecure", or "SMEP is worthless". It's "how does native code generation interact with the roadmap of security features OS developers and hardware manufacturers are planning"? Also, of course, it's "JIT spraying is cool and fun to implement, and here's a new place to try it."


Actual code generation at the driver level seems utterly wrong.

"Utterly wrong" is a pretty black-and-white judgement. A JIT in the kernel has costs and benefits. You could as easily say that the ability to load modules dynamically into the kernel is "utterly wrong," since it opens up a code-injection vector that makes rootkits and other malware much easier to write. In some ways dynamically-loadable modules could be seen as even more risky than a JIT, because a JIT can only generate a subset of all possible machine code. It would be pretty hard to write a rootkit as a Berkeley Packet Filter program.


However, it wouldn't be impossible. I am quite sure (although I don't have a proof) that the BPF bytecode can be made turing complete, so an arbitrary program (maybe a rootkit, much more likely patches to a few kernel structures) could be implemented in it. Examples of things that are very unintendedly turing complete include HTML5 without JS(the clicky rule 110), ELF relocations (you can write a program to be interpreted by the loader in a few symbols and relocations without changing code, see elf-bf-tools on GitHub) and the Intel interrupt handling mechanism(unreleased, see talk at 29c3).

Furthermore, you don't need dynamically loadable modules in the kernel for kernel code injection, see http://www.phrack.org/issues.html?id=7&issue=58


elf-bf-tools is an impressive hack, thanks for the link! To add to your list, you might want to mention that simply parsing C++ is turing-complete; see http://yosefk.com/c++fqa/web-vs-c++.html#misfeature-3 . The short of it is that you have to perform full template instantiation in some cases just to parse code that uses those templates!

As to your point about /dev/kmem, to me that is just another argument against the idea of banning JITs from the kernel. There are already lots of vectors for getting attack payloads into the kernel; the JIT angle only helps an attack if you somehow can make the kernel jump to a specified address but don't have root.


I think this just goes to illustrate that not only are non-self-synchronizing instruction sets bad for decoding, they're also bad for security. In point of fact you could use this method to execute one arbitrary instruction in a modern variable length instruction set like Thumb-2, but only one.


Why was this only tested against grsec but not SELinux? Is there some reason that it's not interesting to test SELinux against this exploit??


SELinux does not aim to protect or harden the kernel. SELinux aims to enforce a more complicated(maybe expressive), MAC policy on the filesystem and a few related objects. The SELinux threat model is helpless against a kernel vulnerability because it does not address application security.


innuendo galore.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: