首页
社区
课程
招聘
[原创]从ciscn2022半决赛一道一解题浅析msg_msg结构体
发表于: 2023-4-2 13:22 20397

[原创]从ciscn2022半决赛一道一解题浅析msg_msg结构体

2023-4-2 13:22
20397

2022 ciscn实践赛西南赛区半决赛只有两道pwn,一道简单vm栈溢出,还有一道1解kernel。

因为题目没有泄露函数,所以我依赖msg_msg构造越界读&任意写的原语,同时借助pipe_buffer完成内核地址泄露。

笔者对msg源码进行了浅要的剖析,有基础 or 对源码不感兴趣 的读者可自行选择跳过。

消息队列是Linux的一种通信机制,这种通信机制传递的数据具有某种结构,而不是简单的字节流。消息队列的本质其实是一个内核提供的链表,内核基于这个链表,实现了一个数据结构。

可以通过消息队列实现进程间通信等。

/include/linux/msg.h中有关于msg_msg结构体的定义:

其中list_head为双向链表结构体,储存nextprev指针:

ipc/msgutil.c中有对msg_msgseg的定义,还有申请msg_msg结构体的函数:

可以看到msg_msgseg就是一个嵌套的结构体指针。

其中参数含义:

调用msgget函数会创建新的消息队列,或者获取已有的消息队列,若创建新的消息队列,会创建一个msg_queue结构体当消息队列msg_msg双向循环链表的起始节点。

需要注意的是后续若某进程调用msgsnd函数对消息队列进行写操作,需要该进程有写权限;同理msgrcv需要有读权限。这是由msgget函数中的第二个参数中的权限控制符所决定的。

引自Roland师傅的图:(概括性的,若读者不想看我对源码的分析可以直接参考这个表)

调用 msgsnd 系统调用在指定消息队列上发送一条指定大小的 message 时,会建立msg_msg结构体。

查看实现msgsnd系统调用的do_msgsnd函数部分源码:

查看load_msg函数:

调用了alloc_msg函数分配空间,同时将用户数据拷贝到内核msg_msg队列中。

再查看alloc_msg函数:

从该函数源码我们可以知道:

通俗点来说,msg_msgmsg_msgseg结构体最大size均不能超过page_size

最后单个msg_msg消息会形成如下的单向链表结构:

msg_msg之间则是用list_head 来链接,形成的是以msg_queue为首节点的双向循环链表结构,大致如下:

申请msg_msg的调用链:

同样引自Roland师傅的图:(进行了一点小更正)

msgrcv系统调用能从消息队列上接受指定大小的消息,并且选择性(是否)释放msg_msg结构体。

具体实现源码在/ipc/msg.cdo_msgrcv中。

该函数中源码中使用了内核源码中常见的一个宏定义:list_for_each_entry。该宏定义可以理解为一个for循环。

它实际上是一个 for 循环,利用传入的 pos 作为循环变量,从表头 head 开始,逐项向后(next 方向)移动 pos,直至又回head。

该循环遍历了msg_queue为首节点的双向循环链表,也就是遍历了所有msg_msg队列的头节点。

然后调用testmsg,根据mode和传入的msgtyp来筛选:

其中modeconvert_mode决定:

综合起来,可以看到用户是通过控制msgtyp来控制do_msg_rcv拷贝/取得 哪条队列信息:

特例:MSG_COPY位为1的时候,modeSEARCH-NUMBER,在find_msg中会返回msg_msg双向循环链表中,第msgtypmsg_msg,也就是返回第msgtyp条消息,而不是上述表格中根据msgtyp去和msg->m_type进行匹配。

/ipc/msg.cdo_msgrcv

内核首先会调用 list_del() 将其从 msg_queue 的双向链表上 unlink,之后再调用 free_msg() 释放 msg_msg 单向链表上的所有消息。

do_msg_rcv函数最后,调用了msg_handler,看参数像是进行内核-->用户的数据拷贝。

其中msg_handlerdo_msgrcv传进来的参数,是一个函数指针,向上看调用do_msgrcv的调用链:

可知msg_handler具体函数指针为do_msg_fill

其中调用store_msg进行数据拷贝:

可以看到拷贝过程和之前msg_msg结构内存申请相对应:

ps:拷贝结束的标志均为seg->next指针为NULL

拷贝的总长度则由msgsz决定,而msgsz

可以看到若bufsz足够的情况下,拷贝数据总长度是由msg->m_ts决定的。

需要注意的是,若我们带有MSG_COPY标志,则不会在双向链表上unlink,只会进行copy操作,具体实现在do_msgrcv中部分源码:

若有MSG_COPY标志,源码注释:If we are copying, then do not unlink message and do not update queue parameters.

不会调用list_del()去进行unlink,并且最后free_msg()释放的是我们在内核中copy出来的堆块。也就是说,我们可以通过设置MSG_COPY多次读取一条消息。

通过find_msg定位一节已经讲过:

MSG_COPY位为1的时候,modeSEARCH-NUMBER,在find_msg中会返回msg_msg双向循环链表中,第msgtypmsg_msg,也就是返回第msgtyp条消息,而不是上述表格中根据msgtyp去和msg->m_type进行匹配。

由于MSG_COPY位为1的时候,内核会调用prepare_copy再申请一块内存出来。

申请内存大小为我们传入do_msgrcvbufsz

两个msg_msg之间的拷贝则由copy_msg负责,而在copy_msg函数中有一段代码如下:

若源src->m_ts大于目标dst->m_ts,则会发生溢出,因此会直接返回不会拷贝。

同时copy_msg函数末尾还有赋值操作:

因此我们要满足的条件是src->m_ts<=dst->m_ts即可。

bufsz>=src->m_ts

经过对源码的阅读和分析,可以想到我们可以做到的事情:

(1)改掉msg_msg->m_ts

可以读取最多一页的内存,实现越界读。

(2)改掉msg_msg->m_tsmsg_msg->m_list中的next指针。

可以利用堆喷其他结构体+msg_msg越界读,获得一些堆地址 or 内核地址。

可以堆喷一些一些消息队列,每个消息队列上只有一条消息:即msg->queue双向循环链表里只有一个节点:

可以通过某个msg_msg的越界读,有几率读到其他消息队列的msg_msgm_list字段,而我们构造每条消息队列上只有一条消息

,泄露其m_list,即为msg_queue的地址。泄露完之后,继续伪造msg_msg->next字段可泄露整个该消息队列中每个结构体的地址。

可以实现任意地址读。

但是需要注意的是,我们需要伪造我们需要读的地址target的next指针为NULL,不然在store_msg进行数据拷贝的时候,是以NULL指针为结束判断条件,因此我们需要满足target->next==NULL or target->next->next==NULL,反正需要我们伪造的任意读链表存在一个NULL节点,且中途不能到达不可读地址,否则会造成kernel panic。

do_msgsnd函数中调用了load_msg进行用户到内核的数据拷贝,若我们利用userfault机制暂停一个线程,再在另一个线程中篡改掉msg->next指针,则可以实现任意地址写。

模板采用的arttnba3师傅的模板:

pipe是Linux系统跨进程通信的一种方式。管道是连接一个读进程和一个写进程,以实现它们之间通信的共享文件。基于pipe族系统调用实现(而非open())。而这个文件不是真正的文件,向管道文件读写数据其实是在读写内核缓冲区

pipe() 创建一个管道,一个可用于进程间通信的单向数据通道。 数组 pipefd 用于返回两个指向管道末端的文件描述符。 pipefd[0] 是管道的读端fd。 pipefd[1] 是管道的写端fd。 写端把数据写入管道,直到读端读取数据。

管道不需要open,但需要close释放。

定义在/include/linux/pipe_fs_i.h中:

size为0x30。

在建立管道时,内核首先会申请一个pipe_inode_info结构体,然后在其pipe_inode_info->buf字段申请pipe_buffer结构体:

其中PIPE_DEF_BUFFERS=16;因此会申请0x10*0x30(size of pipe_buffer)的内存,也就是会从kmalloc-1k中取。

主要关注其release指针,在我们关闭一个管道的两端之后,管道会被释放,同样pipe_buffer也会被释放。调用的是函数表中的release指针。调用路径为:free_pipe_info->pipe_buf_release

pipe_buffer中的*pipe_buf_operations成员能泄露内核基地址。

PS:需要注意的是,利用pipe系统调用后需要调用一次写管道才能对函数表进行初始化:

覆写pipe_buffer->pipe_buf_operations->release为某些栈迁移指针。将rsi-->rsp

题目开启了KPTI,SMAP,SMEP等正常保护。

内核版本:

5.11内核版本开始,就禁止非特权用户使用userfaultfd了。所以这道题是userfaultfd版本最后的荣光 (bushi

kernel_release函数中存在指针未清零的情况:

但是kernel_open函数判断了flag字段:

因此我们无法调用两次kernel_open后利用kernel_release的指针悬挂,来造成0x100 size的一个object的UAF。

但是由于kernel_readkernel_writekernel_openkernel_release均未加锁,且readwrite中含有类似于如下的copy_to_user操作:

因此我们可以考虑使用userfaultfd卡住当前进程,在另外一个线程中调用kernel_release。这样同样可以达到一个0x100 size的UAF。

所以笔者是考虑用这个简单的洞来泄露内核基址,用的是0x100 size对应的timerfd_ctx结构体。

不过笔者用这种方法无论如何都无法泄露出内核基址,后来咨询arttnba3师傅后得知:

内核调用fput对文件描述符进行释放,对于文件描述符的关闭会被delay,直到我们读取数据后才会关闭

因此靠这种操作leak内核数据是不可行的。

程序除了module自身的openreleasereadwrite操作。

提供了简单的菜单堆功能:

所有功能都未加锁,因此可以用userfaultfd在edit时将进程卡死,在另一个线程中free掉这个堆块后申请某些object到该地址上,实现0x400 size object的UAF(只能更改一次值)。

申请一个消息队列,上面只放一条消息,且size为0x400。同时申请一个pipe_buffer

改大msg_msg->m_ts,用带有MSG_COPY位的msgrcv进行越界读,泄露出pipe_buffer上的函数表。

得到内核基址

leak内核地址分别消耗了一次add,delete,edit操作。因此我们还有一次UAF的机会。

笔者最先考虑的是用pipe_buffer提权,刚好满足0x400的size,因此我们利用UAF将pipe_buffer->pipe_buf_operations->release函数指针更改为某个栈迁移gadget即可。

但是我并没有找到可利用的gadget,其中有一条可能能达成的:

不过retf是按32位pop eipcs,的,而32位根本不足以储存一个内核地址。

同时还有例如mov esp,esi类型的gadget,由于intel x86&x64的调用约定,当对32位寄存器进行赋值操作的时候,会将高32位寄存器值清零,因此也不可用。

同时注意到程序没有开启CONFIG_STATIC_USERMODEHELPER保护,因此笔者选择用UAF劫持0x400 size的freelistmodprobe_path附近,更改modprobe_path

笔者是申请msg_msg结构体申请到modprobe_path附近,由于size太大,会将modprobe_path附近的所有内容全部清空。

直接进行接下来的提权or 读取flag操作会在成功前引起kernel panic,因此我们需要恢复modprobe_path附近的函数指针。

其中,kmod的函数指针恢复是必要的:

因为modprobe_path是一个Linux程序,最初由Rusty Russell编写,用于在Linux内核中添加一个可加载的内核模块,或者从内核中移除一个可加载的内核模块,因此modprobe是安装某个内核模块,而kmod是一个用于控制linux内核模块的程序,因此在后续调用中需要用到

最后直接利用modprobe_path_hijack更改flag权限后读取即可。

这里由于gcc编译的poc文件过大,远程超时,因此我选择musl-gcc进行编译。

但是奇怪的是,按理来说两种编译方式不会对poc造成影响,gcc的可以正常运行,而musl-gccmodprobe_path_hijack后,第一次调用system时,内核会panic在slub里。

估计是system系统调用execve申请内存时,寄在了某一个没有修复好的freelist里,但是我的这种解法,应该是无法修复freelist的。

后续选择uclibc进行编译就成功了。如果有读者了解为什么musl-gcc编译出来会有这种情况,请务必教教我。

 
 
 
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 */
};
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 */
};
struct list_head {
    struct list_head *next, *prev;
};
struct list_head {
    struct list_head *next, *prev;
};
struct msg_msgseg {
    struct msg_msgseg *next;
    /* the next part of the message follows immediately */
};
struct msg_msgseg {
    struct msg_msgseg *next;
    /* the next part of the message follows immediately */
};
int msgget(key_t key, int msgflag)
int msgget(key_t key, int msgflag)
参数 参数意义
key key的值为函数ftok的返回值或IPC_PRIVATE,若为IPC_PRIVATE则直接创建新的消息队列
msgflag IPC_CREAT:创建新的消息队列。 IPC_EXCL:与IPC_CREAT一同使用,表示如果要创建的消息队列已经存在,则返回错误。(IPC_EXCL没有什么实质性的意义,但是可以帮我们确定是新建了消息队列而不是返回已经存在的消息队列) IPC_NOWAIT:读写消息队列要求无法满足时,不阻塞。返回值: 调用成功返回队列标识符,否则返回-1. 其中该参数需要配合权限控制符,例如0666 OR IPC_CREAT
 
 
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg)
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg)
 
 
static long do_msgsnd(int msqid, long mtype, void __user *mtext,
        size_t msgsz, int msgflg)
{
    struct msg_queue *msq;
    struct msg_msg *msg;
    int err;
    struct ipc_namespace *ns;
    DEFINE_WAKE_Q(wake_q);
 
    ns = current->nsproxy->ipc_ns;
 
    if (msgsz > ns->msg_ctlmax || (long) msgsz < 0 || msqid < 0)
        return -EINVAL;
    if (mtype < 1)
        return -EINVAL;
 
    msg = load_msg(mtext, msgsz);
 
   ...........
static long do_msgsnd(int msqid, long mtype, void __user *mtext,
        size_t msgsz, int msgflg)
{
    struct msg_queue *msq;
    struct msg_msg *msg;
    int err;
    struct ipc_namespace *ns;
    DEFINE_WAKE_Q(wake_q);
 
    ns = current->nsproxy->ipc_ns;
 
    if (msgsz > ns->msg_ctlmax || (long) msgsz < 0 || msqid < 0)
        return -EINVAL;
    if (mtype < 1)
        return -EINVAL;
 
    msg = load_msg(mtext, msgsz);
 
   ...........
struct msg_msg *load_msg(const void __user *src, size_t len)
{
    struct msg_msg *msg;
    struct msg_msgseg *seg;
    int err = -EFAULT;
    size_t alen;
 
    msg = alloc_msg(len);
    if (msg == NULL)
        return ERR_PTR(-ENOMEM);
 
    alen = min(len, DATALEN_MSG);
    if (copy_from_user(msg + 1, src, alen))
        goto out_err;
 
    for (seg = msg->next; seg != NULL; seg = seg->next) {
        len -= alen;
        src = (char __user *)src + alen;
        alen = min(len, DATALEN_SEG);
        if (copy_from_user(seg + 1, src, alen))
            goto out_err;
    }
 
    err = security_msg_msg_alloc(msg);
    if (err)
        goto out_err;
 
    return msg;
 
out_err:
    free_msg(msg);
    return ERR_PTR(err);
}
struct msg_msg *load_msg(const void __user *src, size_t len)
{
    struct msg_msg *msg;
    struct msg_msgseg *seg;
    int err = -EFAULT;
    size_t alen;
 
    msg = alloc_msg(len);
    if (msg == NULL)
        return ERR_PTR(-ENOMEM);
 
    alen = min(len, DATALEN_MSG);
    if (copy_from_user(msg + 1, src, alen))
        goto out_err;
 
    for (seg = msg->next; seg != NULL; seg = seg->next) {
        len -= alen;
        src = (char __user *)src + alen;
        alen = min(len, DATALEN_SEG);
        if (copy_from_user(seg + 1, src, alen))
            goto out_err;
    }
 
    err = security_msg_msg_alloc(msg);
    if (err)
        goto out_err;
 
    return msg;
 
out_err:
    free_msg(msg);
    return ERR_PTR(err);
}
 
#define DATALEN_MSG    ((size_t)PAGE_SIZE-sizeof(struct msg_msg))
#define DATALEN_SEG    ((size_t)PAGE_SIZE-sizeof(struct msg_msgseg))
 
 
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);
    msg = kmalloc(sizeof(*msg) + alen, GFP_KERNEL_ACCOUNT);
    if (msg == NULL)
        return NULL;
 
    msg->next = NULL;
    msg->security = NULL;
 
    len -= alen;
    pseg = &msg->next;
    while (len > 0) {
        struct msg_msgseg *seg;
 
        cond_resched();
 
        alen = min(len, DATALEN_SEG);
        seg = kmalloc(sizeof(*seg) + alen, GFP_KERNEL_ACCOUNT);
        if (seg == NULL)
            goto out_err;
        *pseg = seg;
        seg->next = NULL;
        pseg = &seg->next;
        len -= alen;
    }
 
    return msg;
 
out_err:
    free_msg(msg);
    return NULL;
}
#define DATALEN_MSG    ((size_t)PAGE_SIZE-sizeof(struct msg_msg))
#define DATALEN_SEG    ((size_t)PAGE_SIZE-sizeof(struct msg_msgseg))
 
 
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);
    msg = kmalloc(sizeof(*msg) + alen, GFP_KERNEL_ACCOUNT);
    if (msg == NULL)
        return NULL;
 
    msg->next = NULL;
    msg->security = NULL;
 
    len -= alen;
    pseg = &msg->next;
    while (len > 0) {
        struct msg_msgseg *seg;
 
        cond_resched();
 
        alen = min(len, DATALEN_SEG);
        seg = kmalloc(sizeof(*seg) + alen, GFP_KERNEL_ACCOUNT);
        if (seg == NULL)
            goto out_err;
        *pseg = seg;
        seg->next = NULL;
        pseg = &seg->next;
        len -= alen;
    }
 
    return msg;
 
out_err:
    free_msg(msg);
    return NULL;
}
 
 
 
 
do_msgsnd-->load_msg-->alloc_msg
do_msgsnd-->load_msg-->alloc_msg
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg)
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg)
 
 
 
static struct msg_msg *find_msg(struct msg_queue *msq, long *msgtyp, int mode)
{
    struct msg_msg *msg, *found = NULL;
    long count = 0;
 
    list_for_each_entry(msg, &msq->q_messages, m_list)
    {
        if (testmsg(msg, *msgtyp, mode) &&
            !security_msg_queue_msgrcv(&msq->q_perm, msg, current,
                           *msgtyp, mode))
        {
            if (mode == SEARCH_LESSEQUAL && msg->m_type != 1)
            {
                *msgtyp = msg->m_type - 1;
                found = msg;
            }
            else if (mode == SEARCH_NUMBER)
            {
                if (*msgtyp == count)
                    return msg;
            }
            else
                return msg;
            count++;
        }
    }
 
    return found ?: ERR_PTR(-EAGAIN);
}
static struct msg_msg *find_msg(struct msg_queue *msq, long *msgtyp, int mode)
{
    struct msg_msg *msg, *found = NULL;
    long count = 0;
 
    list_for_each_entry(msg, &msq->q_messages, m_list)
    {
        if (testmsg(msg, *msgtyp, mode) &&
            !security_msg_queue_msgrcv(&msq->q_perm, msg, current,
                           *msgtyp, mode))
        {
            if (mode == SEARCH_LESSEQUAL && msg->m_type != 1)
            {
                *msgtyp = msg->m_type - 1;
                found = msg;
            }
            else if (mode == SEARCH_NUMBER)
            {
                if (*msgtyp == count)
                    return msg;
            }
            else
                return msg;
            count++;
        }
    }
 
    return found ?: ERR_PTR(-EAGAIN);
}
 
 
 
static int testmsg(struct msg_msg *msg, long type, int mode)
{
    switch (mode) {
    case SEARCH_ANY:
    case SEARCH_NUMBER:
        return 1;
    case SEARCH_LESSEQUAL:
        if (msg->m_type <= type)
            return 1;
        break;
    case SEARCH_EQUAL:
        if (msg->m_type == type)
            return 1;
        break;
    case SEARCH_NOTEQUAL:
        if (msg->m_type != type)
            return 1;
        break;
    }
    return 0;
}
static int testmsg(struct msg_msg *msg, long type, int mode)
{
    switch (mode) {
    case SEARCH_ANY:
    case SEARCH_NUMBER:
        return 1;
    case SEARCH_LESSEQUAL:
        if (msg->m_type <= type)
            return 1;
        break;
    case SEARCH_EQUAL:
        if (msg->m_type == type)
            return 1;
        break;
    case SEARCH_NOTEQUAL:
        if (msg->m_type != type)
            return 1;
        break;
    }
    return 0;
}
static inline int convert_mode(long *msgtyp, int msgflg)
{
    if (msgflg & MSG_COPY)
        return SEARCH_NUMBER;
    /*
     *  find message of correct type.
     *  msgtyp = 0 => get first.
     *  msgtyp > 0 => get first message of matching type.
     *  msgtyp < 0 => get message with least type must be < abs(msgtype).
     */
    if (*msgtyp == 0)
        return SEARCH_ANY;
    if (*msgtyp < 0) {
        if (*msgtyp == LONG_MIN) /* -LONG_MIN is undefined */
            *msgtyp = LONG_MAX;
        else
            *msgtyp = -*msgtyp;
        return SEARCH_LESSEQUAL;
    }
    if (msgflg & MSG_EXCEPT)
        return SEARCH_NOTEQUAL;
    return SEARCH_EQUAL;
}
static inline int convert_mode(long *msgtyp, int msgflg)
{
    if (msgflg & MSG_COPY)
        return SEARCH_NUMBER;
    /*
     *  find message of correct type.
     *  msgtyp = 0 => get first.
     *  msgtyp > 0 => get first message of matching type.
     *  msgtyp < 0 => get message with least type must be < abs(msgtype).
     */
    if (*msgtyp == 0)
        return SEARCH_ANY;
    if (*msgtyp < 0) {
        if (*msgtyp == LONG_MIN) /* -LONG_MIN is undefined */
            *msgtyp = LONG_MAX;
        else
            *msgtyp = -*msgtyp;
        return SEARCH_LESSEQUAL;
    }
    if (msgflg & MSG_EXCEPT)
        return SEARCH_NOTEQUAL;
    return SEARCH_EQUAL;
}
msgtyp mode 效果
<0 SEARCH_LESSEQUAL 找到一个msg->m_type小于msgtypmsg->m_type最小的msg_msg
=0 SEARCH_ANY 找到msg_msg队列中第一个msg_msg
>0 SEARCH_EQUAL\ \ SEARCH_NOTEQUAL 找到第一个msg->m_type等于/不等于msgtypmsg_msg
 
static long do_msgrcv(int msqid, void __user *buf, size_t bufsz, long msgtyp, int msgflg,
           long (*msg_handler)(void __user *, struct msg_msg *, size_t))
{
    int mode;
    struct msg_queue *msq;
    struct ipc_namespace *ns;
    struct msg_msg *msg, *copy = NULL;
    ...........
    ...........
    list_del(&msg->m_list);
    ...........
    ...........
    free_msg(msg);
 
    return bufsz;
static long do_msgrcv(int msqid, void __user *buf, size_t bufsz, long msgtyp, int msgflg,
           long (*msg_handler)(void __user *, struct msg_msg *, size_t))
{
    int mode;
    struct msg_queue *msq;
    struct ipc_namespace *ns;
    struct msg_msg *msg, *copy = NULL;
    ...........
    ...........
    list_del(&msg->m_list);
    ...........
    ...........
    free_msg(msg);
 
    return bufsz;
bufsz = msg_handler(buf, msg, bufsz);
bufsz = msg_handler(buf, msg, bufsz);
long ksys_msgrcv(int msqid, struct msgbuf __user *msgp, size_t msgsz,
         long msgtyp, int msgflg)
{
    return do_msgrcv(msqid, msgp, msgsz, msgtyp, msgflg, do_msg_fill);
}
long ksys_msgrcv(int msqid, struct msgbuf __user *msgp, size_t msgsz,
         long msgtyp, int msgflg)
{
    return do_msgrcv(msqid, msgp, msgsz, msgtyp, msgflg, do_msg_fill);
}
static long do_msg_fill(void __user *dest, struct msg_msg *msg, size_t bufsz)
{
    struct msgbuf __user *msgp = dest;
    size_t msgsz;
 
    if (put_user(msg->m_type, &msgp->mtype))
        return -EFAULT;
 
    msgsz = (bufsz > msg->m_ts) ? msg->m_ts : bufsz;
    if (store_msg(msgp->mtext, msg, msgsz))
        return -EFAULT;
    return msgsz;
}
static long do_msg_fill(void __user *dest, struct msg_msg *msg, size_t bufsz)
{
    struct msgbuf __user *msgp = dest;
    size_t msgsz;
 
    if (put_user(msg->m_type, &msgp->mtype))
        return -EFAULT;
 
    msgsz = (bufsz > msg->m_ts) ? msg->m_ts : bufsz;
    if (store_msg(msgp->mtext, msg, msgsz))
        return -EFAULT;
    return msgsz;
}
int store_msg(void __user *dest, struct msg_msg *msg, size_t len)
{
    size_t alen;
    struct msg_msgseg *seg;
 
    alen = min(len, DATALEN_MSG);
    if (copy_to_user(dest, msg + 1, alen))
        return -1;
 
    for (seg = msg->next; seg != NULL; seg = seg->next) {
        len -= alen;
        dest = (char __user *)dest + alen;
        alen = min(len, DATALEN_SEG);
        if (copy_to_user(dest, seg + 1, alen))
            return -1;
    }
    return 0;
}
int store_msg(void __user *dest, struct msg_msg *msg, size_t len)
{
    size_t alen;
    struct msg_msgseg *seg;
 
    alen = min(len, DATALEN_MSG);
    if (copy_to_user(dest, msg + 1, alen))
        return -1;
 
    for (seg = msg->next; seg != NULL; seg = seg->next) {
        len -= alen;
        dest = (char __user *)dest + alen;
        alen = min(len, DATALEN_SEG);
        if (copy_to_user(dest, seg + 1, alen))
            return -1;
    }
    return 0;
}
 
msgsz = (bufsz > msg->m_ts) ? msg->m_ts : bufsz;
msgsz = (bufsz > msg->m_ts) ? msg->m_ts : bufsz;
 
 
if (!IS_ERR(msg)) {
            /*
             * Found a suitable message.
             * Unlink it from the queue.
             */
            if ((bufsz < msg->m_ts) && !(msgflg & MSG_NOERROR)) {
                msg = ERR_PTR(-E2BIG);
                goto out_unlock0;
            }
            /*
             * If we are copying, then do not unlink message and do
             * not update queue parameters.
             */
            if (msgflg & MSG_COPY) {
                msg = copy_msg(msg, copy);
                goto out_unlock0;
            }
 
            list_del(&msg->m_list);
            msq->q_qnum--;
            msq->q_rtime = ktime_get_real_seconds();
            ipc_update_pid(&msq->q_lrpid, task_tgid(current));
            msq->q_cbytes -= msg->m_ts;
            atomic_sub(msg->m_ts, &ns->msg_bytes);
            atomic_dec(&ns->msg_hdrs);
            ss_wakeup(msq, &wake_q, false);
 
            goto out_unlock0;
        }
if (!IS_ERR(msg)) {
            /*
             * Found a suitable message.
             * Unlink it from the queue.
             */
            if ((bufsz < msg->m_ts) && !(msgflg & MSG_NOERROR)) {
                msg = ERR_PTR(-E2BIG);
                goto out_unlock0;
            }
            /*
             * If we are copying, then do not unlink message and do
             * not update queue parameters.
             */
            if (msgflg & MSG_COPY) {
                msg = copy_msg(msg, copy);
                goto out_unlock0;
            }
 
            list_del(&msg->m_list);
            msq->q_qnum--;
            msq->q_rtime = ktime_get_real_seconds();
            ipc_update_pid(&msq->q_lrpid, task_tgid(current));
            msq->q_cbytes -= msg->m_ts;
            atomic_sub(msg->m_ts, &ns->msg_bytes);
            atomic_dec(&ns->msg_hdrs);
            ss_wakeup(msq, &wake_q, false);
 
            goto out_unlock0;
        }
 
static inline struct msg_msg *prepare_copy(void __user *buf, size_t bufsz)
{
    struct msg_msg *copy;
 
    /*
     * Create dummy message to copy real message to.
     */
    copy = load_msg(buf, bufsz);
    if (!IS_ERR(copy))
        copy->m_ts = bufsz;
    return copy;
static inline struct msg_msg *prepare_copy(void __user *buf, size_t bufsz)
{
    struct msg_msg *copy;
 
    /*
     * Create dummy message to copy real message to.
     */
    copy = load_msg(buf, bufsz);
    if (!IS_ERR(copy))
        copy->m_ts = bufsz;
    return copy;
 
if (src->m_ts > dst->m_ts)
        return ERR_PTR(-EINVAL);
if (src->m_ts > dst->m_ts)
        return ERR_PTR(-EINVAL);
 
dst->m_ts = src->m_ts
dst->m_ts = src->m_ts
 
 
 
 
 
 
 
 
 
 
 
struct list_head {
    uint64_t    next;
    uint64_t    prev;
};
 
struct msg_msg {
    struct list_head m_list;
    uint64_t    m_type;
    uint64_t    m_ts;
    uint64_t    next;
    uint64_t    security;
};
 
struct msg_msgseg {
    uint64_t    next;
};
 
 
struct msgbuf {
    long mtype;
    char mtext[0];
};
 
 
int getMsgQueue(void)
{
    return msgget(IPC_PRIVATE, 0666 | IPC_CREAT);
}
 
int readMsg(int msqid, void *msgp, size_t msgsz, long msgtyp)
{
    return msgrcv(msqid, msgp, msgsz, msgtyp, 0);
}
 
/**
 * the msgp should be a pointer to the `struct msgbuf`,
 * and the data should be stored in msgbuf.mtext
 */
int writeMsg(int msqid, void *msgp, size_t msgsz, long msgtyp)
{
    ((struct msgbuf*)msgp)->mtype = msgtyp;
    return msgsnd(msqid, msgp, msgsz, 0);
}
 
/* for MSG_COPY, `msgtyp` means to read no.msgtyp msg_msg on the queue */
int peekMsg(int msqid, void *msgp, size_t msgsz, long msgtyp)
{
    return msgrcv(msqid, msgp, msgsz, msgtyp,
                  MSG_COPY | IPC_NOWAIT | MSG_NOERROR);
}
 
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;
}
struct list_head {
    uint64_t    next;
    uint64_t    prev;
};
 
struct msg_msg {
    struct list_head m_list;
    uint64_t    m_type;
    uint64_t    m_ts;
    uint64_t    next;
    uint64_t    security;
};
 
struct msg_msgseg {
    uint64_t    next;
};
 
 
struct msgbuf {
    long mtype;
    char mtext[0];
};
 
 
int getMsgQueue(void)
{
    return msgget(IPC_PRIVATE, 0666 | IPC_CREAT);
}
 
int readMsg(int msqid, void *msgp, size_t msgsz, long msgtyp)
{
    return msgrcv(msqid, msgp, msgsz, msgtyp, 0);
}
 
/**
 * the msgp should be a pointer to the `struct msgbuf`,
 * and the data should be stored in msgbuf.mtext
 */
int writeMsg(int msqid, void *msgp, size_t msgsz, long msgtyp)
{
    ((struct msgbuf*)msgp)->mtype = msgtyp;
    return msgsnd(msqid, msgp, msgsz, 0);
}
 
/* for MSG_COPY, `msgtyp` means to read no.msgtyp msg_msg on the queue */
int peekMsg(int msqid, void *msgp, size_t msgsz, long msgtyp)
{
    return msgrcv(msqid, msgp, msgsz, msgtyp,
                  MSG_COPY | IPC_NOWAIT | MSG_NOERROR);
}
 
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;
}
#include <unistd.h>
int pipe(int pipefd[2]);
#include <unistd.h>
int pipe(int pipefd[2]);
 
struct pipe_buffer {
    struct page *page;
    unsigned int offset, len;
    const struct pipe_buf_operations *ops;
    unsigned int flags;
    unsigned long private;
};
struct pipe_buffer {
    struct page *page;
    unsigned int offset, len;
    const struct pipe_buf_operations *ops;
    unsigned int flags;
    unsigned long private;
};
struct pipe_inode_info *alloc_pipe_info(void)
{
    struct pipe_inode_info *pipe;
    unsigned long pipe_bufs = PIPE_DEF_BUFFERS;
    struct user_struct *user = get_current_user();
    unsigned long user_bufs;
    unsigned int max_size = READ_ONCE(pipe_max_size);
 
    pipe = kzalloc(sizeof(struct pipe_inode_info), GFP_KERNEL_ACCOUNT);
    if (pipe == NULL)
        goto out_free_uid;
 
    if (pipe_bufs * PAGE_SIZE > max_size && !capable(CAP_SYS_RESOURCE))
        pipe_bufs = max_size >> PAGE_SHIFT;
 
    user_bufs = account_pipe_buffers(user, 0, pipe_bufs);
 
    if (too_many_pipe_buffers_soft(user_bufs) && pipe_is_unprivileged_user()) {
        user_bufs = account_pipe_buffers(user, pipe_bufs, PIPE_MIN_DEF_BUFFERS);
        pipe_bufs = PIPE_MIN_DEF_BUFFERS;
    }
 
    if (too_many_pipe_buffers_hard(user_bufs) && pipe_is_unprivileged_user())
        goto out_revert_acct;
 
    pipe->bufs = kcalloc(pipe_bufs, sizeof(struct pipe_buffer),
                 GFP_KERNEL_ACCOUNT);
 
    if (pipe->bufs) {
        init_waitqueue_head(&pipe->rd_wait);
        init_waitqueue_head(&pipe->wr_wait);
        pipe->r_counter = pipe->w_counter = 1;
        pipe->max_usage = pipe_bufs;
        pipe->ring_size = pipe_bufs;
        pipe->nr_accounted = pipe_bufs;
        pipe->user = user;
        mutex_init(&pipe->mutex);
        return pipe;
    }
 
out_revert_acct:
    (void) account_pipe_buffers(user, pipe_bufs, 0);
    kfree(pipe);
out_free_uid:
    free_uid(user);
    return NULL;
}
struct pipe_inode_info *alloc_pipe_info(void)
{
    struct pipe_inode_info *pipe;
    unsigned long pipe_bufs = PIPE_DEF_BUFFERS;
    struct user_struct *user = get_current_user();
    unsigned long user_bufs;
    unsigned int max_size = READ_ONCE(pipe_max_size);
 
    pipe = kzalloc(sizeof(struct pipe_inode_info), GFP_KERNEL_ACCOUNT);
    if (pipe == NULL)
        goto out_free_uid;
 
    if (pipe_bufs * PAGE_SIZE > max_size && !capable(CAP_SYS_RESOURCE))
        pipe_bufs = max_size >> PAGE_SHIFT;
 
    user_bufs = account_pipe_buffers(user, 0, pipe_bufs);
 
    if (too_many_pipe_buffers_soft(user_bufs) && pipe_is_unprivileged_user()) {
        user_bufs = account_pipe_buffers(user, pipe_bufs, PIPE_MIN_DEF_BUFFERS);
        pipe_bufs = PIPE_MIN_DEF_BUFFERS;
    }
 
    if (too_many_pipe_buffers_hard(user_bufs) && pipe_is_unprivileged_user())
        goto out_revert_acct;
 
    pipe->bufs = kcalloc(pipe_bufs, sizeof(struct pipe_buffer),
                 GFP_KERNEL_ACCOUNT);
 
    if (pipe->bufs) {
        init_waitqueue_head(&pipe->rd_wait);
        init_waitqueue_head(&pipe->wr_wait);
        pipe->r_counter = pipe->w_counter = 1;
        pipe->max_usage = pipe_bufs;
        pipe->ring_size = pipe_bufs;
        pipe->nr_accounted = pipe_bufs;
        pipe->user = user;
        mutex_init(&pipe->mutex);
        return pipe;
    }
 
out_revert_acct:
    (void) account_pipe_buffers(user, pipe_bufs, 0);
    kfree(pipe);
out_free_uid:
    free_uid(user);
    return NULL;
}
unsigned long pipe_bufs = PIPE_DEF_BUFFERS;
pipe->bufs = kcalloc(pipe_bufs, sizeof(struct pipe_buffer),
                 GFP_KERNEL_ACCOUNT);
unsigned long pipe_bufs = PIPE_DEF_BUFFERS;
pipe->bufs = kcalloc(pipe_bufs, sizeof(struct pipe_buffer),
                 GFP_KERNEL_ACCOUNT);
struct pipe_buf_operations {
    /*
     * ->confirm() verifies that the data in the pipe buffer is there
     * and that the contents are good. If the pages in the pipe belong
     * to a file system, we may need to wait for IO completion in this
     * hook. Returns 0 for good, or a negative error value in case of
     * error.  If not present all pages are considered good.
     */
    int (*confirm)(struct pipe_inode_info *, struct pipe_buffer *);
 
    /*
     * When the contents of this pipe buffer has been completely
     * consumed by a reader, ->release() is called.
     */
    void (*release)(struct pipe_inode_info *, struct pipe_buffer *);
 
    /*
     * Attempt to take ownership of the pipe buffer and its contents.
     * ->try_steal() returns %true for success, in which case the contents
     * of the pipe (the buf->page) is locked and now completely owned by the
     * caller. The page may then be transferred to a different mapping, the
     * most often used case is insertion into different file address space
     * cache.
     */
    bool (*try_steal)(struct pipe_inode_info *, struct pipe_buffer *);
 
    /*
     * Get a reference to the pipe buffer.
     */
    bool (*get)(struct pipe_inode_info *, struct pipe_buffer *);
};
struct pipe_buf_operations {
    /*
     * ->confirm() verifies that the data in the pipe buffer is there
     * and that the contents are good. If the pages in the pipe belong
     * to a file system, we may need to wait for IO completion in this
     * hook. Returns 0 for good, or a negative error value in case of
     * error.  If not present all pages are considered good.
     */
    int (*confirm)(struct pipe_inode_info *, struct pipe_buffer *);
 
    /*
     * When the contents of this pipe buffer has been completely
     * consumed by a reader, ->release() is called.
     */
    void (*release)(struct pipe_inode_info *, struct pipe_buffer *);
 
    /*
     * Attempt to take ownership of the pipe buffer and its contents.
     * ->try_steal() returns %true for success, in which case the contents
     * of the pipe (the buf->page) is locked and now completely owned by the
     * caller. The page may then be transferred to a different mapping, the
     * most often used case is insertion into different file address space
     * cache.
     */
    bool (*try_steal)(struct pipe_inode_info *, struct pipe_buffer *);

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2023-4-7 15:15 被kanxue编辑 ,原因:
上传的附件:
收藏
免费 6
支持
分享
最新回复 (7)
雪    币: 1221
活跃值: (1157)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
2
链接:https://pan.baidu.com/s/1jKxH_0HSjse1rIQMSTKHXQ 
提取码:Lotu
2023-4-2 13:23
0
雪    币: 128
活跃值: (8224)
能力值: ( LV13,RANK:438 )
在线值:
发帖
回帖
粉丝
3
2023-4-2 15:49
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
4
2023-4-2 16:13
0
雪    币: 2267
活跃值: (1523)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
5
学习
2023-4-4 08:27
0
雪    币: 262
活跃值: (758)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
6

大佬太强了

最后于 2023-4-10 19:56 被_emmm_编辑 ,原因:
2023-4-10 19:56
0
雪    币: 1221
活跃值: (1157)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
7
_emmm_ 大佬太强了
think yyds
2023-4-11 12:03
0
雪    币: 6008
活跃值: (2585)
能力值: ( LV9,RANK:250 )
在线值:
发帖
回帖
粉丝
8

这里提供一个gadget可以直接通过pipe_buffer提权,0xFFFFFFFF812C4CCE -- push rsi;pop rsp;----;pop_4;ret

最后于 2023-10-9 16:42 被XiaozaYa编辑 ,原因:
2023-10-9 16:41
0
游客
登录 | 注册 方可回帖
返回
//