The Ultimate Aim of Kernel Exploitation - Process Credentials
Overview
Userspace exploitation often has the end goal of code execution. In the case of kernel exploitation, we already have code execution; our aim is to escalate privileges, so that when we spawn a shell (or do anything else) using execve("/bin/sh", NULL, NULL)
we are dropped as root
.
To understand this, we have a talk a little about how privileges and credentials work in Linux.
The cred struct
The cred
struct contains all the permissions a task holds. The ones that we care about are typically these:
These fields are all unsigned int
fields, and they represent what you would expect - the UID, GID, and a few other less common IDs for other operations (such as the FSUID, which is checked when accessing a file on the file system). As you can expect, overwriting one or more of these fields is likely a pretty desirable goal.
Note the __randomize_layout
here at the end! This is a compiler flag that tells it to mix the layout up on each load, making it harder to target the structure!
task_struct
The kernel needs to store information about each running task, and to do this it uses the task_struct
structure. Each kernel task has its own instance.
The task_struct
instances are stored in a linked list, with a global kernel variable init_task
pointing to the first one. Each task_struct
then points to the next.
Along with linking data, the task_struct
also (more importantly) stores real_cred
and cred
, which are both pointers to a cred
struct. The difference between the two is explained here:
In effect, cred
is the permission when we are trying to act on something, and real_cred
when something it trying to act on us. The majority of the time, both will point to the same structure, but a common exception is with setuid executables, which will modify cred
but not real_cred
.
So, which set of credentials do we want to target with an arbitrary write? Honestly, I'm not entirely sure - it feels as if we want to update cred
, as that will change our abilities to read and execute files. Despite that, I have seen writeups overwrite real_cred
, so perhaps I am wrong in that - though, again, they usually point to the same struct and therefore would have the same effect.
Once I work it out, I shall update this (TODO!).
prepare_kernel_cred() and commit_creds()
As an alternative to overwriting cred
structs in the unpredictable kernel heap, we can call prepare_kernel_cred()
to generate a new valid cred
struct and commit_creds()
to overwrite the real_cred
and cred
of the current task_struct
.
prepare_kernel_cred()
The function can be found here, but there's not much to say - it creates a new cred
struct called new
then destroys the old
. It returns new
.
If NULL is passed as the argument, it will return a new set of credentials that match the init_task
credentials, which default to root credentials. This is very important, as it means that calling prepare_kernel_cred(0)
results in a new set of root creds!
This last part is actually not true on newer kernel versions - check out Debugging the Kernel Module section!
commit_creds()
This function is found here, but ultimately it will update task->real_cred
and task->cred
to the new credentials:
Resources and References
Last updated