[PE]结构分析与代码实现
阅读原文时间:2023年07月12日阅读:1

PE结构浅析

知识导向:

程序最开始是存放在磁盘上的,运行程序首先需要申请4GB的内存,将程序从磁盘copy到内存,但不是直接复制,而是进行拉伸处理。

这也就是为什么会有一个文件中地址和一个VirtualAddress,即所谓的FOA和VA

RVA是相对地址,也就是相对于可选头中ImageBase的存放地址,文件中的VA都是RVA

先上pe总览图

:)好吧,看上去还是挺复杂的,但慢慢分析,还是可以大概分析清楚的

  • DOS_HEADER

    typedef struct _IMAGE_DOS_HEADER { // DOS .EXE header
    WORD e_magic; // Magic number
    WORD e_cblp; // Bytes on last page of file
    WORD e_cp; // Pages in file
    WORD e_crlc; // Relocations
    WORD e_cparhdr; // Size of header in paragraphs
    WORD e_minalloc; // Minimum extra paragraphs needed
    WORD e_maxalloc; // Maximum extra paragraphs needed
    WORD e_ss; // Initial (relative) SS value
    WORD e_sp; // Initial SP value
    WORD e_csum; // Checksum
    WORD e_ip; // Initial IP value
    WORD e_cs; // Initial (relative) CS value
    WORD e_lfarlc; // File address of relocation table
    WORD e_ovno; // Overlay number
    WORD e_res[4]; // Reserved words
    WORD e_oemid; // OEM identifier (for e_oeminfo)
    WORD e_oeminfo; // OEM information; e_oemid specific
    WORD e_res2[10]; // Reserved words
    LONG e_lfanew; // File address of new exe header
    } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

e_magic 用于标识是否是可执行文件

e_lfanew NT头的偏移,也就是说DOS头和NT头之间不是连续的,中间有一部分的空闲空间可用于存放说明信息

通过偏移得到NT头所在的位置,NT头中主要是文件头和可选头

NT头的第一个DWORD 是NT头签名,用于说明可执行文件的类型,例如PE32 PE64等

  • FileHeader 文件头

    typedef struct _IMAGE_FILE_HEADER {
    WORD Machine;
    WORD NumberOfSections;
    DWORD TimeDateStamp;
    DWORD PointerToSymbolTable;
    DWORD NumberOfSymbols;
    WORD SizeOfOptionalHeader;
    WORD Characteristics;
    } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

NumberOfSections 节的数量,用于解析节

SizeOfOptionalHeader 可选头的大小,32位为F0 64位为E0

比邻的是OptionalHeader 可选头,说是可选头,实际是必需的

typedef struct _IMAGE_OPTIONAL_HEADER {
    //
    // Standard fields.
    //

    WORD    Magic;
    BYTE    MajorLinkerVersion;
    BYTE    MinorLinkerVersion;
    DWORD   SizeOfCode;
    DWORD   SizeOfInitializedData;
    DWORD   SizeOfUninitializedData;
    DWORD   AddressOfEntryPoint;
    DWORD   BaseOfCode;
    DWORD   BaseOfData;

    DWORD   ImageBase;
    DWORD   SectionAlignment;
    DWORD   FileAlignment;
    WORD    MajorOperatingSystemVersion;
    WORD    MinorOperatingSystemVersion;
    WORD    MajorImageVersion;
    WORD    MinorImageVersion;
    WORD    MajorSubsystemVersion;
    WORD    MinorSubsystemVersion;
    DWORD   Win32VersionValue;
    DWORD   SizeOfImage;
    DWORD   SizeOfHeaders;
    DWORD   CheckSum;
    WORD    Subsystem;
    WORD    DllCharacteristics;
    DWORD   SizeOfStackReserve;
    DWORD   SizeOfStackCommit;
    DWORD   SizeOfHeapReserve;
    DWORD   SizeOfHeapCommit;
    DWORD   LoaderFlags;
    DWORD   NumberOfRvaAndSizes;
    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

ImageBase 程序加载到内存时,默认的存放位置,但有可能实际情况不一样。(可能设置的默认位置已经被占据了)

FileAlignment 文件在磁盘上的对齐大小

SectionAlignment 文件在内存中的对齐大小

什么是文件对齐?什么是内存对齐?为什么要对齐?

SizeOfImage 文件在内存中对齐后的大小

SizeOfHeaders 头和节表在内存对齐后的大小

  • SectionHeader

    typedef 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;
    } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

知识拓展

union联合体

联合体声明和结构体声明差不多,唯一的区别在于底层存储。union中的数据在底层按最大的元素分配一块内存,其余所有属性共享这一片内存。

可以参考:https://www.cnblogs.com/leezhxing/p/4619185.html

每个节的大小都一样,这也就是为什么文件头中需要设置节的数量,如果不设置,解析器循环读多少次节表才停止呢?

节存储的时候不是连续的,所以需要节表中确定节的位置和大小

DWORD   VirtualAddress;    //节在内存中的RVA
DWORD   SizeOfRawData;    //节在磁盘上的大小
DWORD   PointerToRawData;//节在文件中的偏移

- 导出表

- 导入表

- 重定位表

- 导入绑定表

#include<iostream>
#include<Windows.h>
using namespace std;
#define ESize 0x2000    //设定拓展节的大小

char FilePath[] = "E:\\Code\\DLL01\\Debug\\notepad.exe"; //Target File
char SaveFilePath[] = "E:\\Code\\DLL01\\Debug\\DLL03.dll"; //Save File

char* Buffer;
int FileSize;

PIMAGE_DOS_HEADER dosHeader;
PIMAGE_FILE_HEADER fileHeader;
PIMAGE_OPTIONAL_HEADER optionalHeader;
PIMAGE_SECTION_HEADER NewsectionHeader;

void readFile2Buffer() {

    FILE* fp = fopen(FilePath,"rb");
    fseek(fp, 0, SEEK_END);
    FileSize = ftell(fp);
    rewind(fp);

    Buffer = (char*)malloc(sizeof(char)*(FileSize+ ESize));
    fread(Buffer,1,FileSize,fp);

    fclose(fp);

}

void readBuffer2File(int BufferSize,char* Buffer) {
    FILE* fp = fopen(SaveFilePath, "wb");
    fwrite(Buffer,1,BufferSize,fp);
    fclose(fp);
}

DWORD RVA2FOA(DWORD RVA) {
    //在header节
    if (RVA < optionalHeader->SizeOfHeaders) {
        return RVA;
    }
    //在其他节
    DWORD  Alignment = optionalHeader->SectionAlignment;
    for (int i = 0; i < fileHeader->NumberOfSections; i++) {
        NewsectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)Buffer +dosHeader->e_lfanew + 4 + 20 + fileHeader->SizeOfOptionalHeader + i * 40);
        if (RVA >= NewsectionHeader->VirtualAddress && RVA < (NewsectionHeader->VirtualAddress + (NewsectionHeader->SizeOfRawData / Alignment + 1) * Alignment)) {
            return NewsectionHeader->PointerToRawData + RVA- NewsectionHeader->VirtualAddress;
        }
    }
}

DWORD FOA2RVA(DWORD FOA) {
    //在header节
    if (FOA < optionalHeader->SizeOfHeaders) {
        return FOA;
    }
    //在其他节
    DWORD  Alignment = optionalHeader->FileAlignment;
    for (int i = 0; i < fileHeader->NumberOfSections; i++) {
        NewsectionHeader = (PIMAGE_SECTION_HEADER)(Buffer+dosHeader->e_lfanew + 4 + 0x14 + fileHeader->SizeOfOptionalHeader + i * 40);
        if (FOA >= NewsectionHeader->PointerToRawData && FOA < ( NewsectionHeader->PointerToRawData + (NewsectionHeader->SizeOfRawData / Alignment+1)* Alignment)) {
            return FOA - NewsectionHeader->PointerToRawData+NewsectionHeader->VirtualAddress;
        }
    }
}

void PEParse() {
    if (*(WORD*)Buffer != IMAGE_DOS_SIGNATURE) {
        cout << "Error Format" << endl;
        return;
    }
    //dosHeader
    dosHeader = (PIMAGE_DOS_HEADER)Buffer;

    if (*(PDWORD)(Buffer +dosHeader->e_lfanew) != IMAGE_NT_SIGNATURE) {
        cout << "Not PE" << endl;
        return;
    }
    //fileHeader
    fileHeader = (PIMAGE_FILE_HEADER)(Buffer + dosHeader->e_lfanew + 4);
    cout << "NumberOfSections:" << fileHeader->NumberOfSections<<endl;
    cout << "SizeOfOptionalHeader:" << fileHeader->SizeOfOptionalHeader<<endl;

    //optionalHeader
    optionalHeader = (PIMAGE_OPTIONAL_HEADER)(Buffer + dosHeader->e_lfanew + 24);

    //NewsectionHeader
    for (int i = 0; i < fileHeader->NumberOfSections; i++) {
        NewsectionHeader = (PIMAGE_SECTION_HEADER)(Buffer+dosHeader->e_lfanew + 4 + 0x14 + fileHeader->SizeOfOptionalHeader + i * 40);
        cout << "************NewsectionHeader**************" << endl;
        cout << "VirtualAddress:" << NewsectionHeader->VirtualAddress << endl;
        cout << "PointerToRawData:" << NewsectionHeader->PointerToRawData << endl;
        cout << "SizeOfRawData:" << NewsectionHeader->SizeOfRawData << endl;
    }
    cout << "************************************************" << endl;
    cout << "*********        PE 解析完毕       ************" << endl;
    cout << "************************************************" << endl;
    cout << endl;
    cout << endl;
}
/*
    #define IMAGE_DIRECTORY_ENTRY_EXPORT          0   // Export Directory
    #define IMAGE_DIRECTORY_ENTRY_IMPORT          1   // Import Directory
    #define IMAGE_DIRECTORY_ENTRY_RESOURCE        2   // Resource Directory
    #define IMAGE_DIRECTORY_ENTRY_EXCEPTION       3   // Exception Directory
    #define IMAGE_DIRECTORY_ENTRY_SECURITY        4   // Security Directory
    #define IMAGE_DIRECTORY_ENTRY_BASERELOC       5   // Base Relocation Table
    #define IMAGE_DIRECTORY_ENTRY_DEBUG           6   // Debug Directory
    //      IMAGE_DIRECTORY_ENTRY_COPYRIGHT       7   // (X86 usage)
    #define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE    7   // Architecture Specific Data
    #define IMAGE_DIRECTORY_ENTRY_GLOBALPTR       8   // RVA of GP
    #define IMAGE_DIRECTORY_ENTRY_TLS             9   // TLS Directory
    #define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG    10   // Load Configuration Directory
    #define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT   11   // Bound Import Directory in headers
    #define IMAGE_DIRECTORY_ENTRY_IAT            12   // Import Address Table
    #define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT   13   // Delay Load Import Descriptors
    #define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14   // COM Runtime descriptor
*/

DWORD ExportTable() {
    /*
        typedef struct _IMAGE_EXPORT_DIRECTORY {
            DWORD   Characteristics;
            DWORD   TimeDateStamp;
            WORD    MajorVersion;
            WORD    MinorVersion;
            DWORD   Name;
            DWORD   Base;                   //Base of sequence
            DWORD   NumberOfFunctions;
            DWORD   NumberOfNames;
            DWORD   AddressOfFunctions;     // RVA from base of image   DWORD
            DWORD   AddressOfNames;         // RVA from base of image   DWORD
            DWORD   AddressOfNameOrdinals;  // RVA from base of image   WORD
        } IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
    */
    cout << "===============================" << endl;
    cout << "IMAGE_DIRECTORY_ENTRY_BASERELOC" << endl;
    cout << "===============================" << endl;

    if (optionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size == 0) {
        cout << "No export table!!!" << endl;
        return 0;
    }

    cout << "VirtualAddress:" << hex << optionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress<<endl;
    cout << "Size:" << hex << optionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size << endl;
    cout << sizeof(IMAGE_EXPORT_DIRECTORY) << endl;

    DWORD FOA = RVA2FOA(optionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
    PIMAGE_EXPORT_DIRECTORY ExportTable = (PIMAGE_EXPORT_DIRECTORY)(FOA + (DWORD)Buffer);
    cout << "^^^^^^^^^^^^^Export Table^^^^^^^^^^^^^^" << endl;
    cout << "Export Table Name:" << (char*)((DWORD)Buffer+RVA2FOA(ExportTable->Name)) << endl;
    cout << "Base:" << ExportTable->Base << endl;
    cout << "NumberOfFunctions:" << ExportTable->NumberOfFunctions << endl;
    cout << "NumberOfNames:" << ExportTable->NumberOfNames << endl;

    cout << (DWORD)Buffer << endl;
    cout << RVA2FOA(ExportTable->AddressOfNames) << endl;

    for (int i = 0; i < ExportTable->NumberOfNames; i++) {
        cout << "==============Export Function============" << endl;
        cout << "FunctionName:" << (char*)((DWORD)Buffer + RVA2FOA(*(DWORD*)((DWORD)Buffer + RVA2FOA(ExportTable->AddressOfNames) + i * 4))) << endl;
        //cout << "FunctionOrd:" << (*(WORD*)((DWORD)Buffer + RVA2FOA(ExportTable->AddressOfNameOrdinals))+i*2) << endl;
        cout << "FunctionAddr(RVA):" << *(DWORD*)((DWORD)Buffer + RVA2FOA(ExportTable->AddressOfFunctions) + 4 * (*(WORD*)((DWORD)Buffer + RVA2FOA(ExportTable->AddressOfNameOrdinals) + i * 2)))<<endl;
    }
    return (DWORD)ExportTable-(DWORD)Buffer;
}

DWORD RelocationTable() {
    cout << "===============================" << endl;
    cout << "IMAGE_DIRECTORY_ENTRY_BASERELOC" << endl;
    cout << "===============================" << endl;

    if (optionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress == 0 && optionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size == 0) {
        cout << "Relocation Table Is Null" << endl;
        return 0;
    }

    DWORD VirtualAddress = optionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress;
    DWORD SizeOfBlock = optionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size;

    DWORD FOA = (DWORD)Buffer+RVA2FOA(VirtualAddress);
    while (*((DWORD*)FOA) != 0 || *((DWORD*)FOA + 1) != 0) {
        DWORD VirtualAddress = *((DWORD*)FOA);
        DWORD SizeOfBlock = *((DWORD*)FOA + 1);

        cout << "*********************************" << endl;
        cout << "VirtualAddress:" << hex <<VirtualAddress << endl;
        for (int i = 0; i < (SizeOfBlock - 8) / 2; i++) {
            WORD li = *((WORD*)(FOA + 8) + i);
            printf("*%02X* RVA:%08X ATTR:%d\n", i, VirtualAddress + (li & 0x0fff), (li & 0xf000) >> 12);
        }
        FOA += SizeOfBlock;
    }
    return RVA2FOA(VirtualAddress); //返回重定位表在文件中的位置
}

void ImportTable() {
    DWORD importTable = (DWORD)Buffer + RVA2FOA(optionalHeader->DataDirectory[3].VirtualAddress);
    //下一个导入表为全0导入表结束
    while (1) {
        PIMAGE_IMPORT_DESCRIPTOR table = (PIMAGE_IMPORT_DESCRIPTOR)importTable;
        if (
            table->FirstThunk == 0&&
            table->OriginalFirstThunk == 0
        ) {
            cout << "*******************" << endl;
            cout << "End of Import Table" << endl;
            cout << "*******************" << endl;
            break;
        }

        cout << (char*)((DWORD)Buffer + RVA2FOA(table->Name)) << endl;
        bool isBounded = false;
        if (table->TimeDateStamp == 0xffffffff)
            isBounded = true;

        DWORD table1 = (DWORD)Buffer + RVA2FOA(table->OriginalFirstThunk);
        DWORD table2 = (DWORD)Buffer + RVA2FOA(table->FirstThunk);

        cout << "-------------------------------" << endl;
        while (1) {
            cout << "FirstThunk:";
            DWORD data = *(DWORD*)table2;
            if (data == 0)
                break;
            if (isBounded) {
                cout << (data & 0x7fffffff) << endl;
            }
            else if (((data & 0x80000000) >> 31) == 1)
                cout << (data & 0x7fffffff);
            else {
                PIMAGE_IMPORT_BY_NAME tmp = (PIMAGE_IMPORT_BY_NAME)((DWORD)Buffer + RVA2FOA(data));
                cout << "HINT:" << hex << tmp->Hint << "-";
                cout << "Name:" << tmp->Name;
            }
            table2 += 4;

            cout << "OrigionalThunk:";
            DWORD data1 = *(DWORD*)table1;
            if (((data1 & 0x80000000) >> 31) == 1)
                cout << (data1 & 0x7fffffff) << endl;
            else {
                PIMAGE_IMPORT_BY_NAME tmp = (PIMAGE_IMPORT_BY_NAME)((DWORD)Buffer + RVA2FOA(data1));
                cout << "HINT:" << hex << tmp->Hint << "-";
                cout << "Name:" << tmp->Name << endl;
            }
            table1 += 4;
        }
        importTable += sizeof(IMAGE_IMPORT_DESCRIPTOR);
    }
}

int main() {
    //Copy File to FileBuffer
    readFile2Buffer();
    //PE Parse
    PEParse();
    /*
        Print Export Table
    */
    //ExportTable();

    /*
        Print Relocation Table
    */
    //RelocationTable();

    /*
        Print Import Table
    */
    //ImportTable();

    //Free Buffer
    free(Buffer);
    return 0;
}

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章