前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >逆向初级-PE(五)

逆向初级-PE(五)

作者头像
zhang_derek
发布2021-04-20 10:53:17
1.2K0
发布2021-04-20 10:53:17
举报
文章被收录于专栏:有趣的django有趣的django

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
image

<2>不要仅仅通过文件的后缀名来认定PE文件

5.2.PE文件的两种状态

1、PE文件主要结构体

image
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
image

5.3.DOS头属性说明

IMAGE_DOS_HEADER结构体

代码语言:javascript
复制
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;

主要就看两个成员

代码语言:javascript
复制
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
image

5.4.标志PE头属性说明

1、PE头

代码语言:javascript
复制
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字节)

代码语言:javascript
复制
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
image

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

image
image

5.5.扩展PE头属性说明

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

代码语言:javascript
复制
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

代码语言:javascript
复制
ImageBase; //内存镜像基址
AddressOfEntryPoint; // 程序入口,相对于ImageBase的偏移

实例

代码语言:javascript
复制
程序入口:0193BE
内存镜像:400000

程序真正入口=内存镜像+程序入口=4193BE
image
image

通过DTDebug确认

image
image

3、 DllCharacteristics文件特性

image
image

5.6.PE节表

节表结构体(占40字节)

代码语言:javascript
复制
#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
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

代码语言:javascript
复制
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
image

构造自己的代码,找一段空白区,写上自己的代码

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

代码语言:javascript
复制
构造要写入的代码

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

修改程序入口

image
image

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

image
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
image

修改VirtualSize和SizeOfRawData值

image
image

扩大节,添加1000h,也就是十进制4096字节。右键-->粘贴-->粘贴零字节-->4096

image
image

修改SizeOflmage的值,先内存对齐后再加1000

image
image

SizeOflmage结果为

image
image

5.10.新增节

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

2、节表结构

代码语言:javascript
复制
#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
image

改为8000

image
image

修改新增加节的VirtualSize和SizeOfRawData,因为新增加的节大小为1000h

image
image

新增加节的VirtualAddress = 上一个节内存对齐后的大小+上一个节.VirtualAddress

代码语言:javascript
复制
新增加节
VirtualAddress = 00008000+0002B000 = 00033000
PointerToRawData=VirtualAddress
image
image

修改sizeOflmage的大小

image
image

修改为34000

image
image

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

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

image
image

在最后面添加1000h字节

image
image

5.11.导出表

1、如何查找导出表

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

代码语言:javascript
复制
typedef struct _IMAGE_DATA_DIRECTORY {
    DWORD   VirtualAddress;     //表的起始位置RVA
    DWORD   Size; 				//表的大小
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

2、导出表结构

代码语言:javascript
复制
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
image

起始位置2AD80

image
image

Name:2ADBC (RVA),然后从2ADBC的位置开始找,到以0结尾,就是导出表的名字

image
image

NumberOfFunctions:导出函数的个数 2个

image
image

NumberOfNames:以函数名字导出的函数个数 2个

image
image
image
image

AddressOfFunctions:导出函数地址表RVA

image
image

AddressOfNames:导出函数名称表RVA

image
image

AddressOfNameOrdinals:导出函数序号表RVA。序号是两个字节,序号的个数跟函数名称的个数相同

这里序号为0和1

image
image

4、参考

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

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

1、定位导入表

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

第一个导入表开始的位置:22A10

image
image

2、导入表结构

代码语言:javascript
复制
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
image

4、查看依赖的模块名

第一个模块名字

image
image

查看

image
image

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

1、确定需要导入的函数

image
image

第一个成员指向的是一张表INT(导入名称表),INT表里面每个成员都是结构体IMAGE_THUNK_DATA,大小是4个字节

代码语言:javascript
复制
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
image

INT表

image
image

3、确定需要导入的函数的名字

image
image

确定函数名字为ExitThread

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

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

PE文件加载前

image
image

PE文件加载后

image
image

5.15.重定位表

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

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

代码语言:javascript
复制
typedef struct _IMAGE_BASE_RELOCATION {
    DWORD   VirtualAddress;     
    DWORD   SizeOfBlock;        
} IMAGE_BASE_RELOCATION;
typedef IMAGE_BASE_RELOCATION UNALIGNED * PIMAGE_BASE_RELOCATION;
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 5.1.PE文件结构
  • 5.2.PE文件的两种状态
  • 5.3.DOS头属性说明
  • 5.4.标志PE头属性说明
  • 5.5.扩展PE头属性说明
  • 5.6.PE节表
  • 5.7.RVA与FOA的转换
  • 5.8.空白区添加代码
  • 5.9.扩大节
  • 5.10.新增节
  • 5.11.导出表
  • 5.12.导入表_确定依赖模块
  • 5.13.导入表_确定依赖函数
  • 5.14.导入表_确定函数地址
  • 5.15.重定位表
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档