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

20 décembre 2024
La sécurité informatique peut paraître, pour beaucoup, comme un centre de coût et de complexité : plan d’audits à mettre en […]
16 décembre 2024
Après avoir exploré les vulnérabilités inhérentes aux modèles de langage à grande échelle (LLM) dans notre série d'articles, il est […]
28 novembre 2024
L'exfiltration de modèles LLM (Model Theft in english) par des acteurs malveillants ou des groupes de cyberespionnage avancés est une […]
26 novembre 2024
La surconfiance (Overreliance en anglais) peut survenir lorsqu'un LLM produit des informations erronées et les présente de manière autoritaire [...]
25 novembre 2024
Avec une souche éprouvée, des outils bien choisis et des cibles stratégiques, 8Base se distingue comme une menace particulièrement redoutable. […]
13 novembre 2024
Un système basé sur les LLM (Large Language Models) est souvent doté d'un certain degré d'autonomie par son développeur, [...]
12 novembre 2024
Les plugins pour LLM sont des extensions qui, lorsqu'ils sont activés, sont automatiquement appelés par le modèle pendant les interactions […]
7 novembre 2024
Les LLM ont le potentiel de révéler des informations sensibles (Sensitive Information Disclosure en anglais), des algorithmes propriétaires ou d'autres […]
6 novembre 2024
Le machine learning étend les vulnérabilités aux modèles pré-entraînés et aux données d'entraînement fournis par des tiers, qui sont susceptibles […]
31 octobre 2024
Un déni de service du modèle (Model Denial of Service en anglais) se produit quand un attaquant interagit avec un […]