PE文件结构小结

此篇文章简单记录一下PE文件结构,作为笔记,方便平时查阅!
部分数据目录未总结,后面学习用到时会补充!


PE文件结构图:
在这里插入图片描述
在这里插入图片描述

DOS头

Dos头一共64(0x40)个字节

typedef struct _IMAGE_DOS_HEADER
{
    WORD   e_magic;       // DOS头的标识  "MZ"  4Dh  5Ah(2个字节)      #define IMAGE_DOS_SIGNATURE  0x5A4D
    WORD   e_cblp;        // 文件最后一页中的字节数
    WORD   e_cp;          // 文件中的全部页数
    WORD   e_crlc;        // 重定位表中的指针数
    WORD   e_cparhdr;     // 头部尺寸,以段落为单位
    WORD   e_minalloc;    // 所需的最小附加段  
    WORD   e_maxalloc;    // 所需的最大附加段  
    WORD   e_ss;          // 初始的SS值(相对偏移量)
    WORD   e_sp;          // 初始的SP值
    WORD   e_csum;        // 补码校验值
    WORD   e_ip;          // 初始的IP值   
    WORD   e_cs;          // 初始的CS值
    WORD   e_lfarlc;      // 重定位表的字节偏移量 
    WORD   e_ovno;        // 覆盖号
    WORD   e_res[4];      // 保留字
    WORD   e_oemid;       // OEM 标识符(相对e_oeminfo) 
    WORD   e_oeminfo;     // OEM 信息
    WORD   e_res2[10];    // 保留字
    // DosHeader + 0x3C 正好定位到e_lfanew
    LONG    e_lfanew;     // NT头相对于文件起始地址的偏移, 4字节, 指示NT头的位置
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

DOS头下方紧跟着的并不是NT头,而是一段Stub,它随着链接器的不同而不同,大小不确定。
因此不能通过IMAGE_DOS_HEADER + 0x40来定位NT头,而应该是IMAGE_DOS_HEADER + IMAGE_DOS_HEADER->e_lfanew

NT头

NT头在32位和64位下大小不同,主要在于可选头的不同。
32位:4 + 20(0x14) + 224(0xE0)= 248(0xF8)字节
64位:4 + 20(0x14) + 240(0xF0)= 264(0x108)字节

typedef struct _IMAGE_NT_HEADERS
{
    DWORD Signature;                         //标志位:"PE\0\0"  50 45 00 00(4字节)  #define IMAGE_NT_SIGNATURE  0x00004550
    IMAGE_FILE_HEADER FileHeader;            //PE文件头(PE标准头)
    IMAGE_OPTIONAL_HEADER32 OptionalHeader;  //PE可选头(PE扩展头)
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

typedef struct _IMAGE_NT_HEADERS64
{
    DWORD Signature;
    IMAGE_FILE_HEADER FileHeader;
    IMAGE_OPTIONAL_HEADER64 OptionalHeader;
} IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;

文件头

文件头一共20(0x14)个字节

typedef struct _IMAGE_FILE_HEADER
{
    WORD Machine;                //PE文件运行的平台,值为IMAGE_FILE_MACHINE_I386(0x14c)表示是x86处理器,
                                 //IMAGE_FILE_MACHINE_AMD64(0x8664)或IMAGE_FILE_MACHINE_IA64(0x200)表示是x64处理器。
    WORD NumberOfSections;       //文件中存在的节的个数,如果想在PE文件中增加或删除节,必须变更此处的值
    DWORD TimeDateStamp;         //创建此文件时的时间戳
    DWORD PointerToSymbolTable;  //COFF符号表的文件偏移,对于映像文件来说,此值为0
    DWORD NumberOfSymbols;       //符号表中元素的数目,对于映像文件来说,此值为0
    WORD SizeOfOptionalHeader;   //可选头的大小,32位下默认为00E0h,64位下默认为00F0h
    WORD Characteristics;        //文件属性标志,exe一般是010fh,dll一般是210eh
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

在这里插入图片描述

判断一个文件是不是PE文件

ImageDosHeader = (PIMAGE_DOS_HEADER)FileContent;
if (ImageDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
{
    return FALSE;
}
ImageNtHeaders = (PIMAGE_NT_HEADERS)(FileContent + ImageDosHeader->e_lfanew);
if (ImageNtHeaders->Signature != IMAGE_NT_SIGNATURE)
{
    return FALSE;
}
return TRUE;

判断一个PE文件是不是dll

DWORD Flag = IMAGE_FILE_DLL;
if ((Flag & ImageNtHeaders->FileHeader.Characteristics) == Flag)
{
    return TRUE;
}

判断一个PE文件是不是exe

DWORD Flag = IMAGE_FILE_EXECUTABLE_IMAGE;
if (!IsDll() &&
    !IsSys() &&
    (Flag & ImageNtHeaders->FileHeader.Characteristics) == Flag)
{
    return TRUE;
}

判断一个PE文件是32位还是64位(方法1,2)

方法一:

if (ImageNtHeaders->FileHeader.SizeOfOptionalHeader == 0xF0)
{
    Is64PeFile = TRUE;
}
else
{
    Is64PeFile = FALSE;
}

方法二:

if (ImageNtHeaders->FileHeader.Machine == IMAGE_FILE_MACHINE_I386)
{
    Is64PeFile = TRUE;
}
if (ImageNtHeaders->FileHeader.Machine == IMAGE_FILE_MACHINE_AMD64 || ImageNtHeaders->FileHeader.Machine == IMAGE_FILE_MACHINE_IA64)
{
    Is64PeFile = FALSE;
}

可选头

32位下: 96(0x60)+ 128(0x80)= 224字节(0xE0)
64位下:112(0x70)+ 128(0x80)= 240字节(0xF0)

64位和32位的区别:

  • 64位没有成员BaseOfData
  • 64位的ImageBaseSizeOfStackReserveSizeOfStackCommitSizeOfHeapReserveSizeOfHeapCommitULONGLONG类型,为了适应增大的进程虚拟内存。
typedef struct _IMAGE_OPTIONAL_HEADER 
{
#define IMAGE_NT_OPTIONAL_HDR32_MAGIC      0x10b
#define IMAGE_NT_OPTIONAL_HDR64_MAGIC      0x20b
    WORD    Magic;                      //文件的类型。0x10b 表示文件为PE32,0x107表示文件为ROM映像,0x20b 表示PE64    
    BYTE    MajorLinkerVersion;         //链接器的主版本号  
    BYTE    MinorLinkerVersion;         //链接器的次版本号
    DWORD   SizeOfCode;                 //所有代码节长度的总和,该大小是基于文件对齐的。
    DWORD   SizeOfInitializedData;      //所有包含初始化数据的节的总长度
    DWORD   SizeOfUninitializedData;    //所有包含未初始化数据的节的总长度。这些数据在文件中不占用空间,但在被加载到内存以后,PE加载程序应该为这些数据分配适当大小的虚拟地址空间
    DWORD   AddressOfEntryPoint;        //程序执行入口的RVA,对于exe这个地址可以理解为WinMain的RVA
    DWORD   BaseOfCode;                 //代码节起始地址的RVA,表示映像被加载进内存时代码节的开头相对于映像基址的偏移地址。一般情况下,代码节紧跟在PE头部后面
    DWORD   BaseOfData;                  //数据节起始地址的RVA。一般情况下,数据节位于文件末尾,64位没有这个成员
    DWORD   ImageBase;                   //文件被系统装入内存后的默认基地址(建议程序装载地址)。链接器默认exe的装入地址是0x400000,dll的是0x10000000。如果一个进程用到了多个Dll,其装入地址可能会发生冲突,PE加载器会调整其中的地址,使所有的dll文件都能被正确装入。
    DWORD   SectionAlignment;            //内存中节的对齐粒度,32位下为0x1000
    DWORD   FileAlignment;               //文件中节的对齐粒度(对齐是为了提高文件从磁盘加载的效率),32位下为0x200,SectionAlignment必须大于或等于FileAlignment 
    WORD    MajorOperatingSystemVersion; //操作系统的版本号  
    WORD    MinorOperatingSystemVersion;
    WORD    MajorImageVersion;           //映象的版本号  
    WORD    MinorImageVersion;
    WORD    MajorSubsystemVersion;       //所需子系统版本号  
    WORD    MinorSubsystemVersion;
    DWORD   Win32VersionValue;           //保留,必须为0  
    DWORD   SizeOfImage;                 //内存中整个PE文件的映射大小,PE文件加载到内存中空间是连续的,这个值指定占用虚拟空间的大小  
    DWORD   SizeOfHeaders;               //所有头+节表按照文件对齐粒度对齐后的大小
    DWORD   CheckSum;                    //校验和  
    WORD    Subsystem;                   //运行该PE文件所需的子系统,指示是一个控制台程序(IMAGE_SUBSYSTEM_WINDOWS_CUI)还是图形界面程序(IMAGE_SUBSYSTEM_WINDOWS_GUI)
    WORD    DllCharacteristics;          //DLL文件属性
    DWORD   SizeOfStackReserve;          //初始化时保留的栈大小,默认为0x100000(1MB)
    DWORD   SizeOfStackCommit;           //初始化时实际提交的栈大小
    DWORD   SizeOfHeapReserve;           //初始化时保留的堆大小,默认为0x100000(1MB)
    DWORD   SizeOfHeapCommit;            //初始化时实际提交的堆大小,默认为1页(0x1000)
    DWORD   LoaderFlags;                 //保留,必须为0  
    DWORD   NumberOfRvaAndSizes;         //数据目录结构的数量,即下面这个数组的项数,一般为16个
    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];   // 数据目录
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

typedef struct _IMAGE_OPTIONAL_HEADER64
{
    WORD  Magic;
    BYTE  MajorLinkerVersion;
    BYTE  MinorLinkerVersion;
    DWORD SizeOfCode;
    DWORD SizeOfInitializedData;
    DWORD SizeOfUninitializedData;
    DWORD AddressOfEntryPoint;
    DWORD BaseOfCode;
    ULONGLONG ImageBase;
    DWORD SectionAlignment;
    DWORD FileAlignment;
    WORD  MajorOperatingSystemVersion;
    WORD  MinorOperatingSystemVersion;
    WORD  MajorImageVersion;
    WORD  MinorImageVersion;
    WORD  MajorSubsystemVersion;
    WORD  MinorSubsystemVersion;
    DWORD Win32VersionValue;
    DWORD SizeOfImage;
    DWORD SizeOfHeaders;
    DWORD CheckSum;
    WORD  Subsystem;
    WORD  DllCharacteristics;
    ULONGLONG SizeOfStackReserve;
    ULONGLONG SizeOfStackCommit;
    ULONGLONG SizeOfHeapReserve;
    ULONGLONG SizeOfHeapCommit;
    DWORD LoaderFlags;
    DWORD NumberOfRvaAndSizes;
    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER64, *PIMAGE_OPTIONAL_HEADER64;

判断一个PE文件是32位还是64位(方法3)

if (ImageNtHeaders->OptionalHeader.magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC)
{
    Is64PeFile = TRUE;
}
else
{
    Is64PeFile = FALSE;
}

数据目录

IMAGE_OPTIONAL_HEADER->DataDirectory
是一个结构体数组,共有16个成员,由16个IMAGE_DATA_DIRECTORY排列而成。

typedef struct _IMAGE_DATA_DIRECTORY 
{
    DWORD VirtualAddress;     //数据块的起始RVA  
    DWORD Size;               //数据块的长度  
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 		 16
#define IMAGE_DIRECTORY_ENTRY_EXPORT              0   // 导出表(.edata),指向一个IMAGE_EXPORT_DIRECTORY结构
#define IMAGE_DIRECTORY_ENTRY_IMPORT              1   // 导入表(.idata),指向一个IMAGE_IMPORT_DESCRIPTOR结构数组
#define IMAGE_DIRECTORY_ENTRY_RESOURCE            2   // 资源表(.rsrc),指向一个IMAGE_RESOURCE_DIRECTORY结构
#define IMAGE_DIRECTORY_ENTRY_EXCEPTION           3   // 异常表(.pdata),指向一个IMAGE_RUNTIME_FUNCTION_ENTRY结构数组,CPU特定的并且基于表的异常处理,用于除x86之外的其它CPU上
#define IMAGE_DIRECTORY_ENTRY_SECURITY            4   // 安全表,指向一个WIN_CERTIFICATE结构的列表,它定义在WinTrust.h中,不会被映射到内存中。因此VirtualAddress域是一个文件偏移,而不是一个RVA
#define IMAGE_DIRECTORY_ENTRY_BASERELOC           5   // 重定位表(.reloc),指向基址重定位信息
#define IMAGE_DIRECTORY_ENTRY_DEBUG               6   // 调试信息表(.debug),指向一个IMAGE_DEBUG_DIRECTORY结构数组,其中每个结构描述了映像的一些调试信息
// #define IMAGE_DIRECTORY_ENTRY_COPYRIGHT        7   // (X86 usage) 
#define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE        7   // 版权信息表,指向一个IMAGE_ARCHITECTURE_HEADER结构数组
#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR           8   // 全局指针表
#define IMAGE_DIRECTORY_ENTRY_TLS                 9   // TLS 线程局部存储(.tls),指向线程局部存储初始化节
#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG        10   // 配置加载表,指向一个IMAGE_LOAD_CONFIG_DIRECTORY结构
#define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT       11   // 绑定导入表,指向一个IMAGE_BOUND_IMPORT_DESCRIPTOR结构数组,每一个结构对应一个DLL。数组元素中的时间戳允许加载器快速判断绑定是否是新的,如果不是则加载器忽略绑定信息并且按正常方式解决导入API
#define IMAGE_DIRECTORY_ENTRY_IAT                12   // 导入函数地址表,指向第一个导入地址表(IAT)的起始位置,对应于每个被导入DLL的IAT都连续地排列在内存中,Size域指出了所有IAT的总大小。
#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT       13   // 延迟加载导入表
#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR     14   // COM Runtime descriptor

导出表

导出表描述的是该PE文件向其他程序提供的可供调用函数的情况。
只有一个IMAGE_EXPORT_DESCRIPTOR

typedef struct _IMAGE_EXPORT_DIRECTORY
{
    DWORD   Characteristics;
    DWORD   TimeDateStamp;          //导出表创建的时间
    WORD    MajorVersion;           //导出表的版本号
    WORD    MinorVersion;
    DWORD   Name;                   //指向该导出表的文件名称字符串
    DWORD   Base;                   //导出函数地址表中第一个地址所对应函数的编号。
    DWORD   NumberOfFunctions;      //所有导出函数的个数
    DWORD   NumberOfNames;          //通过名称导出的函数的个数
    DWORD   AddressOfFunctions;     //指向导出函数地址表,存放所有导出函数所在地址的RVA值,大小为NumberOfFunctions
    DWORD   AddressOfNames;         //指向函数名称地址表,存放导出函数名称字符串所在地址的RVA值,大小为NumberOfNames
    DWORD   AddressOfNameOrdinals;  //指向函数名称索引表的RVA,存放名称导出函数的导出索引,它的值是对应函数在AddressOfFunctions中的数组下标,大小为NumberOfNames
}IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

三个数组

在这里插入图片描述

根据函数名称获得函数地址

FunctionAddress = DllBase + AddressOfFunctions[AddressOfNameOrdinals[i]]; //i为遍历AddressOfNames数组得到的数组下标

导入表

导入表描述的是该PE文件中的程序调用了其他动态链接库函数的情况。
是一个IMAGE_IMPORT_DESCRIPTOR数组。
操作系统在加载PE文件时,会将其导入表里的所有DLL一起加载,并根据DLL导出表中对导入函数的描述修正IAT的值。

typedef struct _IMAGE_IMPORT_DESCRIPTOR
{
    union {
        DWORD Characteristics;
        DWORD OriginalFirstThunk;   //桥1,指向导入名称表(INT),INT是一个IMAGE_THUNK_DATA结构的数组,每个IMAGE_THUNK_DATA结构指向IMAGE_IMPORT_BY_NAME结构,最后一个IMAGE_THUNK_DATA的内容为0。
        // OriginalFirstThunk:双字最高位为0表示导入符号是一个数值,该数值是一个RVA;双字最高位为1,表示导入符号是一个名称
    };
    DWORD TimeDateStamp;    //时间戳,一般为0;如果该导入表项被绑定,那么绑定后的这个时间戳就是被设置为对应DLL文件的时间戳。操作系统在加载时,可以通过这个时间戳来判断绑定的信息是否过时。
    DWORD ForwarderChain;   //链表的前一个结构
    DWORD Name;             //指向该结构对应的DLL文件的名称,以“\0”结尾的Anisi字符串
    DWORD FirstThunk;       //桥2,指向导入函数地址表(IAT)的RVA,IAT是一个IMAGE_THUNK_DATA结构的数组,它定义了针对Name这个动态链接库导入的所有导入函数。
}IMAGE_IMPORT_DESCRIPTOR, *PIMAGE_IMPORT_DESCRIPTOR;

双桥结构

桥1和桥2本来是通向一个目的地,导入函数的“索引-名称”(Hint/Name)描述部分。

typedef struct _IMAGE_IMPORT_BY_NAME 
{
    USHORT Hint;
	UCHAR Name[1];
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

在这里插入图片描述
当PE文件被加载到内存后,IAT的内容会被修改成函数的VA,这个修改会导致桥2发生断裂。
在内存中,
桥1可以让你找到调用函数的名称和索引;
桥2可以让你找到调用函数在内存空间的地址。
在这里插入图片描述

判断一个PE文件是不是sys

看它的导入表中有没有ntoskrnl.exehal.dllndis.sysbootvid.dllkdcom.dll

while (ImageImportDescriptor->Characteristics != 0)
{
    PCHAR Name = (char*)((ULONG_PTR)FileImageBase + RvaToFoa(ImageNtHeaders, ImageImportDescriptor->Name));
    PCHAR ModuleName[5] = { "ntoskrnl.exe", "hal.dll", "ndis.sys", "bootvid.dll", "kdcom.dll" };
    for (UINT32 i = 0; i < 5; i++)
    {
        if (strcmp(Name, ModuleName[i]) == 0)
        {
            return TRUE;
        }
    }
    ImageImportDescriptor++;
}

资源表

程序中常用的有六类资源:位图(BITMAP)、光标(CURSOR)、图标(ICON)、菜单(MENU)、对话框(DIALOG)、自定义资源。
资源表描述了资源数据在PE中的分布情况。PE文件的资源组织方式类似于文件管理。从根目录开始,下有一级子目录(按资源类型分类)、二级子目录(按资源ID分类)、三级子目录(按资源的代码页分类),三级子目录下的才是真正的资源数据项。
这么说可能比较抽象,直接查看一个实例的资源表:
在这里插入图片描述
根目录资源目录根据资源类型分成了10个资源目录项;资源目录项1,它又根据资源ID分成了17个资源目录项。
在这里插入图片描述
这个新的资源目录项1按照资源代码页分类,只得到一个资源目录项1,它下面的才是真正的资源数据项。
在这里插入图片描述
所以,PE文件的资源表共涉及到3个数据结构:资源目录头、资源目录项、资源数据项。

// 资源目录头
typedef struct _IMAGE_RESOURCE_DIRECTORY_
{
    DWORD Characteristics;      //资源的属性,保留为将来使用,必须为0
    DWORD TimeDataStamp;        //资源的创建时间
    WORD  MajorVersion;         //资源的主版本号
    WORD  MinorVersion;         //资源的次版本号
    WORD  NumberOfNamedEntries; //以名称命名的目录项个数
    WORD  NumberOfIdEntries;    //以ID命名的目录项个数
}IMAGE_RESOURCE_DIRECTORY, *PIMAGE_RESOURCE_DIRECTORY;
// 资源目录项
typedef struct _IMAGE_RESOURCE_DIRECTORY_ENTRY_
{
    DWORD Name;         //目录项的名称或ID,最高位(31位)为1时表示低地址部分为一个指向Unicode字符串的指针;为0时表示该字段是一个编号。
    DWORD OffsetToData; //一个指针,最高位(31位)为1时表示低位数据指向下一级目录块的起始地址;为0时表示指针指向的是描述资源数据块的指针,通常出现再第三级目录中
}IMAGE_RESOURCE_DIRECTORY_ENTRY, *PIMAGE_RESOURCE_DIRECTORY_ENTRY;
// 资源数据项
typedef struct _IMAGE_RESOURCE_DATA_ENTRY_
{
    DWORD OffsetToData; //指向资源数据块的指针,是一个RVA值,再文件中访问时需要注意转换成文件偏移
    DWORD Size;         //资源数据的大小
    DWORD CodePage;     //代码页,未用,大多数情况下为0
    DWORD Reserved;     //保留字段,总是为0
}IMAGE_RESOURCE_DATA_ENTRY, *PIMAGE_RESOURCE_DATA_ENTRY;

安全表

如果一个PE文件有数字签名,则其安全表不为NULL。

typedef struct _WIN_CERTIFICATE
{
    DWORD dwLength;
    WORD  wRevision;            //证书版本,WIN_CERT_REVISION_xxx
    WORD  wCertificateType;     //证书类型,WIN_CERT_TYPE_xxx
    BYTE  bCertificate[ANYSIZE_ARRAY];	//包含一个或多个证书,一般来说这个证书的内容一直到安全表的末尾
} WIN_CERTIFICATE, *PWIN_CERTIFICATE;

重定位表

PE文件被装入内存时,其基地址是由OptionalHeader->ImageBase决定的。但是如果装入时该位置已被占用,则操作系统会重新选择一个基地址,此时就需要对所有的重定位信息进行修正,修正的依据就是重定位表。
它与导入表类似,是一个结构体数组。

typedef struct _IMAGE_BASE_RELOCATION_
{
    DWORD VirtualAddress;   //重定位内存页的起始RVA
    DWORD SizeOfBlock;      //重定位块的大小
}IMAGE_BASE_RELOCATION, *PIMAGE_BASE_RELOCATION;

调试信息表

typedef struct _IMAGE_DEBUG_DIRECTORY
{
    DWORD Characteristics;
    DWORD TimeDataStamp;    //Debug信息的时间
    WORD  MajorVersion;     //Debug的主版本
    WORD  MinorVersion;     //Debug的次版本
    DWORD Type;             //Debug信息的类型
    DWORD SizeOfData;       //Debug数据的大小
    DWORD AddressOfRawData; //当被映射到内存时Debug数据的大小
    DWORD PointerToRawData; //Debug数据的文件偏移
}IMAGE_DEBUG_DIRECTORY, *PIMAGE_DEBUG_DIRECTORY;

TLS

typedef struct _IMAGE_TLS_DIRECTORY32 
{
  ULONG StartAddressOfRawData;	// TLS模板的起始地址
  ULONG EndAddressOfRawData;	// TLS模板的结束地址,最后一个字节的地址,不包括用于填充的0
  ULONG AddressOfIndex;			// TLS索引的位置,索引的具体值由加载器确定。这个位置在.data中。
  ULONG AddressOfCallBacks;		// 指向TLS回调函数的指针数组,数组以NULL结尾。若没有回调函数,则指向的位置是4个字节的0
  ULONG SizeOfZeroFill;			// 用0填充的字节数
  ULONG Characteristics;		// 保留
} IMAGE_TLS_DIRECTORY32, *PIMAGE_TLS_DIRECTORY32;
typedef struct _IMAGE_TLS_DIRECTORY64
{
  ULONGLONG StartAddressOfRawData;
  ULONGLONG EndAddressOfRawData;
  ULONGLONG AddressOfIndex;
  ULONGLONG AddressOfCallBacks;
  ULONG SizeOfZeroFill;
  ULONG Characteristics;
} IMAGE_TLS_DIRECTORY64, *PIMAGE_TLS_DIRECTORY64;

节表

节表是PE文件中所有节的目录。
节表的大小 = 节的数量(IMAGE_FILE_HEADER->NumberOfSections) * 40字节

typedef struct _IMAGE_SECTION_HEADER 
{
    BYTE    Name[8];                // 节名称,8字节,一般情况是一个以“\0”结尾的ASCII码字符串,#define IMAGE_SIZEOF_SHORT_NAME  8
    union 
    {
        DWORD   PhysicalAddress;    // 物理地址  
        DWORD   VirtualSize;        // 实际的节区大小,就是节的数据在没有对齐前的真实尺寸
    } Misc;
    DWORD   VirtualAddress;         // 节区起始数据的RVA,一般为0x1000(我自己认为的,并不是书上的)
    DWORD   SizeOfRawData;          // 节在文件中对齐后的大小
    DWORD   PointerToRawData;       // 节区起始数据在文件中的偏移(即FOA)
    DWORD   PointerToRelocations;   // 指向重定位表的指针(在“.obj”文件中使用)
    DWORD   PointerToLinenumbers;   // 行号表的偏移(供调试使用)  
    WORD    NumberOfRelocations;    // 重定位表的个数(在“.obj”文件中使用)  
    WORD    NumberOfLinenumbers;    // 行号表中行号的数目  
    DWORD   Characteristics;        // 节区的属性,如可读、可写、可执行等  
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

当PE文件被加载到内存中时,其占用的地址空间要比在磁盘中大一点,因为各个节在内存中是按页对齐的。
在这里插入图片描述

通过RVA获得FOA

ULONG
RvaToFoa(
    IN PIMAGE_NT_HEADERS NtHeaders,
    IN ULONG             Rva
)
{
    PIMAGE_SECTION_HEADER SecHeader = IMAGE_FIRST_SECTION(NtHeaders);
    for (UINT32 i = 0; i < NtHeaders->FileHeader.NumberOfSections; i++, SecHeader++)
    {
        if (Offset >= SecHeader->VirtualAddress &&
            Offset <= SecHeader->VirtualAddress + SecHeader->Misc.VirtualSize)
        {
            return SecHeader->PointerToRawData + (Rva - SecHeader->VirtualAddress);
        }
    }
    return 0xFFFFFFFF;
}

常见的节

  • .text:包含CPU执行指令,是唯一可以执行的节。
  • .rdata:包含只读数据,包括导入与导出函数信息。有的文件还会包含.idata和.edata节,来存储导入导出信息。
  • .data:包含全局数据。
  • .rsrc:包含由可执行文件所使用的资源,这些内容并不是可执行的,如图标、图片、菜单项和字符串等。

在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值