kernel_pwn中uffd机制的利用
如何利用 Linux 的 userfaultfd 机制,在用户空间处理缺页异常(page fault)。
1. 整体概述
代码主要流程如下:
- 内存映射
在main()
中使用mmap
分配了 2 页内存(每页 4KB)。 - 注册 userfaultfd
调用register_uffd()
将映射区域注册到 userfaultfd,使得对这些区域的访问触发缺页异常。 - 缺页处理线程
启动一个专门处理缺页事件的线程(fault_handler_thread
),该线程通过poll()
、read()
捕获缺页事件,然后利用UFFDIO_COPY
将预先准备的数据填充到缺页区域。 - 触发缺页
在main()
中通过读取映射内存触发缺页事件,最终观察到填充的数据。
2. main() 函数解析
int main() {
void *page;
page = mmap(NULL, 0x2000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (page == MAP_FAILED)
fatal("mmap");
printf("[+] mmap two pages at 0x%llx\n", (long long unsigned int)page);
register_uffd(page, 0x2000);
char buf[0x100];
puts("[*] reading from page#1");
strcpy(buf, (char *)(page));
printf("[+] 0x0000: %s\n", buf);
puts("[*] reading from page#2");
strcpy(buf, (char *)(page + 0x1000));
printf("[+] 0x1000: %s\n", buf);
puts("[*] reading from page#1");
strcpy(buf, (char *)(page));
printf("[+] 0x0000: %s\n", buf);
puts("[*] reading from page#2");
strcpy(buf, (char *)(page + 0x1000));
printf("[+] 0x1000: %s\n", buf);
getchar();
return 0;
}
主要步骤
- 内存映射
使用mmap
分配了 0x2000 字节(2 页)的内存,设置了读写权限。映射类型为匿名且私有。 - 打印映射地址
打印分配内存的起始地址,便于调试。 - 注册 userfaultfd
调用register_uffd(page, 0x2000)
将分配的 2 页内存区域注册到 userfaultfd。 - 触发缺页
通过对映射区域内存的读取(使用strcpy
拷贝数据到缓冲区),第一次访问将触发缺页异常。代码中读取两次每个页面,借此观察缺页处理线程的填充行为(根据缺页次数返回不同的字符串)。 - 暂停程序
使用getchar()
阻塞程序退出,以便观察程序运行过程中的输出。
3. register_uffd() 函数解析
int register_uffd(void *addr, size_t len) {
struct uffdio_api uffdio_api;
struct uffdio_register uffdio_register;
long uffd;
pthread_t th;
puts("[*] registering userfaultfd");
uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
if (uffd == -1)
fatal("userfaultfd");
uffdio_api.api = UFFD_API;
uffdio_api.features = 0;
if (ioctl(uffd, UFFDIO_API, &uffdio_api) == -1)
fatal("ioctl(UFFDIO_API)");
uffdio_register.range.start = (unsigned long)addr;
uffdio_register.range.len = len;
uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING