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:
struct cred {
/* ... */
kuid_t uid; /* real UID of the task */
kgid_t gid; /* real GID of the task */
kuid_t suid; /* saved UID of the task */
kgid_t sgid; /* saved GID of the task */
kuid_t euid; /* effective UID of the task */
kgid_t egid; /* effective GID of the task */
kuid_t fsuid; /* UID for VFS ops */
kgid_t fsgid; /* GID for VFS ops */
/* ... */
} __randomize_layout;
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.
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.
struct task_struct {
/* ... */
/*
* Pointers to the (original) parent process, youngest child, younger sibling,
* older sibling, respectively. (p->father can be replaced with
* p->real_parent->pid)
*/
/* Real parent process: */
struct task_struct __rcu *real_parent;
/* Recipient of SIGCHLD, wait4() reports: */
struct task_struct __rcu *parent;
/*
* Children/sibling form the list of natural children:
*/
struct list_head children;
struct list_head sibling;
struct task_struct *group_leader;
/* ... */
/* Objective and real subjective task credentials (COW): */
const struct cred __rcu *real_cred;
/* Effective (overridable) subjective task credentials (COW): */
const struct cred __rcu *cred;
/* ... */
};
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:
/*
* The security context of a task
*
* The parts of the context break down into two categories:
*
* (1) The objective context of a task. These parts are used when some other
* task is attempting to affect this one.
*
* (2) The subjective context. These details are used when the task is acting
* upon another object, be that a file, a task, a key or whatever.
*
* Note that some members of this structure belong to both categories - the
* LSM security pointer for instance.
*
* A task has two security pointers. task->real_cred points to the objective
* context that defines that task's actual details. The objective part of this
* context is used whenever that task is acted upon.
*
* task->cred points to the subjective context that defines the details of how
* that task is going to act upon another object. This may be overridden
* temporarily to point to another security context, but normally points to the
* same context as task->real_cred.
*/
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:
rcu_assign_pointer(task->real_cred, new);
rcu_assign_pointer(task->cred, new);
Resources and References
Last updated
Was this helpful?