Rechercher
Fermer ce champ de recherche.

Vous êtes victime d’un incident de sécurité ? Contactez notre CERT

09/07/2018

Blog technique

Portable Executable format, compilation timestamps and /Brepro flag

Equipe CERT

Portable Executable binaries embed timestamps stored by the compiler, which may in some cases appear inconsistent. This article details the origin of these inconsistencies and gives a code sample which may be used to get only the correct timestamps for threat hunting purposes.

When performing threat hunting, a quick detection might be a comparison between compilation timestamps set by the compiler in the file itself and operating system timestamps: if the file has been compiled after its local filesystem creation, there is definitely something to investigate as something obvisouly changed this system creation date. Compilation timestamps are set by the compiler in the Portable Executable (PE) file in multiple locations: in its PE header itself (in the IMAGE_FILE_HEADER.TimeDateStamp field) or in several data directories which may also have a TimeDateStamp field.

While performing our tests, we actually found a lot of issues on a basic Windows installation. Almost all legitimate Microsoft PE files appeared having obvious invalid timestamps (i.e set to future or really old dates)… Moreover, any timestamp in the various data directories were set to the same invalid value (except for IMAGE_IMPORT_DESCRIPTOR/IMAGE_RESOURCE_DIRECTORY ones which are almost always set to 0x0 or 0xFFFFFFFF). We also found that loading these modules in the WinDbg Preview debugger and printing their information (e.g with lmDvmKERNEL32) spawned two messages, « Image was built with /Brepro flag. » and « (This is a reproducible build file hash, not a timestamp) », indicating that this value is not a regular timestamp, and that there is something which indicates that it has been built with this particular linker flag.

In order to find this something, we tried setting this /Brepro flag in the VS2013 Express linker. The resulting PE files had an invalid timestamp, set to 0xFFFFFFFF. In VS2017 the /Brepro linker flag actually produces « Build IDs », not 0xFFFFFFFF ones. We did not try with VS2015 nor searched to find out how these « Build IDs » are computed. This flag actually strips any timestamp information in the final file, resulting in « reproductible » builds, but seems to remain undocumented by Microsoft.

As we’re a little lazy and did not want to perform some WinDbg reverse engineering, and thought that this information should be embedded either in the Rich header, in the PE header itself or in its data directories. We quickly diffed the files with the pefile python tool and found that all of the /Brepro compiled ones had a custom IMAGE_DEBUG_DIRECTORY entry with a 0x10 type (undocumented). When changing this type, WinDbg Preview did not display the « /Brepro Build ID » message anymore.

Therefore we think that:

  • IMAGE_DEBUG_DIRECTORY entries should definitely be considered when looking at compilation timestamps;
  • attackers who spoof their compilation timestamps manually (i.e without the /Brepro linker flag) may forget changing the timestamps present in some data directories;
  • timestamps of files without « /Brepro » IMAGE_DEBUG_DIRECTORY entries should be considered as valid ones. However we did not check if other compilers set invalid compilation timestamps without this particular debug directory, so let us know if you find ones;
  • a PE file with a « /Brepro » IMAGE_DEBUG_DIRECTORY entry should also embed other debugging information (as its main purpose is debugging) and have been built by Microsoft (as it’s not a widely documented feature).

You may find a quick and dirty C code below which parses a PE file in order to print the compilation timestamp and searchs for this particular debug directory.

				
					#define BUFF_SIZE 0x1000
BOOL GetPEFileCompilationTimeStamp(
    __in LPCSTR fPath) {
    UCHAR fileData[BUFF_SIZE];
    HANDLE hFile = NULL;
    ULONG cbRead = 0, peHeaderOffset = 0, i = 0, sectionsCount = 0, debugDirectoryRVA = 0, debugDirectoryOffset = 0, debugDirectorySize = 0;
    PIMAGE_NT_HEADERS64 peHeaderPtr = NULL;
    PIMAGE_DATA_DIRECTORY pImageDataDirectory = NULL;
    PIMAGE_SECTION_HEADER pCurrentSection = NULL;
    PIMAGE_DEBUG_DIRECTORY pDebugDirectory = NULL;
    SIZE_T currentMaxAddr = 0;

    hFile = CreateFileA(fPath,
        GENERIC_READ,
        FILE_SHARE_READ,
        NULL,
        OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL,
        0
        );
    if (hFile == INVALID_HANDLE_VALUE) {
        printf("CreateFileA errorn");
        return FALSE;
    }
    printf("%sn", fPath);

    if (ReadFile(hFile, fileData, BUFF_SIZE, &cbRead, NULL) == TRUE) {
        if (cbRead > 0xF0) {
            currentMaxAddr = (SIZE_T)fileData + cbRead;

            if (fileData[0] == 'M' && fileData[1] == 'Z') {
                peHeaderOffset = *(PULONG)(fileData + 0x3C);

                if (peHeaderOffset + sizeof(IMAGE_NT_HEADERS64) < cbRead) {
                    peHeaderPtr = (PIMAGE_NT_HEADERS64)(peHeaderOffset + fileData);

                    if (peHeaderPtr->Signature == 0x4550) {
                        printf("tPE.TimeDateStamp: %xn", peHeaderPtr->FileHeader.TimeDateStamp);

                        // is there any debug data directory?
                        pImageDataDirectory = NULL;
                        if ((peHeaderPtr->FileHeader.Machine & IMAGE_FILE_MACHINE_AMD64)&nbsp;!= 0) {
                            sectionsCount = peHeaderPtr->FileHeader.NumberOfSections;
                            pCurrentSection = (PIMAGE_SECTION_HEADER)((SIZE_T)&(peHeaderPtr->OptionalHeader) + peHeaderPtr->FileHeader.SizeOfOptionalHeader);

                            if (peHeaderPtr->FileHeader.SizeOfOptionalHeader&nbsp;!= 0) {
                                pImageDataDirectory = (PIMAGE_DATA_DIRECTORY)((SIZE_T)(&peHeaderPtr->OptionalHeader) + 160);
                            }
                        }
                        else if ((peHeaderPtr->FileHeader.Machine & IMAGE_FILE_MACHINE_I386)&nbsp;!= 0) {
                            if (peHeaderPtr->FileHeader.SizeOfOptionalHeader&nbsp;!= 0) {
                                pImageDataDirectory = (PIMAGE_DATA_DIRECTORY)((SIZE_T)(&peHeaderPtr->OptionalHeader) + 144);
                            }
                        }

                        if (pImageDataDirectory&nbsp;!= NULL) {

                            if ((SIZE_T)pImageDataDirectory + sizeof(IMAGE_DATA_DIRECTORY) < currentMaxAddr) {

                                debugDirectoryRVA = pImageDataDirectory->VirtualAddress;
                                debugDirectorySize = pImageDataDirectory->Size;

                                if (debugDirectoryRVA&nbsp;!= 0) {
                                    printf("tDebug Image Data Directory found, RVA: %x, Size: %xn", debugDirectoryRVA, debugDirectorySize);

                                    // let's find its raw file offset
                                    if (sectionsCount * sizeof(IMAGE_SECTION_HEADER) + (SIZE_T)pCurrentSection < currentMaxAddr) {
                                        for (i = 0; i < sectionsCount; i++) {
                                            if (pCurrentSection[i].VirtualAddress < debugDirectoryRVA &&
                                                (pCurrentSection[i].SizeOfRawData + pCurrentSection[i].VirtualAddress) > debugDirectoryRVA) {
                                                debugDirectoryOffset = debugDirectoryRVA - pCurrentSection[i].VirtualAddress + pCurrentSection[i].PointerToRawData;
                                                break;
                                            }
                                        }
                                    }
                                }

                                if (debugDirectoryOffset&nbsp;!= 0 && debugDirectorySize < BUFF_SIZE) {

                                    printf("tDebug Image Data Directory at offset %xn", debugDirectoryOffset);
                                    if (SetFilePointer(hFile, debugDirectoryOffset, 0, FILE_BEGIN)&nbsp;!= INVALID_SET_FILE_POINTER) {

                                        // let's read the whole data directory and walk it
                                        if (ReadFile(hFile, fileData, debugDirectorySize, &cbRead, NULL) == TRUE) {

                                            pDebugDirectory = (PIMAGE_DEBUG_DIRECTORY)fileData;
                                            for (i = 0; i < debugDirectorySize / sizeof(IMAGE_DEBUG_DIRECTORY); i++) {
                                                if ((SIZE_T)&pDebugDirectory[i] < (SIZE_T)fileData + cbRead) {
                                                    //printf("tDebug directory #%d: type 0x%xn", i, pDebugDirectory[i].Type);
                                                    if (pDebugDirectory[i].Type == 0x10) {
                                                        printf("tDebug Image Data Directory with 0x10 type found, /Brepro flag was set (TimeDateStamp: %x)n", pDebugDirectory[i].TimeDateStamp);
                                                        break;
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    CloseHandle(hFile);
    hFile = NULL;
    return TRUE;
}

				
			

Voir les derniers articles du Blog technique

12 juillet 2024
A travers cet article, plusieurs write ups sur la crypto détaillant notre méthode de résolution de challenges et la façon […]
27 juin 2024
A travers cet article, plusieurs WriteUps sur le reverse engineering détaillant notre méthode de résolution de challenges et la façon […]
4 juin 2024
A series about post-quantum cryptography: Part III: Hybridization’s DIY Tutorial
4 juin 2024
A series about post-quantum cryptography: Part II: Hybridization
4 juin 2024
A serie about post-quantum cryptography: Part I: A Post-Quantum World
11 avril 2024
M&NTIS Platform est une solution SaaS destinée au test d'efficacité de produits de défense (AV, EDR, sondes réseau/NDR, SIEM, XDR, […]
25 mars 2024
Through this article, we propose a way to find LPE in Windows applications, by using SysInternals tools. What and how […]
31 janvier 2024
During the inter-CESTI challenge organized by ANSSI, many vulnerabilities were included to test our abilities to find them and, if […]
4 décembre 2023
Find here the crypto and reverse challenges that our teams created for the European Cyber Week pre-qualification and qualification tests […]
29 mars 2023
On the 24th of January, AMOSSYS and Malizen put together a Blue Team CTF, for the SupSec seminar organized by […]