Windows PE结构

3、区段与数据目录表定位

1、课程目标

  1. 理解PE区段的作用和结构
  2. 掌握区段头的各个字段含义
  3. 理解数据目录表的作用
  4. 学会定位各种数据目录

2、名词解释

名词 全称 解释
Section 区段/节 PE文件的数据分区
Section Header 区段头 描述区段属性的结构
.text - 代码段
.data - 已初始化数据段
.rdata - 只读数据段
.bss - 未初始化数据段
.rsrc - 资源段
.reloc - 重定位段
Data Directory 数据目录 重要数据表的位置信息

3、使用工具

  • CFF Explorer
  • PE-bear
  • PEview
  • x64dbg

4、技术原理

4.1、区段头结构

#define IMAGE_SIZEOF_SHORT_NAME 8

typedef struct _IMAGE_SECTION_HEADER {
    BYTE    Name[IMAGE_SIZEOF_SHORT_NAME];  // 区段名(最多8字节)
    union {
        DWORD   PhysicalAddress;
        DWORD   VirtualSize;                 // 虚拟大小(内存中)
    } Misc;
    DWORD   VirtualAddress;                  // 虚拟地址(RVA)
    DWORD   SizeOfRawData;                   // 文件中的大小
    DWORD   PointerToRawData;                // 文件中的偏移(FOA)
    DWORD   PointerToRelocations;            // 重定位表指针
    DWORD   PointerToLinenumbers;            // 行号表指针
    WORD    NumberOfRelocations;             // 重定位项数
    WORD    NumberOfLinenumbers;             // 行号数
    DWORD   Characteristics;                 // 区段特征
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

// 区段特征标志
#define IMAGE_SCN_CNT_CODE                  0x00000020  // 包含代码
#define IMAGE_SCN_CNT_INITIALIZED_DATA      0x00000040  // 包含已初始化数据
#define IMAGE_SCN_CNT_UNINITIALIZED_DATA    0x00000080  // 包含未初始化数据
#define IMAGE_SCN_MEM_DISCARDABLE           0x02000000  // 可丢弃
#define IMAGE_SCN_MEM_NOT_CACHED            0x04000000  // 不可缓存
#define IMAGE_SCN_MEM_NOT_PAGED             0x08000000  // 不可分页
#define IMAGE_SCN_MEM_SHARED                0x10000000  // 可共享
#define IMAGE_SCN_MEM_EXECUTE               0x20000000  // 可执行
#define IMAGE_SCN_MEM_READ                  0x40000000  // 可读
#define IMAGE_SCN_MEM_WRITE                 0x80000000  // 可写

4.2、数据目录表

typedef struct _IMAGE_DATA_DIRECTORY {
    DWORD   VirtualAddress;     // 数据表的RVA
    DWORD   Size;               // 数据表的大小
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

// 数据目录索引
#define IMAGE_DIRECTORY_ENTRY_EXPORT          0   // 导出表
#define IMAGE_DIRECTORY_ENTRY_IMPORT          1   // 导入表
#define IMAGE_DIRECTORY_ENTRY_RESOURCE        2   // 资源表
#define IMAGE_DIRECTORY_ENTRY_EXCEPTION       3   // 异常表
#define IMAGE_DIRECTORY_ENTRY_SECURITY        4   // 安全/证书表
#define IMAGE_DIRECTORY_ENTRY_BASERELOC       5   // 重定位表
#define IMAGE_DIRECTORY_ENTRY_DEBUG           6   // 调试信息
#define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE    7   // 架构信息
#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR       8   // 全局指针
#define IMAGE_DIRECTORY_ENTRY_TLS             9   // TLS表
#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG     10  // 加载配置表
#define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT    11  // 绑定导入表
#define IMAGE_DIRECTORY_ENTRY_IAT             12  // 导入地址表
#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT    13  // 延迟导入表
#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR  14  // CLR头
#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES      16  // 数据目录数量

5、代码实现

5.1、解析区段

#include <windows.h>
#include <stdio.h>

// 获取区段特征描述
void GetSectionCharacteristics(DWORD chars, char* buffer) {
    buffer[0] = '\0';
    
    if (chars & IMAGE_SCN_CNT_CODE) strcat(buffer, "CODE ");
    if (chars & IMAGE_SCN_CNT_INITIALIZED_DATA) strcat(buffer, "IDATA ");
    if (chars & IMAGE_SCN_CNT_UNINITIALIZED_DATA) strcat(buffer, "UDATA ");
    if (chars & IMAGE_SCN_MEM_DISCARDABLE) strcat(buffer, "DISC ");
    if (chars & IMAGE_SCN_MEM_SHARED) strcat(buffer, "SHARED ");
    if (chars & IMAGE_SCN_MEM_EXECUTE) strcat(buffer, "X ");
    if (chars & IMAGE_SCN_MEM_READ) strcat(buffer, "R ");
    if (chars & IMAGE_SCN_MEM_WRITE) strcat(buffer, "W ");
}

// 解析所有区段
void ParseSections(LPVOID fileData) {
    PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)fileData;
    PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)(
        (PBYTE)fileData + dosHeader->e_lfanew
    );
    
    WORD numSections = ntHeaders->FileHeader.NumberOfSections;
    PIMAGE_SECTION_HEADER sections = IMAGE_FIRST_SECTION(ntHeaders);
    
    printf("\n========== Sections (%d) ==========\n\n", numSections);
    
    for (WORD i = 0; i < numSections; i++) {
        // 区段名(可能不是以0结尾)
        char name[9] = {0};
        memcpy(name, sections[i].Name, 8);
        
        printf("Section #%d: %s\n", i, name);
        printf("  VirtualSize:       0x%08X (%d bytes)\n", 
               sections[i].Misc.VirtualSize, sections[i].Misc.VirtualSize);
        printf("  VirtualAddress:    0x%08X (RVA)\n", sections[i].VirtualAddress);
        printf("  SizeOfRawData:     0x%08X (%d bytes)\n", 
               sections[i].SizeOfRawData, sections[i].SizeOfRawData);
        printf("  PointerToRawData:  0x%08X (FOA)\n", sections[i].PointerToRawData);
        printf("  Characteristics:   0x%08X\n", sections[i].Characteristics);
        
        char chars[256];
        GetSectionCharacteristics(sections[i].Characteristics, chars);
        printf("                     [%s]\n", chars);
        
        // 计算内存范围
        DWORD memStart = sections[i].VirtualAddress;
        DWORD memEnd = memStart + sections[i].Misc.VirtualSize;
        printf("  Memory Range:      0x%08X - 0x%08X\n", memStart, memEnd);
        
        // 计算文件范围
        DWORD fileStart = sections[i].PointerToRawData;
        DWORD fileEnd = fileStart + sections[i].SizeOfRawData;
        printf("  File Range:        0x%08X - 0x%08X\n", fileStart, fileEnd);
        
        printf("\n");
    }
}

// 通过名称查找区段
PIMAGE_SECTION_HEADER FindSectionByName(LPVOID fileData, const char* name) {
    PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)fileData;
    PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)(
        (PBYTE)fileData + dosHeader->e_lfanew
    );
    
    WORD numSections = ntHeaders->FileHeader.NumberOfSections;
    PIMAGE_SECTION_HEADER sections = IMAGE_FIRST_SECTION(ntHeaders);
    
    for (WORD i = 0; i < numSections; i++) {
        char secName[9] = {0};
        memcpy(secName, sections[i].Name, 8);
        
        if (_stricmp(secName, name) == 0) {
            return &sections[i];
        }
    }
    
    return NULL;
}

// 通过RVA查找区段
PIMAGE_SECTION_HEADER FindSectionByRva(LPVOID fileData, DWORD rva) {
    PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)fileData;
    PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)(
        (PBYTE)fileData + dosHeader->e_lfanew
    );
    
    WORD numSections = ntHeaders->FileHeader.NumberOfSections;
    PIMAGE_SECTION_HEADER sections = IMAGE_FIRST_SECTION(ntHeaders);
    
    for (WORD i = 0; i < numSections; i++) {
        DWORD start = sections[i].VirtualAddress;
        DWORD end = start + sections[i].Misc.VirtualSize;
        
        if (rva >= start && rva < end) {
            return &sections[i];
        }
    }
    
    return NULL;
}

5.2、解析数据目录

// 数据目录名称
const char* DataDirectoryNames[] = {
    "Export Table",
    "Import Table",
    "Resource Table",
    "Exception Table",
    "Certificate Table",
    "Base Relocation Table",
    "Debug",
    "Architecture",
    "Global Ptr",
    "TLS Table",
    "Load Config Table",
    "Bound Import",
    "Import Address Table",
    "Delay Import Descriptor",
    "CLR Runtime Header",
    "Reserved"
};

// 获取数据目录
PIMAGE_DATA_DIRECTORY GetDataDirectories(LPVOID fileData, DWORD* count) {
    PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)fileData;
    PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)(
        (PBYTE)fileData + dosHeader->e_lfanew
    );
    
    BOOL is64 = (ntHeaders->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC);
    
    if (is64) {
        PIMAGE_OPTIONAL_HEADER64 opt = (PIMAGE_OPTIONAL_HEADER64)&ntHeaders->OptionalHeader;
        *count = opt->NumberOfRvaAndSizes;
        return opt->DataDirectory;
    } else {
        PIMAGE_OPTIONAL_HEADER32 opt = (PIMAGE_OPTIONAL_HEADER32)&ntHeaders->OptionalHeader;
        *count = opt->NumberOfRvaAndSizes;
        return opt->DataDirectory;
    }
}

// 解析数据目录表
void ParseDataDirectories(LPVOID fileData) {
    DWORD count;
    PIMAGE_DATA_DIRECTORY dirs = GetDataDirectories(fileData, &count);
    
    printf("\n========== Data Directories (%d) ==========\n\n", count);
    printf("%-5s %-25s %-12s %-12s\n", "Idx", "Name", "RVA", "Size");
    printf("%s\n", "------------------------------------------------------");
    
    for (DWORD i = 0; i < count && i < IMAGE_NUMBEROF_DIRECTORY_ENTRIES; i++) {
        const char* name = (i < 16) ? DataDirectoryNames[i] : "Unknown";
        
        if (dirs[i].VirtualAddress != 0 || dirs[i].Size != 0) {
            printf("[%2d] %-25s 0x%08X   0x%08X\n",
                   i, name, dirs[i].VirtualAddress, dirs[i].Size);
            
            // 查找所在区段
            PIMAGE_SECTION_HEADER section = FindSectionByRva(fileData, 
                                                             dirs[i].VirtualAddress);
            if (section) {
                char secName[9] = {0};
                memcpy(secName, section->Name, 8);
                printf("     -> Located in section: %s\n", secName);
            }
        } else {
            printf("[%2d] %-25s <empty>\n", i, name);
        }
    }
}

// 获取特定数据目录的数据指针
LPVOID GetDataDirectoryData(LPVOID fileData, DWORD index) {
    DWORD count;
    PIMAGE_DATA_DIRECTORY dirs = GetDataDirectories(fileData, &count);
    
    if (index >= count || dirs[index].VirtualAddress == 0) {
        return NULL;
    }
    
    DWORD rva = dirs[index].VirtualAddress;
    DWORD foa = RvaToFoa(fileData, rva);
    
    return (LPVOID)((PBYTE)fileData + foa);
}

5.3、添加新区段

// 添加新区段
BOOL AddSection(LPVOID fileData, DWORD* fileSize, 
                const char* name, DWORD dataSize, DWORD characteristics) {
    PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)fileData;
    PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)(
        (PBYTE)fileData + dosHeader->e_lfanew
    );
    
    WORD numSections = ntHeaders->FileHeader.NumberOfSections;
    PIMAGE_SECTION_HEADER sections = IMAGE_FIRST_SECTION(ntHeaders);
    
    // 检查是否有空间添加区段头
    DWORD headersEnd = (DWORD)((PBYTE)&sections[numSections] - (PBYTE)fileData);
    headersEnd += sizeof(IMAGE_SECTION_HEADER);  // 新区段头大小
    
    if (headersEnd > ntHeaders->OptionalHeader.SizeOfHeaders) {
        printf("[-] 没有足够空间添加区段头\n");
        return FALSE;
    }
    
    // 获取最后一个区段
    PIMAGE_SECTION_HEADER lastSection = &sections[numSections - 1];
    
    // 计算新区段的位置
    DWORD sectionAlignment = ntHeaders->OptionalHeader.SectionAlignment;
    DWORD fileAlignment = ntHeaders->OptionalHeader.FileAlignment;
    
    // 新区段的RVA(对齐到SectionAlignment)
    DWORD newRva = lastSection->VirtualAddress + lastSection->Misc.VirtualSize;
    newRva = (newRva + sectionAlignment - 1) & ~(sectionAlignment - 1);
    
    // 新区段的文件偏移(对齐到FileAlignment)
    DWORD newFoa = lastSection->PointerToRawData + lastSection->SizeOfRawData;
    newFoa = (newFoa + fileAlignment - 1) & ~(fileAlignment - 1);
    
    // 对齐数据大小
    DWORD alignedSize = (dataSize + fileAlignment - 1) & ~(fileAlignment - 1);
    
    // 填充新区段头
    PIMAGE_SECTION_HEADER newSection = &sections[numSections];
    ZeroMemory(newSection, sizeof(IMAGE_SECTION_HEADER));
    
    strncpy((char*)newSection->Name, name, 8);
    newSection->Misc.VirtualSize = dataSize;
    newSection->VirtualAddress = newRva;
    newSection->SizeOfRawData = alignedSize;
    newSection->PointerToRawData = newFoa;
    newSection->Characteristics = characteristics;
    
    // 更新文件头
    ntHeaders->FileHeader.NumberOfSections++;
    
    // 更新SizeOfImage
    DWORD newImageSize = newRva + dataSize;
    newImageSize = (newImageSize + sectionAlignment - 1) & ~(sectionAlignment - 1);
    ntHeaders->OptionalHeader.SizeOfImage = newImageSize;
    
    // 更新文件大小
    *fileSize = newFoa + alignedSize;
    
    printf("[+] 添加区段: %s\n", name);
    printf("    RVA: 0x%08X, FOA: 0x%08X, Size: 0x%08X\n", 
           newRva, newFoa, alignedSize);
    
    return TRUE;
}

// 扩展最后一个区段
BOOL ExtendLastSection(LPVOID fileData, DWORD* fileSize, DWORD extraSize) {
    PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)fileData;
    PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)(
        (PBYTE)fileData + dosHeader->e_lfanew
    );
    
    WORD numSections = ntHeaders->FileHeader.NumberOfSections;
    PIMAGE_SECTION_HEADER lastSection = IMAGE_FIRST_SECTION(ntHeaders) + numSections - 1;
    
    DWORD fileAlignment = ntHeaders->OptionalHeader.FileAlignment;
    DWORD sectionAlignment = ntHeaders->OptionalHeader.SectionAlignment;
    
    // 扩展区段大小
    DWORD oldRawSize = lastSection->SizeOfRawData;
    DWORD newRawSize = oldRawSize + extraSize;
    newRawSize = (newRawSize + fileAlignment - 1) & ~(fileAlignment - 1);
    
    lastSection->SizeOfRawData = newRawSize;
    lastSection->Misc.VirtualSize += extraSize;
    
    // 更新SizeOfImage
    DWORD newImageSize = lastSection->VirtualAddress + lastSection->Misc.VirtualSize;
    newImageSize = (newImageSize + sectionAlignment - 1) & ~(sectionAlignment - 1);
    ntHeaders->OptionalHeader.SizeOfImage = newImageSize;
    
    *fileSize = lastSection->PointerToRawData + newRawSize;
    
    char name[9] = {0};
    memcpy(name, lastSection->Name, 8);
    printf("[+] 扩展区段 %s: 0x%08X -> 0x%08X\n", name, oldRawSize, newRawSize);
    
    return TRUE;
}

5.4、修改区段属性

// 修改区段特征
BOOL ModifySectionCharacteristics(LPVOID fileData, 
                                   const char* sectionName, 
                                   DWORD newChars) {
    PIMAGE_SECTION_HEADER section = FindSectionByName(fileData, sectionName);
    
    if (!section) {
        printf("[-] 未找到区段: %s\n", sectionName);
        return FALSE;
    }
    
    DWORD oldChars = section->Characteristics;
    section->Characteristics = newChars;
    
    printf("[+] 修改区段 %s 特征: 0x%08X -> 0x%08X\n", 
           sectionName, oldChars, newChars);
    
    return TRUE;
}

// 使区段可执行
BOOL MakeSectionExecutable(LPVOID fileData, const char* sectionName) {
    PIMAGE_SECTION_HEADER section = FindSectionByName(fileData, sectionName);
    
    if (!section) {
        return FALSE;
    }
    
    section->Characteristics |= IMAGE_SCN_MEM_EXECUTE;
    return TRUE;
}

// 使区段可写
BOOL MakeSectionWritable(LPVOID fileData, const char* sectionName) {
    PIMAGE_SECTION_HEADER section = FindSectionByName(fileData, sectionName);
    
    if (!section) {
        return FALSE;
    }
    
    section->Characteristics |= IMAGE_SCN_MEM_WRITE;
    return TRUE;
}

6、课后作业

  1. 编写区段分析工具

    • 列出所有区段信息
    • 分析区段权限
    • 检测异常区段特征
  2. 实现区段操作功能

    • 添加新区段
    • 扩展现有区段
    • 修改区段属性
  3. 分析数据目录

    • 定位所有数据目录
    • 分析各目录所在区段
    • 研究目录之间的关系