PE文件结构
PE文件格式主要来自于UNIX操作系统所通用的COFF规范,同时为了保证与旧版本MS-DOS及Windows操作系统的兼容,PE文件格式也保留了MS-DOS中熟悉的MZ头部。
PE文件格式被组织为一个线性的数据流,它由一个MS-DOS头部开始,接着是一个是模式的程序残余以及一个PE文件标志,这之后紧接着PE文件头和可选头部。这些之后是所有的段头部,段头部之后跟随着所有的段实体。文件的结束处是一些其它的区域,其中是一些混杂的信息,包括重分配信息、符号表信息、行号信息以及字串表数据。
PE文件格式的第一个组成部分是MS-DOS头部。在PE文件格式中,它并非一个新概念,因为它与MS-DOS 2.0以来就已有的MS-DOS头部是完全一样的。保留这个相同结构的最主要原因是,当你尝试在Windows 3.1以下或MS-DOS 2.0以上的系统下装载一个文件的时候,操作系统能够读取这个文件并明白它是和当前系统不相兼容的。换句话说,当你在MS-DOS 6.0下运行一个Windows NT可执行文件时,你会得到这样一条消息:This program cannot be run in DOS mode.
MS-DOS头部占据了PE文件的头64个字节,描述它内容的结构如下:
//WINNT.H
typedef struct _IMAGE_DOS_HEADER { // DOS的.EXE头部
USHORT e_magic; // 魔术数字
USHORT e_cblp; // 文件最后页的字节数
USHORT e_cp; // 文件页数
USHORT e_crlc; // 重定义元素个数
USHORT e_cparhdr; // 头部尺寸,以段落为单位
USHORT e_minalloc; // 所需的最小附加段
USHORT e_maxalloc; // 所需的最大附加段
USHORT e_ss; // 初始的SS值(相对偏移量)
USHORT e_sp; // 初始的SP值
USHORT e_csum; // 校验和
USHORT e_ip; // 初始的IP值
USHORT e_cs; // 初始的CS值(相对偏移量)
USHORT e_lfarlc; // 重分配表文件地址
USHORT e_ovno; // 覆盖号
USHORT e_res[4]; // 保留字
USHORT e_oemid; // OEM标识符(相对e_oeminfo)
USHORT e_oeminfo; // OEM信息
USHORT e_res2[10]; // 保留字
LONG e_lfanew; // 新exe头部的文件地址
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
第一个域e_magic,被称为魔术数字,它被用于表示一个MS-DOS兼容的文件类型。所有MS-DOS兼容的可执行文件都将这个值设为0x
PE文件头结构被定义为:
//WINNT.H
typedef struct _IMAGE_FILE_HEADER {
USHORT Machine;
USHORT NumberOfSections;
ULONG TimeDateStamp;
ULONG PointerToSymbolTable;
ULONG NumberOfSymbols;
USHORT SizeOfOptionalHeader;
USHORT Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
#define IMAGE_SIZEOF_FILE_HEADER 20
PE文件头结构中另一个有用的入口是NumberOfSections域,它表示如果你要方便地提取文件信息的话,就需要了解多少个段——更明确一点来说,有多少个段头部和多少个段实体。每一个段头部和段实体都在文件中连续地排列着,所以要决定段头部和段实体在哪里结束的话,段的数目是必需的。
PE文件规范由目前为止定义的那些头部以及一个名为“段”的一般对象组成。段包含了文件的内容,包括代码、数据、资源以及其它可执行信息,每个段都有一个头部和一个实体(原始数据)。PE文件格式中,所有的段头部位于可选头部之后。每个段头部为40个字节长,并且没有任何的填充信息。段头部被定义为以下的结构:
//WINNT.H
#define IMAGE_SIZEOF_SHORT_NAME 8
typedef struct _IMAGE_SECTION_HEADER {
UCHAR Name[IMAGE_SIZEOF_SHORT_NAME];
union {
ULONG PhysicalAddress;
ULONG VirtualSize;
} Misc;
ULONG VirtualAddress;
ULONG SizeOfRawData;
ULONG PointerToRawData;
ULONG PointerToRelocations;
ULONG PointerToLinenumbers;
USHORT NumberOfRelocations;
USHORT NumberOfLinenumbers;
ULONG Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
其中Name是段的名称,每个段都有一个8字符长的名称域,并且第一个字符一般是一个句点。PointerToRawData是一个文件中段实体位置的偏移量。
具体实现
待配置的程序为test.exe,其代码如下:
#include "stdio.h"
#pragma data_seg(".mydata")
int i=10; // 待配置的变量
#pragma data_seg()
void main()
{
printf("%d\n", i);
}
配置程序可以改写test.exe中数据段的内容,示例代码如下:
// 声明变量
int newi = 100;
UCHAR SectionName[9];
char pFileName[MAX_PATH];
IMAGE_FILE_HEADER FileHeader;
IMAGE_SECTION_HEADER *pSectionHeader;
int nSectionCount;
FILE *fp;
int i;
LONG e_lfanew;
// 打开待配置的PE文件
GetCurrentDirectory(MAX_PATH, pFileName);
strcat(pFileName, "\\test.exe");
fp = fopen(pFileName, "r+b");
if(fp==NULL)
{
printf("File not found!\n");
return;
}
// 定位并获得IMAGE_DOS_HEADER 结构中的e_lfanew
fseek(fp, sizeof(IMAGE_DOS_HEADER) - sizeof(LONG), SEEK_SET);
fread(&e_lfanew, sizeof(LONG), 1, fp);
// 定位IMAGE_FILE_HEADER
fseek(fp, e_lfanew + SIZE_OF_NT_SIGNATURE, SEEK_SET);
fread(&FileHeader, sizeof(FileHeader), 1, fp);
// 获得节的数目并分配相应的存储空间
nSectionCount = FileHeader.NumberOfSections;
pSectionHeader = (IMAGE_SECTION_HEADER *)calloc(nSectionCount, sizeof(IMAGE_SECTION_HEADER));
// 定位到第一个节
fseek(fp, e_lfanew + sizeof(IMAGE_NT_HEADERS), SEEK_SET);
fread(pSectionHeader, sizeof(IMAGE_SECTION_HEADER), nSectionCount, fp);
// 遍历所有的节
for (i = 0; i < nSectionCount; i++)
{
// 根据节名称判断是否是我们要定位的节
memset(SectionName, 0, sizeof(SectionName));
memcpy(SectionName, pSectionHeader->Name, 8);
if(!stricmp((const char*)SectionName, ".mydata"))
{
// 写入新配置
fseek(fp, pSectionHeader->PointerToRawData, SEEK_SET);
fwrite(&newi, sizeof(int), 1, fp);
break;
}
else
pSectionHeader++;
}
// 释放内存,关闭文件
pSectionHeader -= i;
if(pSectionHeader != NULL)
{
free(pSectionHeader);
pSectionHeader = NULL;
}
fclose(fp);

回复Comments
{commenttime}{commentauthor}
{CommentUrl}
{commentcontent}