Abusing OS X TrustedBSD framework to install r00t backdoors…

While poking around OS X implementation of TrustedBSD to write the sandbox guide I had the idea of trying to abuse it for backdooring purposes. It’s kind of funny that something designed to protect can be so “easily” abused to install backdoors.
This is not rocket science or a big breakthru post – I was just curious about the possibility to abuse the framework. You still need to find a way to install the kernel module!

So without further delay, I present you Rex, The Wonder Dog. It is a very simple policy module for TrustedBSD that gives r00t privileges to a process named “xyz”, if it calls task_for_pid(). For some unknown reason I couldn’t yet do the same with fork() (it was only working for Safari). I was doing this at 3am so I really didn’t bothered too much about it. It is based on SEDarwin sample policies code.
I had some trouble to compile the policy moduleΒ (duplicate symbols) if I try to use the macro to initialize the module. I strongly suspect this is because I am using XCode’s kernel extension template. This is just a lazy PoC πŸ˜‰

The code is unstable. Processes start crashing and crash reporter isn’t executing. It could be due to the very lazy way that r00t privileges are changed for the target process (it only starts to happen after backdoor is activated). Kernel land is dangerous territory! It is tested only with Snow Leopard 10.6.8. Might work with Lion without any problems. Load it as a normal kernel module, with kextload.

Dmesg log when module is loaded:

calling mpo_policy_init for rex_the_wonder_dog
calling mpo_policy_initbsd for rex_the_wonder_dog
Security policy loaded: Rex, the wonder dog! (rex_the_wonder_dog)

Starting the backdoor and getting a r00t shell:

$ ./xyz
[info] calling task_for_pid()
[info] task for pid returned 0
[info] uid 501 euid 0
[info] setting uid to 0...
[info] uid 0 euid 0
[info] executing r00t shell...
# id
uid=0(root) gid=0(wheel) egid=20(staff) groups=0(wheel),204(_developer),100(_lpoperator),98(_lpadmin),80(admin),

Policy modules open interesting possibilities for implementing other things. Maybe your own binary integrity check module? Or maybe some nice anti-debug for software who must use kernel modules πŸ˜‰

Sorry for the lazy and unstable code. I’m not that much interested in backdoors, I was just interested in testing the possibility. It’s (just) a clean way to activate a kernel backdoor. If you improve and want to share your code feel free to send it!

Have fun,

Here are the goodies.

SHA256(rexthewonderdog_v0.1.zip)= 4d75ab5859d6a3259de12a9e21a7ee4530b1bc1adb673e2fb24a4f66b9109eac

SHA256(xyz.c)= 3e24337fc7b61f392066e0812051007e8942060a2906d720d345f019de894576

8 thoughts on “Abusing OS X TrustedBSD framework to install r00t backdoors…

  1. Hey dude,

    The reason it’s causing processes to crash is as you suspected in your comments. In 10.6 (and probably 10.7) the ucred structs are shared by multiple processes that have the same privilege set, so elevating a process’s uid to root by just setting p->p_ucred->cr_uid to 0 will be setting that for a number of processes. I haven’t looked into exactly why it causes processes to crash, but I guess there’s a lot of system processes that aren’t expecting to be running as root.

    The way I’ve done it is something like this:

    // duplicate existing cred
    // prob should be checking if we actually need to, otherwise we might leak ucred structs but whatever
    cr = kauth_cred_dup(p->p_ucred);

    // set privs
    cr->cr_uid = 0;
    cr->cr_ruid = 0;
    cr->cr_svuid = 0;

    // give the process some balls
    p->p_ucred = cr;

    // clean up process

    This code is from something I wrote ~12mo ago so I can’t remember exactly what’s what haha, but iirc kauth_cred_ref() is incrementing the reference count, proc_rele() seems a bit wrong but this works from memory. You may need to steal the source for the kauth* and lck* functions from XNU, I can’t remember which ones were exported and which ones weren’t (I had to rip the source off for some, and this may have changed in 10.7). Give it a try πŸ™‚

    1. Also you may be able to get away without doing the mutex locking on the proc structure but I’m sure it’s there for a reason πŸ™‚

    2. Ahhhhhh I have been trying to mess with the locks but most aren’t exported by the kernel (especially interested in the allproc lock, proc_list_mlock). I am writing a mach-o parser to retrieve them from kernel memory (is there a simple way to retrieve them ? hehehe).

      Thank you for your tips. I will give them a try! Always great to learn πŸ™‚

  2. I actually just copied the code from the kernel source for the lock functions. Each process has its own lock so you don’t need to find the symbol for the allproc lock in this instance. I think I had to rename one of the data structure defs because it conflicted with what was in Kernel.framework or something, it was heaaaps hacky but it worked in the end. I’ll revisit the source and let you know.

    What are you trying to retrieve from kernel memory? Dino Dai Zovi wrote a cool bit of code for iterating through vm allocations and searching for Mach-O images (to detect rogue kernel modules that had been loaded and then removed from the kmod list I think?). The code is here: http://trailofbits.com/2009/08/10/advanced-mac-os-x-rootkits/

    Unfortunately it only works on Leopard because it uses task_for_pid() supplying 0 as the PID to request the Mach task for the kernel, which isn’t allowed any more. The technique could probably be replicated using the vm_* and mach_vm_* functions from within the kernel though, I’m sure, or possibly actually using the technique you’ve described in this article to add a MAC handler for task_for_pid() and explicitly allowing the requestor to get a reference to the kernel task. The way in which it is restricted in the kernel might not allow this, I’d have to look at the code again to verify.

    1. Oh yes I have the structure from the ptrace kernel module and I tried to lock the process with its own lock on the proc structure. It didn’t work. I still have to test your code because my test was to simply lock and unlock the process.

      Just trying to parse the kernel itself. The idea is to parse the symbols and find the allproc lock (it’s configured as an external symbol). Why I am looking for the allproc lock? Was checking FreeBSD rootkits book and it always locks that one and also the process lock, which makes some sense due to what’s happening. If I am mistaken then it’s just coding fun and might be useful for some future project. I will check it out Dino’s code.

      Since the policy module is a kernel module there’s not a big problem reading kernel memory. For tests I enabled /dev/kmem which makes things easier and less prone to crashes πŸ™‚ The task_for_pid check on kernel PID is executed before the MAC check so a new handler wouldn’t overcome it. It’s not a big deal in this specific case πŸ™‚

      1. Cool, are you parsing the kernel from where the sections are already loaded in memory or the binary on disk? I’m in the process of doing something similar at the moment for some self-linking shellcode. It’s a bit of a pain.

        I didn’t realise you needed to lock the allproc lock as well, I can’t remember where I got the ideas from for that code haha.. I have that book as well, will check out that section. Cheers!

        Yeah I wasn’t very clear about Dino’s code – I should have mentioned it does the VM walk from user space. It doesn’t really sound like it would be useful for what you’re doing, but it is interesting all the same πŸ™‚

        1. I have the disk version working and going to quickly implement the version via /dev/kmem, which is easy – read from memory instead of disk.
          The next problem is how to read kernel memory inside the kernel module. The mach related functions aren’t exported. Well I was almost sleeping so I might missed something. Apple didn’t rest after the different rootkit presentations from the past years and fixed/changed some kernel related stuff.

          Yeah Dino’s code needs to be adapted to /dev/kmem for example to make it useful for newest versions. Useful in controlled scenarios – for forensics it’s less useful because /dev/kmem isn’t enabled by default. A helper kernel module could quickly fix this πŸ™‚

          1. Yeah that sounds like fun, I’d be interested to see the code if you blog about walking the memory map from within the kernel. I’m not 100% on how Mach-O loading works and how kxld actually does the linking of kexts, I’m wondering if the LINKEDIT load command actually gets loaded into memory somewhere persistent that can be parsed manually from a kext, or if it is just linked at load time and then jettisoned. Must investigate.

            Very true, Apple seem to have paid a lot of attention to this type of research and changed a bunch of stuff to prevent the techniques from working.

Leave a Reply

Your email address will not be published. Required fields are marked *