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 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!
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!).
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
.
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!
This function is found here, but ultimately it will update task->real_cred
and task->cred
to the new credentials: