FreeBSD Kernelland-Trickery / Gain root access via syscall

FreeBSD Kernelland-Trickery / Gain root access via syscall

Date: 2012-07-16 13:46:50

This time I will focus on FreeBSD kernel developement. The recent stable version of FreeBSD is 9.0, but for this example we will use a version 8.1 with i386 architecture.

After several years of coding in userland I found changing the operating system internals quite enjoyable. In contrast to many programs found in the wild, kernelcode is usually clean and properly written, at least in BSDland. For this first issue I will focus on a simple syscall module which assigns a user uid 0 just by performing a syscall.

First of all, *nix operating systems heavily depend on syscalls. They are what every other operation is based on. Think of opening a file, which starts several syscalls. Most interestingly: open(), read(), close(). Unix knows several hundred system calls, which I will turn to in a later blog entry. For now just accept (which is a syscall as well) that those are quite important and useful.

For building a FreeBSD module your best bet is:

sudo pmset -a destroyfvkeyonstandby 1 hibernatemode 25\

This directory holds everything for a first module. For building a syscall module you will need the following Makefile:

# Makefile for building the sample syscall module # FreeBSD: src/share/examples/kld/syscall/module/Makefile,v 1.2.36.1.4.1 2010/06/14 02:09:06 kensmith Exp KMOD=syscall SRCS=syscall.c .include

Reading the module, an overview:

//(1) #include <sys/param.h> //(2) static int hello(struct thread *td, void *arg) { printf("hello curesec\n"); return (0); } /* * The `sysent\' for the new syscall */ static struct sysent hello_sysent = { 0, /* sy_narg */ hello /* sy_call */ }; /* * The offset in sysent where the syscall is allocated. */ static int offset = NO_SYSCALL; /* * The function called at load/unload. */ //(3) static int load(struct module *module, int cmd, void *arg) { int error = 0; switch (cmd) { case MOD_LOAD : printf("syscall loaded at %d\n", offset); break; case MOD_UNLOAD : printf("syscall unloaded from %d\n", offset); break; default : error = EOPNOTSUPP; break; } return (error); } //(4) SYSCALL_MODULE(syscall, &offset, &hello_sysent, load, NULL);

  • 1. We include the required headerfiles here. Those are important. In bigger projects you might want to store them in a separate file is there are quite a lot of them.
  • 2. This is the only function called by our module when the syscall is executed: We print a “hello curesec”.
  • 3. The function for loading and unloading the module. Please note that a offset for the syscall is printed.
  • 4. The module is defined as SYSCALL_MODULE. We are interested in the first two positions which define the module’s name in the kernel structure and the offset at which the module is called. As already mentioned, Unix has quite some syscalls. When a new one is loaded into the kernel, the next free is taken. On FreeBSD this is usually syscall number 210.

OK, let’s compile the module and execute it:

#make #kldload ./syscall.ko

Switch to console window or execute dmesg to the output of the module. Now for diving a bit deeper into it, lets have a look at the hello() function. It is executed with:

hello(struct thread *td, void *arg)
I search through the headerfiles in /usr/include/sys/proc.h for the different sorts of structs and parameters of struct thread:
struct thread { struct mtx *volatile td_lock; /* replaces sched lock */ struct proc *td_proc; /* (*) Associated process. */ TAILQ_ENTRY(thread) td_plist; /* (*) All threads in this proc. */ TAILQ_ENTRY(thread) td_runq; /* (t) Run queue. */ TAILQ_ENTRY(thread) td_slpq; /* (t) Sleep queue. */ TAILQ_ENTRY(thread) td_lockq; /* (t) Lock queue. */ LIST_ENTRY(thread) td_hash; /* (d) Hash chain. */ struct cpuset *td_cpuset; /* (t) CPU affinity mask. */ struct seltd *td_sel; /* Select queue/channel. */ struct sleepqueue *td_sleepqueue; /* (k) Associated sleep queue. */ struct turnstile *td_turnstile; /* (k) Associated turnstile. */ struct umtx_q *td_umtxq; /* (c?) Link for when we\'re blocked. */ lwpid_t td_tid; /* (b) Thread ID. */ sigqueue_t td_sigqueue; /* (c) Sigs arrived, not delivered. */ }

digging through struct proc, I arrive at “struct ucread”, lets have a short overview:

struct ucred { u_int cr_ref; /* reference count */ #define cr_startcopy cr_uid uid_t cr_uid; /* effective user id */ uid_t cr_ruid; /* real user id */ uid_t cr_svuid; /* saved user id */ int cr_ngroups; /* number of groups */ gid_t cr_rgid; /* real group id */ gid_t cr_svgid; /* saved group id */ }

We can see that through the struct thread of the called function, struct ucred, for user credentials, is accessible. Lets try to change the user credentials by changing the hello function. For this we need to reference the structure in our function and change it to the uid we want to assign ourselves. As this is a Unix the most interesting user is “root”.

Have a look at the following lines:

uprintf("change to root\n"); td->td_proc->p_ucred->cr_uid=0; // effective user id td->td_proc->p_ucred->cr_ruid=0; // real user id

We insert these into our module, compile it, and load it into the kernel. You might have recognized a new function “uprintf” which prints the message on the current console window.

There a various ways for calling the module. For now I choose the path of the test call application delivered in the example’s directory. In short terms it searches for the number of the systemcall with modstat and calls it without any argument. This is all we need.

int syscall_num; struct module_stat stat; stat.version = sizeof(stat); modstat(modfind("sys/syscall"), &stat); syscall_num = stat.data.intval; return syscall (syscall_num);

Please note that you need to change the name of syscall to the name of your module if it is no longer called “syscall”. As root user you can now load this module with the command:

#kldload ./syscall.ko

If you execute kldstat you should see your newly loaded module.

Log in as a non-privileged user and call the module with the caller binary. Check for the current privileges to confirm you are running as systemuser.

%id uid=1001(noone) gid=1001(noone) groups=1001(noone) %./call %id uid=0(root) gid=0(wheel) egid=1001(noone) groups=1001(noone)

For production usage this module will need some sort of protection, otherwise any user on your system knowing about this feature could become instant root, which is not desired. For this purpose you can use the functions copyin and copyout which are for userland and kernelland communication. I will talk about them in another blogentry.

Please keep in mind that techniques like these are also used by attackers to maintain root access to your system.