• 逆向初级-PE(五)


    5.1.PE文件结构

    1、什么是可执行文件?
    可执行文件(executable fle)指的是可以由操作系统进行加载执行的文件。
    可执行文件的格式:
    Windows平台:
    PE(Portable Executable)文件结构
    Linux平台:
    ELF(Executable and Linking Format)文件结构
    哪些领域会用到PE文件格式:
    <1>病毒与反病毒
    <2>外挂与反外挂
    <3>加壳与脱壳(保护与破解)

    <4>无源码修改功能、软件汉化等

    2、如何识别PE文件

    <1> PE文件的特征(PE指纹)
    分别打开.exe .dlI .sys 等文件,观察特征前2个字节。
    image
    <2>不要仅仅通过文件的后缀名来认定PE文件

    5.2.PE文件的两种状态

    1、PE文件主要结构体
    image

    • IMAGE_DOS_HEADER占64个字节。
    • DOS Sub:IMAGE_DOS_HEADER尾部的四个字节指向PE文件的开始位置。IMAGE_DOS_HEADER尾部到PE文件头开始的中间部分是DOS_Sub部分(大小不固定)
    • PE文件头标志:PE头是前面4个字节
    • PE文件表头:IMAGE_FILE_HEADER是20个字节
    • 扩展PE头:IMAGE_OPTIONAL_HEADER在32位中占224个字节(这个大小是可以修改的)
    • IMAGE_SECTION_HEADER:40个字节

    2、PE文件的两种状态
    image

    5.3.DOS头属性说明

    IMAGE_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;
    

    主要就看两个成员

    WORD   e_magic;       //PE文件判断表示   4D5A,ascii是MZ
    LONG   e_lfanew;     //存储PE头首地址
    
    • e_magic两个字节和e_lfanew四个字节内容不能修改
    • 开头e_magic和结尾e_lfanew中间的成员部分可以随意修改
    • e_lfanew到PE头文件中间的DOS Stub部分可以随便修改
      image

    5.4.标志PE头属性说明

    1、PE头

    typedef struct _IMAGE_NT_HEADERS64 {
        DWORD Signature;   //PE标识,占4字节
        IMAGE_FILE_HEADER FileHeader;    //标志PE头
        IMAGE_OPTIONAL_HEADER64 OptionalHeader;    //扩展PE头
    } IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;
    

    PE标识不能破坏,操作系统在启动一个程序的时候会检测这个标识。

    2、标准PE头(占20字节)

    typedef struct _IMAGE_FILE_HEADER {
        WORD    Machine;//可以运行在什么样的CPU上   任意:0    Intel 386以及后续:14C   x64:8664  
        WORD    NumberOfSections;//表示节的数量
        DWORD   TimeDateStamp;//编译器填写的时间戳 与文件属性里面(创建时间、修改时间)无关
        DWORD   PointerToSymbolTable;//调试相关
        DWORD   NumberOfSymbols;//调试相关
        WORD    SizeOfOptionalHeader;//可选PE头的大小(32位PE文件:0xE0  64位PE文件:0xF0)
        WORD    Characteristics;//文件属性
    } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
    

    Characteristics文件属性
    image
    文件属性
    Characteristics值为: 01 0F
    转换为二进制:0000 0001 0000 1111
    说明下标0,1,2,3,8有值,根据下标是不是1,然后查看对应的文件属性
    image

    5.5.扩展PE头属性说明

    1、扩展PE头结构体(总共224字节)

    typedef struct _IMAGE_OPTIONAL_HEADER {
        //
        // Standard fields.
        // 
        WORD    Magic;  // 分辨32位程序还是64位,如果32位则10B,64位则20B
        BYTE    MajorLinkerVersion; //链接器版本号
        BYTE    MinorLinkerVersion; //链接器版本号
        DWORD   SizeOfCode; //所有代码节的总和 文件对齐后的大小 编译器填写的,无用处
        DWORD   SizeOfInitializedData; //已经初始化数据的节的总大小 文件对齐后的大小   编译器填写的,无用处
        DWORD   SizeOfUninitializedData; // 未初始化数据的节的总大小 文件对齐后的大小   编译器填写的,无用处
        DWORD   AddressOfEntryPoint; // 程序入口
        DWORD   BaseOfCode; //代码开始的基址 编译器填写的,无用处
        DWORD   BaseOfData; //数据开始的基址  编译器填写的,无用处
    
        //
        // NT additional fields.
        //
    
        DWORD   ImageBase; //内存镜像基址
        DWORD   SectionAlignment; //内存对齐
        DWORD   FileAlignment; //文件对齐
        WORD    MajorOperatingSystemVersion; //操作系统版本号
        WORD    MinorOperatingSystemVersion; //操作系统版本号
        WORD    MajorImageVersion; //PE文件自身的版本号
        WORD    MinorImageVersion; //PE文件自身的版本号
        WORD    MajorSubsystemVersion; //运行所需要子系统的版本号
        WORD    MinorSubsystemVersion; //运行所需要子系统的版本号
        DWORD   Win32VersionValue; //子系统版本的值,必须为0
        DWORD   SizeOfImage; //内存中整个PE文件的映射尺寸,比实际的值大,必须是SectionAlignment整数倍
        DWORD   SizeOfHeaders; //所有的头+节表按照文件对齐后的大小
        DWORD   CheckSum; //校验和,可伪造
        WORD    Subsystem; //子系统, 驱动程序(1) 图形界面(2) DLL(3)
        WORD    DllCharacteristics;	 //文件特性 不是针对DLL文件的
        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;
    

    2、ImageBase和AddressOfEntryPoint

    ImageBase; //内存镜像基址
    AddressOfEntryPoint; // 程序入口,相对于ImageBase的偏移
    

    实例

    程序入口:0193BE
    内存镜像:400000
    
    程序真正入口=内存镜像+程序入口=4193BE
    

    image
    通过DTDebug确认
    image
    3、 DllCharacteristics文件特性
    image

    5.6.PE节表

    节表结构体(占40字节)

    #define IMAGE_SIZEOF_SHORT_NAME 8              
    typedef struct _IMAGE_SECTION_HEADER {
        BYTE    Name[IMAGE_SIZEOF_SHORT_NAME];  //ASCII字符串 可自定义 只截取8个字节(占8字节)
        union {                     //Misc双子是该字节没有在对齐前的真实尺寸 该值可以不准确(占4字节)
                DWORD   PhysicalAddress;
                DWORD   VirtualSize;
        } Misc;
        DWORD   VirtualAddress;        //在内存中的偏移地址加上ImageBase才是内存中的真正地址
        DWORD   SizeOfRawData;		   //节在文件中对齐后的尺寸	
        DWORD   PointerToRawData;      //节区在文件中的偏移
        DWORD   PointerToRelocations;  //调试相关
        DWORD   PointerToLinenumbers;
        WORD    NumberOfRelocations;
        WORD    NumberOfLinenumbers;
        DWORD   Characteristics;        //节的属性
    } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
    #define IMAGE_SIZEOF_SECTION_HEADER          40
    

    DOS头64字节+PE标识4字节+PE标准头20字节+PE扩展头224字节,然后就是节表的起始位置,每个节表占40个字节
    image

    5.7.RVA与FOA的转换

    1、RVA(相对虚拟地址)到FOA(文件偏移地址)的转换:
    <1>得到RVA的值:内存地址- ImageBase
    <2>判断RVA是否位于PE头中,如果是: FOA== RVA
    <3>判断RVA位于哪个节:
    RVA>=节VirtualAddress
    RVA <=节.VirtualAddress +当前节内存对齐后的大小
    差值= RVA-节VirtualAddress;
    <4> FOA=节.PointerToRawData +差值;

    如果文件对齐和内存对齐的值一样,则RVA=内存地址- ImageBase,F0A=RVA,就可以得出在文件中的地址

    5.8.空白区添加代码

    给程序添加一个MessageBox对话框,步骤

    • 在PE的空白区构造一段代码
    • 修改入口地址为新增代码的地址
    • 新增代码执行后,跳回到入口地址

    1、MessageBox的反汇编硬编码

    E8 表示call

    6A表示push

    9:        MessageBox(0,0,0,0);
    00401028 8B F4                mov         esi,esp
    0040102A 6A 00                push        0
    0040102C 6A 00                push        0
    0040102E 6A 00                push        0
    00401030 6A 00                push        0
    00401032 FF 15 8C 42 42 00    call        dword ptr [__imp__MessageBoxA@16 (0042428c)]
    00401038 3B F4                cmp         esi,esp
    0040103A E8 31 00 00 00       call        __chkesp (00401070)
    
    

    2、找到要运行的程序的MessageBoxA的地址

    用DTDdbug打开程序,点“E”,找到“USER32.DLL”,按“Ctrl+n”,然后找到MessageBoxA函数的地址
    image
    构造自己的代码,找一段空白区,写上自己的代码

    先执行我们要写的代码(弹出信息框),执行完,然后jmp到程序入口位置

    构造要写入的代码
    
    6A 00 6A 00 6A 00 6A 00 E8 00 00 00 00 E9 00 00 00 00
    
    E8表示call 
    E8后面的硬编码 = 要跳转的地址 - E8指令当前的地址 - 5
    
    要跳转的MessageBoxA的地址:77D5050B
    
    E8后面的硬编码 = 77D5050B - (ImageBase+F98)- 5 = 7794F56E
        
    程序入口:000193BE
    ImageBase:00400000
    程序运行入口=ImageBase+程序入口=004193BE
        
    E9后面的硬编码 = 004193BE - 400F9D - 5 = 1841C
    
    最终代码
    6A 00 6A 00 6A 00 6A 00 E8 6E F5 94 77 E9 1C 84 01 00
    

    image
    修改程序入口
    image
    把入口改成我们自己构造的代码的起始位置F90
    image

    5.9.扩大节

    1、为什么要扩大节

    我们可以在任意空白区添加自己的代码,但如果添加的代码比较多,空白区不够怎么办?

    2、扩大节的步骤

    <1>分配一块新的空间,大小为S
    <2>将最后-一个节的SizeOfRawData和VirtualSize改成N
    N = (SizeOfRawData或者VirtualSize内存对齐后的值)+ S
    <3>修改SizeOflmage大小

    S = 1000

    VirtualSize:78B0 当前节内存中没有对齐的实际大小

    SizeOfRawData:8000 当前节文件对齐后的大小

    N = 8000 + 1000 = 9000
    image
    修改VirtualSize和SizeOfRawData值
    image
    扩大节,添加1000h,也就是十进制4096字节。右键-->粘贴-->粘贴零字节-->4096
    image
    修改SizeOflmage的值,先内存对齐后再加1000
    image
    SizeOflmage结果为
    image

    5.10.新增节

    1、新增节的步骤:
    <1>判断是否有足够的空间,可以添加一个节表.
    <2>在节表中新增一个成员.
    <3>修改PE头中节的数量.
    <4>修改sizeOflmage的大小.
    <5>在原有数据的最后,新增一个节的数据(内存对齐的整数倍).
    <6>修正新增节表的属性.

    2、节表结构

    #define IMAGE_SIZEOF_SHORT_NAME 8              
    typedef struct _IMAGE_SECTION_HEADER {
        BYTE    Name[IMAGE_SIZEOF_SHORT_NAME];  //ASCII字符串 可自定义 只截取8个字节(占8字节)
        union {                     //Misc双子是该字节没有在对齐前的真实尺寸 该值可以不准确(占4字节)
                DWORD   PhysicalAddress;
                DWORD   VirtualSize;
        } Misc;
        DWORD   VirtualAddress;        //在内存中的偏移地址加上ImageBase才是内存中的真正地址
        DWORD   SizeOfRawData;		   //节在文件中对齐后的尺寸	
        DWORD   PointerToRawData;      //节区在文件中的偏移
        DWORD   PointerToRelocations;  //调试相关
        DWORD   PointerToLinenumbers;
        WORD    NumberOfRelocations;
        WORD    NumberOfLinenumbers;
        DWORD   Characteristics;        //节的属性
    } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
    #define IMAGE_SIZEOF_SECTION_HEADER          40
    

    在节表中新增一个节,把.txet节的40个字节复制粘贴到新增加的节,然后修改新增加节的成员属性

    • 前8个字节是节的名字:随便改个名字
    • 把之前最后一个节的VirtualSize(内存中没有对齐的实际值)改为内存对齐后的值
      image
      改为8000
      image
      修改新增加节的VirtualSize和SizeOfRawData,因为新增加的节大小为1000h
      image
      新增加节的VirtualAddress = 上一个节内存对齐后的大小+上一个节.VirtualAddress
    新增加节
    VirtualAddress = 00008000+0002B000 = 00033000
    PointerToRawData=VirtualAddress
    

    image
    修改sizeOflmage的大小
    image
    修改为34000
    image
    在原有数据的最后,新增一个节的数据,新增加节的大小为1000h

    先删除第一个节前面的40个字节(因为前面新增加了一个节表,数据全部往后推移了40个字节)
    image
    在最后面添加1000h字节
    image

    5.11.导出表

    1、如何查找导出表

    扩展PE头最后一个成员是一个数组(包含16和元素),每个数组对应一个表(每个表占8字节),如导出表、导入表等。

    typedef struct _IMAGE_DATA_DIRECTORY {
        DWORD   VirtualAddress;     //表的起始位置RVA
        DWORD   Size; 				//表的大小
    } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
    

    2、导出表结构

    typedef struct _IMAGE_EXPORT_DIRECTORY {
        DWORD   Characteristics; 		//未使用
        DWORD   TimeDateStamp;			//时间戳
        WORD    MajorVersion;			//未使用
        WORD    MinorVersion;			//未使用
        DWORD   Name;					//指向该导出表文件名字符串
        DWORD   Base;					//导出函数起始序号
        DWORD   NumberOfFunctions;		//所有导出函数的个数
        DWORD   NumberOfNames;			//以函数名字导出的函数个数
        DWORD   AddressOfFunctions;     // RVA from base of image 导出函数地址表RVA
        DWORD   AddressOfNames;         // RVA from base of image 导出函数名称表RVA
        DWORD   AddressOfNameOrdinals;  // RVA from base of image 导出函数序号表RVA
    } IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
    

    3、导出表成员 40字节

    导出表位置,数组DataDirectory[0]
    image
    起始位置2AD80
    image
    Name:2ADBC (RVA),然后从2ADBC的位置开始找,到以0结尾,就是导出表的名字
    image
    NumberOfFunctions:导出函数的个数 2个
    image
    NumberOfNames:以函数名字导出的函数个数 2个
    image
    image
    AddressOfFunctions:导出函数地址表RVA
    image
    AddressOfNames:导出函数名称表RVA
    image
    AddressOfNameOrdinals:导出函数序号表RVA。序号是两个字节,序号的个数跟函数名称的个数相同

    这里序号为0和1
    image
    4、参考

    • 总共四个函数
    • 所有导出函数的个数为5,因为序号中间隔了个14没有。函数个数 = 最大序号 - 最小序号 + 1
    • 以函数名导出的函数个数为3,因为有一个函数没有名字
    • 把函数地址对应的二进制复制到OD里面,可以查看到具体是什么函数
      image

    5.12.导入表_确定依赖模块

    1、定位导入表

    导入表位置,数组DataDirectory[1]

    第一个导入表开始的位置:22A10
    image
    2、导入表结构

    typedef struct _IMAGE_IMPORT_DESCRIPTOR {
        union {
            DWORD   Characteristics;            // 0 for terminating null import descriptor
            DWORD   OriginalFirstThunk;         // RVA指向IMAGE_THUNK_DATA结构数组
        };
        DWORD   TimeDateStamp;                  // 时间戳
        DWORD   ForwarderChain;                 // -1 if no forwarders
        DWORD   Name;							//RVA 指向dll名字,该名字以0结尾
        DWORD   FirstThunk;                     // RVA 指向IMAGE_THUNK_DATA结构数组
    } IMAGE_IMPORT_DESCRIPTOR;
    

    3、导入表个数

    导入表的个数判断:,每个导入表占20个字节,判断有多少个导入表,以20个0为结尾的位置
    image
    4、查看依赖的模块名

    第一个模块名字
    image
    查看
    image

    5.13.导入表_确定依赖函数

    1、确定需要导入的函数
    image
    第一个成员指向的是一张表INT(导入名称表),INT表里面每个成员都是结构体IMAGE_THUNK_DATA,大小是4个字节

    typedef struct _IMAGE_THUNK_DATA32 {
        union {
            PBYTE  ForwarderString;
            PDWORD Function;
            DWORD Ordinal;
            PIMAGE_IMPORT_BY_NAME  AddressOfData;
        } u1;
    } IMAGE_THUNK_DATA32;
    

    2、INT表里面的结构体

    INT表位置22A88,INT表里面有多少个成员(4个字节),就说明依赖当前导入模块多少个函数。结尾标志:四个字节都是00
    image
    INT表
    image
    3、确定需要导入的函数的名字
    image
    确定函数名字为ExitThread

    typedef struct _IMAGE_IMPORT_BY_NAME {
        WORD    Hint;		//可能为空,编译器决定,如果不为空,是函数在导出表中的索引
        BYTE    Name[1];	//函数名称,以0结尾
    } IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
    

    image

    5.14.导入表_确定函数地址

    PE文件加载前
    image
    PE文件加载后
    image

    5.15.重定位表

    重定位表的位置(第六个表)

    导入表位置,数组DataDirectory[5]

    typedef struct _IMAGE_BASE_RELOCATION {
        DWORD   VirtualAddress;     
        DWORD   SizeOfBlock;        
    } IMAGE_BASE_RELOCATION;
    typedef IMAGE_BASE_RELOCATION UNALIGNED * PIMAGE_BASE_RELOCATION;
    
    作者:zhang_derek

    个性签名:其实人跟树一样,越是向往高处的阳光,它的根就越要伸向黑暗的地底。

  • 相关阅读:
    jar命令打jar包
    kafka的一些参数
    fastdfs-nginx-module-master的一些奇怪的特点
    nginx 禁止恶意域名解析
    tcpdump抓包vrrp
    gitlab提交代码
    [Data]Segment Tree
    [Data]FHQ treap
    [Data]带修改的主席树[树状数组套主席树]
    [Data]可持久化线段树-主席树
  • 原文地址:https://www.cnblogs.com/derek1184405959/p/14674220.html
Copyright © 2020-2023  润新知