⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 subject_19591.htm

📁 一些关于vc的问答
💻 HTM
📖 第 1 页 / 共 2 页
字号:
<p>
序号:19591 发表者:Jonny 发表日期:2002-10-30 00:14:47
<br>主题:哪位阿大知道win32可执行文件的详细结构?
<br>内容:谢谢!
<br><a href="javascript:history.go(-1)">返回上页</a><br><a href=http://www.copathway.com/cndevforum/>访问论坛</a></p>
<hr size=1>
<blockquote><p>
<font color=red>答案被接受</font><br>回复者:Waynew Hsu 回复日期:2002-10-30 06:05:20
<br>内容:关于PE可执行文件的修改<BR> <BR><BR>--------------------------------------------------------------------------------<BR> <BR>作者:ilsy<BR><BR>在windows 9x、NT、2000下,所有的可执行文件都是基于Microsoft设计的一种新的文件格式Portable Executable File Format(可移植的执行体),即PE格式。有一些时候,我们需要对这些可执行文件进行修改,下面文字试图详细的描述PE文件的格式及对PE格式文件的修改。 <BR>1、PE文件框架构成 <BR>DOS MZ header <BR>DOS stub <BR>PE header <BR>Section table <BR>Section 1 <BR>Section 2 <BR>Section ... <BR>Section n <BR>上表是PE文件结构的总体层次分布。所有 PE文件(甚至32位的 DLLs) 必须以一个简单的 DOS MZ header 开始,在偏移0处有DOS下可执行文件的“MZ标志”,有了它,一旦程序在DOS下执行,DOS就能识别出这是有效的执行体,然后运行紧随 MZ header 之后的 DOS stub。DOS stub实际上是个有效的EXE,在不支持 PE文件格式的操作系统中,它将简单显示一个错误提示,类似于字符串 " This program cannot run in DOS mode " 或者程序员可根据自己的意图实现完整的 DOS代码。通常DOS stub由汇编器/编译器自动生成,对我们的用处不是很大,它简单调用中断21h服务9来显示字符串"This program cannot run in DOS mode"。 <BR>紧接着 DOS stub 的是 PE header。 PE header 是PE相关结构 IMAGE_NT_HEADERS 的简称,其中包含了许多PE装载器用到的重要域。可执行文件在支持PE文件结构的操作系统中执行时,PE装载器将从 DOS MZ header的偏移3CH处找到 PE header 的起始偏移量。因而跳过了 DOS stub 直接定位到真正的文件头 PE header。 <BR>PE文件的真正内容划分成块,称之为sections(节)。每节是一块拥有共同属性的数据,比如“.text”节等,那么,每一节的内容都是什么呢?实际上PE格式的文件把具有相同属性的内容放入同一个节中,而不必关心类似“.text”、“.data”的命名,其命名只是为了便于识别,所有,我们如果对PE格式的文件进行修改,理论上讲可以写入任何一个节内,并调整此节的属性就可以了。 <BR>PE header 接下来的数组结构 section table(节表)。 每个结构包含对应节的属性、文件偏移量、虚拟偏移量等。如果PE文件里有5个节,那么此结构数组内就有5个成员。 <BR>以上就是PE文件格式的物理分布,下面将总结一下装载一PE文件的主要步骤: <BR>1、 PE文件被执行,PE装载器检查 DOS MZ header 里的 PE header 偏移量。如果找到,则跳转到 PE header。 <BR>2、PE装载器检查 PE header 的有效性。如果有效,就跳转到PE header的尾部。 <BR>3、紧跟 PE header 的是节表。PE装载器读取其中的节信息,并采用文件映射方法将这些节映射到内存,同时付上节表里指定的节属性。 <BR>4、PE文件映射入内存后,PE装载器将处理PE文件中类似 import table(引入表)逻辑部分。 <BR>上述步骤是一些前辈分析的结果简述。 <BR>2、PE文件头概述 <BR>我们可以在winnt.h这个文件中找到关于PE文件头的定义: <BR>typedef struct _IMAGE_NT_HEADERS { <BR>DWORD Signature; <BR>//PE文件头标志 :“PE\0\0”。在开始DOS header的偏移3CH处所指向的地址开始 <BR>IMAGE_FILE_HEADER FileHeader; //PE文件物理分布的信息 <BR>IMAGE_OPTIONAL_HEADER32 OptionalHeader; //PE文件逻辑分布的信息 <BR>} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32; <BR><BR>typedef struct _IMAGE_FILE_HEADER { <BR>WORD Machine; //该文件运行所需要的CPU,对于Intel平台是14Ch <BR>WORD NumberOfSections; //文件的节数目 <BR>DWORD TimeDateStamp; //文件创建日期和时间 <BR>DWORD PointerToSymbolTable; //用于调试 <BR>DWORD NumberOfSymbols; //符号表中符号个数 <BR>WORD SizeOfOptionalHeader; //OptionalHeader 结构大小 <BR>WORD Characteristics; //文件信息标记,区分文件是exe还是dll <BR>} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER; <BR><BR>typedef struct _IMAGE_OPTIONAL_HEADER { <BR>WORD Magic; //标志字(总是010bh) <BR>BYTE MajorLinkerVersion; //连接器版本号 <BR>BYTE MinorLinkerVersion; // <BR>DWORD SizeOfCode; //代码段大小 <BR>DWORD SizeOfInitializedData; //已初始化数据块大小 <BR>DWORD SizeOfUninitializedData; //未初始化数据块大小 <BR>DWORD AddressOfEntryPoint; //PE装载器准备运行的PE文件的第一个指令的RVA,若要改变整个执行的流程,可以将该值指定到新的RVA,这样新RVA处的指令首先被执行。(许多文章都有介绍RVA,请去了解) <BR>DWORD BaseOfCode; //代码段起始RVA <BR>DWORD BaseOfData; //数据段起始RVA <BR>DWORD ImageBase; //PE文件的装载地址 <BR>DWORD SectionAlignment; //块对齐 <BR>DWORD FileAlignment; //文件块对齐 <BR>WORD MajorOperatingSystemVersion;//所需操作系统版本号 <BR>WORD MinorOperatingSystemVersion;// <BR>WORD MajorImageVersion; //用户自定义版本号 <BR>WORD MinorImageVersion; // <BR>WORD MajorSubsystemVersion; //win32子系统版本。若PE文件是专门为Win32设计的 <BR>WORD MinorSubsystemVersion; //该子系统版本必定是4.0否则对话框不会有3维立体感 <BR>DWORD Win32VersionValue; //保留 <BR>DWORD SizeOfImage; //内存中整个PE映像体的尺寸 <BR>DWORD SizeOfHeaders; //所有头+节表的大小 <BR>DWORD CheckSum; //校验和 <BR>WORD Subsystem; //NT用来识别PE文件属于哪个子系统 <BR>WORD DllCharacteristics; // <BR>DWORD SizeOfStackReserve; // <BR>DWORD SizeOfStackCommit; // <BR>DWORD SizeOfHeapReserve; // <BR>DWORD SizeOfHeapCommit; // <BR>DWORD LoaderFlags; // <BR>DWORD NumberOfRvaAndSizes; // <BR>IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; <BR>//IMAGE_DATA_DIRECTORY 结构数组。每个结构给出一个重要数据结构的RVA,比如引入地址表等 <BR>} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32; <BR><BR>typedef struct _IMAGE_DATA_DIRECTORY { <BR>DWORD VirtualAddress; //表的RVA地址 <BR>DWORD Size; //大小 <BR>} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY; <BR><BR>PE文件头后是节表,在winnt.h下如下定义 <BR>typedef struct _IMAGE_SECTION_HEADER { <BR>BYTE Name[IMAGE_SIZEOF_SHORT_NAME];//节表名称,如“.text” <BR>union { <BR>DWORD PhysicalAddress; //物理地址 <BR>DWORD VirtualSize; //真实长度 <BR>} Misc; <BR>DWORD VirtualAddress; //RVA <BR>DWORD SizeOfRawData; //物理长度 <BR>DWORD PointerToRawData; //节基于文件的偏移量 <BR>DWORD PointerToRelocations; //重定位的偏移 <BR>DWORD PointerToLinenumbers; //行号表的偏移 <BR>WORD NumberOfRelocations; //重定位项数目 <BR>WORD NumberOfLinenumbers; //行号表的数目 <BR>DWORD Characteristics; //节属性 <BR>} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER; <BR><BR>以上结构就是在winnt.h中关于PE文件头的定义,如何我们用C/C++来进行PE可执行文件操作,就要用到上面的所有结构,它详细的描述了PE文件头的结构。 <BR><BR>3、修改PE可执行文件 <BR>现在让我们把一段代码写入任何一个PE格式的可执行文件,代码如下: <BR>-- test.asm -- <BR>.386p <BR>.model flat, stdcall <BR>option casemap:none <BR><BR>include \masm32\include\windows.inc <BR>include \masm32\include\user32.inc <BR>includelib \masm32\lib\user32.lib <BR><BR>.code <BR><BR>start: <BR>INVOKE MessageBoxA,0,0,0,MB_ICONINFORMATION or MB_OK <BR>ret <BR>end start <BR><BR>以上代码只显示一个MessageBox框,编译后得到二进制代码如下: <BR>unsigned char writeline[18]={ <BR>0x6a,0x40,0x6a,0x0,0x6a,0x0,0x6a,0x0,0xe8,0x01,0x0,0x0,0x0,0xe9,0x0,0x0,0x0,0x0 <BR>}; <BR><BR>好,现在让我们看看该把这些代码写到那。现在用Tdump.exe显示一个PE格式得可执行文件信息,可以发现如下描述: <BR>Object table: <BR># Name VirtSize RVA PhysSize Phys off Flags <BR>-- -------- -------- -------- -------- -------- -------- <BR>01 .text 0000CCC0 00001000 0000CE00 00000600 60000020 [CER] <BR>02 .data 00004628 0000E000 00002C00 0000D400 C0000040 [IRW] <BR>03 .rsrc 000003C8 00013000 00000400 00010000 40000040 [IR] <BR><BR>Key to section flags: <BR>C - contains code <BR>E - executable <BR>I - contains initialized data <BR>R - readable <BR>W - writeable <BR><BR>上面描述此文件中存在3个段及每个段得信息,实际上我们的代码可以写入任何一个段,这里我选择“.text”段。 <BR><BR>用如下代码得到一个PE格式可执行文件的头信息: <BR><BR>//writePE.cpp <BR><BR>#include &lt;windows.h&gt; <BR>#include &lt;stdio.h&gt; <BR>#include &lt;io.h&gt; <BR>#include &lt;fcntl.h&gt; <BR>#include &lt;time.h&gt; <BR>#include &lt;SYS\STAT.H&gt; <BR><BR>unsigned char writeline[18]={ <BR>0x6a,0x40,0x6a,0x0,0x6a,0x0,0x6a,0x0,0xe8,0x01,0x0,0x0,0x0,0xe9,0x0,0x0,0x0,0x0 <BR>}; <BR><BR>DWORD space; <BR>DWORD entryaddress; <BR>DWORD entrywrite; <BR>DWORD progRAV; <BR>DWORD oldentryaddress; <BR>DWORD newentryaddress; <BR>DWORD codeoffset; <BR>DWORD peaddress; <BR>DWORD flagaddress; <BR>DWORD flags; <BR><BR>DWORD virtsize; <BR>DWORD physaddress; <BR>DWORD physsize; <BR>DWORD MessageBoxAadaddress; <BR><BR>int main(int argc,char * * argv) <BR>{ <BR>HANDLE hFile, hMapping; <BR>void *basepointer; <BR>FILETIME * Createtime; <BR>FILETIME * Accesstime; <BR>FILETIME * Writetime; <BR>Createtime = new FILETIME; <BR>Accesstime = new FILETIME; <BR>Writetime = new FILETIME; <BR><BR>if ((hFile = CreateFile(argv[1], GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, 0)) == INVALID_HANDLE_VALUE)//打开要修改的文件 <BR>{ <BR>puts("(could not open)"); <BR>return EXIT_FAILURE; <BR>} <BR>if(!GetFileTime(hFile,Createtime,Accesstime,Writetime)) <BR>{ <BR>printf("\nerror getfiletime: %d\n",GetLastError()); <BR>} <BR>//得到要修改文件的创建、修改等时间 <BR>if (!(hMapping = CreateFileMapping(hFile, 0, PAGE_READONLY | SEC_COMMIT, 0, 0, 0))) <BR>{ <BR>puts("(mapping failed)"); <BR>CloseHandle(hFile); <BR>return EXIT_FAILURE; <BR>} <BR>if (!(basepointer = MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, 0))) <BR>{ <BR>puts("(view failed)"); <BR>CloseHandle(hMapping); <BR>CloseHandle(hFile); <BR>return EXIT_FAILURE; <BR>} <BR>//把文件头映象存入baseointer <BR>CloseHandle(hMapping); <BR>CloseHandle(hFile); <BR>map_exe(basepointer);//得到相关地址 <BR>UnmapViewOfFile(basepointer); <BR>printaddress(); <BR>printf("\n\n"); <BR>if(space&lt;50) <BR>{ <BR>printf("\n空隙太小,数据不能写入.\n"); <BR>} <BR>else <BR>{ <BR>writefile();//写文件 <BR>} <BR><BR>if ((hFile = CreateFile(argv[1], GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, 0)) == INVALID_HANDLE_VALUE) <BR>{ <BR>puts("(could not open)"); <BR>return EXIT_FAILURE; <BR>} <BR><BR>if(!SetFileTime(hFile,Createtime,Accesstime,Writetime)) <BR>{ <BR>printf("error settime : %d\n",GetLastError()); <BR>} <BR>//恢复修改后文件的建立时间等 <BR>delete Createtime; <BR>delete Accesstime; <BR>delete Writetime; <BR>CloseHandle(hFile); <BR>return 0; <BR>} <BR><BR>void map_exe(const void *base) <BR>{ <BR>IMAGE_DOS_HEADER * dos_head; <BR>dos_head =(IMAGE_DOS_HEADER *)base; <BR>#include &lt;pshpack1.h&gt; <BR>typedef struct PE_HEADER_MAP <BR>{ <BR>DWORD signature; <BR>IMAGE_FILE_HEADER _head; <BR>IMAGE_OPTIONAL_HEADER opt_head; <BR>IMAGE_SECTION_HEADER section_header[]; <BR>} peHeader; <BR>#include &lt;poppack.h&gt; <BR><BR>if (dos_head-&gt;e_magic != IMAGE_DOS_SIGNATURE) <BR>{ <BR>puts("unknown type of file"); <BR>return; <BR>} <BR><BR>peHeader * header; <BR>header = (peHeader *)((char *)dos_head + dos_head-&gt;e_lfanew);//得到PE文件头 <BR>if (IsBadReadPtr(header, sizeof(*header)) <BR>{ <BR>puts("(no PE header, probably DOS executable)"); <BR>return; <BR>} <BR><BR>DWORD mods; <BR>char tmpstr[4]={0}; <BR>DWORD tmpaddress; <BR>DWORD tmpaddress1; <BR><BR>if(strstr((const char *)header-&gt;section_header[0].Name,".text")!=NULL) <BR>{ <BR>virtsize=header-&gt;section_header[0].Misc.VirtualSize; <BR>//此段的真实长度 <BR>physaddress=header-&gt;section_header[0].PointerToRawData; <BR>//此段的物理偏移 <BR>physsize=header-&gt;section_header[0].SizeOfRawData; <BR>//此段的物理长度 <BR>peaddress=dos_head-&gt;e_lfanew; <BR>//得到PE文件头的开始偏移 <BR><BR>peHeader peH; <BR>tmpaddress=(unsigned long )&peH; <BR>//得到结构的偏移 <BR>tmpaddress1=(unsigned long )&(peH.section_header[0].Characteristics); <BR>//得到变量的偏移 <BR>flagaddress=tmpaddress1-tmpaddress+2; <BR>//得到属性的相对偏移 <BR>flags=0x8000; <BR>//一般情况下,“.text”段是不可读写的,如果我们要把数据写入这个段需要改变其属性,实际上这个程序并没有把数据写入“.text”段,所以并不需要更改,但如果你实现复杂的功能,肯定需要数据,肯定需要更改这个值, <BR><BR>space=physsize-virtsize; <BR>//得到代码段的可用空间,用以判断可不可以写入我们的代码 <BR>//用此段的物理长度减去此段的真实长度就可以得到 <BR>progRAV=header-&gt;opt_head.ImageBase; <BR>//得到程序的装载地址,一般为400000 <BR>codeoffset=header-&gt;opt_head.BaseOfCode-physaddress; <BR>//得到代码偏移,用代码段起始RVA减去此段的物理偏移 <BR>//应为程序的入口计算公式是一个相对的偏移地址,计算公式为: <BR>//代码的写入地址+codeoffset <BR><BR>entrywrite=header-&gt;section_header[0].PointerToRawData+header-&gt;section_header[0].Misc.VirtualSize; <BR>//代码写入的物理偏移 <BR>mods=entrywrite%16; <BR>//对齐边界 <BR>if(mods!=0) <BR>{ <BR>entrywrite+=(16-mods); <BR>} <BR>oldentryaddress=header-&gt;opt_head.AddressOfEntryPoint; <BR>//保存旧的程序入口地址 <BR>newentryaddress=entrywrite+codeoffset; <BR>//计算新的程序入口地址 <BR>return; <BR>} <BR><BR>void printaddress() <BR>{ <BR>HINSTANCE gLibMsg=NULL; <BR>DWORD funaddress; <BR>gLibMsg=LoadLibrary("user32.dll"); <BR>funaddress=(DWORD)GetProcAddress(gLibMsg,"MessageBoxA"); <BR>MessageBoxAadaddress=funaddress; <BR>gLibAMsg=LoadLibrary("kernel32.dll"); <BR>//得到MessageBox在内存中的地址,以便我们使用 <BR>} <BR><BR>void writefile() <BR>{ <BR>int ret; <BR>long retf; <BR>DWORD address; <BR>int tmp; <BR>unsigned char waddress[4]={0}; <BR><BR>ret=_open(filename,_O_RDWR | _O_CREAT | _O_BINARY,_S_IREAD | _S_IWRITE); <BR>if(!ret) <BR>{ <BR>printf("error open\n"); <BR>return; <BR>} <BR><BR>retf=_lseek(ret,(long)peaddress+40,SEEK_SET); <BR>//程序的入口地址在PE文件头开始的40处 <BR>if(retf==-1) <BR>{ <BR>printf("error seek\n"); <BR>return; <BR>} <BR>address=newentryaddress; <BR>tmp=address&gt;&gt;24; <BR>waddress[3]=tmp; <BR>tmp=address&lt;&lt;8; <BR>tmp=tmp&gt;&gt;24; <BR>waddress[2]=tmp; <BR>tmp=address&lt;&lt;16; <BR>tmp=tmp&gt;&gt;24; <BR>waddress[1]=tmp; <BR>tmp=address&lt;&lt;24; <BR>tmp=tmp&gt;&gt;24; <BR>waddress[0]=tmp; <BR>retf=_write(ret,waddress,4); <BR>//把新的入口地址写入文件 <BR>if(retf==-1) <BR>{ <BR>printf("error write: %d\n",GetLastError()); <BR>return; <BR>} <BR><BR>retf=_lseek(ret,(long)entrywrite,SEEK_SET); <BR>if(retf==-1) <BR>{ <BR>printf("error seek\n"); <BR>return; <BR>} <BR>retf=_write(ret,writeline,18); <BR>if(retf==-1) <BR>{ <BR>printf("error write: %d\n",GetLastError()); <BR>return; <BR>} <BR>//把writeline写入我们计算出的空间 <BR><BR>retf=_lseek(ret,(long)entrywrite+9,SEEK_SET); <BR>//更改MessageBox函数地址,它的二进制代码在writeline[10]处 <BR>if(retf==-1) <BR>{ <BR>printf("error seek\n"); <BR>return; <BR>} <BR><BR>address=MessageBoxAadaddress-(progRAV+newentryaddress+9+4); <BR>//重新计算MessageBox函数的地址,MessageBox函数的原地址减去程序的装载地址加上新的入口地址加9(它的二进制代码相对偏移)加上4(地址长度) <BR>tmp=address&gt;&gt;24; <BR>waddress[3]=tmp; <BR>tmp=address&lt;&lt;8; <BR>tmp=tmp&gt;&gt;24; <BR>waddress[2]=tmp; <BR>tmp=address&lt;&lt;16; <BR>tmp=tmp&gt;&gt;24; <BR>waddress[1]=tmp; <BR>tmp=address&lt;&lt;24; <BR>tmp=tmp&gt;&gt;24; <BR>waddress[0]=tmp; <BR>retf=_write(ret,waddress,4); <BR>//写入重新计算的MessageBox地址 <BR>if(retf==-1) <BR>{ <BR>printf("error write: %d\n",GetLastError()); <BR>return; <BR>} <BR><BR>retf=_lseek(ret,(long)entrywrite+14,SEEK_SET); <BR>//更改返回地址,用jpm返回原程序入口地址,其它的二进制代码在writeline[15]处 <BR>if(retf==-1) <BR>{ <BR>printf("error seek\n"); <BR>return; <BR>} <BR><BR>address=0-(newentryaddress-oldentryaddress+4+15); <BR>//返回地址计算的方法是新的入口地址减去老的入口地址加4(地址长度)加15(二进制代码相对偏移)后取反 <BR>tmp=address&gt;&gt;24; <BR>waddress[3]=tmp; <BR>tmp=address&lt;&lt;8; <BR>tmp=tmp&gt;&gt;24; <BR>waddress[2]=tmp; <BR>tmp=address&lt;&lt;16; <BR>tmp=tmp&gt;&gt;24; <BR>waddress[1]=tmp; <BR>tmp=address&lt;&lt;24; <BR>tmp=tmp&gt;&gt;24; <BR>waddress[0]=tmp; <BR>retf=_write(ret,waddress,4); <BR>//写入返回地址 <BR>if(retf==-1) <BR>{ <BR>printf("error write: %d\n",GetLastError()); <BR>return; <BR>} <BR><BR>_close(ret); <BR>printf("\nall done...\n"); <BR>return; <BR>} <BR><BR>//end <BR>由于在PE格式的文件中,所有的地址都使用RVA地址,所以一些函数调用和返回地址都要经过计算才可以得到,以上是我在实践中的心得,如果你有更好的办法,真心的希望你能告诉我。 <BR><BR>如果存在错误,请告诉我,以免误导看这篇文章的人。 <BR>写的较乱,请原谅。 <BR><BR>ilsy@netguard.com.cn <BR><BR> <BR>
<br>
<a href="javascript:history.go(-1)">返回上页</a><br><a href=http://www.copathway.com/cndevforum/>访问论坛</a></p></blockquote>
<hr size=1>
<blockquote><p>
回复者:Samuel 回复日期:2002-10-30 08:34:24
<br>内容:好文章!<BR>建议收录精华区!<BR>允许我转载吗?

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -