DOS病毒编制的关键技术-PE文件

发表于:2015-08-15 14:51 阅读:

  PE Portable Executable) 文件是微软公司设计用于其所有 Win32 操作系统Windows NT/9X/2000/XP)的可执行文件格式。很多时候需要判断文件是否为WIN32 PE格式,以便此文件可以在WIN32系统下正常运行,这在病毒和反病毒软件中尤为重要。与COM文件和EXE文件相比,PE文件格式比较复杂,限于篇幅,我们在这里只简单地作一些介绍,有兴趣的读者可查找有关参考书进行详细的研究。PE文件格式如图4-2所示。

 

4-2 PE文件格式

  我们首先来看看PE 表头。和其它的微软可执行文件格式一样,PE 表头并非在文件的最开始处。所有PE文件必须以一个简单的DOS MZ头开始,紧接着DOS MZ头的是所谓的DOS stubDOS stub是一个极小 DOS 程序(一般是几百个字节),用来输出像该程序不能在DOS模式下运行这样的信息。这也就是为什么在纯DOS方式下运行PE格式文件会给出一条错误提示信息的原因。当 Win32 加载器把一个 PE 文件映像到内存,内存映像文件的第一个字节对应到 DOS Stub 的第一个字节。

  紧接着 DOS stub 的是PE头。PE头是PE相关结构 IMAGE_NT_HEADERS 的简称,其中包含了许多PE装载器用到的重要域。PE头中包含了PE标志,此标志为DWORD类型,其值为“PE\0\0”。执行体在支持PE文件结构的操作系统中执行时,PE装载器将从 DOS MZ头中找到 PE 头的起始偏移量。因而跳过了 DOS stub 直接定位到真正的PE文件头。

  PE文件的真正内容划分成块,称之为段。每段节是一块拥有共同属性的数据。PE头接下来的数据结构是段表,每个结构包含对应段的属性、文件偏移量、虚拟偏移量等内容。PE文件里有多少个段,此结构数组内就有多少个成员。

  下面,我们分析PE文件的有关数据结构。
  
  //NT
  typedef struct _IMAGE_NT_HEADERS {
  DWORD Signature; //PE文件头标志:
“PE\0\0”
  IMAGE_FILE_HEADER FileHeader; //PE文件物理分布的信息

  IMAGE_OPTIONAL_HEADER32 OptionalHeader; //PE文件逻辑分布的信息

  
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
  

  //文件头
  typedef struct _IMAGE_FILE_HEADER {
  WORD Machine;  //该文件运行所需要的CPU,对于Intel平台
0x14C
  WORD NumberOfSections; // EXEOBJ 中的段的个数

  DWORD TimeDateStamp; //文件创建日期和时间

  DWORD PointerToSymbolTable; // COFF 符号表格的偏移位置,此字段只对 COFF 出错信息有用,其中COFF是指Common Object File Format(公共目标文件格式)

  DWORD NumberOfSymbols; //COFF符号表中符号个数
  WORD SizeOfOptionalHeader; //OptionalHeader 结构大小

  WORD Characteristics;  //文件信息标记,区分文件是EXE还是
DLL
  
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
  

  //可选头
  typedef struct _IMAGE_OPTIONAL_HEADER {
  WORD Magic; //用来定义 image 的状态,其中0x0107表示是一个ROM image0x010B表示是一个正常的(一般的)
EXE image
  BYTE MajorLinkerVersion; //连接器主版本号

  BYTE MinorLinkerVersion; //连接器次版本号

  DWORD SizeOfCode; //所有的代码段大小的总和
  DWORD SizeOfInitializedData; //所有已初始化数据块大小的总和

  DWORD SizeOfUninitializedData; //所有未初始化数据块大小的总和
  DWORD AddressOfEntryPoint; //PE装载器准备运行的PE文件的第一个指令的RVA,若要改变整个执行的流程,可以将该值指定到新的RVA,这样新RVA处的指令首先被执行。其中RVA是指Relative Virtual Adress(相对虚拟地址)
  DWORD BaseOfCode; //代码段起始
RVA
  DWORD BaseOfData; //数据段起始
RVA
  DWORD ImageBase; //PE文件的装载地址

  DWORD SectionAlignment; //块对齐,一旦映像到内存中,每一个段的起始地址都是该值的倍数

  DWORD FileAlignment; //文件块对齐,每个段的数据的起始地址都是该值的倍数
  WORD MajorOperatingSystemVersion; //文件执行所需操作系统的主版本号
  WORD MinorOperatingSystemVersion;// 文件执行所需操作系统的次版本号

  WORD MajorImageVersion; //用户自定义的主版本号

  WORD MinorImageVersion; //用户自定义的次版本号

  WORD MajorSubsystemVersion; //win32子系统主版本。

  WORD MinorSubsystemVersion; //win32子系统次版本。若PE文件是专门为Win32设计的,该子系统版本必定是4.0,否则对话框不会有三维立体感
  DWORD Win32VersionValue; //保留,通常取
0
  DWORD SizeOfImage; //内存中整个PE映像体的大小

  DWORD SizeOfHeaders; //所有表头和段表大小的总和
  DWORD CheckSum;  //校验和,通常取
0
  WORD Subsystem;  //NT用来识别PE文件属于哪个子系统

  WORD DllCharacteristics; //DLL特征值,通常取
0
  DWORD SizeOfStackReserve; //线程初始堆栈的保留大小

  DWORD SizeOfStackCommit; //分配给线程初始堆栈的内存数量
  DWORD SizeOfHeapReserve; //保留给最初的进程堆的虚拟内存数量
  DWORD SizeOfHeapCommit; //分配给最初的进程堆的虚拟内存数量

  DWORD LoaderFlags; // 装载标志

  DWORD NumberOfRvaAndSizes; //在数据目录数组中的项目个数
  IMAGE_DATA_DIRECTORY
  
DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; //=16
  } IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

DOS病毒编制的关键技术-PE文件[2]

 进入社区

  //数据目录
  typedef struct _IMAGE_DATA_DIRECTORY {
  DWORD VirtualAddress; //表的RVA地址

  DWORD Size; //大小

  
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
  

  //数据入口
  #define IMAGE_DIRECTORY_ENTRY_EXPORT 0 // 导出目录
  #define IMAGE_DIRECTORY_ENTRY_IMPORT 1 // 导入目录
  #define IMAGE_DIRECTORY_ENTRY_RESOURCE 2 // 资源目录
  #define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3 // 异常目录
  #define IMAGE_DIRECTORY_ENTRY_SECURITY 4 // 安全目录
  #define IMAGE_DIRECTORY_ENTRY_BASERELOC 5 //重定位基表
  #define IMAGE_DIRECTORY_ENTRY_DEBUG 6 // 调试目录
  #define IMAGE_DIRECTORY_ENTRY_COPYRIGHT 7 //描述字符串
  #define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8 // RVA of GP
  #define IMAGE_DIRECTORY_ENTRY_TLS 9 // TLS目录

  #define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10 //装载配置目录
  #define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 11 //在表头中绑定的导入目录
  #define IMAGE_DIRECTORY_ENTRY_IAT 12 // 导入地址表
  #define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 13 //延迟装载导入描述符
  #define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14 //COM运行描述符
  
  //段表
  typedef struct _IMAGE_SECTION_HEADER {
  BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; //段表名,如“.text”

  
union {
  DWORD PhysicalAddress; //物理地址

  DWORD VirtualSize; //真实长度

  
} Misc;
  
DWORD VirtualAddress; //RVA
  DWORD SizeOfRawData; //物理长度

  DWORD PointerToRawData; //段基于文件的偏移量

  DWORD PointerToRelocations; //重定位的偏移

  DWORD PointerToLinenumbers; //行号表的偏移

  WORD NumberOfRelocations; //重定位表中重定位项数目

  WORD NumberOfLinenumbers; //行号表中行的个数

  DWORD Characteristics; //段属性
  
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
  

  //资源目录
  typedef struct _IMAGE_RESOURCE_DIRECTORY {
  DWORD Characteristics; //资源的特性

  DWORD TimeDateStamp; //资源的产生时刻
  WORD MajorVersion; //资源的主版本号,目前总是为0
  WORD MinorVersion; //资源的次版本号,目前总是为
0
  WORD NumberOfNamedEntries; //使用名称的元素的个数

  WORD NumberOfIdEntries; //使用ID的元素的个数
  // IMAGE_RESOURCE_DIRECTORY_ENTRY DirectoryEntries[];
  
} IMAGE_RESOURCE_DIRECTORY, *PIMAGE_RESOURCE_DIRECTORY;
  

  //资源目录入口
  typedef struct _IMAGE_RESOURCE_DIRECTORY_ENTRY {
  
union {
  
struct {
  DWORD NameOffset; //名字偏移地址,取值
31
  DWORD NameIsString; //名字是字符串,取
1
  
};
  DWORD Name; //名字

  WORD Id; //ID
  };
  
union {
  DWORD OffsetToData; //数据偏移地址

  struct {
  DWORD OffsetToDirectory; //目录偏移地址,取
31
  
DWORD DataIsDirectory;
  
};
  
};
  
}IMAGE_RESOURCE_DIRECTORY_ENTRY,
  
*PIMAGE_RESOURCE_DIRECTORY_ENTRY;
  

  //资源目录名
  typedef struct _IMAGE_RESOURCE_DIRECTORY_STRING {
  WORD Length; //目录名长度

  CHAR NameString[ 1 ]; //目录名字串
  }IMAGE_RESOURCE_DIRECTORY_STRING,
  
*PIMAGE_RESOURCE_DIRECTORY_STRING;
  

  //资源目录Unicode
  typedef struct _IMAGE_RESOURCE_DIR_STRING_U {
  WORD Length; //目录名长度

  WCHAR NameString[ 1 ]; //目录名字串
  } IMAGE_RESOURCE_DIR_STRING_U, *PIMAGE_RESOURCE_DIR_STRING_U;
  

  //资源数据入口
  
typedef struct _IMAGE_RESOURCE_DATA_ENTRY {
  DWORD OffsetToData;  //数据偏移地址

  DWORD Size;  //大小
  DWORD CodePage; //代码页
  DWORD Reserved; //保留
  } IMAGE_RESOURCE_DATA_ENTRY, *PIMAGE_RESOURCE_DATA_ENTRY;

  装载一个PE文件的主要步骤如下:

  (1)当PE文件被执行,PE装载器检查 DOS MZ头里的PE头偏移量。如果找到,则跳转到PE头。
  (2PE装载器检查PE头的有效性。如果有效,就跳转到PE头的尾部。

  (3)紧跟PE头的是段表,PE装载器读取其中的段的信息,并采用文件映射方法将这些段映射到内存,同时附上段表里指定的段的属性。

  (4PE文件映射入内存后,PE装载器将处理PE文件中类似引入表逻辑部分。

此文章节选自《计算机病毒与木马程序剖析》


(非特殊说明,本文版权归原作者所有,转载请注明出处 )
鸣人致力于为企业提供数据恢复、机房建设、数据库运行、运营及安全等全方位服务。




想在手机上、随时获取互联网前沿、设计资讯以及各种意想不到的"福利"吗?通过微信扫描二维码快速添加