Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / programming / file

Reversing PE files statically modified by Microsoft Detours

4.99/5 (19 votes)
14 Jul 2024LGPL314 min read 19.3K   539  
Investigation of PE file modifications performed by Microsoft Detours
Microsoft Detours allows hooking function calls of imported symbols from Portable Executable files. This can be performed via specific modifications of the original file. This article inspects the resulting PE file after it has been statically modified by Microsoft Detours.

Introduction

Microsoft Detours allows hooking function calls of imported symbols during execution time of Portable Executable files. This can be performed either without modification of the original file (hooking at execution time), or by specific manipulations of the original file (statically). Microsoft Detours allows to forward calls of imported symbols to a customized external DLL which intercepts the invocation and finally forwards to the original destination of the call [codeProAbramov].

For what is this useful? In application programming, when you rely on external libraries, you sometimes need to debug the general program flow behavior, but what if you don't have access to the library's source code? Or another case: Imagine, your software imports external libraries (where you are not able to modify their source code) but it's necessary to adjust the behavior of specific functions? Or what if there occur errors within your application in production mode at your customer, and you want to log function calls belatedly without re-compiling the application? ([detoursPaperHunt] p. 1, p. 8)

These all are use cases, where Microsoft Detours may help you to influence what happens when you invoke that external library.

This article focuses on a technical view according to the modifications Microsoft Detours performs on executable files, if you use it to adjust the file statically before runtime. We will inspect and analyze the modified and added contents within the resulting file and their purpose. The executable file example is taken from the source code package from the original Detours project on GitHub. [gitHubDetoursPro]
Static hooking of an existing PE file (non-execution time) can be performed by Microsoft Detours as well, as described at: [blogChanjun]

In order to extract the executable file contents that are of interest, I'm going to use TypeScript-based code snippets accepted by a special web platform which is concerned with low-level detail analysis of executable files. You can reproduce all execution results of these scripts at this platform: TRANScurity Platform

Getting Started

Within this article, some basic vocabulary from the executable file format of Windows and the reverse engineering context is used. At first, we must clarify their meanings to help the reader to understand the concepts.

The Basic Layout of Portable Executable (PE)

The file format of binary executable files on Windows is called Portable Executable (short: PE). It is an extension of the old COFF format ([detoursPaperHunt] p. 3).
Different file extensions are used for PE files: EXE, DLL, SCR...
PE files usually consist of a header area with meta information and a couple of sections. Theoretically, a section may contain arbitrary data (used by the program), but there are also some kind of sections with a special meaning. For instance, one section may contain machine codes that represent the program and will be executed by the CPU (code section). Or there may be a resource section with a specific format which stores required resources (icons, strings, menus). One further possible section, the import section, is responsible for referencing and importing functionality from external PE files (e.g., functions that can be invoked by the program). ([codeBreakersGoppit] p. 4 f.)

┌─────────────┐
│ DOS Header  │ ──┐
├─────────────┤   │
│  DOS Stub   │   │
├─────────────┤   ├── Header area
│  PE Header  │   │
├─────────────┤   │
│Section Table│ ──┘
╞═════════════╡
│  Section 1  │ ──┐
├─────────────┤   │
│     ...     │   ├── Section area
├─────────────┤   │
│  Section n  │ ──┘
└─────────────┘

The DOS header and stub are a legacy part, which becomes active if you try to execute the file on the old DOS operating system. DOS stub stores the program to be run on DOS. The most PE files contain a little program which simply prints "This program cannot be run on DOS mode..." and terminates. ([codeBreakersGoppit] p. 6)

The PE header holds common information about the PE's layout, required CPU architecture for execution, available sections and the program's entry point. ([codeBreakersGoppit] p. 8)

One part of the PE header is the so-called Data Directory. It keeps track of the presence of sections with special meanings (if they exist), for instance, the import section. In the most cases, if the code section of a PE calls functions from external PE files, these functions (also called symbols) will be imported by the import sections, first. But, theoretically, they could also be invoked directly with help of specific Windows API, possibly in order to conceal malicious intentions.

Each Data Directory entry describes the relative virtual start address of the concerning data structure and its size (winnt.h, [codeBreakersGoppit] p. 17):

C++
struct IMAGE_DATA_DIRECTORY {
  DWORD VirtualAddress; // relative virtual address referencing the data structure
  DWORD           Size; // size of the data structure
};

The PE header is followed by the section table with an overview of all present sections. The information consists of data like: Relative virtual start address in address space, size, and characteristics (readable, writable, executable). (winnt.h, [codeBreakersGoppit] p. 18 f.)

C++
struct IMAGE_SECTION_HEADER {
  BYTE    Name[IMAGE_SIZEOF_SHORT_NAME];
  union {
    DWORD PhysicalAddress;
    DWORD VirtualSize;
  } Misc;
  DWORD   VirtualAddress;
  DWORD   SizeOfRawData;
  DWORD   PointerToRawData;
  DWORD   PointerToRelocations;
  DWORD   PointerToLinenumbers;
  WORD    NumberOfRelocations;
  WORD    NumberOfLinenumbers;
  DWORD   Characteristics;
};

The Import Directory

This data structure is referenced by the 2nd entry of the Data Directory (winnt.h, [codeBreakersGoppit] p. 16):

IMAGE_DIRECTORY_ENTRY_EXPORT         0
IMAGE_DIRECTORY_ENTRY_IMPORT         1 <---
IMAGE_DIRECTORY_ENTRY_RESOURCE       2
IMAGE_DIRECTORY_ENTRY_EXCEPTION      3
IMAGE_DIRECTORY_ENTRY_SECURITY       4
IMAGE_DIRECTORY_ENTRY_BASERELOC      5
IMAGE_DIRECTORY_ENTRY_DEBUG          6
IMAGE_DIRECTORY_ENTRY_COPYRIGHT      7
IMAGE_DIRECTORY_ENTRY_GLOBALPTR      8
IMAGE_DIRECTORY_ENTRY_TLS            9
IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG    10
IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT   11
IMAGE_DIRECTORY_ENTRY_IAT            12
IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT   13
IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14
IMAGE_DIRECTORY_ENTRY_RESERVED       15

It consists of an array of IMAGE_IMPORT_DESCRIPTOR structures. The very last element with all its bytes set to zero indicates the array's end. (winnt.h, [codeBreakersGoppit] p. 29 ff.)

C++
struct IMAGE_IMPORT_DESCRIPTOR {
  union {
    DWORD Characteristics;
    DWORD OriginalFirstThunk;
  } Misc;
  DWORD   TimeDateStamp;
  DWORD   ForwarderChain;
  DWORD   Name;
  DWORD   FirstThunk;
};

For every imported PE file, one entry exists in this array. The following fields are of interest for us: OriginalFirstThunk, Name and FirstThunk.

OriginalFirstThunk and FirstThunk each reference an array of IMAGE_THUNK_DATA elements (with a zero-bytes item at the end), whereas Name stores a relative virtual address to a zero-terminated string:

C++
struct IMAGE_THUNK_DATA32 {
  union {
    DWORD ForwarderString;
    DWORD Function;
    DWORD Ordinal;
    DWORD AddressOfData;
  } u1;
};

Each array element stands for one single imported symbol within the imported PE file. If the most significant bit of the value of the IMAGE_THUNK_DATA element is set, then it represents an ordinal value of the imported function. Otherwise, it is a relative virtual address to a IMAGE_IMPORT_BY_NAME structure:

C++
struct IMAGE_IMPORT_BY_NAME {
  WORD Hint;
  BYTE Name[1];
};

The referenced array of OriginalFirstThunk is called "Import Name Table", and the one of FirstThunk has the name "Import Address Table". Both arrays pointed to are a copy of the other one. This is because, the Import Name Table won't be modified by the Windows Loader so that it can examine the names of imported functions later, if required. The entries of the Import Address Table will be overwritten with the real function addresses at load time of the PE.

Image 1

Layout of the Import Directory, [codeBreakersGoppit] p. 31

Microsoft Detours

As mentioned in the introduction, Microsoft Detours represents a tool, which allows intercepting function calls of PE imports. This is useful for debugging purpose or if you want to modify their functionality. ([detoursPaperHunt], p. 1)

Microsoft Detours overwrites the first few instructions of the target function, to be invoked, by a jump into a user-provided detour function. This detour function may perform any kind of pre-processing (if required) before it jumps to a trampoline function. The trampoline function stores the first few lost bytes (overwritten with the jump to the detour function), followed by a jump to the target function with the remaining instructions (that have not been overwritten). In the end, the target function returns to the detour function which could perform some post-processing if needed, until it gives control back to the invoker.
The resulting modified invocation flow is as followed: invokertarget functiondetour functiontrampoline functiontarget functiondetour functioninvoker ([detoursPaperHunt], p. 2)

Image 2

Invocation with and without interception, [detoursPaperHunt], p. 2

Microsoft Detours also supports editing the import table of a PE file. By this way, it is possible to redirect existing imports to another custom DLL file. This is what we will do later.

Preparations

If you would like to hook imports statically (not at runtime) by modification of the concerning PE file, then the Microsoft Detours tool set provides one program which can help you to do this: setdll.exe.

Following steps must be done:

  • Providing the PE file whose imports must be intercepted
  • Implementing a custom DLL, which stores the interception code for concerning imports
  • Referencing of the new DLL file within the import table of your concerning PE file

DLL files may contain code which becomes invoked during specific life-cycle steps when importing them: Starting process/thread, terminating process/thread. Your custom DLL must contain the signature of the target function to be intercepted and your custom function. Furthermore, it requires the mentioned life-cycle function (DllMain) which must invoke a specific API from the Microsoft Detours library which initiates modification of the function control flow (Detour function, trampoline function, etc.). With both PE files (your custom DLL and your target PE file), setdll.exe is able to edit the Import Directory by adding the import to your custom DLL. As soon as you execute the target PE file and Windows Loader loads this custom DLL, it also invokes the implemented life-cycle function (DllMain) that, in turn, calls Microsoft Detours which finally modifies the invocation flow of the function to be intercepted. ([gitHubDetoursPro] Wiki: DetourAttach, [msDocDllMain])

For our analysis, we use the example from the Microsoft Detours sources at: [gitHubDetoursPro]
The following blog describes how to use DllMain: [blogChanjun]

The example consists of a target PE file (sleep5.exe) which prints out text, waits 5 seconds, prints out text, again, and terminates. Additionally, we have a special DLL file with life-cycle function (simple32.dll), which re-routes the invocation of the function which waits 5 seconds (SleepEx). This custom implementation of the re-routing adds further logging and invokes the original function to be invoked. ([gitHubDetoursPro] samples/simple/*.cpp)

C++
...
#include "detours.h"
...
BOOL WINAPI DllMain(HINSTANCE hinst, DWORD dwReason, LPVOID reserved)
{
    LONG error;
    (void)hinst;
    (void)reserved;

    if (DetourIsHelperProcess()) {
        return TRUE;
    }

    if (dwReason == DLL_PROCESS_ATTACH) {
        DetourRestoreAfterWith();

        printf("simple" DETOURS_STRINGIFY(DETOURS_BITS) ".dll: Starting.\n");
        fflush(stdout);

        DetourTransactionBegin();
        DetourUpdateThread(GetCurrentThread());
        DetourAttach(&(PVOID&)TrueSleepEx, TimedSleepEx); // TimedSleepEx represents 
                       // the detour function that replaces the original sleep function
        error = DetourTransactionCommit();

        if (error == NO_ERROR) {
            printf("simple" DETOURS_STRINGIFY(DETOURS_BITS) 
                   ".dll: Detoured SleepEx().\n");
        }
        else {
            printf("simple" DETOURS_STRINGIFY(DETOURS_BITS) 
                   ".dll: Error detouring SleepEx(): %ld\n", error);
        }
    }
    else if (dwReason == DLL_PROCESS_DETACH) {
        DetourTransactionBegin();
        DetourUpdateThread(GetCurrentThread());
        DetourDetach(&(PVOID&)TrueSleepEx, TimedSleepEx);
        error = DetourTransactionCommit();

        printf("simple" DETOURS_STRINGIFY(DETOURS_BITS) 
        ".dll: Removed SleepEx() (result=%ld), slept %ld ticks.\n", error, dwSlept);
        fflush(stdout);
    }
    return TRUE;
}
...

An invocation of setdll.exe with the following parameters injects the custom DLL into the target PE file's Import Directory:

setdll.exe /d:simple32.dll sleep5.exe

And this one restores the original Import Directory, again:

setdll.exe /r sleep5.exe

Analysis

For this analysis, we are concerned with following executable files:

SHA-256: File name: Role:
7DCA63A349DCB322B0CC58553AD497AD0CED7B25707288DA36EF11D382FC6354
sleep5.pe The original unmodified PE file, which waits a few seconds and then terminates.
5A22F948E1FB2BAD9E5F18FBD3F7B7775BF425A0EDBD7F2837CEBF6BBF39E92B
sleep5_detours.pe The original file, modified by Microsoft Detours. It performs extra-logging due to detouring of the invocation of the Sleep function.
A7C3B976F35B44711D32F26317849318C79F3AF0722385586EF1669476962E67
simple32.dll

Stores the customized version of the Sleep function.

In order to get a top-level overview, it is helpful to detect the basic differences between the original file and the modified one. The following script shows the deviating areas of the PE structure:

Source script: Differences.ts

TypeScript
(async function() {
        const sha_original = 
              '7DCA63A349DCB322B0CC58553AD497AD0CED7B25707288DA36EF11D382FC6354'
        const sha_modified = 
              '5A22F948E1FB2BAD9E5F18FBD3F7B7775BF425A0EDBD7F2837CEBF6BBF39E92B'
        const differences = await transcurity.Executables.comparePEs
                            (sha_original, sha_modified)
        let resultTable = '';

        resultTable += `<tr><td><h2>Area</h2></td><td><h2>Element</h2>
        </td><td><h2>Left Value</h2></td><td><h2>Right Value</h2></td></tr>`
        for (const area of differences) {
            const name = await area.getCategory()
            const results = await area.getResults()

            resultTable += `<tr><td><b>${name}:</b></td><td></td><td></td><td></td></tr>`
            for (const diff of results) {
                const element = await diff.getIdentifier()
                const left = await diff.getLeftSide()
                const right = await diff.getRightSide()
                resultTable += `<tr><td></td><td>${element}</td>
                                <td>${left}</td><td>${right}</td></tr>`
            }
        }

        output(`<table rules="rows" cellpadding="2px">${resultTable}</table>`)
})()
Area Element Left Value Right Value
IMAGE_DOS_HEADER:      
  e_lfanew F0 50
DOS_STUB:      
    Length: 176, 0E1FBA0E00B409CD21B8... Length: 16, 0E1FBA0E00B409CD21B8...
IMAGE_FILE_HEADER:      
  NumberOfSections 04 05
IMAGE_DIRECTORY_ENTRY_IMPORT[1]:      
  VirtualAddress 0182B4 01C850
  isize 28 3C
IMAGE_IMPORT_DESCRIPTOR[0]:      
  Misc 0182DC 01C130
  Name KERNEL32.dll simple32.dll
  FirstThunk 012000 01C248
IMAGE_IMPORT_DESCRIPTOR[1]:      
  Misc >NOT EXISTING< 01C138
  TimeDateStamp >NOT EXISTING< 00
  ForwarderChain >NOT EXISTING< 00
  Name >NOT EXISTING< KERNEL32.dll
  FirstThunk >NOT EXISTING< 012000
IMAGE_SECTION_HEADER[4]:      
  Name1 >NOT EXISTING< .detour
  Misc >NOT EXISTING< 0890
  VirtualAddress >NOT EXISTING< 01C000
  SizeOfRawData >NOT EXISTING< 0A00
  PointerToRawData >NOT EXISTING< 019600
  PointerToRelocations >NOT EXISTING< 00
  PointerToLinenumbers >NOT EXISTING< 00
  NumberOfRelocations >NOT EXISTING< 00
  NumberOfLinenumbers >NOT EXISTING< 00
  Characteristics >NOT EXISTING< C0000040
SECTION_TABLE_PADDING:      
    Length: 376, 00000000000000000000... Length: 496, 2E72646174610000CC68...
SECTION_BODY[4]:      
    >NOT EXISTING< Length: 2560, 40000000447472005008...

Obviously, DOS header and DOS stub contain the first differences. Only the very last field of DOS header (e_lfanew) was changed. This makes sense, since it refers to the file offset directly after the end of DOS stub. Since DOS stub's size was changed, this field must also be adjusted.

Furthermore, IMAGE_FILE_HEADER indicates a different total number of present sections in the file. The modified PE has one additional section, now. Even the import table seems to have one extra import.

Between the end of the section table and the first section's start, there are differences as well. The original file contains these sections:

Source script: Section headers original.ts

TypeScript
(async function() {
        const sha = 
        '7DCA63A349DCB322B0CC58553AD497AD0CED7B25707288DA36EF11D382FC6354' // original
        const model = transcurity.Executables.getPE(sha)

        outputClear()
        const importDirectory = 
        (await model.getNtHeaders().getImageOptionalHeader().getDataDirectoryElement())[1]
        const importDirectoryRva = await importDirectory.VirtualAddress.getInt()
        const sectionHeaders = await model.getSectionTable().getImageSectionHeaders()
        let i = 0
        let importDirectorySection
        for (const header of sectionHeaders) {
            outputAppend(`Section ${i++}: ${await header.getName()}<br>`)

            if (importDirectoryRva >= await header.VirtualAddress.getInt() && 
                importDirectoryRva < await header.VirtualAddress.getInt() + 
                await header.Misc.VirtualSize.getInt()) {
                importDirectorySection = await header.getName()
            }
        }

        outputAppend(`<p></p>Section for import directory: ${importDirectorySection}`)
})()
Section 0: .text
Section 1: .rdata
Section 2: .data
Section 3: .reloc

Section for import directory: .rdata

Whereby the modified file stores these sections:

Source script: Section headers modified.ts

TypeScript
...
        const sha = 
        '5A22F948E1FB2BAD9E5F18FBD3F7B7775BF425A0EDBD7F2837CEBF6BBF39E92B' // modified
...
Section 0: .text
Section 1: .rdata
Section 2: .data
Section 3: .reloc
Section 4: .detour

Section for import directory: .detour

As we have seen in the previous paragraphs, the Data Directory references locations within special section contents. The following sections are referenced by single Data Directory entries of the original file:

Source script: Data directory section association original.ts

TypeScript
import DataDirectoryTypes = org.pevalidation.pemodel.structs.constants.DataDirectoryTypes;
import IMAGE_SECTION_HEADER = 
       org.pevalidation.pemodel.structs.nativestructs.IMAGE_SECTION_HEADER;

(async function() {
        const sha = 
        '7DCA63A349DCB322B0CC58553AD497AD0CED7B25707288DA36EF11D382FC6354' // original
        const model = transcurity.Executables.getPE(sha)
		let sectionsLayout = 
            await transcurity.Visualization.visualizeSectionLayoutForPE(sha)
        let result = ''

        const sectionAlignment = await model.getNtHeaders().getImageNtHeaders().
              getOptionalHeaderElement().getSectionAlignmentElement().getLong()
        const dataDirs = await model.getNtHeaders().getOptionalHeader().
                         getImageOptionalHeader().getDataDirectoryElement()
        const sectionHeaders = await model.getSectionTable().getImageSectionHeaders()
        for (const dir of dataDirs) {
            const start = await dir.VirtualAddress.getLong()
            const end = start + await dir.VirtualAddress.getLong()
            const containedSections = await getContainingSections
                                      (sectionHeaders, sectionAlignment, start, end)

            if (containedSections.length > 0)
                result += `<tr><td>${DataDirectoryTypes[await dir.getType()]}</td>
                           <td>${containedSections.join(', ')}</td></tr>`
        }

        output(`<p>${sectionsLayout}</p><table>${result}</table>`)
})()

async function getContainingSections(sectionHeaders: IMAGE_SECTION_HEADER[], 
      sectionAlignment: number, start: number, end: number): Promise<string[]> {
    const result = []
    if (start == 0){
        return new Promise<string[]>(resolve => resolve(result))
    }

    const alignedEnd = await transcurity.ByteUtil.getAligned(end, sectionAlignment)
    for (const header of sectionHeaders) {
        const sectionStart = await header.VirtualAddress.getLong()
        const sectionEnd = sectionStart + await header.Misc.VirtualSize.getLong()
        const alignedSectionEnd = await transcurity.ByteUtil.getAligned
                                  (sectionEnd, sectionAlignment)

        if (start >= sectionStart && start < alignedSectionEnd)
            result.push(await header.getName())
        else if (alignedEnd >= sectionStart && alignedEnd < alignedSectionEnd)
            result.push(await header.getName())
    }

    return new Promise<string[]>(resolve => resolve(result))
}
  .text, 1000-12000: ███████████████████████████████████████████████████
.rdata, 12000-19000: ···················································█████████████████████
 .data, 19000-1B000: ········································································██████
.reloc, 1B000-1C000: ··············································································███

IMAGE_DIRECTORY_ENTRY_IMPORT      .rdata
IMAGE_DIRECTORY_ENTRY_BASERELOC   .reloc
IMAGE_DIRECTORY_ENTRY_DEBUG       .rdata
IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG .rdata
IMAGE_DIRECTORY_ENTRY_IAT         .rdata

And these are the associations within the modified file:

Source script: Data directory section association modified.ts

   .text, 1000-12000: ███████████████████████████████████████████████████
 .rdata, 12000-19000: ···················································█████████████████████
  .data, 19000-1B000: ········································································██████
 .reloc, 1B000-1C000: ··············································································███
.detour, 1C000-1D000: ·················································································███

IMAGE_DIRECTORY_ENTRY_IMPORT      .detour
IMAGE_DIRECTORY_ENTRY_BASERELOC   .reloc
IMAGE_DIRECTORY_ENTRY_DEBUG       .rdata
IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG .rdata
IMAGE_DIRECTORY_ENTRY_IAT         .rdata

It's interesting that the original Import Directory was contained by the section .rdata, but in the modified file, it suddenly appears within the new section .detour. Possibly, even the imported symbols are different, now. The original file has the following imports:

Source script: Data directory original.ts

TypeScript
(async function() {
        const sha = 
        '7DCA63A349DCB322B0CC58553AD497AD0CED7B25707288DA36EF11D382FC6354' // original
        const model = transcurity.Executables.getPE(sha)

        outputClear()
        const importDescriptors = await model.getNtHeaders().
              getDataDirectory().getImportDescriptor().getImportDescriptors()
        for (const descriptor of importDescriptors) {
            const dllName = await descriptor.getName()
            outputAppend(`${dllName}<br>`)

            const thunkDatas = await descriptor.getThunkData()
            for (const thunkData of thunkDatas) {
                if (await thunkData.isImportByOrdinal()) {
                    const ordinal = 
                    parseInt((await thunkData.getOrdinalValue()).toString())
                    outputAppend(`&nbsp;&nbsp;&nbsp;Ordinal: ${ordinal}<br>`)
                } else {
                    const symbol = await thunkData.getImageImportByName().getAsciiName()
                    outputAppend(`&nbsp;&nbsp;&nbsp;Name: ${symbol}<br>`)
                }
            }
        }
})()
KERNEL32.dll
   Name: Sleep
   Name: WriteConsoleW
   Name: QueryPerformanceCounter
   Name: GetCurrentProcessId
   Name: GetCurrentThreadId
   Name: GetSystemTimeAsFileTime
   Name: InitializeSListHead
   Name: IsDebuggerPresent
   Name: UnhandledExceptionFilter
   Name: SetUnhandledExceptionFilter
   Name: GetStartupInfoW
   Name: IsProcessorFeaturePresent
   Name: GetModuleHandleW
   Name: GetCurrentProcess
   Name: TerminateProcess
   Name: RtlUnwind
   Name: GetLastError
   Name: SetLastError
   Name: EnterCriticalSection
   Name: LeaveCriticalSection
   Name: DeleteCriticalSection
   Name: InitializeCriticalSectionAndSpinCount
   Name: TlsAlloc
   Name: TlsGetValue
   Name: TlsSetValue
   Name: TlsFree
   Name: FreeLibrary
   Name: GetProcAddress
   Name: LoadLibraryExW
   Name: RaiseException
   Name: GetStdHandle
   Name: WriteFile
   Name: GetModuleFileNameW
   Name: ExitProcess
   Name: GetModuleHandleExW
   Name: GetCommandLineA
   Name: GetCommandLineW
   Name: HeapAlloc
   Name: HeapFree
   Name: CompareStringW
   Name: LCMapStringW
   Name: GetFileType
   Name: FindClose
   Name: FindFirstFileExW
   Name: FindNextFileW
   Name: IsValidCodePage
   Name: GetACP
   Name: GetOEMCP
   Name: GetCPInfo
   Name: MultiByteToWideChar
   Name: WideCharToMultiByte
   Name: GetEnvironmentStringsW
   Name: FreeEnvironmentStringsW
   Name: SetEnvironmentVariableW
   Name: SetStdHandle
   Name: GetStringTypeW
   Name: GetProcessHeap
   Name: FlushFileBuffers
   Name: GetConsoleOutputCP
   Name: GetConsoleMode
   Name: GetFileSizeEx
   Name: SetFilePointerEx
   Name: HeapSize
   Name: HeapReAlloc
   Name: CloseHandle
   Name: CreateFileW
   Name: DecodePointer

Imports of the modified file:

Source script: Data directory modified.ts

simple32.dll
   Ordinal: 1
KERNEL32.dll
   Name: Sleep
   Name: WriteConsoleW
   ...

All imports are the same, except that there's one further import from an additional DLL file which was intentionally injected by Microsoft Detours: simple32.dll::1
It looks like that the new section .detour plays an important role. It stores the following content:

Source script: Detours section bytes.ts

TypeScript
(async function() {
        const sha = 
        '5A22F948E1FB2BAD9E5F18FBD3F7B7775BF425A0EDBD7F2837CEBF6BBF39E92B' // modified
        const model = transcurity.Executables.getPE(sha)

        const sectionStart = await 
        (await model.getSectionTable().getImageSectionHeaders())[4].VirtualAddress.getLong()
        const bytes = await (await model.getSectionBody().getSectionBodies())[4].getBytes()
        output(`<b>Detours section:</b> 
                <p></p>${await transcurity.ByteUtil.toHexView(bytes, sectionStart)}`)
})()
Detours section:

         0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F
01C000  40 00 00 00 44 74 72 00 50 08 00 00 50 08 00 00  @...Dtr.P...P...
01C010  B4 82 01 00 28 00 00 00 00 00 00 00 00 00 00 00  ´...(...........
01C020  00 20 01 00 10 01 00 00 00 C0 01 00 F0 00 00 00  . .......À..ð...
01C030  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
01C040  4D 5A 90 00 03 00 00 00 04 00 00 00 FF FF 00 00  MZ..........ÿÿ..
01C050  B8 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00  ¸.......@.......
01C060  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
01C070  00 00 00 00 00 00 00 00 00 00 00 00 F0 00 00 00  ............ð...
01C080  0E 1F BA 0E 00 B4 09 CD 21 B8 01 4C CD 21 54 68  ..º..´.Í!¸.LÍ!Th
01C090  69 73 20 70 72 6F 67 72 61 6D 20 63 61 6E 6E 6F  is program canno
01C0A0  74 20 62 65 20 72 75 6E 20 69 6E 20 44 4F 53 20  t be run in DOS 
01C0B0  6D 6F 64 65 2E 0D 0D 0A 24 00 00 00 00 00 00 00  mode....$.......
01C0C0  8A E4 18 C4 CE 85 76 97 CE 85 76 97 CE 85 76 97  .ä.ÄÎ.v.Î.v.Î.v.
01C0D0  DA EE 75 96 C4 85 76 97 DA EE 73 96 42 85 76 97  Úîu.Ä.v.Úîs.B.v.
01C0E0  DA EE 72 96 DC 85 76 97 A2 F1 73 96 EB 85 76 97  Úîr.Ü.v.¢ñs.ë.v.
01C0F0  A2 F1 72 96 DF 85 76 97 A2 F1 75 96 DF 85 76 97  ¢ñr.ß.v.¢ñu.ß.v.
01C100  DA EE 77 96 CD 85 76 97 CE 85 77 97 9F 85 76 97  Úîw.Í.v.Î.w...v.
01C110  18 F1 73 96 CF 85 76 97 18 F1 74 96 CF 85 76 97  .ñs.Ï.v..ñt.Ï.v.
01C120  52 69 63 68 CE 85 76 97 00 00 00 00 00 00 00 00  RichÎ.v.........
01C130  01 00 00 80 00 00 00 00 7C C3 01 00 84 C3 01 00  ........|Ã...Ã..
01C140  94 C3 01 00 AE C3 01 00 C4 C3 01 00 DA C3 01 00  .Ã..®Ã..ÄÃ..ÚÃ..
01C150  F4 C3 01 00 0A C4 01 00 1E C4 01 00 3A C4 01 00  ôÃ...Ä...Ä..:Ä..
01C160  58 C4 01 00 6A C4 01 00 86 C4 01 00 9A C4 01 00  XÄ..jÄ...Ä...Ä..
01C170  AE C4 01 00 C2 C4 01 00 CE C4 01 00 DE C4 01 00  ®Ä..ÂÄ..ÎÄ..ÞÄ..
01C180  EE C4 01 00 06 C5 01 00 1E C5 01 00 36 C5 01 00  îÄ...Å...Å..6Å..
01C190  5E C5 01 00 6A C5 01 00 78 C5 01 00 86 C5 01 00  ^Å..jÅ..xÅ...Å..
01C1A0  90 C5 01 00 9E C5 01 00 B0 C5 01 00 C2 C5 01 00  .Å...Å..°Å..ÂÅ..
01C1B0  D4 C5 01 00 E4 C5 01 00 F0 C5 01 00 06 C6 01 00  ÔÅ..äÅ..ðÅ...Æ..
01C1C0  14 C6 01 00 2A C6 01 00 3C C6 01 00 4E C6 01 00  .Æ..*Æ..<Æ..NÆ..
01C1D0  5A C6 01 00 66 C6 01 00 78 C6 01 00 88 C6 01 00  ZÆ..fÆ..xÆ...Æ..
01C1E0  96 C6 01 00 A2 C6 01 00 B6 C6 01 00 C6 C6 01 00  .Æ..¢Æ..¶Æ..ÆÆ..
01C1F0  D8 C6 01 00 E2 C6 01 00 EE C6 01 00 FA C6 01 00  ØÆ..âÆ..îÆ..úÆ..
01C200  10 C7 01 00 26 C7 01 00 40 C7 01 00 5A C7 01 00  .Ç..&Ç..@Ç..ZÇ..
01C210  74 C7 01 00 84 C7 01 00 96 C7 01 00 A8 C7 01 00  tÇ...Ç...Ç..¨Ç..
01C220  BC C7 01 00 D2 C7 01 00 E4 C7 01 00 F4 C7 01 00  ¼Ç..ÒÇ..äÇ..ôÇ..
01C230  08 C8 01 00 14 C8 01 00 22 C8 01 00 30 C8 01 00  .È...È.."È..0È..
01C240  3E C8 01 00 00 00 00 00 01 00 00 80 00 00 00 00  >È..............
01C250  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
...
01C360  73 69 6D 70 6C 65 33 32 2E 64 6C 6C 00 00 4B 45  simple32.dll..KE
01C370  52 4E 45 4C 33 32 2E 64 6C 6C 00 00 81 05 53 6C  RNEL32.dll....Sl
01C380  65 65 70 00 15 06 57 72 69 74 65 43 6F 6E 73 6F  eep...WriteConso
01C390  6C 65 57 00 4F 04 51 75 65 72 79 50 65 72 66 6F  leW.O.QueryPerfo
01C3A0  72 6D 61 6E 63 65 43 6F 75 6E 74 65 72 00 1B 02  rmanceCounter...
01C3B0  47 65 74 43 75 72 72 65 6E 74 50 72 6F 63 65 73  GetCurrentProces
01C3C0  73 49 64 00 1F 02 47 65 74 43 75 72 72 65 6E 74  sId...GetCurrent
01C3D0  54 68 72 65 61 64 49 64 00 00 EC 02 47 65 74 53  ThreadId..ì.GetS
01C3E0  79 73 74 65 6D 54 69 6D 65 41 73 46 69 6C 65 54  ystemTimeAsFileT
01C3F0  69 6D 65 00 66 03 49 6E 69 74 69 61 6C 69 7A 65  ime.f.Initialize
01C400  53 4C 69 73 74 48 65 61 64 00 82 03 49 73 44 65  SListHead...IsDe
01C410  62 75 67 67 65 72 50 72 65 73 65 6E 74 00 B1 05  buggerPresent.±.
01C420  55 6E 68 61 6E 64 6C 65 64 45 78 63 65 70 74 69  UnhandledExcepti
01C430  6F 6E 46 69 6C 74 65 72 00 00 71 05 53 65 74 55  onFilter..q.SetU
01C440  6E 68 61 6E 64 6C 65 64 45 78 63 65 70 74 69 6F  nhandledExceptio
01C450  6E 46 69 6C 74 65 72 00 D3 02 47 65 74 53 74 61  nFilter.Ó.GetSta
01C460  72 74 75 70 49 6E 66 6F 57 00 89 03 49 73 50 72  rtupInfoW...IsPr
01C470  6F 63 65 73 73 6F 72 46 65 61 74 75 72 65 50 72  ocessorFeaturePr
01C480  65 73 65 6E 74 00 7B 02 47 65 74 4D 6F 64 75 6C  esent.{.GetModul
01C490  65 48 61 6E 64 6C 65 57 00 00 1A 02 47 65 74 43  eHandleW....GetC
01C4A0  75 72 72 65 6E 74 50 72 6F 63 65 73 73 00 90 05  urrentProcess...
01C4B0  54 65 72 6D 69 6E 61 74 65 50 72 6F 63 65 73 73  TerminateProcess
01C4C0  00 00 D5 04 52 74 6C 55 6E 77 69 6E 64 00 64 02  ..Õ.RtlUnwind.d.
01C4D0  47 65 74 4C 61 73 74 45 72 72 6F 72 00 00 34 05  GetLastError..4.
01C4E0  53 65 74 4C 61 73 74 45 72 72 6F 72 00 00 34 01  SetLastError..4.
01C4F0  45 6E 74 65 72 43 72 69 74 69 63 61 6C 53 65 63  EnterCriticalSec
01C500  74 69 6F 6E 00 00 C1 03 4C 65 61 76 65 43 72 69  tion..Á.LeaveCri
01C510  74 69 63 61 6C 53 65 63 74 69 6F 6E 00 00 13 01  ticalSection....
01C520  44 65 6C 65 74 65 43 72 69 74 69 63 61 6C 53 65  DeleteCriticalSe
01C530  63 74 69 6F 6E 00 62 03 49 6E 69 74 69 61 6C 69  ction.b.Initiali
01C540  7A 65 43 72 69 74 69 63 61 6C 53 65 63 74 69 6F  zeCriticalSectio
01C550  6E 41 6E 64 53 70 69 6E 43 6F 75 6E 74 00 A2 05  nAndSpinCount.¢.
01C560  54 6C 73 41 6C 6C 6F 63 00 00 A4 05 54 6C 73 47  TlsAlloc..¤.TlsG
01C570  65 74 56 61 6C 75 65 00 A5 05 54 6C 73 53 65 74  etValue.¥.TlsSet
01C580  56 61 6C 75 65 00 A3 05 54 6C 73 46 72 65 65 00  Value.£.TlsFree.
01C590  AE 01 46 72 65 65 4C 69 62 72 61 72 79 00 B1 02  ®.FreeLibrary.±.
01C5A0  47 65 74 50 72 6F 63 41 64 64 72 65 73 73 00 00  GetProcAddress..
01C5B0  C7 03 4C 6F 61 64 4C 69 62 72 61 72 79 45 78 57  Ç.LoadLibraryExW
01C5C0  00 00 64 04 52 61 69 73 65 45 78 63 65 70 74 69  ..d.RaiseExcepti
01C5D0  6F 6E 00 00 D5 02 47 65 74 53 74 64 48 61 6E 64  on..Õ.GetStdHand
01C5E0  6C 65 00 00 16 06 57 72 69 74 65 46 69 6C 65 00  le....WriteFile.
01C5F0  77 02 47 65 74 4D 6F 64 75 6C 65 46 69 6C 65 4E  w.GetModuleFileN
01C600  61 6D 65 57 00 00 61 01 45 78 69 74 50 72 6F 63  ameW..a.ExitProc
01C610  65 73 73 00 7A 02 47 65 74 4D 6F 64 75 6C 65 48  ess.z.GetModuleH
01C620  61 6E 64 6C 65 45 78 57 00 00 D9 01 47 65 74 43  andleExW..Ù.GetC
01C630  6F 6D 6D 61 6E 64 4C 69 6E 65 41 00 DA 01 47 65  ommandLineA.Ú.Ge
01C640  74 43 6F 6D 6D 61 6E 64 4C 69 6E 65 57 00 48 03  tCommandLineW.H.
01C650  48 65 61 70 41 6C 6C 6F 63 00 4C 03 48 65 61 70  HeapAlloc.L.Heap
01C660  46 72 65 65 00 00 9E 00 43 6F 6D 70 61 72 65 53  Free....CompareS
01C670  74 72 69 6E 67 57 00 00 B5 03 4C 43 4D 61 70 53  tringW..µ.LCMapS
01C680  74 72 69 6E 67 57 00 00 51 02 47 65 74 46 69 6C  tringW..Q.GetFil
01C690  65 54 79 70 65 00 78 01 46 69 6E 64 43 6C 6F 73  eType.x.FindClos
01C6A0  65 00 7E 01 46 69 6E 64 46 69 72 73 74 46 69 6C  e.~.FindFirstFil
01C6B0  65 45 78 57 00 00 8F 01 46 69 6E 64 4E 65 78 74  eExW....FindNext
01C6C0  46 69 6C 65 57 00 8F 03 49 73 56 61 6C 69 64 43  FileW...IsValidC
01C6D0  6F 64 65 50 61 67 65 00 B5 01 47 65 74 41 43 50  odePage.µ.GetACP
01C6E0  00 00 9A 02 47 65 74 4F 45 4D 43 50 00 00 C4 01  ....GetOEMCP..Ä.
01C6F0  47 65 74 43 50 49 6E 66 6F 00 F3 03 4D 75 6C 74  GetCPInfo.ó.Mult
01C700  69 42 79 74 65 54 6F 57 69 64 65 43 68 61 72 00  iByteToWideChar.
01C710  02 06 57 69 64 65 43 68 61 72 54 6F 4D 75 6C 74  ..WideCharToMult
01C720  69 42 79 74 65 00 3A 02 47 65 74 45 6E 76 69 72  iByte.:.GetEnvir
01C730  6F 6E 6D 65 6E 74 53 74 72 69 6E 67 73 57 00 00  onmentStringsW..
01C740  AD 01 46 72 65 65 45 6E 76 69 72 6F 6E 6D 65 6E  .FreeEnvironmen
01C750  74 53 74 72 69 6E 67 73 57 00 16 05 53 65 74 45  tStringsW...SetE
01C760  6E 76 69 72 6F 6E 6D 65 6E 74 56 61 72 69 61 62  nvironmentVariab
01C770  6C 65 57 00 4E 05 53 65 74 53 74 64 48 61 6E 64  leW.N.SetStdHand
01C780  6C 65 00 00 DA 02 47 65 74 53 74 72 69 6E 67 54  le..Ú.GetStringT
01C790  79 70 65 57 00 00 B7 02 47 65 74 50 72 6F 63 65  ypeW..·.GetProce
01C7A0  73 73 48 65 61 70 00 00 A2 01 46 6C 75 73 68 46  ssHeap..¢.FlushF
01C7B0  69 6C 65 42 75 66 66 65 72 73 00 00 03 02 47 65  ileBuffers....Ge
01C7C0  74 43 6F 6E 73 6F 6C 65 4F 75 74 70 75 74 43 50  tConsoleOutputCP
01C7D0  00 00 FF 01 47 65 74 43 6F 6E 73 6F 6C 65 4D 6F  ..ÿ.GetConsoleMo
01C7E0  64 65 00 00 4F 02 47 65 74 46 69 6C 65 53 69 7A  de..O.GetFileSiz
01C7F0  65 45 78 00 25 05 53 65 74 46 69 6C 65 50 6F 69  eEx.%.SetFilePoi
01C800  6E 74 65 72 45 78 00 00 51 03 48 65 61 70 53 69  nterEx..Q.HeapSi
01C810  7A 65 00 00 4F 03 48 65 61 70 52 65 41 6C 6C 6F  ze..O.HeapReAllo
01C820  63 00 89 00 43 6C 6F 73 65 48 61 6E 64 6C 65 00  c...CloseHandle.
01C830  CE 00 43 72 65 61 74 65 46 69 6C 65 57 00 0C 01  Î.CreateFileW...
01C840  44 65 63 6F 64 65 50 6F 69 6E 74 65 72 00 00 00  DecodePointer...
01C850  30 C1 01 00 00 00 00 00 00 00 00 00 60 C3 01 00  0Á..........`Ã..
01C860  48 C2 01 00 38 C1 01 00 00 00 00 00 00 00 00 00  HÂ..8Á..........
01C870  6E C3 01 00 00 20 01 00 00 00 00 00 00 00 00 00  nÃ... ..........
01C880  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
...
01C9F0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

According to the Microsoft Detours code on [gitHubDetoursPro], we can assume that this kind of introduced special section starts with a well-defined header structure which is used for later recovering of the original PE file:

Source script: Detour Section Header structure definition.ts

TypeScript
(async function() {
        const sha = 
              '5A22F948E1FB2BAD9E5F18FBD3F7B7775BF425A0EDBD7F2837CEBF6BBF39E92B' // modified
        const model = transcurity.Executables.getPE(sha)

        const header = model.getSectionBody().getSpecialSections().
                       getDetoursSection().getHeader()
        const structureDefinition = await transcurity.Visualization.dumpStructure(header, true)

        output(`${structureDefinition}`)
})()
C++
typedef struct _DETOUR_SECTION_HEADER {
   DWORD    cbHeaderSize;                       // 40
   DWORD    nSignature;                         // 727444
   DWORD    nDataOffset;                        // 850
   DWORD    cbDataSize;                         // 850
   DWORD    nOriginalImportVirtualAddress;      // 182B4
   DWORD    nOriginalImportSize;                // 28
   DWORD    nOriginalBoundImportVirtualAddress; // 0
   DWORD    nOriginalBoundImportSize;           // 0
   DWORD    nOriginalIatVirtualAddress;         // 12000
   DWORD    nOriginalIatSize;                   // 110
   DWORD    nOriginalSizeOfImage;               // 1C000
   DWORD    cbPrePE;                            // F0
   DWORD    nOriginalClrFlags;                  // 0
   DWORD    reserved1;                          // 0
   DWORD    reserved2;                          // 0
   DWORD    reserved3;                          // 0
} DETOUR_SECTION_HEADER;

The Data Directory's Import Directory entry refers to a location within this section. Since an array of IMAGE_IMPORT_DESCRIPTOR is expected as data structure, we can interpret those bytes accordingly:

...
01C850  30 C1 01 00 00 00 00 00 00 00 00 00 60 C3 01 00  0Á..........`Ã..
01C860  48 C2 01 00 38 C1 01 00 00 00 00 00 00 00 00 00  HÂ..8Á..........
01C870  6E C3 01 00 00 20 01 00 00 00 00 00 00 00 00 00  nÃ... ..........
01C880  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
...

Once again, each array element consists of the following fields:

Source script: Dumped Image Import Descriptors.ts

TypeScript
(async function() {
        const sha = 
        '5A22F948E1FB2BAD9E5F18FBD3F7B7775BF425A0EDBD7F2837CEBF6BBF39E92B' // modified
        const model = transcurity.Executables.getPE(sha)

        let result = ''
        const importDescriptors = await model.getNtHeaders().getDataDirectory().
                                  getImportDescriptor().getImportDescriptors()
        for (let i = 0; i < importDescriptors.length; i++) {
            result += `<p><b>IMAGE_IMPORT_DESCRIPTOR[${i}]:</b>
            ${await transcurity.Visualization.dumpStructure(importDescriptors[i], true)}</p>`
        }

        output(result)
})()
C++
IMAGE_IMPORT_DESCRIPTOR[0]:
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
   Union    Misc;           // 1C130
   DWORD    TimeDateStamp;  // 0
   DWORD    ForwarderChain; // 0
   DWORD    Name;           // 1C360
   DWORD    FirstThunk;     // 1C248
} IMAGE_IMPORT_DESCRIPTOR;        

IMAGE_IMPORT_DESCRIPTOR[1]:
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
   Union    Misc;           // 1C138
   DWORD    TimeDateStamp;  // 0
   DWORD    ForwarderChain; // 0
   DWORD    Name;           // 1C36E
   DWORD    FirstThunk;     // 12000
} IMAGE_IMPORT_DESCRIPTOR;

The field Name represents a relative virtual address (RVA) to a zero-terminated string. In our case, the RVAs of each of the Name fields in these two entries reference a location within the same section, a few bytes above:

...
01C360  73 69 6D 70 6C 65 33 32 2E 64 6C 6C 00 00 4B 45  simple32.dll..KE
01C370  52 4E 45 4C 33 32 2E 64 6C 6C 00 00 81 05 53 6C  RNEL32.dll....Sl
...

These two strings are resolved to the names of the injected library name simple32.dll and the name of the previous imported library KERNEL32.dll.

...
01C850  30 C1 01 00 00 00 00 00 00 00 00 00 60 C3 01 00  0Á..........`Ã..
01C860  48 C2 01 00 38 C1 01 00 00 00 00 00 00 00 00 00  HÂ..8Á..........
01C870  6E C3 01 00 00 20 01 00 00 00 00 00 00 00 00 00  nÃ... ..........
01C880  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
...

Misc represents a field named OriginalFirstThunk. OriginalFirstThunk and FirstThunk of the first IMAGE_IMPORT_DESCRIPTOR element (which describes imports from simple32.dll) reference IMAGE_THUNK_DATA data structures within the .detour section:

...
01C130  01 00 00 80 00 00 00 00 7C C3 01 00 84 C3 01 00  ........|Ã...Ã..
...
01C240  3E C8 01 00 00 00 00 00 01 00 00 80 00 00 00 00  >È..............
...

Since the most significant bit of the referenced value is set (80), it is an import by ordinal value (ordinal here: 01)

The second and last IMAGE_IMPORT_DESCRIPTOR array element stores its referenced data for OriginalFirstThunk within .detour section as well, but for FirstThunk it points into a different section (.rdata), which represents an original, unmodified section of the original file:

Source script: rdata section bytes.ts

TypeScript
(async function() {
        const sha = 
        '5A22F948E1FB2BAD9E5F18FBD3F7B7775BF425A0EDBD7F2837CEBF6BBF39E92B' // modified
        const model = transcurity.Executables.getPE(sha)

        const sectionStart = await 
        (await model.getSectionTable().getImageSectionHeaders())[1].VirtualAddress.getLong()
        const bytes = await (await model.getSectionBody().getSectionBodies())[1].getBytes()
        const name = await (await model.getSectionTable().getImageSectionHeaders())[1].getName()
        output(`<b>${name} section:</b> <p></p>
                ${await transcurity.ByteUtil.toHexView(bytes, sectionStart)}`)
})()
...
012000  EC 83 01 00 AC 88 01 00 02 84 01 00 1C 84 01 00  ì...¬...........
...
01C130  01 00 00 80 00 00 00 00 7C C3 01 00 84 C3 01 00  ........|Ã...Ã..
...

The values at the referenced locations are, in turn, RVAs to IMAGE_IMPORT_BY_NAME structures. Those structures stored by the Import Address Table (pointed to by FirstThunk) will later be replaced with real addresses of their associated functions by Windows Loader on start up of the executable. ([codeBreakersGoppit] p. 30)

...
0183E0  9E 88 01 00 BC 88 01 00 00 00 00 00 81 05 53 6C  ....¼.........Sl
0183F0  65 65 70 00 4B 45 52 4E 45 4C 33 32 2E 64 6C 6C  eep.KERNEL32.dll
...
0188A0  43 72 65 61 74 65 46 69 6C 65 57 00 15 06 57 72  CreateFileW...Wr
0188B0  69 74 65 43 6F 6E 73 6F 6C 65 57 00 0C 01 44 65  iteConsoleW...De
...
01C370  52 4E 45 4C 33 32 2E 64 6C 6C 00 00 81 05 53 6C  RNEL32.dll....Sl
01C380  65 65 70 00 15 06 57 72 69 74 65 43 6F 6E 73 6F  eep...WriteConso
01C390  6C 65 57 00 4F 04 51 75 65 72 79 50 65 72 66 6F  leW.O.QueryPerfo
...

All in all, it seems no special rocket science was done with the Import Directory. One last deviation from Differences.ts is remaining: The different contents between the end of the section table and the start of the very first section:

When skimming the ASCII interpretation (especially the section names), you may perceive that there are some duplicate sections within the modified executable:

Source script: Header bytes.ts

TypeScript
(async function() {
        const sha = 
        '5A22F948E1FB2BAD9E5F18FBD3F7B7775BF425A0EDBD7F2837CEBF6BBF39E92B' // modified
        const model = transcurity.Executables.getPE(sha)
        const bytes = await model.getHeaderBytes()
        
        output(await transcurity.ByteUtil.toHexView(bytes, 0))
})()
       0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F
0000  4D 5A 90 00 03 00 00 00 04 00 00 00 FF FF 00 00  MZ..........ÿÿ..
0010  B8 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00  ¸.......@.......
0020  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0030  00 00 00 00 00 00 00 00 00 00 00 00 50 00 00 00  ............P...
0040  0E 1F BA 0E 00 B4 09 CD 21 B8 01 4C CD 21 2A 2A  ..º..´.Í!¸.LÍ!**
0050  50 45 00 00 4C 01 05 00 A6 AA 44 60 00 00 00 00  PE..L...¦ªD`....
0060  00 00 00 00 E0 00 02 01 0B 01 0E 1C 00 0E 01 00  ....à...........
0070  00 8E 00 00 00 00 00 00 21 13 00 00 00 10 00 00  ........!.......
0080  00 20 01 00 00 00 40 00 00 10 00 00 00 02 00 00  . ....@.........
0090  06 00 00 00 00 00 00 00 06 00 00 00 00 00 00 00  ................
00A0  00 D0 01 00 00 04 00 00 00 00 00 00 03 00 40 81  .Ð............@.
00B0  00 00 10 00 00 10 00 00 00 00 10 00 00 10 00 00  ................
00C0  00 00 00 00 10 00 00 00 00 00 00 00 00 00 00 00  ................
00D0  50 C8 01 00 3C 00 00 00 00 00 00 00 00 00 00 00  PÈ..<...........
00E0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00F0  00 B0 01 00 FC 0E 00 00 BC 7A 01 00 38 00 00 00  .°..ü...¼z..8...
0100  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0110  00 00 00 00 00 00 00 00 F8 7A 01 00 40 00 00 00  ........øz..@...
0120  00 00 00 00 00 00 00 00 00 20 01 00 10 01 00 00  ......... ......
0130  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0140  00 00 00 00 00 00 00 00 2E 74 65 78 74 00 00 00  .........text...
0150  63 0C 01 00 00 10 00 00 00 0E 01 00 00 04 00 00  c...............
0160  00 00 00 00 00 00 00 00 00 00 00 00 20 00 00 60  ............ ..`
0170  2E 72 64 61 74 61 00 00 CC 68 00 00 00 20 01 00  .rdata..Ìh... ..
0180  00 6A 00 00 00 12 01 00 00 00 00 00 00 00 00 00  .j..............
0190  00 00 00 00 40 00 00 40 2E 64 61 74 61 00 00 00  ....@..@.data...
01A0  E4 12 00 00 00 90 01 00 00 0A 00 00 00 7C 01 00  ä............|..
01B0  00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 C0  ............@..À
01C0  2E 72 65 6C 6F 63 00 00 FC 0E 00 00 00 B0 01 00  .reloc..ü....°..
01D0  00 10 00 00 00 86 01 00 00 00 00 00 00 00 00 00  ................
01E0  00 00 00 00 40 00 00 42 2E 64 65 74 6F 75 72 00  ....@..B.detour.
01F0  90 08 00 00 00 C0 01 00 00 0A 00 00 00 96 01 00  .....À..........
0200  00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 C0  ............@..À
0210  2E 72 64 61 74 61 00 00 CC 68 00 00 00 20 01 00  .rdata..Ìh... ..
0220  00 6A 00 00 00 12 01 00 00 00 00 00 00 00 00 00  .j..............
0230  00 00 00 00 40 00 00 40 2E 64 61 74 61 00 00 00  ....@..@.data...
0240  E4 12 00 00 00 90 01 00 00 0A 00 00 00 7C 01 00  ä............|..
0250  00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 C0  ............@..À
0260  2E 72 65 6C 6F 63 00 00 FC 0E 00 00 00 B0 01 00  .reloc..ü....°..
0270  00 10 00 00 00 86 01 00 00 00 00 00 00 00 00 00  ................
0280  00 00 00 00 40 00 00 42 00 00 00 00 00 00 00 00  ....@..B........
0290  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
...
03F0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

But according to the real present section table, it, indeed, only has totally 5 sections. What has happened? The answer is simple: Microsoft Detours uses the original PE header and overwrites its content as required. Since the DOS stub was shortened, only the first parts of the original header becomes overwritten and the remaining bytes stay the same but represent unused garbage bytes. It looks like they are meaningful, but for real they have no effect and don't matter.

Conclusions

What insights about Microsoft Detours did we gain in this article? Microsoft Detours does not only have a low impact on executable files when their imports must be intercepted, dynamically, it also has a small footprint if it statically modifies executable files with help of its setdll tool. The most internal structures within the executable are kept untouched as far as possible, and only a minimum of data which must be adjusted will be moved into a new dedicated section - remaining structures are reused. The more complicated work itself is done dynamically by the invoked Microsoft Detours API when it patches opcodes of the target functions.

In summary, the following basic modifications were performed on the inspected example:

  • Appending of one further new section (.detour)
  • Redirection of the reference to the Import Directory into this new section
  • Duplication of the entire Import Name Table, inclusive adding of one further import of your custom DLL, whereby all references (except of your custom DLL) of the Import Address Table reference the original section with the original data

Bibliography

History

  • 18th January, 2022: Initial version

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)