[kernel exploit] 消息队列msg系列在内核漏洞利用中的应用

本文详细分析了Linux内核中消息队列msg的使用,包括msgget、msgsnd和msgrcv的源码逻辑,以及在内核漏洞利用中的常见应用场景。msgget用于创建消息队列,msgsnd用于发送消息,msgrcv用于接收消息。msg队列在堆占位、地址泄露、UAF构造和任意地址读写等方面有广泛应用。通过MSG_COPY标志,可以避免释放消息时的链表异常,实现任意地址读。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

[kernel exploit] 消息队列msg系列在内核漏洞利用中的应用

简介

消息队列msg 和共享内存一样是linux 内核提供的一种进程间通信(IPC)方式,但它除了用于进程间通信,在内核堆漏洞利用中确是基本100%出场的常客,无论是堆占位、泄露地址、构造UAF、任意地址读写等操作它都能完成,所以这篇文章分析一下消息队列msg 的源码逻辑和在linux kernel 漏洞利用中的常见应用场景。

本篇分析使用内核代码版本5.13。

源码阅读与逻辑分析

在漏洞利用中,主要就使用msgget、msgsnd和msgrcv 三个函数。接下来分别分析这三个函数。

msgget 创建消息队列

msgget 原型

首先要使用消息队列,要调用msgget 创建消息队列。msgget 的原型如下:

int msgget(key_t key, int msgflg);
  • 参数key:键值,linux IPC初始化基本都需要一个键值,通常是通过ftok 生成或是IPC_PRIVATE。在我们漏洞利用场景中一般就是用IPC_PRIVATE 就可以了。
  • 参数msgflag:创建消息队列的操作和读写权限,可以设置IPC_CREAT(返回当前key 对应的msg队列id,若不存在则创建) 或IPC_CREAT | IPC_EXCL(返回当前key 对应的msg队列id,如果已经存在则返回错误)。在我们漏洞利用的场景中由于key 都是IPC_PRIVATE ,这里直接使用IPC_CREAT 即可,读写权限通常0666。
  • 返回值:返回msg 队列id,用于后续msgsnd 和msgrcv。

msgget 手册

msgget 源码分析

入口位置在ksys_msgget

linux\ipc\msg.c:

SYSCALL_DEFINE2(msgget, key_t, key, int, msgflg)
{
   
	return ksys_msgget(key, msgflg);
}

调用栈

  • ksys_msgget
    • ipcget
    • ipcget_new (key为IPC_PRIVATE)
      • newque

其中在ipcget 中,会判断key,如果是IPC_PRIVATE,则会直接调用ipcget_new:

linux\ipc\util.c:

int ipcget(struct ipc_namespace *ns, struct ipc_ids *ids,
			const struct ipc_ops *ops, struct ipc_params *params)
{
   
	if (params->key == IPC_PRIVATE)
		return ipcget_new(ns, ids, ops, params);
	else
		return ipcget_public(ns, ids, ops, params);
}

最后在newque 函数中,进行msg_queue 结构体的初始化,并且将该msg_queue 存入当前ipc_namespace中,消息队列msg_queue 结构体和相关代码如下;

linux\ipc\msg.c:

struct msg_queue {
   //msg队列结构体
	struct kern_ipc_perm q_perm; //每个ipc 相关结构体都要有q_perm
	time64_t q_stime;		/* last msgsnd time */
	time64_t q_rtime;		/* last msgrcv time */
	time64_t q_ctime;		/* last change time */
	unsigned long q_cbytes;		/* 当前消息队列中的字节数 */
	unsigned long q_qnum;		/* 当前消息队列中的消息数 */
	unsigned long q_qbytes;		/* 消息队列中允许的最大字节数 */
	struct pid *q_lspid;		/* pid of last msgsnd */
	struct pid *q_lrpid;		/* last receive pid */

	struct list_head q_messages;/* 消息队列 */
	struct list_head q_receivers;
	struct list_head q_senders;
} __randomize_layout;

static int newque(struct ipc_namespace *ns, struct ipc_params *params)
{
   
	struct msg_queue *msq;
	··· ···

	msq = kvmalloc(sizeof(*msq), GFP_KERNEL);//申请空间
    ··· ···
	/*
	 * 初始化 msq-> q_perm 和其他成员
	 */
	retval = ipc_addid(&msg_ids(ns), &msq->q_perm, ns->msg_ctlmni); //[1]
	··· ···

	return msq->q_perm.id;
}
  • [1] : 这里调用ipc_addid 将该msq 结构体的q_perm 成员加入到当前ipc_namespace 中,并返回对应该msg队列的id,在后续寻找的时候可以通过msq->q_perm 的地址用container_of 找到所属msq 的地址。

msq 没啥东西,主要看一下接下来的msgsnd 和msgrcv。

msgsnd 发送消息与消息队列模型

msgsnd 原型

msgsnd 用于向指定id 的有写权限的消息队列发送消息,原型如下:

int msgsnd(int  msqid , const void * msgp , size_t  msgsz , int  msgflg );
  • 参数msqid:指定消息队列id,由msgget 返回。

  • 参数msgp:发送的消息结构体指针,结构体如下:

    struct msgbuf {
         
        long mtype;       /* 消息类型,后续也会用于接收消息 */
        char mtext[1];    /* 用于发送的消息文本 */
    };
    
  • 参数msgsz:发送消息的大小

  • 参数msgflg:一般会加一个IPC_NOWAIT,如果队列满了就不等待直接返回错误,默认状态会阻塞等待队列空出位置。

  • 返回值:0成功,-1失败。

msgsnd手册

msgsnd 源码分析

入口位置在ksys_msgsnd

linux\ipc\msg.c:

SYSCALL_DEFINE4(msgsnd, int, msqid, struct msgbuf __user *, msgp, size_t, msgsz, int, msgflg)
{
   
	return ksys_msgsnd(msqid, msgp, msgsz, msgflg);
}

调用栈:

  • ksys_msgsnd
    • do_msgsnd
      • load_msg
        • alloc_msg

根据代码分析消息队列的结构,根据调用栈从后往前分析比较清晰,首先看一下msg 相关的结构体:

struct msg_msg {
   //主消息段头部
	struct list_head m_list; //消息双向链表指针
	long m_type;
	size_t m_ts;		/* 消息大小 */
	struct msg_msgseg *next; //指向消息第二段
	void *security;
	/* 后面接着消息的文本 */
};

struct msg_msgseg {
   //子消息段头部
	struct msg_msgseg *next; //指向下一段的指针,最多三段
	/* 后面接着消息第二/三段的文本 */
};

消息分为两种结构体,主消息段头部和辅消息段头部,头部结构体后面紧跟着的就是消息的文本。然后分析申请结构体的alloc_msg 函数:

linux\ipc\msgutil.c:

#define DATALEN_MSG	((size_t)PAGE_SIZE-sizeof(struct msg_msg)) //0xfd0
#define DATALEN_SEG	((size_t)PAGE_SIZE-sizeof(struct msg_msgseg))//0xff8

static struct msg_msg *alloc_msg(size_t len)
{
   
	struct msg_msg *msg;
	struct msg_msgseg **pseg;
	size_t alen;

	alen = min(len, DATALEN_MSG);//[1] 获得第一段消息长度
	msg = kmalloc(sizeof(*msg) + alen, GFP_KERNEL_ACCOUNT);//为消息段申请内存
	if (msg == NULL)
		return NULL;

	msg->next = NULL;
	msg->security = NULL;

	len -= alen;//[2] 
	pseg = &msg->next;
	while (len > 0) {
   //[2] 查看消息是否需要分段
		struct msg_msgseg *seg;

		cond_resched();

		alen = min(len, DATALEN_SEG); //获得第二段消息长度
		seg = kmalloc(sizeof(*seg) + alen, GFP_KERNEL_ACCOUNT
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值