[摘要]体验修改PE文件的乐趣 适合读者:程序员、破解爱好者前置知识:汇编语法体验修改PE文件的乐趣 文/图 牛渊 我们大家都知道, 在Windows 9x、NT、... 体验修改PE文件的乐趣 适合读者:程序员、破解爱好者 前置知识:汇编语法 体验修改PE文件的乐趣 文/图 牛渊 我们大家都知道, 在Windows 9x、NT、2000下, 所有的可执行文件都是基于Microsoft设计的一种新的文件格式Portable Executable File Format(可移植的执行体), 即PE格式。 有一些时候, 我们需要对这些可执行文件进行修改, 下面文字试图详细的描述PE文件的格式及对PE格式文件的修改。 PE文件框架构成 dos MZ header dos Stub PE header Section table Section 1 Section 2 Section... Section n 上表是PE文件结构的总体层次分布。 所有 PE文件(甚至32位的 DLLs) 必须以一个简单的 dos MZ header开始, 在偏移0处有dos下可执行文件的“MZ标志”, 有了它, 一旦程序在dos下执行, dos就能识别出这是有效的执行体, 然后运行紧随MZ header之后的dos Stub。 紧接着dos Stub的是PE header。 PE header是PE相关结构IMAGE_NT_HEADERS的简称, 其中包含了许多PE装载器用到的重要域。 可执行文件在支持PE文件结构的操作系统中执行时, PE装载器将从dos MZ header的偏移3CH处找到PE header的起始偏移量。 因而跳过了dos Stub直接定位到真正的文件头PE header。 小知识:dos Stub实际上是个有效的EXE, 在不支持PE文件格式的操作系统中, 它将简单显示一个错误提示, 类似于字符串“This program cannot run in dos mode”或者程序员可根据自己的意图实现完整的dos代码。 通常dos Stub由汇编器/编译器自动生成, 对我们的用处不是很大, 它简单调用中断21h服务9来显示字符串“This program cannot run in dos mode”。 PE文件的真正内容划分成块, 称之为Sections(节)。 每节是一块拥有共同属性的数据, 比如“.text”节等, 那么, 每一节的内容都是什么呢?实际上PE格式的文件把具有相同属性的内容放入同一个节中, 而不必关心类似“.text”、“.data”的命名, 其命名只是为了便于识别, 所有, 我们如果对PE格式的文件进行修改, 理论上讲可以写入任何一个节内, 并调整此节的属性就可以了。 PE header 接下来的数组结构Section table(节表)。 每个结构包含对应节的属性、文件偏移量、虚拟偏移量等。 如果PE文件里有5个节, 那么此结构数组内就有5个成员。 以上就是PE文件格式的物理分布, 下面将总结一下装载一PE文件的主要步骤: 1.PE文件被执行, PE装载器检查dos MZ header里的PE header偏移量。 如果找到, 则跳转到PE header。 2.PE装载器检查PE header的有效性。 如果有效, 就跳转到PE header的尾部。 3.紧跟 PE header的是节表。 PE装载器读取其中的节信息, 并采用文件映射方法将这些节映射到内存, 同时附上节表里指定的节属性。 4.PE文件映射入内存后, PE装载器将处理PE文件中类似Import table(引入表)逻辑部分。 PE文件头定义 我们可以在Winnt.h这个文件中找到关于PE文件头的定义: typedef struct _IMAGE_NT_HEADERS { DWORD Signature; //PE文件头标志 :“PE\0\0”。 在开始dos header的偏移3CH处所指向的地址开始 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平台是14Ch WORD NumberOfSections; //文件的节数目 DWORD TimeDateStamp; //文件创建日期和时间 DWORD PointerToSymbolTable; //用于调试 DWORD NumberOfSymbols; //符号表中符号个数 WORD SizeOfOptionalHeader; //OptionalHeader 结构大小 WORD Characteristics; //文件信息标记, 区分文件是exe还是dll } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER; typedef struct _IMAGE_OPTIONAL_HEADER { WORD Magic; //标志字(总是010bh) BYTE MajorLinkerVersion; //连接器版本号 BYTE MinorLinkerVersion; // DWORD SizeOfCode; //代码段大小 DWORD SizeOfInitializedData; //已初始化数据块大小 DWORD SizeOfUninitializedData; //未初始化数据块大小 DWORD AddressOfEntryPoint; PE装载器准备运行的PE文件的第一个指令的RVA, 若要改变整个执行的流程, 可以将该值指定到新的RVA, 这样新RVA处的指令首先被执行(以往许多文章都有介绍RVA, 请大家先了解)。 DWORD BaseOfCode; //代码段起始RVA DWORD BaseOfData; //数据段起始RVA DWORD ImageBase; //PE文件的装载地址 DWORD SectionAlignment; //块对齐 DWORD FileAlignment; //文件块对齐 WORD MajorOperatingSystemVersion;//所需操作系统版本号 WORD MinorOperatingSystemVersion;// WORD MajorImageVersion; //用户自定义版本号 WORD MinorImageVersion; // WORD MajorSubsystemVersion; //win32子系统版本。 若PE文件是专门为Win32设计的 WORD MinorSubsystemVersion; //该子系统版本必定是4.0否则对话框不会有3维立体感 DWORD Win32VersionValue; //保留 DWORD SizeOfImage; //内存中整个PE映像体的尺寸 DWORD SizeOfHeaders; //所有头+节表的大小 DWORD CheckSum; //校验和 WORD Subsystem; //NT用来识别PE文件属于哪个子系统 WORD DllCharacteristics; // DWORD SizeOfStackReserve; // DWORD SizeOfStackCommit; // DWORD SizeOfHeapReserve; // DWORD SizeOfHeapCommit; // DWORD LoaderFlags; // DWORD NumberOfRvaAndSizes; // IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; //IMAGE_DATA_DIRECTORY 结构数组。 每个结构给出一个重要数据结构的RVA, 比如引入地址表等 } IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32; typedef struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress; //表的RVA地址 DWORD Size; //大小 } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY; PE文件头后是节表, 在winnt.h下如下定义 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;以上结构就是在Winnt.h中关于PE文件头的定义, 如何我们用C/C++来进行PE可执行文件操作, 就要用到上面的所有结构, 它详细的描述了PE文件头的结构。 修改PE可执行文件 现在让我们把一段代码写入任何一个PE格式的可执行文件, 代码如下: -- test.asm -- .386p .model flat, stdcall option casemap:none include \masm32\include\windows.inc include \masm32\include\user32.inc includelib \masm32\lib\user32.lib .code start: INVOKE MessageBoxA,0,0,0,MB_ICONINFORMATION or MB_OK ret end start以上代码只显示一个MessageBox框, 编译后得到二进制代码如下: unsigned char writeline[18]={ 0x6a,0x40,0x6a,0x0,0x6a,0x0,0x6a,0x0,0xe8,0x01,0x0,0x0,0x0,0xe9,0x0,0x0,0x0,0x0 };好, 现在让我们看看该把这些代码写到那。 现在用Tdump.exe显示一个PE格式得可执行文件信息, 可以发现如下描述: Object table: # Name VirtSize RVA PhysSize Phys off Flags -- -------- -------- -------- -------- -------- -------- 01 .text 0000CCC0 00001000 0000CE00 00000600 60000020 [CER] 02 .data 00004628 0000E000 00002C00 0000D400 C0000040 [IRW] 03 .rsrc 000003C8 00013000 00000400 00010000 40000040 [IR] Key to section flags: C - contains code E - executable I - contains initialized data R - readable W - writeable上面描述此文件中存在3个段及每个段的信息, 实际上我们的代码可以写入任何一个段, 这里我选择“.text”段。 用光盘中提供的代码可以得到一个PE格式可执行文件的头信息。 由于在PE格式的文件中, 所有的地址都使用RVA地址, 所以一些函数调用和返回地址都要经过计算才可以得到。 以上都是我在实践中的心得, 希望大家能够得到受益, 另外如果大家有更好的思路和方法, 希望大家能告诉我, 多多交流。 (文中涉及到的源代码已收录到杂志配套光盘“杂志相关”栏目, 按文章名查找即可)
上面是电脑上网安全的一些基础常识,学习了安全知识,几乎可以让你免费电脑中毒的烦扰。
|