Thursday, April 16, 2009

Interesting vulnerability in udevd

I used to love exploiting memory corruption vulnerabilities. It usually requires some reverse engineering, good knowledge of the underlaying operating system and some ingenuity to write reliable exploits. And if you try to circumvent clever protections such as PaX, it can get very tricky.

But besides kernel vulnerabilities, exploitable memory corruption vulnerabilities these days are mostly buffer overflows. It's a bit monotonous.

I get more excited by other kind of vulnerabilities such as Solaris' telnet -froot or the Debian/OpenSSL fiasco.

Last night, my friend Raph pointed me to this udev flaw. If you read this patch you can notice an extra check in get_netlink_msg():
if ((snl.nl_groups != 1) || (snl.nl_pid != 0))

This checks if the message recieved by udevd had been sent to a specific multicast group (sending to netlink multicast groups is privileged and can only be done with CAP_NET_ADMIN) and also if it was sent from the kernel's unicast address.

From now on, the vulnerability is pretty obvious: before the patch, udevd didn't check the origin of messages it was recieving through netlink.

So can we spoof the kernel and send arbitrary messages to udevd? Yes! And it's easy, it suffices to create a NETLINK socket with the NETLINK_KOBJECT_UEVENT protocol and to send a unicast message to the correct unicast address. In udevd, this address will be the pid of the process who bound the NETLINK socket (udevd's parent). You can easily find it in /proc/net/netlink (thanks Phil)). Et voilĂ !

My idea to exploit this was to create a 666 device node that would give direct access to a mounted partition and to chmod +s some binary file we control by directly writing to the block device (there are userland tools and lib to do this easily, see debugfs for instance).

Phil also came-up with the idea of replacing /dev/urandom and /dev/random with /dev/zero (so called "debian emulation" backdoor).
Raph then found an even better way: on Ubuntu, Debian and others, you can exploit "95-udev-late.rules" and run arbitrary commands by using the "remove" action.

And that's it for a slick exploit. 40 lines of C (5 lines of Python for Phil). Pretty simple, cross architecture, reliable.
And it can escape chroots and some MAC-constrained environments (as long as you can create netlink sockets).

14 comments:

  1. 5 lines of Python for PhilWe were so used to see Scapy one-liners... Kinda disappointing, don't you think ? ;)

    ReplyDelete
  2. ... and where can one get a sample of those lines?

    ReplyDelete
  3. Post the codes :)

    ReplyDelete
  4. grep -r RUN /etc/udev/rules.d
    result varies for every distrib.

    ReplyDelete
  5. And this blog post from the guy who has disclosed this bug.

    ReplyDelete
  6. What about mapping /dev/mem or /dev/kmem ? ;)

    ReplyDelete
  7. You can get access (as far as DAC is concerned) to any device you want easily.
    However, /dev/kmem is usually otherwise restricted.
    /dev/mem will work, but will be more complex than any other way unless you already have this code ready, even without STRICT_DEVMEM on.

    ReplyDelete
  8. As some people stated in other blog,
    wouldn't action="remove", RUN+= work like a charm? without any hassle of managing any devices?

    ReplyDelete
  9. Yes absolutely, and it's mentioned here too! Where else did you see it?

    ReplyDelete
  10. i have the base code up and running. 50 lines of C code for me :P. Do we need to send it sh commands or udev command/rules? Thanks.

    ReplyDelete
  11. did anyone come up with something like this?

    http://pastebin.ca/1395979

    ReplyDelete
  12. http://seclists.org/fulldisclosure/2009/Apr/att-0198/udev_txt :)

    ReplyDelete
  13. Hello there,

    I was experimenting with this vulnerability on a box that had 'noexec' enabled on all partitions, so, I was unable to use the LD_PRELOAD technique. Instead I coded a python exploit (yeap not affected by noexec) that makes use of all the ideas discussed here :-)

    It replaces /dev/urandom with /dev/mem (/dev/sda, /dev/hda are also ok!). What's really interesting is the fact that this always results in a device mode equal to 0666 whithout requiring any specific rules in /etc/udev/rules.d/ :-)

    So next step is a /dev/mem backdoor or a debugfs equivalent writen in Python (lol).

    ./hk

    ReplyDelete