PE文件格式”1.9版 完整译文(二)

总结一下:如果你想从“knurr”DLL中查找输入函数“foo”的信息,第一步你先找到数据目录中的IMAGE_DIRECTORY_ENTRY_IMPORT(输入目录项)项,得到一个RVA,再在原始节数据中找到那个地址,现在你就得到一个IMAGE_IMPORT_DESCRIPTOR(输入描述结构)数组了。通过查看根据它们的“名称”被指向的字符串,得到和“knurr”DLL有关的这个数组的成员(即一个输入描述结构)。在你找到正确的IMAGE_IMPORT_DESCRIPTOR(输入描述结构)后,顺着它的“OriginalFirstThunk”(原始第一个换长)得到被指向的IMAGE_THUNK_DATA(换长数据)数组;再通过查询RVA找到“foo”函数。

好了,为什么我们有“两”列指向IMAGE_IMPORT_BY_NAME(输入名字)的指针呢?这是因为在运行时,应用程序不需要输入函数的名字,只需要地址。在这里输入地址表又出现了。加载器将从相关的DLL文件的输出目录中查找每一个输入符号,并用DLL文件入口点的线性地址替换“FirstThunk”(第一个换长)列表中的IMAGE_THUNK_DATA(换长数据)元素(到现在之前它还是指向IMAGE_IMPORT_BY_NAME(输入名字)的)。

请记住带有象“__imp__symbol”标签的地址列表;被数据目录IMAGE_DIRECTORY_ENTRY_IAT(输入地址表目录项)所指向的输入地址表,就是被“FirstThunk”(第一个换长)所指向的列表。[在从好几个DLL文件输入的情况下,输入地址表是包含所有DLL文件的“FirstThunk”(第一个换长)数组。目录项IMAGE_DIRECTORY_ENTRY_IAT(输入地址表目录项)可能会丢失,但输入(函数)仍能工作良好。]

“OriginalFirstThunk”(原始第一个换长)数组保持不变,因此你总能通过“OriginalFirstThunk”(原始第一个换长)列表查找原始的输入名字列表。

现在输入已经被用正确的线性地址修正,如下所示: 原始第一个换长 第一个换长
| |
| |
| |
V V
0--> 函数1 0--> 输出函数1
1--> 函数2 1--> 输出函数2
2--> 函数3 2--> 输出函数3
3--> foo 3--> 输出函数foo
4--> mumpitz 4--> 输出函数mumpitz
5--> knuff 5--> 输出函数knuff
6-->0 0<--6

这是简单情况下的基本结构。现在我们将要学习输入目录中的需细讲的东西。

第一,当数组中IMAGE_THUNK_DATA元(换长数据)素的IMAGE_ORDINAL_FLAG(序数标志)位(也是:MSB,参见注释)被置1时,表示列表中没有符号的名字信息,符号只以序数输入。你可通过查看IMAGE_THUNK_DATA(换长数据)中的低地址word来得到序数。

通过序数输入是不鼓励的,通过名字输入会更安全,因为如果输出DLL文件不是预期的版本时输出序数可能会改变。

第二,有所谓的“绑定输入”。

请思考一下加载器的工作:当它想执行的一个二进制文件需要一个DLL中的函数时,加载器会载入该DLL,找到它的输出目录,查找函数的RVA并计算函数的入口点。然后用这样找到的地址修正“FirstThunk”(第一个换长)列表。

假设程序员很聪明,给DLL文件提供的唯一优先载入地址不会发生冲突,那么我们就能认为函数的入口点将总是相同的。它们在链接时能被算出并被补进“FirstThunk”(第一个换长)列表中,这就是“绑定输入”所发生的一切。(“绑定”工具就是干这个的,它是Win32SDK的一部分。)

当然,你得慎重:用户的DLL可能是不同的版本,或者DLL必须重定位,这些都会使先前修正的“FirstThunk”(第一个换长)列表不再有效;此时,加载器仍能查寻“OriginalFirstThunk”(原始第一个换长)列表,找出输入符号并重新补正“FirstThunk”(第一个换长)列表。加载器知道这是必须的,当:1)输出DLL文件的版本不符,或2)输出DLL文件需要重定位时。

确定有没有重定位表对加载器来说不是问题,但该怎样找出版本的不同呢?这时IMAGE_IMPORT_DESCRIPTOR(输入描述结构)的“时间戳”就派上用场了。如果它是0,表明输入列表没有被绑定,加载器总是要修复入口点。否则的话,输入被绑定,“时间戳”必须要和“文件头”中的输出DLL文件的“时间戳”相符;如果不符的话,加载器就认为该二进制文件被绑到一个“错误”的DLL文件上并重新补正输入列表。

这里有另外一个有关输入列表中的“中转”的怪事。一个DLL文件能输出一个不定义在本DLL文件中却需从另一个DLL文件中输入的符号;这样的符号据说就是被中转的(参见上面的输出目录描述)。

现在,很明显的,你不能通过查看那个实际上并不包含入口点信息的DLL文件的时间戳来确定一个符号的入口点是否有效。因此,出于安全的原因,中转符号的入口点必须总是被修正。在二进制文件的输入列表中,中转符号的输入必须被找出,以便加载器能补正它们。

这一点可通过“ForwarderChain”(中转链)来做到。它是一个指向换长列表中的索引值;被索引位置的输入就是一个中转输出,并且此位置的“FirstThunk”(第一个换长)列表中的内容就是“下一个”中转输入的索引值,以此类推,直到索引值为-1,就表明已没有其他的中转了。如果根本就没有中转,那么“ForwarderChain”(中转链)的值本身就为-1。

这就是所谓的“老式”绑定。

至此,我们应该总结一下我们目前已掌握的情况:-)

OK,我将认为你已找到了IMAGE_DIRECTORY_ENTRY_IMPORT(输入目录项)并且已根据它找到了它的输入目录,位于某个节中。现在你已处于IMAGE_IMPORT_DESCRIPTOR(输入描述结构)数组的开头了,此类数组的最后一个将以全0字节填充。

要读懂一个IMAGE_IMPORT_DESCRIPTOR(输入描述结构),你得先查看它的“名字”项,根据它的RVA,你就能找到输出DLL文件的名字。下一步你得确定输入是否是绑定的;如果输入是绑定的,“时间戳”就会是非“0”的。如果它们是绑定的,现在就是你通过比较“时间戳”来检查DLL文件的版本是否相符的好机会了。

现在你根据“OriginalFirstThunk”(原始第一个换长)的RVA来到了IMAGE_THUNK_DATA(换长数据)数组;过完这些数组(它是0结尾的),它的每个成员都将是一个IMAGE_IMPORT_BY_NAME(输入名字)的RVA(除非它的高位被置1,此时你找不到名字只有序数)。根据那个RVA,并跳过2字节(即‘提示’),现在你就得到一个以0结尾的字符串,这就是输入函数的名字。

在绑定输入时要找到提供的入口点,先根据“FirstThunk”(第一个换长)平行的来到“OriginalFirstThunk”(原始第一个换长)数组;数组成员就是入口点的线性地址(暂时不考虑中转的话题)。

还有一件我到现在都没有提及的事情:明显地有些链接器在构建输入目录时会产生bug(我就发现一个还在被一个BorlandC链接器使用的bug)。这些链接器把IMAGE_IMPORT_DESCRIPTOR(输入描述结构)中的“OriginalFirstThunk”(原始第一个换长)设为0,并只建立“FirstThunk”(第一个换长)。很明显的,这样的输入目录不能被绑定(否则重修输入的必须信息就会丢失----你根本找不到函数名字)。在这种情况下,你得根据“FirstThunk”(第一个换长)数组来取得输入符号名字,你将永远得不到预先补正的入口地址。我已发现一个TIS文件(参考书目[6]),讲述一个在某种程度上和此bug兼容的输入目录,因此那个文件可能就是该bug的起源。

TIS文件规定:

IMPORTFLAGS(输入标志)

TIME/DATESTAMP(时间/日期戳)

MAJORVERSION-MINORVERSION(主版本号-小版本号)

NAMERVA(名字的RVA)

IMPORTLOOKUPTABLERVA(输入查询表的RVA)

IMPORTADDRESSTABLERVA(输入地址表的RVA)

而别处使用的对应结构是:

OriginalFirstThunk(原始第一个换长)

TimeDateStamp(时间日期戳)

ForwarderChain(中转链)

Name(名字)

FirstThunk(第一个换长)

最后一个关于输入目录的需要细讲的就是所谓的“新式”绑定(在参考书目[3]中讲述),它也可以由“绑定”工具来处理。当使用这种方式时,“时间日期戳”的所有位被置为1,并且没有中转链;此时所有输入符号的地址都将被补正,而不管它们是不是中转的。尽管如此,你还是需要知道DLL的版本,并且你还是需要将序数符号从中转符号中区分开来。为了达到这个目的,IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT(绑定输入目录项)目录被创建了。就我所见,它将不被放在节中,而是被放在头中,处于节头之后第一节之前。(咳,这不是我的发明,我只是讲述它而已!)

这个目录告诉你,每一个已使用的DLL文件的中转输出是从哪些别的DLL文件中来的。

结构是IMAGE_BOUND_IMPORT_DESCRIPTOR(绑定输入描述结构)形式的,包括(按这个顺序):

一个32位数字,“时间戳”。

一个16位数字,“OffsetModuleName(模块名字偏移量)”,是从目录开头到以0结尾的DLL文件名的偏移量;

一个16位数字,“NumberOfModuleForwarderRefs(模块中转参考的数字)”,给出这个DLL文件为它的中转使用的DLL文件数。

紧随这个结构之后你会发现“NumberOfModuleForwarderRefs(模块中转参考的数字)”结构,告诉你这个DLL文件的中转所来自的DLL文件的名称和版本。这些结构就是“IMAGE_BOUND_FORWARDER_REF(绑定中转参考)”结构的:

一个32位的数字“时间日期戳”(TimeDateStamp);

一个16位的数字“模块名称偏移量”(OffsetModuleName),它就是从目录开头到中转来自的那个DLL文件的0结尾的名字处的偏移量;

一个16位的未使用单元。

跟在“IMAGE_BOUND_FORWARDER_REF(绑定中转参考)”后的是下一个“IMAGE_BOUND_IMPORT_DESCRIPTOR(绑定输入描述结构)”,以此类推;列表最终以一个全部为0位的IMAGE_BOUND_IMPORT_DESCRIPTOR(绑定输入描述结构)结束。

我对由此(描述)造成的不便表示歉意,但这就是它看起来的样子:-)

现在,如果你有一个新的绑定输入目录,你得载入所有的DLL文件,并使用目录指针IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT(绑定输入目录项)找到IMAGE_BOUND_IMPORT_DESCRIPTOR(绑定输入描述结构),扫描整个IMAGE_BOUND_IMPORT_DESCRIPTOR(绑定输入描述结构),并检查被载入的DLL文件的“时间日期戳”和这个目录中提供的是否相符。如果不符,就将输入目录中“第一换长”(FirstThunk)中的错误全部修改过来。

8.资源(resources)

-------------------

资源,比如对话框、菜单、图标等等,都存储在IMAGE_DIRECTORY_ENTRY_RESOURCE(“资源目录项”)指向的数据目录中。它们处于一个至少“IMAGE_SCN_CNT_INITIALIZED_DATA(已初始化数据内容节)”和“IMAGE_SCN_MEM_READ(内存可读节)”标志位都被置为1的节中。

资源的基础是“资源目录”(IMAGE_RESOURCE_DIRECTORY);它包含好几个“资源目录项”(IMAGE_RESOURCE_DIRECTORY_ENTRY),其中的每一项反过来又可能指向一个“资源目录”。按照这种方式,你就得到一个以“资源目录项”为树叶的“资源目录”树;它们的树叶指向实际的资源数据。

在实际使用中,情况会稍微简单些。一般你不会遇到不可能理清的特别复杂的树的。

通常,它的层次结构是这样的:一个目录作为根。它指向很多目录,每种资源类型都有一个。这些目录又指向子目录,每个子目录都有一个名字或者ID号并指向这个资源所提供的各种语言的目录;每种语言你都能找到一个资源项,资源项最终指向(具体的)数据。(注意:多语言资源不能在Win95上运行。即使程序有好几种语言,Win95也总是使用相同的资源----我没有查出是哪一种,但我猜测肯定是它最先碰到的那种。多语言资源在NT系统上可以运行。)

没有指针的树大致象这样:

(根)
|
+----------------+------------------+
| | |
菜单 对话框 图标
| | |
+-----+-----+ +-+----+ +-+----+----+
| | | | | | |
"main" "popup" 0x10 "maindlg" 0x1000x1100x120
| | | | | | |
+---+-+ | | | | | |
| | default english default def. def. def.
germanenglish

一个“资源目录项”(IMAGE_RESOURCE_DIRECTORY)包含:

32位未使用标志,叫做“特征”(Characteristics);

32位“时间日期戳”(同样按常用的time_t表示法),告诉你资源被创建的时间(如果此项被设置的话);

16位“主版本号”(MajorVersion)和16位“小版本号”(MinorVersion),以允许你据此维护资源的几个版本;

16位“已命名项目数”(NumberOfNamedEntries)和另一个16位的“ID项目数”(NumberOfIdEntries)。

紧随此结构后的是“已命名项目数”+“ID项目数”两结构体,它们都是“资源目录项”格式,都以名字开头。它们可能指向下一个“资源目录”或者指向实际的资源数据。

一个“资源目录项”由下面组成:

32位单元提供你它所描述的资源的ID或者是目录;

32位的到数据的偏移量或者是到下一个子目录的偏移量。

ID的含义取决于树中的层次;ID可能是一个数字(如果最高位为0)也可能是一个名字(如果最高位为1)。如果是一个名字,它的低31位就是从资源节原始数据的开始到这个名字(名字有16位长并由unicode的宽字符而不是0结尾符作为结束)的偏移量。

如果你位于根目录之中,且如果ID是一个数字的话,那么它指的就是下面的一种资源类型:

1:光标

2:位图

3:图标

4:菜单

5:对话框

6:字串表

7:字体目录

8:字体

9:快捷键

10:未格式化资源数据

11:信息表

12:组光标

14:组图标

16:版本信息

任何其它数字都是用户自定义的。任何有类型名的资源类型也是用户自定义的。

如果你处于(树的)下一层当中,此时ID一定是一个数字,且就是资源的一个特例的语言ID号;例如,你可以(同时)拥有澳大利亚英语、加拿大法语和瑞士德语等本地化形式的对话框,并且它们分享同一个资源ID。系统会根据线程的地点来选择要使用的对话框,反过来地点又反映了用户的“区域设置”。(如果资源找不到线程地点,系统将先使用一个中性的子语言资源作为地点,比如它将寻找标准法语而不是用户所拥有的加拿大法语;如果它还是找不到,就使用最小语言ID号的那个实例。必须注意,所有这些只工作于NT系统之上的。)

为便于辨认语言ID,使用宏PRIMARYLANGID()(意为“主语言ID”)和SUBLANGID()(意为“子语言ID”)将它分开为主语言ID和子语言ID,分别使用它的0-9位和10-15位。这些值定义在“winresrc.h”文件中。

语言资源只支持快捷键、对话框、菜单、资源数据或字符串等;其它资源类型必须为LANG_NEUTRAL/SUBLANG_NEUTRAL(中性语言/中性子语言)。

要确定资源目录的下一层是不是另一个目录,你可查看它的偏移量的最高位。如果它是1,剩下的31位就是从资源节原始数据的开始到下一层目录的偏移量,还是按“资源目录”后接“资源目录项”的格式。如果高位为0,它就是从资源节原始数据的开始到资源的原始数据描述,即一个资源数据项的偏移量。资源的原始数据描述包含32位的“OffsetToData”(到数据的偏移量)(指的是到原始数据的偏移量,从资源节原始数据的开头算起),32位的数据的“Size”(大小),32位的“CodePage”(代码页)和一个未使用的32位单元。

(不鼓励使用代码页,你应该使用“语言”的特性来支持多地域。)

原始数据格式依赖于资源类型;详细的介绍可在微软的SDK文档中找到。注意:除了用户自定义资源,资源中的任何字符串总是按UNICODE格式,明显的,用户自定义的资源按的是开发者选定的格式。

9.重定位(relocations)

-----------------------

我将要描述的最后一个数据目录是基址重定位目录。它是由可选头数据目录中的IMAGE_DIRECTORY_ENTRY_BASERELOC(基址重定位目录项)项来指向的。典型的,它包含在自己的节中,名字象“.reloc”这样,并且IMAGE_SCN_CNT_INITIALIZED_DATA(已初始化数据内容节)、IMAGE_SCN_MEM_DISCARDABLE(内存可丢弃节)和IMAGE_SCN_MEM_READ(内存可读节)等标志位被置1。

如果映象文件不能被加载到可选头中提到的优先载入地址“ImageBase”(映象基址)时,重定位数据对加载器来说就是必须的。此时,链接器所提供的固定地址就不再有效,并且加载器将不得不对静态变量、字符串文字等使用的绝对地址进行修正。

所谓重定位目录就是一些连续的块,每一块都包含4K映象文件的重定位信息。块由一个“IMAGE_BASE_RELOCATION(基址重定位)”结构体开始,这个结构体包含一个32位的“VirtualAddress(虚拟地址)”项和一个32位的“SizeOfBlock(块大小)”项。跟在它们后面的就是块的实际重定位数据,每一条都是16位的。

“VirtualAddress(虚拟地址)”就是重定位所在块需要应用的基本的RVA;“SizeOfBlock(块大小)”就是整个块的字节大小;跟在后面的重定位的数目是:('SizeOfBlock'-sizeof(IMAGE_BASE_RELOCATION))/2个。当你碰到一个“VirtualAddress(虚拟地址)”值为0的“IMAGE_BASE_RELOCATION(基址重定位)”结构体时,重定位信息就结束了。

每一个16位的重定位信息由低12位的重定位位置和高4位的重定位类型组成。要得到重定位的RVA,你需要用这个12位的位置加上“IMAGE_BASE_RELOCATION(基址重定位)”中的“VirtualAddress(虚拟地址)”。类型是下面之一:

IMAGE_REL_BASED_ABSOLUTE(0)

这种不需操作;用于将块按32位边界对齐。位置应该为0。

IMAGE_REL_BASED_HIGH(1)

重定位的高16位必须被用于被偏移量所指向的那个16位的WORD单元,此WORD是一个32位的DWORD的高位WORD。

IMAGE_REL_BASED_LOW(2)

重定位的低16位必须被用于被偏移量所指向的那个16位的WORD单元,此WORD是一个32位的DWORD的低位WORD。

IMAGE_REL_BASED_HIGHLOW(3)

重定位的全部32位必须应用于上面所说的全部32位。这种(和不需操作的第“0”种)是我在二进制文件种实际发现的仅有的重定位类型。

IMAGE_REL_BASED_HIGHADJ(4)

这是一种复杂的。请自己参阅(参考文献[6]),并努力弄懂它的意思:“高调整。这种修正要求一个全32位值。高16位定位于偏移量处,低16位定位在下一个数组元素(此数组元素包括在大小的域中)的偏移量处。它们两个需要被连成一个有符号的变量。加上32位的增量。然后加上0x8000并将有符号变量的高16位存储在偏移量处的16位域中。”

IMAGE_REL_BASED_MIPS_JMPADDR(5)

不清楚

IMAGE_REL_BASED_SECTION(6)

不清楚

IMAGE_REL_BASED_REL32(7)

不清楚

举一个例子,如果你发现重定位信息是

0x00004000 (32位,开始的RVA)
0x00000010 (32位,块的大小)
0x3012 (16位的重定位数据)
0x3080 (16位的重定位数据)
0x30f6 (16位的重定位数据)
0x0000 (16位的重定位数据)
0x00000000 (下一块的RVA)
0xff341234

你知道第一块描述的重定位开始于RVA0x4000处,有16字节长。因为头用掉了8字节,并且一个重定位要用2字节,所以块中计有(16-8)/2=4个重定位。第一个重定位被应用于0x4012处的DWORD,第二个于0x4080处的DWORD,第三个于0x40f6处的DWORD。最后一个不需操作。

下一块的RVA是0,列表结束。

好,你怎么处理一个重定位呢?

你能知道映象文件“被”重定位到可选头“ImageBase(映象基址)”的优先载入地址;你也能知道你真正载入的地址。如果它们相同,你什么也不用做。如果它们不同,你需计算出实际基址-优先基址的差并加上重定位位置的值(有符号,可能为负值),此值你可通过上面讲述的方法找到。

九、致谢(Acknowledgments)

---------------------------

感谢DavidBinette的调试和校读。(剩下的错误全部都是我的。)

也感谢wotsit.org网站让我将此文放到他们的网站上。

十、版权(Copyright)

---------------------

本文的版权属于B.Luevelsmeyer,1999年。它是免费的,你可以任意的使用,但后果自负。它含有错误并不完整,特此警告。

十一、Bug报告(Bugreports)

----------------------------

Bug报告(或其他建议)请发送至:bernd.luevelsmeyer@iplan.heitec.net

十二、版本(Versions)

----------------------

你可在文件的顶部找到当前的版本号。

1998-04-06

第一次公开发表

1998-07-29

将映象文件版本和子系统版本中错误的“byte”改为“word”

更正“栈只限于1MB”的错误(实际上没有上限)

更正一些输入错误

1999-03-15

更正输出目录的描述,原来非常不全

调整输入目录的描述,原来讲的不清

更正输入错误并为其它节改了一些词句

十三、参考文献(Literature)

----------------------------

[1]

"PeeringInsidethePE:ATouroftheWin32PortableExecutableFile

Format"(M.Pietrek),in:MicrosoftSystemsJournal3/1994

[2]

"WhytoUse_declspec(dllimport)&_declspec(dllexport)InCode",MS

KnowledgeBaseQ132044

[3]《Windows问与答》

"WindowsQ&A"(M.Pietrek),in:MicrosoftSystemsJournal8/1995

[4]《编写多语言资源》

"WritingMultiple-LanguageResources",MSKnowledgeBaseQ89866

[5]

"ThePortableExecutableFileFormatfromToptoBottom"(RandyKath),

in:MicrosoftDeveloperNetwork

[6]《Windows下TIS格式规范1.0版》

ToolInterfaceStandard(TIS)FormatsSpecificationforWindowsVersion

1.0(IntelOrderNumber241597,IntelCorporation1993)

附录(Appendix:helloworld):

-------------------------------

在这个附录中我将给大家展示一下怎样手工建立一个程序。因为我不会DECAlpha的,本例将使用Intel汇编语言。

本程序相当于

#include<stdio.h>
intmain(void)
{
puts(hello,world);
return0;
}

首先,我使用Win32函数来翻译它以取代C运行时库:

#defineSTD_OUTPUT_HANDLE-11UL
#definehello"hello,world "
__declspec(dllimport)unsignedlong__stdcall
GetStdHandle(unsignedlonghdl);
__declspec(dllimport)unsignedlong__stdcall
WriteConsoleA(unsignedlonghConsoleOutput,
constvoid*buffer,
unsignedlongchrs,
unsignedlong*written,
unsignedlongunused
);
staticunsignedlongwritten;
voidstartup(void)
{
WriteConsoleA(GetStdHandle(STD_OUTPUT_HANDLE),hello,sizeof(hello)-1,&written,0);
return;
}

现在我将笨拙的将它汇编出来:

startup:
;WriteConsole()的参数,反向的
6A00 push 0x00000000
68???????? push offset_written
6A0D push 0x0000000d
68???????? push offsethello
;GetStdHandle()的参数
6AF5 push 0xfffffff5
2EFF15???????? call dwordptrcs:__imp__GetStdHandle@4
;结果是WriteConsole()的参数
50 push eax
2EFF15???????? call dwordptrcs:__imp__WriteConsoleA@20
C3 ret
hello:
68656C6C6F2C20776F726C640A "hello,world "
_written:
00000000

以上就是编译的部分。任何人都能做到这点。从现在起让我们扮演起链接器的角色,这会非常有趣:-)

我需要先找出函数WriteConsoleA()和GetStdHandle()。碰巧它们都在“kernel32.dll”中。(这是“输入库”部分。)

现在我开始做可执行文件。问号代表待定的值;它们将在以后被修正。

首先是DOS-根,开始于0x0,有0x40字节长:

00|4d5a0000000000000000000000000000
10|00000000000000000000000000000000
20|00000000000000000000000000000000
30|00000000000000000000000040000000

正如你所见到的,这不是真正的MS-DOS程序。它只是一个开始部分有“MZ”签名的头和紧跟在头后面的e_lfanew指针,没有任何代码。这是因为它并非打算运行于MS-DOS之上;它之所以在这里只是因为规范的需要。

然后是PE签名,开始于0x40,有0x4字节长:

50450000

现在到了文件头,开始于0x44,有0x14字节长:

Machine 4c01 ;i386
NumberOfSections 0200 ;代码段和数据段
TimeDateStamp 00000000;谁管它?
PointerToSymbolTable 00000000;未用
NumberOfSymbols 00000000;未用
SizeOfOptionalHeader e000 ;常量
Characteristics 0201 ;32位机器上的可执行文件

接着是可选头,开始于0x58,有0x60字节长:

Magic 0b01 ;常量
MajorLinkerVersion 00 ;我是0.0版:-)
MinorLinkerVersion 00 ;
SizeOfCode 20000000;32字节代码
SizeOfInitializedData ????????;待找出
SizeOfUninitializedData 00000000;我们没有BSS节
AddressOfEntryPoint ????????;待定
BaseOfCode ????????;待定
BaseOfData ????????;待定
ImageBase 00001000;1MB,随意选
SectionAlignment 20000000;32字节对齐
FileAlignment 20000000;32字节对齐
MajorOperatingSystemVersion 0400 ;NT4.0
MinorOperatingSystemVersion 0000 ;
MajorImageVersion 0000 ;0.0版
MinorImageVersion 0000 ;
MajorSubsystemVersion 0400 ;Win324.0
MinorSubsystemVersion 0000 ;
Win32VersionValue 00000000;未使用?
SizeOfImage ????????;待定
SizeOfHeaders ????????;待定
CheckSum 00000000;非驱动不用
Subsystem 0300 ;Win32控制台
DllCharacteristics 0000 ;未用(不是一个DLL)
SizeOfStackReserve 00001000;1MB栈
SizeOfStackCommit 00100000;开始时4KB
SizeOfHeapReserve 00001000;1MB堆
SizeOfHeapCommit 00100000;开始时4KB
LoaderFlags 00000000;未知
NumberOfRvaAndSizes 10000000;常量

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值