苦学kernel(二)——kernel如何pwn
kernel pwn介绍
kernel pwn其实就是自己写一个程序在kernel的用户态下跑,这时候我们是完全可以自己写个system("/bin/sh")来getshell的,这一点与用户态的pwn有所不同,我们虽然可以轻松拿到shell,但是我们并没有读取flag文件的权限,所以这时候我们要把自己的权限提升,所以kernel pwn的主要目的就是提权,从而通过自己起的shell读取flag
提权
内核提权是指普通用户可以获取到root用户的权限,访问原先受限的资源(即flag),提权的方法大致分为两类
改变自身:通过改变自身进程的权限,使得自己获得root权限
改变别人:通过影响高权限进程的执行,使其完成我们想要的功能
改变自己
内核会通过进程的task_struct结构体中的cred指针来索引cred结构体,然后根据cred的内容来判断一个进程拥有的权限,如果cred结构体成员中的uid-fsgid都为0,那么一般就会认为进程具有root权限
struct cred {
atomic_t usage;
#ifdef CONFIG_DEBUG_CREDENTIALS
atomic_t subscribers; /* number of processes subscribed */
void *put_addr;
unsigned magic;
#define CRED_MAGIC 0x43736564
#define CRED_MAGIC_DEAD 0x44656144
#endif
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 */
...
}
这时候我们就有两种思路来提权
1、直接修改cred的uid-fsgid
2、伪造一个满足要求且合法的cred结构体,然后将task_struct结构体的cred指针指向我们伪造的cred结构体
直接修改cred
寻找cred的地址
直接定位
cred结构体的最前面记录的各种id信息,对于一个普通的进程而言,uid-fsgid都是执行进程的用户的身份,因此我们可以通过扫描内存来定位cred
在实际的执行过程中,我们可能会发现很多满足要求的cred,这主要是因为cred结构体可能会被拷贝、释放。
间接定位
struct
进程的task_struct结构体中会有指向cred的指针
1、定位当前进程task_struct结构体的地址
2、根据cred指针相对于task_struct结构体的偏移计算出cred指针储存的地址
3、获取cred具体的地址
coom
coom是用来标记可执行文件的名字,位于进程的task_struct结构体中,我们可以发现comm其实在cred的正下方,所以我们也可以先定位comm,然后定位cred的地址
/* Process credentials: */
/* Tracer's credentials at attach: */
const struct cred __rcu *ptracer_cred;
/* Objective and real subjective task credentials (COW): */
const struct cred __rcu *real_cred;
/* Effective (overridable) subjective task credentials (COW): */
const struct cred __rcu *cred;
#ifdef CONFIG_KEYS
/* Cached requested key. */
struct key *cached_requested_key;
#endif
/*
* executable name, excluding path.
*
* - normally initialized setup_new_exec()
* - access it with [gs]et_task_comm()
* - lock it with task_lock()
*/
char comm[TASK_COMM_LEN];
然而,在进程名字并不特殊的情况下,内核中可能会有多个同样的字符串,这会影响搜索的正确性和效率,所以,我们可以使用pprctl设置进程的comm为一个特殊的字符串,然后再开始定位comm
修改
直接修改
在这种方法下我们需要知道cred的地址,然后使用任意地址写或者rop执行代码来修改cred
间接修改
在这种方法下我们并不需要知道cred的地址
我们知道cred本质就是一个堆,那么创建cred的时候必然是需要进行堆分配的,那么我们就可以使用uaf来修改cred了
1、申请一个与cred结构体大小一样的堆块
2、释放该堆块
3、fork出新进程,恰好使用刚刚释放的堆块
4、通过修改cred从而提权
修改cred指针
定位
很明显其实就是上面所讲述的间接定位的两种方法
修改
直接修改
1、修改cred指针为内核镜像中已有的init_cred的地址,这种方法适合我们能够直接修改cred指针以及知道init_cred地址的情况。
2、伪造一个cred,然后修改cred指针指向该地址,这种方法比较麻烦,一般不使用。
间接修改
我们可以使用commit_creds(prepare_kernel_cred(0))进行提权,该函数会自动生成一个合法的cred,并且定位当前线程的task_struct的位置,然后修改它的cred为新的cred,这种方法比较适合用于控制程序执行流后使用。


浙公网安备 33010602011771号