EMET failed
Il y a quelques jours, la team MSRC de Microsoft a sortit EMET (Enhanced Mitigation Evaluation Toolkit). Un outil aidant à la gestion des différentes protections fournies par Windows, comme le DEP. Je me suis dit qu’il serait intéressant d’y jeter un oeil pour voir si toutes les protections mises en place sont solides.
EMET est constitué de plusieurs binaires :
- EMET_conf.exe qui permet de configurer les binaires qu’on veut protéger.
- EMET_launcher.exe et EMET_launcher64.exe qui vont initialiser le processus à protéger
- EMET.dll la DLL qui est injecté dans le processus pour mettre en place la protection.
En fait EMET se base sur l’Image File Execution Options. Cette feature de Windows permet d’exécuter un binaire à la place d’un autre, cela est indiqué par la clé registre ‘HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options’. On spécifie une sous-clé contenant le nom du processus et les valeurs qui vont déterminer le comportement à avoir quand ce processus sera lancé. EMET utilise la valeur « Debugger » pour faire appel au launcher qui va aller lire « HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\EMET » pour parser les options à appliquer au niveau de la protection.
Une des features qui m’a le plus intéressé est l’anti-allocation en 0. Je cite :
NULL page allocation
This blocks attackers from being able to take advantage of NULL dereferences in user mode. It functions by allocating the first page of memory before the program starts. Right now the exploitation techniques for these types of vulnerabilities are only theoretical. However, this mitigation will protect you even if that changes. Please note this protection does not impact kernel mode NULL dereferences as the current version of EMET only supports user mode mitigations.
J’ai disass EMET.dll et je suis tombé sur le code suivant :
int __fastcall sub_110E0(int a1) { int v1; // eax@3 HMODULE v2; // eax@6 FARPROC v3; // eax@7 int result; // eax@9 int v5; // [sp+4h] [bp-4h]@1 v5 = a1; if ( sub_11070(L"heap_allocations") == 1 ) sub_11190(); v1 = sub_11070(L"null_allocation"); if ( v1 == 1 ) VirtualAlloc((LPVOID)v1, 0x400u, 0x3000u, v1); if ( sub_11070(L"enable_dep") == 1 ) { v2 = GetModuleHandleW(L"Kernel32.dll"); if ( v2 ) { v3 = GetProcAddress(v2, "SetProcessDEPPolicy"); if ( v3 ) ((int (__stdcall *)(signed int))v3)(3); } } result = sub_11070(L"sehop"); if ( result == 1 ) { GetModuleHandleExW(5, sub_110E0, &v5); result = sub_11610(); dword_1B6B8 = 1; } return result; }
On voit très bien que la protection ‘anti-allocation-en-0′ est basé sur cet appel à VirtualAlloc() :
VirtualAlloc(1, 1024, MEM_COMMIT|MEM_RESERVE, PAGE_NOACCESS);
Sachant cela je me code un petit binaire qui effectue exactement la même opération et là c’est le drame, VirtuallAlloc renvoie 0, indiquant une erreur de type ‘ERROR_INVALID_PARAMETER (00000057)’.
Je trace alors un peu le code de VirtuaAlloc pour tomber sur ceci :
LPVOID __stdcall VirtualAlloc(LPVOID lpAddress, DWORD dwSize, DWORD flAllocationType, DWORD flProtect) { return VirtualAllocEx((HANDLE)0xFFFFFFFF, lpAddress, dwSize, flAllocationType, flProtect); } LPVOID __stdcall VirtualAllocEx(HANDLE hProcess, LPVOID lpAddress, DWORD dwSize, DWORD flAllocationType, DWORD flProtect) { int v5; // eax@2 int v7; // [sp+Ch] [bp-20h]@2 CPPEH_RECORD ms_exc; // [sp+14h] [bp-18h]@2 if ( lpAddress && (unsigned int)lpAddress < 0x1000) ) { SetLastError(0x57u); } else { v5 = NtAllocateVirtualMemory(hProcess, &lpAddress, 0, &dwSize, flAllocationType, flProtect); v7 = v5; ms_exc.disabled = -1; if ( v5 >= 0 ) return lpAddress; BaseSetLastNTError(v5); } return 0; }
En fait si VirtualAlloc me renvoie une erreur c’est parce que le paramètre ‘lpAddress’ est inférieur à 0×1000). On regarde la doc :
lpAddress [in, optional]
The starting address of the region to allocate. If the memory is being reserved, the specified address is rounded down to the nearest multiple of the allocation granularity. If the memory is already reserved and is being committed, the address is rounded down to the next page boundary. To determine the size of a page and the allocation granularity on the host computer, use the GetSystemInfo function. If this parameter is NULL, the system determines where to allocate the region.
On nous dit que si ce paramètre est à 0 alors le système choisit l’adresse de l’allocation, sinon l’utilisateur peut spécifier une adresse.
Apparemment une adresse inférieure à 0×1000 n’est pas apprécié par VirtualAlloc c’est pourquoi dans EMET.dll l’appel échoue. On peut donc tout naturellement allouer de la mémoire en 0 dans un processus protéger avec EMET grâce ce code :
// // Allocate NULL address in userland memory space // BOOL AllocateNullPage() { NTSTATUS Status; DWORD Addr=1; DWORD Len=0x1000; Status=ZwAllocateVirtualMemory(GetCurrentProcess(), Addr, 0, &Len, MEM_RESERVE|MEM_COMMIT|MEM_TOP_DOWN, PAGE_EXECUTE_READWRITE); if(!NT_SUCCESS(Status)) { printf("[-] Error with ZwAllocateVirtualMemory : 0x%x\n", Status); return FALSE; } return TRUE; }
On utilise directement ZwAllocateVirtualMemory qui nous évite de passer par VirtualAlloc.
Donc, quand je lis dans le readme.rtf de EMET :
EMET has been tested with the following OS:
32 bit: Windows XP, Server 2003, Vista, Server 2008 and Windows 7
64 bit: Vista and Windows 7 and Windows 2008 R2
Je me dis : failed ! (pour info je suis sous WinXP SP3).
Pour corriger le tir il suffirait de faire appel directement à ZwAllocateVirtualMemory pour mettre la page en 0 en PAGE_NOACCESS.
Enfin je trouve qu’EMET ressemble pas mal à WehnTrust.
Sinon hdmoore s’est fait plaisir en lâchant les adresses d’opcodes intéressantes pour l’exploitation au sein de la DLL
12 comments novembre 1st, 2009