修改PE节,配置可执行程序

      Program 2006-3-17 16:24

PE文件结构

 

PE文件格式主要来自于UNIX操作系统所通用的COFF规范,同时为了保证与旧版本MS-DOSWindows操作系统的兼容,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兼容的可执行文件都将这个值设为0x5A4D,表示ASCII字符MZMS-DOS头部之所以有的时候被称为MZ头部,就是这个缘故。还有许多其它的域对于MS-DOS操作系统来说都有用,但是对于Windows NT来说,这个结构中只有一个有用的域——最后一个域e_lfnew,一个4字节的文件偏移量,PE文件头部就是由它定位的。对于Windows NTPE文件来说,PE文件头部是紧跟在MS-DOS头部和实模式程序残余之后的。

 

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

 

标签集:TAGS:
回复Comments() 点击Count()

回复Comments

{commenttime}{commentauthor}

{CommentUrl}
{commentcontent}