zoukankan      html  css  js  c++  java
  • PE基础1

    PE文件概述

    文件格式

    .png 、.mp4、.gif、.dll等等,这些文件都具有不同格式 不能随意修改这些文件,否则将无法打开 PE文件(可执行文件)

    学习PE文件目标

    掌握PE文件就掌握winodws运行秘密 掌握PE文件对逆向,病毒分析,调试,漏洞.....不可替代作

    PE文件常见术语

    字段:结构体中某个成员

    头: 说明书本的目录或者前言

    区段:书本中的章节

    偏移:用于文件偏移

    镜像:磁盘上的文件

    映像:将这个镜像加载到内存

    DOS头

    其中有用的就两个字段e_magic ,e_lfanew

    e_magic必须实时IMAGE_DOS_SINGATURE 0x5A4D e_lfanew指向NT头

    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;

     

     

     

    NT头

    结构

     

    typedef struct _IMAGE_NT_HEADERS 
    {    DWORD Signature;                        // [0x00]PE标识  
    IMAGE_FILE_HEADER FileHeader;           // [0x04]文件头  
    IMAGE_OPTIONAL_HEADER32 OptionalHeader; // [0x18]扩展头
    }IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

    第一个字段 signature是一个 PE00标志 这个标志始终都是0x00004550, 宏 IMAGE_NT_SIGNATURE表示。 同它可以知道这个文件是否是PE文件

    bool ispe_file(char * pbuff) { 
       PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)pbuff;
       if(pDos.e_magic != IMAGE_DOS_SIGNATRUE )  
      {
           return false;  
      }  
       PMIAGE_NT_HEADER pNt = (PMIAGE_NT_HEADER)(pDos.e_lfanew + (DWORD)pbuff);  
       if(pNt.signature != IMAGE_NT_SIGNATURE)  
      {
           return false;  
      }  
       return true;
    }

    第二个字段是一个文件头结构体

    typedef struct _IMAGE_FILE_HEADER {  
       WORD     Machine;               //[0x04] (1)运行平台  
       WORD     NumberOfSections;      //[0x06] (2)区段的数量*  
       DWORD   TimeDateStamp;          //[0x08] (3)文件创建时间  
       DWORD   PointerToSymbolTable;   //[0x0C] (4)符号表指针  
       DWORD   NumberOfSymbols;        //[0x10] (5)符号的数量  
       WORD     SizeOfOptionalHeader;  //[0x14] (6)扩展头大小* 32大小E0,64位F0  
       WORD     Characteristics;       //[0x16] (7)文件属性
    } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

    运行平台有很多宏,常见的两个i386(x86) 值0x14c, AMD64(x64) 值0x8666;

     

    基本术语

    加载基址:内存中起始位置

    虚拟地址:在进程中的空间某个位置

    相对虚拟地址: 相对于加载基址 公式 虚拟地址(VA) = 加载基址(imagebase) + 相对虚拟地址(RVA)

    对齐 文件对齐:区段在文件中对齐,一般以0x200

    内存对齐: 区段在内存中对齐,一般以0x1000(刚好一页4k)

    扩展头

    扩展头位于NT头的后一个字段 IMAGE_OPTIONAL_HEADER

    typedef struct _IMAGE_OPTIONAL_HEADER {    // 标准域    
       WORD     Magic;     //[0x18] (1) 标志位  
       BYTE       MajorLinkerVersion;  //[0x1A] (2) 连接器主版本号  
       BYTE       MinorLinkerVersion;  //[0x1B] (3) 连接器子版本号    
       DWORD   SizeOfCode;         //[0x1C] (4) 所有代码段 的总大小    
       DWORD   SizeOfInitializedData;  //[0x20] (5) 所有初始化段总大小    
       DWORD   SizeOfUninitializedData;    //[0x24] (6) 所有未初始化段总大小  
       DWORD   AddressOfEntryPoint;    //[0x28] (7) 程序执行入口RVA*  
       DWORD   BaseOfCode;         //[0x2C] (8) 代码段起始RVA  
       DWORD   BaseOfData;         //[0x30] (9) 数据段起始RVA    // NT 附加域  
       DWORD   ImageBase;      //[0x34] (10) 程序默认载入基地址*    
       DWORD   SectionAlignment;   //[0x38] (11) 内存中的段对齐值  
       DWORD   FileAlignment;      //[0x3C] (12) 文件中的段对齐值  
       WORD    MajorOperatingSystemVersion; //[0x40] (13) 系统主版本号
       WORD    MinorOperatingSystemVersion; //[0x42] (14) 系统子版本号    
       WORD    MajorImageVersion;  //[0x44] (15) 自定义的主版本号  
       WORD    MinorImageVersion;  //[0x46] (16) 自定义的子版本号  
       WORD    MajorSubsystemVersion; //[0x48] (17) 所需子系统主版本号  
       WORD    MinorSubsystemVersion; //[0x4A] (18) 所需子系统子版本号    
       DWORD   Win32VersionValue;//[0x4C] (19) 保留,通常为0x00    
       DWORD   SizeOfImage;    //[0x50] (20) 内存中映像总尺寸*    
       DWORD   SizeOfHeaders;  //[0x54] (21) 各个文件头的总尺寸*  
       DWORD   CheckSum;       //[0x58] (22) 映像文件校验和  
       WORD     Subsystem;                           //[0x5C] (23) 文件子系统  
       WORD     DllCharacteristics;    //[0x5E] (24) DLL标志位  
       DWORD   SizeOfStackReserve;         //[0x60] (25) 初始化栈大小  
       DWORD   SizeOfStackCommit;         //[0x64] (26) 初始化实际提交栈大小  
       DWORD   SizeOfHeapReserve;         //[0x68] (27) 初始化保留栈大小  
       DWORD   SizeOfHeapCommit;         //[0x6C] (28) 初始化实际保留栈大小  
       DWORD   LoaderFlags;                       //[0x70] (29) 调试相关,默认0x00  
       DWORD   NumberOfRvaAndSizes;  //[0x74] (30) 数据目录表的数量*  
       IMAGE_DATA_DIRECTORY DataDirectory[0x10]; //[0x78] (31) 数据目录表*
    } IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

    扩展头后一个字段是一个数据目录表,默认都是16项 每一项都指向IMAGE_DATA_DIRECTORY 结构体

    typedef struct _IMAGE_DATA_DIRECTORY {   
       DWORD   VirtualAddress; // 数据块的起始RVA地址*  
       DWORD   Size;       // 数据块的长度
    } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
    #define IMAGE_DIRECTORY_ENTRY_EXPORT    0x0     //[0x78] (1)导出表 
    #define IMAGE_DIRECTORY_ENTRY_IMPORT   0x1     //[0x80] (2)导入表
    #define IMAGE_DIRECTORY_ENTRY_RESOURCE 0x2     //[0x88] (3)资源表
    #define IMAGE_DIRECTORY_ENTRY_EXCEPTION 0x3     //[0x90] (4)
    #define IMAGE_DIRECTORY_ENTRY_SECURITY 0x4     //[0x98] (5)
    #define IMAGE_DIRECTORY_ENTRY_BASERELOC 0x5     //[0xA0] (6)重定位表
    #define IMAGE_DIRECTORY_ENTRY_DEBUG 0x6     //[0xA8] (7)
    #define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE 0x7     //[0xB0] (8)
    #define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 0x8     //[0xB8] (9)
    #define IMAGE_DIRECTORY_ENTRY_TLS       0x9     //[0xC0] (10)TLS表
    #define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG   0xA     //[0xC8] (11)
    #define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 0xB     //[0xD0] (12)
    #define IMAGE_DIRECTORY_ENTRY_IAT       0xC     //[0xD8] (13)
    #define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 0xD     //[0xE0] (14)延迟加载表
    #define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR     0xE     //[0xE8] (15)

    区段头

    扩展头后就是区段头,通过IMAGE_FIRST_SECTION32(NtHeader) 可以获取区段头

    typedef struct _IMAGE_SECTION_HEADER 
    {  
       BYTE    Name[0x8];                         // (1) 区段名    
       union {          
           DWORD   PhysicalAddress;          
           DWORD   VirtualSize;                  // (2) *区段大小
      } Misc;                      
       DWORD   VirtualAddress;     // (3)区段的RVA地址*  
       DWORD   SizeOfRawData;      // (4) 文件中的区段对齐大小*    
       DWORD   PointerToRawData;       // (5) 区段在文件中的偏移*  
       DWORD   PointerToRelocations;   // (6) 重定位的偏移(OBJ)    
       DWORD   PointerToLinenumbers;   // (7) 行号表的偏移(调试)    
       WORD    NumberOfRelocations;    // (8) 重定位项数量(OBJ)  
       WORD    NumberOfLinenumbers;    // (9) 行号表项数量  
       DWORD   Characteristics;        // (10) 区段的属性
    } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

    使用LOADPE查看区段信息

    使用010edit查看区段

     

    代码解析 NT头->区段头

     

    //显示区段信息 
    void Show_SectionInfo(char * pbuff)
    {    //1.获取DOS  
       //2.获取NT  
       //3.获取区段头
       //4.遍历区段显示信息
       PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)pbuff;  
       PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + (DWORD)pbuff);
       // 区段头位置= pNT+4+sizeof(IMAGE_FILE_HEADERS)+sizeof(IMAGE_OPTIONAL_HEADERS)  
       PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNt);
       for (int i = 0; i < pNt->FileHeader.NumberOfSections; i++)  
      {      
           //输出区段信息        
           char name[9];      
           strncpy_s(name, (char*)pSection[i].Name, 8);  
           printf("区段名字:%s ", name);    
           printf("区段RVA: %08x ", pSection[i].VirtualAddress);  
      }
    }

    RVA TO FOA

    逆向的时候在OD找到了内存位置,那么通过这个位置找到文件对应的位置。

     

    //RVA 转 FOA 
    DWORD RvaToOffset(char * pbuff, DWORD RVA)
    {    
       //1. 遍历判断RVA落在哪个区段  
       //2. 计算FOA = RVA - VOffset + ROffset;  
       PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)pbuff;  
       PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + (DWORD)pbuff);
       // 区段头位置= pNT+4+sizeof(IMAGE_FILE_HEADERS)+sizeof(IMAGE_OPTIONAL_HEADERS)
       PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNt);
       for (int i = 0; i < pNt->FileHeader.NumberOfSections; i++)  
      {      
           //判断落在什么区段
             if (RVA >= pSection[i].VirtualAddress &&
                 RVA < (pSection[i].VirtualAddress + pSection[i].SizeOfRawData))
            {          
                 //返回计算后的FOA        
                 return RVA - pSection[i].VirtualAddress + pSection[i].PointerToRawData;
            }  
      }  
       return -1;
    }

    010Edit模板 安装EXE模板

    例子

    main.cpp


    #include "pch.h"
    #include <iostream>
    #include "PE.h"
    int main()
    {
        PE pe;
        pe.ReadPe(L"..\Debug\test-PE.exe");
    ​
        DWORD RVA = pe.GetNtHeader()->OptionalHeader.AddressOfEntryPoint;
        DWORD FOA = pe.RvaToFoa(RVA);
        printf("RVA = %08X  FOA = %08X
    ",RVA,FOA);
    ​
        pe.ShowNTInfo();
        pe.ShowSectionInfo();
    }

    PE.h

    #pragma once
    #include<windows.h>class PE
    {
    public:
        PE();
        ~PE();
        //读取PE文件
        char * ReadPe(const TCHAR * szPath);
    ​
        //获取DOS头
        PIMAGE_DOS_HEADER GetDosHeader();
    ​
        //获取NT头
        PIMAGE_NT_HEADERS GetNtHeader();
    ​
        //显示NT头信息
        void ShowNTInfo();
    ​
        //获取区段头
        PIMAGE_SECTION_HEADER GetSectionHeader();
    ​
        //显示区段信息
        void ShowSectionInfo();
    ​
        //Rva To FoA
        DWORD RvaToFoa(DWORD dwRva);
    ​
    ​
    ​
        //PE文件指针
        char *m_pBuff;
    ​
    };
    ​

    PE.cpp

    #include "pch.h"
    #include "PE.h"
    #include <cstdio>
    ​
    ​
    PE::PE()
    {
    }
    ​
    ​
    PE::~PE()
    {
    }
    ​
    char * PE::ReadPe(const TCHAR * szPath)
    {
        //1.打开一个文件
        HANDLE hFile = CreateFile(
            szPath,         //打开的文件名
            GENERIC_READ,   //读方式打开
            FILE_SHARE_READ,//共享方式
            NULL,           //安全属性
            OPEN_EXISTING,  //创建标志
            FILE_ATTRIBUTE_NORMAL,  //属性
            NULL);
    ​
        //2. 读取到内存中
        //2.1获取文件大小
        DWORD dwSize;
        dwSize = GetFileSize(hFile, 0);
    ​
        //2.2 开辟空间
        m_pBuff= new char[dwSize];
    ​
        //2.3 读取文件
        DWORD dwReadSize;
        ReadFile(hFile, m_pBuff, dwSize, &dwReadSize, 0);
    ​
        //3.关闭文件
        CloseHandle(hFile);
    ​
        return m_pBuff;
    ​
    ​
    ​
        return nullptr;
    }
    ​
    PIMAGE_DOS_HEADER PE::GetDosHeader()
    {
        return (PIMAGE_DOS_HEADER)(m_pBuff);
    }
    ​
    PIMAGE_NT_HEADERS PE::GetNtHeader()
    {
        //计算 nt头 = DOS.e_lfnew + m_pbuff
    //获取dos头
        PIMAGE_DOS_HEADER pDos =  GetDosHeader();
    ​
        //获取NT头
        return (PIMAGE_NT_HEADERS)(pDos->e_lfanew + (DWORD)m_pBuff);
    }
    ​
    ​
    ​
    ​
    void PE::ShowNTInfo()
    {
        //获取NT头
        PIMAGE_NT_HEADERS pNt = GetNtHeader();
    ​
        // pNt->Signature    PE00,0x00004550
        printf("NT标志:%08X 
    ", pNt->Signature);
    ​
        //文件头  区段数量  运行平台  扩展头大小
        //运行平台  0x014c i386  ;  0x8664  amd64
        //#define IMAGE_FILE_MACHINE_I386              0x014c
        //#define IMAGE_FILE_MACHINE_AMD64             0x8664  // AMD64 (K8)
    ​
        printf("区段个数:%08X 
    ", pNt->FileHeader.NumberOfSections);
        printf("运行平台:%08X 
    ", pNt->FileHeader.Machine);
        printf("扩展头大小:%08X 
    ", pNt->FileHeader.SizeOfOptionalHeader);
    ​
        //输出扩展头信息
        printf("程序入口点:%08X 
    ", pNt->OptionalHeader.AddressOfEntryPoint);
        //对齐方式
        //文件中:默认对齐0x200
        //内存中:默认对齐0x1000
        printf("文件对齐:%08X 
    ", pNt->OptionalHeader.FileAlignment);
        printf("内存对齐:%08X 
    ", pNt->OptionalHeader.SectionAlignment);
        //默认exe加载基址 0x00400000
        //默认dll加载基址 0x10000000
        printf("加载基址:%08X 
    ", pNt->OptionalHeader.ImageBase);
        
        //数据目录表
        for (int i = 0; i < 16; i++)
        {
            printf("  数据目录表 %d  RVA:%08X  Size:%08X
    ",
                i,
                pNt->OptionalHeader.DataDirectory[i].VirtualAddress,
                pNt->OptionalHeader.DataDirectory[i].Size
            );
        }
    ​
    }
    ​
    ​
    ​
    PIMAGE_SECTION_HEADER PE::GetSectionHeader()
    {
        //区段头在 NT头后面
        //1. 获取NT头
        PIMAGE_NT_HEADERS pNt =  GetNtHeader();
    ​
        //2.计算NT头后的区段头
        return IMAGE_FIRST_SECTION(pNt);
    }
    ​
    void PE::ShowSectionInfo()
    {
        //1.获取区段个数 文件头中保存区段个数
        DWORD dwCount = GetNtHeader()->FileHeader.NumberOfSections;
    ​
        //2. 输出区段信息
        PIMAGE_SECTION_HEADER pSection = GetSectionHeader();
        for (DWORD i = 0; i < dwCount; i++)
        {
            char szName[9] = {0};
            strncpy_s(szName, (char*)pSection[i].Name, 8);
            printf("区段名:    %s
    ", szName);
    ​
            printf("    VOffset:    %08X
    ", pSection[i].VirtualAddress);
    printf("    VSize:    %08X
    ", pSection[i].Misc.VirtualSize);
    printf("    ROffset:    %08X
    ", pSection[i].PointerToRawData);
    //文件中的大小 0x200对齐后的大小
    printf("    RSize:    %08X
    ", pSection[i].SizeOfRawData);
        }
    }
    ​
    DWORD PE::RvaToFoa(DWORD dwRva)
    {
    //1. 获取区段头表
    PIMAGE_SECTION_HEADER pSection = GetSectionHeader();
    ​
    //2. 遍历区段
    DWORD dwCount = GetNtHeader()->FileHeader.NumberOfSections;
    for (DWORD i = 0; i < dwCount; i++)
        {
    //3. 判断这个RVA落在什么区段上
    if (dwRva >= pSection[i].VirtualAddress
    && dwRva < (pSection[i].VirtualAddress + pSection[i].SizeOfRawData)
        )
        {
    //3.1 FOA = RVA - 内存中区段位置 + 文件中区段位置
    return dwRva - pSection[i].VirtualAddress + pSection[i].PointerToRawData;
        }
        }
    return 0;
    }

     

  • 相关阅读:
    Win32编程day11 学习笔记
    Win32编程day06 学习笔记
    Win32编程day15 学习笔记
    Win32编程day12 学习笔记
    Win32编程day09 学习笔记
    Win32编程day07 学习笔记
    Win32编程day10 学习笔记
    生命周期
    组件中的data为什么不是一个对象而是一个函数?
    asp.net Request.ServerVariables 各参数说明集合
  • 原文地址:https://www.cnblogs.com/ltyandy/p/11077846.html
Copyright © 2011-2022 走看看