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

  • 相关阅读:
    php环境配置中各个模块在网站建设中的功能
    PHP+Apache+MySQL+phpMyAdmin在win7系统下的环境配置
    August 17th 2017 Week 33rd Thursday
    August 16th 2017 Week 33rd Wednesday
    August 15th 2017 Week 33rd Tuesday
    August 14th 2017 Week 33rd Monday
    August 13th 2017 Week 33rd Sunday
    August 12th 2017 Week 32nd Saturday
    August 11th 2017 Week 32nd Friday
    August 10th 2017 Week 32nd Thursday
  • 原文地址:https://www.cnblogs.com/lsgxeva/p/9822289.html
Copyright © 2011-2022 走看看