zoukankan      html  css  js  c++  java
  • PE文件解析 基础篇

    PE文件解析 基础篇

    来源 https://bbs.pediy.com/thread-247114.htm

    前言

    • 之前学习了PE格式,为了更好的理解,决定写一个类似LoadPE的小工具。
    • 编译器是VS2015,采用MFC框架。
    • 此系列文章采用边介绍知识点,边写代码的形式,以免变的无聊丧失兴趣。
    • PE知识请参照《加密与解密》第10章
    文章有错误或则不清楚的地方还请您指出。
     

    PE文件格式

     

    1.PE文件基本概念

    • PE文件是windows系统中遵循PE结构的文件,比如以.exe   .dll为后缀名的文件 以及系统驱动文件。(PE结构框架看下图)
    PE文件大体分为两部分,头(包括下图中的DOS头,PE文件头,块表)与主体(块),
     
    •  PE文件从磁盘当中像内存中的映射,不是简单的“1对1”的关系,而是“拉长”了。具体的位置表现在块。 但是磁盘上的数据结构与在内存中的结构是一致的。
     

    • 无论PE文件在磁盘中还是在内存中,都少不了地址的概念,理解一下几个概念至关重要。
           虚拟地址(VA): 在一个程序运行起来的时候,会被加载到内存中,并且每个进程都有自己的4GB,这个4GB当中的某个位置叫做**虚拟地址**,由物理地址映射过来的,4GB的空间,并没有全部被用到。
           基地址( Imagebase ):       磁盘中的文件加载到内存当中的时候可以加载到任意位置,而这个位置就是程序的基址。EXE默认的加载基址是400000h,DLL文件默认基址是10000000h。需要注意的是基地址不是程序的入口点。
           相对虚拟地址(RVA):为了避免PE文件中有确定的内存地址,引入了相对虚拟地址的概念。RVA是在内存中相对与载入地址(基地址)
    的偏移量,所以你可以发现前三个概念的关系 :  虚拟地址(VA)=   基地址+ 相对虚拟地址(RVA)
           文件偏移地址(FOA):当PE文件储存在某个磁盘当中的时候,某个数据的位置相对于文件头的偏移量。
           入口点(OEP):首先明确一个概念就是OEP是一个RVA,,然后使用 OEP + Imagebase == 入口点的VA,通常情况下,OEP指向的不是main函数。
     
     
    • 存了张图 比较好的解释了各部分的关系
     
     
    接下来依次介绍PE结构框图的每个部分
     
    2.DOS头部
    每个PE文件都是以DOS头开始的,IMAGE_DOS_HEADER 结构如下所示
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    (最左边是文件头的偏移量。) 
    IMAGE_DOS_HEADER STRUCT 
    +0h WORD    e_magic        //   MZ(4Dh 5Ah)     DOS可执行文件标记 
    +2h     WORD    e_cblp            
    +4h WORD    e_cp                         
    +6h WORD    e_crlc                      
    +8h WORD    e_cparhdr      
    +0ah    WORD    e_minalloc       
    +0ch    WORD    e_maxalloc  
    +0eh    WORD    e_ss           
    +10h    WORD    e_sp       
    +12h    WORD    e_csum      
    +14h    WORD    e_ip        
    +16h    WORD    e_cs        
    +18h    WORD    e_lfarlc       
    +1ah    WORD    e_ovno          
    +1ch    WORD    e_res[4]        
    +24h    WORD    e_oemid         
    +26h    WORD    e_oeminfo    
    +29h    WORD    e_res2[10]  
    +3ch    DWORD   e_lfanew     //  RVA     指向PE文件头 
    } IMAGE_DOS_HEADER ENDS
     
    需要关注的点是结构体的第一个和第二个元素。
    e_magic:DOS头的标记位,值为4D5Ah。ASCII为”MZ“,判断一个文件是否为PE文件是会用
    e_lfanew:这是一个RVA,代表了PE文件头到基址的偏移量,我们可以用它来找到PE文件头的位置。
     
    我们用010editor打开一个exe文件
     
     
     

    3.PE文件头

     
    IMAGE_NT_HEADERS STRUCT  结构体
    1
    2
    3
    4
    5
    6
    IMAGE_NT_HEADERS STRUCT 
    {
    +0h       DWORD    Signature  
    +4h       IMAGE_FILE_HEADER    FileHeader 
    +18h      IMAGE_OPTIONAL_HEADER32   OptionalHeader   
    } IMAGE_NT_HEADERS ENDS
     
     
    • Signature  字段
    在一个PE文件中Signature字段被设置为4550h,ASCII码为”PE00“。如上图所示。  
     
     
    • IMAGE_FILE_HEADER  结构体
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    struct IMAGE_FILE_HEADER
    {
        WORD Machine; //运行平台
        WORD NumberOfSections; //区块表的个数
        DWORD TimeDataStamp;//文件创建时间,是从1970年至今的秒数
        DWORD PointerToSymbolicTable;//指向符号表的指针
        DWORD NumberOfSymbols;//符号表的数目
        WORD SizeOfOptionalHeader;//IMAGE_NT_HEADERS结构中OptionHeader成员的大小,对于win32平台这个值通常是0x00e0
        WORD Characteristics;//文件的属性值
    }
     
    在010 Editor上查看一下
     
     
    • IMAGE_OPTIONAL_HEADER 结构体
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    typedef struct _IMAGE_OPTIONAL_HEADER
    {
        //
        // Standard fields.  
        //
    +18h    WORD    Magic;                   // 标志字, ROM 映像(0107h),普通可执行文件(010Bh)
    +1Ah    BYTE    MajorLinkerVersion;      // 链接程序的主版本号
    +1Bh    BYTE    MinorLinkerVersion;      // 链接程序的次版本号
    +1Ch    DWORD   SizeOfCode;              // 所有含代码的节的总大小
    +20h    DWORD   SizeOfInitializedData;   // 所有含已初始化数据的节的总大小
    +24h    DWORD   SizeOfUninitializedData; // 所有含未初始化数据的节的大小
    +28h    DWORD   AddressOfEntryPoint;     // 程序执行入口RVA
    +2Ch    DWORD   BaseOfCode;              // 代码的区块的起始RVA
    +30h    DWORD   BaseOfData;              // 数据的区块的起始RVA
        //
        // NT additional fields.    以下是属于NT结构增加的领域。
        //
    +34h    DWORD   ImageBase;               // 程序的首选装载地址
    +38h    DWORD   SectionAlignment;        // 内存中的区块的对齐大小
    +3Ch    DWORD   FileAlignment;           // 文件中的区块的对齐大小
    +40h    WORD    MajorOperatingSystemVersion;  // 要求操作系统最低版本号的主版本号
    +42h    WORD    MinorOperatingSystemVersion;  // 要求操作系统最低版本号的副版本号
    +44h    WORD    MajorImageVersion;       // 可运行于操作系统的主版本号
    +46h    WORD    MinorImageVersion;       // 可运行于操作系统的次版本号
    +48h    WORD    MajorSubsystemVersion;   // 要求最低子系统版本的主版本号
    +4Ah    WORD    MinorSubsystemVersion;   // 要求最低子系统版本的次版本号
    +4Ch    DWORD   Win32VersionValue;       // 莫须有字段,不被病毒利用的话一般为0
    +50h    DWORD   SizeOfImage;             // 映像装入内存后的总尺寸
    +54h    DWORD   SizeOfHeaders;           // 所有头 + 区块表的尺寸大小
    +58h    DWORD   CheckSum;                // 映像的校检和
    +5Ch    WORD    Subsystem;               // 可执行文件期望的子系统
    +5Eh    WORD    DllCharacteristics;      // DllMain()函数何时被调用,默认为 0
    +60h    DWORD   SizeOfStackReserve;      // 初始化时的栈大小
    +64h    DWORD   SizeOfStackCommit;       // 初始化时实际提交的栈大小
    +68h    DWORD   SizeOfHeapReserve;       // 初始化时保留的堆大小
    +6Ch    DWORD   SizeOfHeapCommit;        // 初始化时实际提交的堆大小
    +70h    DWORD   LoaderFlags;             // 与调试有关,默认为 0 
    +74h    DWORD   NumberOfRvaAndSizes;     // 下边数据目录的项数,这个字段自Windows NT 发布以来一直是16
    +78h    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];   
    // 数据目录表
    } IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
     
    重要的有
    AddressOfEntryPoint: 也就是上文提到的OEP,程序源入口点。
    ImageBase: 默认加载基址,
    SectionAlignment:  内存当中的块对齐数,一般为0x1000
    FileAlignment:磁盘当中块对齐数,一般为0x200
    SizeOfHeaders:所有头部大小 也就是DOS头 文件头 以及区块头的总大小 ,文件主体相对文件其实的偏移。
    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]:数据目录表,保存了各种表的RVA及大小。
     
    来看一下数据目录的定义
    1
    2
    3
    4
    IMAGE_DATA_DIRECTORY STRUCT
          VirtualAddress    DWORD       ?   ; 数据的起始RVA
          Size             DWORD       ?   ; 数据块的长度
    IMAGE_DATA_DIRECTORY ENDS
     
    在010 Editor上查看一下 
     
     

    4.写代码操作一下

     
    主要解析了DOS头与PE文件头比较重要的字段,直接放代码。
     
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    //打开文件
    m_hFile = CreateFile(
        m_DeleFileName,GENERIC_READ,NULL,NULL,OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL,NULL);
     
    DWORD dwSize = GetFileSize(m_hFile, NULL);
     
    PBYTE pBuf = new BYTE[dwSize]{};
     
    //读取
    ReadFile(m_hFile,pBuf,dwSize,&dwSize,NULL);
     
    //判断是否为PE文件
    m_pDos = PIMAGE_DOS_HEADER(pBuf);
    if (m_pDos->e_magic!=IMAGE_DOS_SIGNATURE)
    {
        MessageBox(L"不是有效的PE文件  ");
        CloseHandle(m_hFile);
        m_hFile = NULL;
        return;
    }
    m_pNTHeader = PIMAGE_NT_HEADERS(pBuf+m_pDos->e_lfanew);
    if (m_pNTHeader->Signature!= IMAGE_NT_SIGNATURE)
    {
        MessageBox(L"不是有效的PE文件  ");
        CloseHandle(m_hFile);
        m_hFile = NULL;
        return;
    }
     
     
    //读取文件头信息
    m_pFileHeader = &(m_pNTHeader->FileHeader);
     
    m_NumberOfSections.Format(L"%X",m_pFileHeader->NumberOfSections);
    m_TimeDateStamp.Format(L"%p", m_pFileHeader->TimeDateStamp);
    m_SizeOfOptionalHeader.Format(L"%X", m_pFileHeader->SizeOfOptionalHeader);
     
    //拓展头信息
    m_pOptionalHeader = &(m_pNTHeader->OptionalHeader);
     
    m_AddressOfEntryPoint.Format(L"%X",m_pOptionalHeader->AddressOfEntryPoint);
    m_SizeOfHeaders.Format(L"%X", m_pOptionalHeader->SizeOfHeaders);
    m_ImageBase.Format(L"%X", m_pOptionalHeader->ImageBase);
    m_SizeOfImage.Format(L"%X", m_pOptionalHeader->ImageBase);
    m_BaseOfCode.Format(L"%X", m_pOptionalHeader->BaseOfCode);
    m_DllCharacteristics.Format(L"%X", m_pOptionalHeader->DllCharacteristics);
    m_BaseOfData.Format(L"%X", m_pOptionalHeader->BaseOfData);
    m_NumberOfRvaAndSizes.Format(L"%X", m_pOptionalHeader->NumberOfRvaAndSizes);
    m_SectionAlignment.Format(L"%X", m_pOptionalHeader->SectionAlignment);
    m_FileAlignment.Format(L"%X", m_pOptionalHeader->FileAlignment);
    m_CheckSum.Format(L"%X", m_pOptionalHeader->CheckSum);
    m_Magic.Format(L"%X", m_pOptionalHeader->CheckSum);
    m_Subsystem.Format(L"%X", m_pOptionalHeader->Subsystem);
     
     
     
    实现的效果如下:
     
     
    第一部分比较简单,完整代码放到附件。

    ============== End

  • 相关阅读:
    加分二叉树
    飞扬的小鸟
    洛谷P2066 机器分配
    解方程
    洛谷P1781 宇宙总统
    洛谷P1311 选择客栈
    洛谷P1081 开车旅行70分
    CSS清除浮动
    常见的内联元素与块状元素
    标签的权值问题(优先级)
  • 原文地址:https://www.cnblogs.com/lsgxeva/p/9822289.html
Copyright © 2011-2022 走看看