65.9K
CodeProject 正在变化。 阅读更多。
Home

静态修改 Microsoft Detours 的 PE 文件逆向

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.99/5 (19投票s)

2022 年 1 月 18 日

LGPL3

14分钟阅读

viewsIcon

20110

downloadIcon

596

调查 Microsoft Detours 执行的 PE 文件修改。

引言

Microsoft Detours 允许在可移植可执行文件 (PE) 运行时钩取导入符号的函数调用。这可以通过不修改原始文件(运行时钩取)或通过对原始文件进行特定修改(静态修改)来实现。Microsoft Detours 允许将导入符号的调用转发到一个自定义的外部 DLL,该 DLL 会拦截调用,然后转发到原始的调用目标 [codeProAbramov]。

这有什么用?在应用程序编程中,当你依赖外部库时,有时你需要调试一般的程序流程行为,但如果你无法访问库的源代码怎么办?或者另一种情况:设想你的软件导入了外部库(你无法修改其源代码),但有必要调整特定函数行为?或者当你的应用程序在客户那里出现生产模式错误时,你想在不重新编译应用程序的情况下延迟记录函数调用?([detoursPaperHunt] p. 1, p. 8)

这些都是 Microsoft Detours 可以帮助你影响调用外部库时发生情况的用例。

本文档重点关注 Microsoft Detours 在运行时之前静态修改可执行文件时对这些文件所做的技术性修改。我们将检查和分析结果文件中修改和添加的内容及其目的。可执行文件示例来自 GitHub 上原始 Detours 项目的源代码包。[gitHubDetoursPro]
静态钩取现有 PE 文件(非运行时)也可以通过 Microsoft Detours 完成,如:[blogChanjun] 所述。

为了提取感兴趣的可执行文件内容,我将使用基于 TypeScript 的代码片段,这些代码片段将被一个专门的 Web 平台接受,该平台关注可执行文件的底层细节分析。你可以在这个平台重现这些脚本的所有执行结果:TRANScurity Platform

入门

本文档中使用了一些 Windows 可执行文件格式和逆向工程方面的基本词汇。首先,我们必须澄清它们的含义,以帮助读者理解这些概念。

可移植可执行文件 (PE) 的基本布局

Windows 二进制可执行文件的文件格式称为可移植可执行文件(简称 PE)。它是旧 COFF 格式的扩展 ([detoursPaperHunt] p. 3)。
PE 文件使用不同的文件扩展名:EXE、DLL、SCR 等。
PE 文件通常包含一个包含元信息的头区域和几个节。理论上,节可以包含任意数据(供程序使用),但也有一些具有特殊含义的节。例如,一个节可能包含代表程序并将由 CPU 执行的机器码(代码节)。或者可能有一个具有特定格式的资源节,其中存储了所需的资源(图标、字符串、菜单)。另一个可能的节,导入节,负责引用和导入外部 PE 文件中的功能(例如,程序可以调用的函数)。([codeBreakersGoppit] p. 4 f.)

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

DOS 头和 DOS stub 是遗留部分,当你在旧 DOS 操作系统上尝试执行文件时,它们会变得活跃。DOS stub 存储要在 DOS 上运行的程序。大多数 PE 文件包含一个简单的程序,它会打印“This program cannot be run on DOS mode...”然后终止。

PE 头包含有关 PE 布局、所需 CPU 架构、可用节以及程序入口点的通用信息。([codeBreakersGoppit] p. 8)

PE 头的一部分是所谓的数据目录。它跟踪具有特殊含义的节(如果存在)的存在情况,例如导入节。在大多数情况下,如果 PE 的代码节调用外部 PE 文件中的函数,这些函数(也称为符号)将首先由导入节导入。但是,理论上,它们也可以通过特定的 Windows API 直接调用,可能为了掩盖恶意意图。

每个数据目录条目描述了相关数据结构的相对虚拟起始地址及其大小 (winnt.h, [codeBreakersGoppit] p. 17)。

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

PE 头后面是节表,其中包含所有存在的节的概述。信息包括:在地址空间中的相对虚拟起始地址、大小和特征(可读、可写、可执行)。(winnt.h, [codeBreakersGoppit] p. 18 f.)

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;
};

导入目录

此数据结构由数据目录的第 2 个条目引用(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

它包含一个 IMAGE_IMPORT_DESCRIPTOR 结构数组。最后一个字节全部设置为零的元素表示数组的结束。(winnt.h, [codeBreakersGoppit] p. 29 ff.)。

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

对于每个导入的 PE 文件,此数组中都存在一个条目。以下字段对我们很重要:OriginalFirstThunkNameFirstThunk

OriginalFirstThunkFirstThunk 各自引用一个 IMAGE_THUNK_DATA 元素数组(末尾有一个零字节项),而 Name 存储指向以零结尾字符串的相对虚拟地址。

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

每个数组元素代表导入的 PE 文件中的一个单独导入符号。如果 IMAGE_THUNK_DATA 元素的值的最高有效位被设置,则它表示导入函数的序数值。否则,它指向 IMAGE_IMPORT_BY_NAME 结构的相对虚拟地址。

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

引用的 OriginalFirstThunk 数组称为“导入名称表”,而 FirstThunk 的数组称为“导入地址表”。指向的两个数组都是彼此的副本。这是因为导入名称表不会被 Windows 加载器修改,以便以后可以检查导入函数的名称。导入地址表的条目将在 PE 加载时被实际函数地址覆盖。

导入目录布局,[codeBreakersGoppit] p. 31

Microsoft Detours

如引言所述,Microsoft Detours 是一个允许拦截 PE 导入函数调用的工具。这对于调试目的很有用,或者当你想要修改其功能时。([detoursPaperHunt], p. 1)

Microsoft Detours 会用一个跳转到用户提供的 detour 函数来覆盖目标函数的前几个指令。此 detour 函数可以在需要时执行任何预处理,然后跳转到 trampoline 函数。trampoline 函数存储丢失的前几个字节(被跳转到 detour 函数的代码覆盖),后面跟着一个跳转到目标函数的指令,并包含剩余未被覆盖的指令。最后,目标函数返回到 detour 函数,该函数可以在需要时执行一些后处理,然后将控制权交还给调用者。
最终修改后的调用流程如下:invokertarget functiondetour functiontrampoline functiontarget functiondetour functioninvoker ([detoursPaperHunt], p. 2)。

带拦截和不带拦截的调用,[detoursPaperHunt], p. 2

Microsoft Detours 还支持编辑 PE 文件的导入表。通过这种方式,可以将现有导入重定向到另一个自定义 DLL 文件。这正是我们稍后将要做的。

准备工作

如果你想静态地(而不是在运行时)通过修改相关的 PE 文件来钩取导入,那么 Microsoft Detours 工具集提供了一个可以帮助你完成此任务的程序:setdll.exe

必须执行以下步骤:

  • 提供需要拦截导入的 PE 文件
  • 实现一个自定义 DLL,该 DLL 存储相关导入的拦截代码
  • 在你的相关 PE 文件的导入表中引用新的 DLL 文件

DLL 文件可能包含在导入它们时在特定生命周期阶段被调用的代码:启动进程/线程,终止进程/线程。你的自定义 DLL 必须包含要拦截的目标函数的签名和你的自定义函数。此外,它还需要前面提到的生命周期函数 (DllMain),该函数必须调用 Microsoft Detours 库中的特定 API,该 API 启动函数控制流的修改(Detour 函数、trampoline 函数等)。有了这两个 PE 文件(你的自定义 DLL 和你的目标 PE 文件),setdll.exe 就可以通过添加对你自定义 DLL 的导入来编辑 *Import* 目录。一旦你执行了目标 PE 文件,Windows 加载器就会加载这个自定义 DLL,它也会调用实现的生命周期函数 (DllMain),该函数反过来调用 Microsoft Detours,后者最终修改要拦截函数的调用流程。([gitHubDetoursPro] Wiki: DetourAttach, [msDocDllMain])

在我们的分析中,我们使用了来自 Microsoft Detours 的示例:[gitHubDetoursPro]。
以下博客描述了如何使用 DllMain:[blogChanjun]。

该示例包含一个目标 PE 文件(sleep5.exe),它打印文本,等待 5 秒,再次打印文本,然后终止。此外,我们还有一个带有生命周期函数的特殊 DLL 文件(simple32.dll),该文件重定向对等待 5 秒的函数(SleepEx)的调用。这个自定义的重定向实现增加了额外的日志记录,并调用了原始函数。([gitHubDetoursPro] samples/simple/*.cpp)

...
#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;
}
...

使用以下参数调用 setdll.exe 将自定义 DLL 注入目标 PE 文件的导入目录:

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

而这个命令则再次恢复原始的导入目录:

setdll.exe /r sleep5.exe

分析

对于这次分析,我们关注以下可执行文件:

SHA-256 文件名 角色
7DCA63A349DCB322B0CC58553AD497AD0CED7B25707288DA36EF11D382FC6354
sleep5.pe 原始未修改的 PE 文件,它会等待几秒钟然后终止。
5A22F948E1FB2BAD9E5F18FBD3F7B7775BF425A0EDBD7F2837CEBF6BBF39E92B
sleep5_detours.pe 原始文件,由 Microsoft Detours 修改。由于 Sleep 函数的调用被 detoured,它执行了额外的日志记录。
A7C3B976F35B44711D32F26317849318C79F3AF0722385586EF1669476962E67
simple32.dll

存储 Sleep 函数的自定义版本。

为了获得顶层概述,检测原始文件和修改后的文件之间的基本差异很有帮助。以下脚本显示了 PE 结构中不同的区域:

源脚本:Differences.ts

(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 元素 左值 右值
IMAGE_DOS_HEADER      
  e_lfanew F0 50
DOS_STUB      
    长度: 176, 0E1FBA0E00B409CD21B8... 长度: 16, 0E1FBA0E00B409CD21B8...
IMAGE_FILE_HEADER      
  NumberOfSections 04 05
IMAGE_DIRECTORY_ENTRY_IMPORT[1]      
  VirtualAddress 0182B4 01C850
  isize 28 3C
IMAGE_IMPORT_DESCRIPTOR[0]      
  杂项 0182DC 01C130
  名称 KERNEL32.dll simple32.dll
  FirstThunk 012000 01C248
IMAGE_IMPORT_DESCRIPTOR[1]      
  杂项 >不存在< 01C138
  TimeDateStamp >不存在< 00
  ForwarderChain >不存在< 00
  名称 >不存在< KERNEL32.dll
  FirstThunk >不存在< 012000
IMAGE_SECTION_HEADER[4]      
  Name1 >不存在< .detour
  杂项 >不存在< 0890
  VirtualAddress >不存在< 01C000
  SizeOfRawData >不存在< 0A00
  PointerToRawData >不存在< 019600
  PointerToRelocations >不存在< 00
  PointerToLinenumbers >不存在< 00
  NumberOfRelocations >不存在< 00
  NumberOfLinenumbers >不存在< 00
  Characteristics >不存在< C0000040
SECTION_TABLE_PADDING      
    长度: 376, 00000000000000000000... 长度: 496, 2E72646174610000CC68...
SECTION_BODY[4]      
    >不存在< 长度: 2560, 40000000447472005008...

显然,DOS 头和 DOS stub 包含第一个差异。只有 DOS 头最后的一个字段(e_lfanew)被更改了。这是有意义的,因为它直接指向 DOS stub 结束后的文件偏移量。由于 DOS stub 的大小发生了变化,因此必须调整此字段。

此外,IMAGE_FILE_HEADER 表明文件中存在的节的总数不同。修改后的 PE 现在多了一个节。甚至导入表似乎也多了一个导入。

节表结束和第一个节开始之间也存在差异。**原始文件**包含这些节:

源脚本:Section headers original.ts

(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

而**修改后的文件**存储这些节:

源脚本:Section headers modified.ts

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

Section for import directory: .detour

正如我们在前面段落中看到的,数据目录引用了特殊节内容中的位置。以下节由**原始文件**的单个数据目录条目引用:

源脚本:Data directory section association original.ts

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

这些是**修改后的文件**中的关联:

源脚本: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

有趣的是,原始的导入目录包含在 .rdata 节中,但在修改后的文件中,它突然出现在新的 .detour 节中。可能,导入的符号现在也不同了。**原始文件**具有以下导入:

源脚本:Data directory original.ts

(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

**修改后的文件**的导入:

源脚本:Data directory modified.ts

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

所有导入都相同,只是有一个来自 Microsoft Detours 故意注入的额外 DLL 文件的新导入:simple32.dll::1
看起来新的 .detour 节起着重要作用。它存储以下内容:

源脚本:Detours section bytes.ts

(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  ................

根据 [gitHubDetoursPro] 上的 Microsoft Detours 代码,我们可以假设引入的这种特殊节以一个定义良好的头部结构开始,该结构用于以后恢复原始 PE 文件。

源脚本:Detour Section Header structure definition.ts

(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}`)
})()
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;

数据目录的导入目录条目引用了该节内的位置。由于需要 IMAGE_IMPORT_DESCRIPTOR 数组作为数据结构,我们可以相应地解释这些字节:

...
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  ................
...

再次,每个数组元素都包含以下字段:

源脚本:Dumped Image Import Descriptors.ts

(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)
})()
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;

Name 字段表示指向零结尾字符串的相对虚拟地址 (RVA)。在我们的例子中,这两个条目中每个 Name 字段的 RVA 都指向同一节内稍靠前的一个位置:

...
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
...

这两个字符串分别解析为注入的库名 simple32.dll 和之前导入的库名 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 代表一个名为 OriginalFirstThunk 的字段。第一个 IMAGE_IMPORT_DESCRIPTOR 元素(描述来自 simple32.dll 的导入)的 OriginalFirstThunkFirstThunk 指向 .detour 节内的 IMAGE_THUNK_DATA 数据结构。

...
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  >È..............
...

由于引用的值的最高有效位被设置 (80),因此它是通过序数值导入的(此处序数值为:01)。

第二个也是最后一个 IMAGE_IMPORT_DESCRIPTOR 数组元素将其引用的 OriginalFirstThunk 数据存储在 .detour 节内,但 FirstThunk 指向不同的节(.rdata),该节代表原始文件中的一个原始、未修改的节。

源脚本:rdata section bytes.ts

(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  ........|Ã...Ã..
...

所引用位置的值反过来是指向 IMAGE_IMPORT_BY_NAME 结构的 RVA。导入地址表(由 FirstThunk 指向)中存储的这些结构将在可执行文件启动时由 Windows 加载器替换为它们的关联函数的实际地址。([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
...

总而言之,导入目录似乎没有进行什么特殊的魔法操作。Differences.ts 中只剩下最后一个偏差:节表结束和第一个节开始之间的内容不同。

粗略浏览 ASCII 解释(尤其是节名称)时,您可能会发现修改后的可执行文件中存在一些重复的节。

源脚本:Header bytes.ts

(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  ................

但根据实际存在的节表,它实际上只有 5 个节。发生了什么?答案很简单:Microsoft Detours 使用原始 PE 头并根据需要覆盖其内容。由于 DOS stub 被缩短了,只有原始头部的开头部分被覆盖,其余字节保持不变,但代表未使用的垃圾字节。它们看起来很有意义,但实际上它们没有效果,也不重要。

结论

本文档让我们对 Microsoft Detours 获得了哪些见解?Microsoft Detours 在需要动态拦截可执行文件的导入时,对可执行文件影响很小,并且在使用其 setdll 工具静态修改可执行文件时,占用的空间也很小。可执行文件内部的大多数结构都尽可能保持不变,只有最少需要调整的数据才会被移入一个专门的新节 - 其余结构被重用。更复杂的工作本身是由被调用的 Microsoft Detours API 完成的,当它修补目标函数的指令时。

总而言之,在检查的示例中执行了以下基本修改:

  • 附加一个新节(.detour
  • 将导入目录的引用重定向到这个新节
  • 复制整个导入名称表,包括添加一个对你自定义 DLL 的额外导入,其中导入地址表中除你自定义 DLL 外的所有引用都指向包含原始数据的原始节

参考文献

历史

  • 2022 年 1 月 18 日:初始版本
© . All rights reserved.