greatdong
驱动牛犊
驱动牛犊
  • 注册日期2004-11-06
  • 最后登录2008-12-18
  • 粉丝0
  • 关注0
  • 积分7分
  • 威望40点
  • 贡献值0点
  • 好评度10点
  • 原创分1分
  • 专家分0分
阅读:2134回复:1

An In-Depth Look into the Win32 Portable Executable File Format-1

楼主#
更多 发布于:2005-03-12 08:19

PE File Sections

PE 文件的 section 表示了某种代码或数据.代码自然就是代码,但数据却有许多种.除 read/write program 数据之外(如全局变量), 其它的还包括API import 表与 export 表, 资源和重定位信息. 每个 section 在内存中都有自己的属性, 包括是否包含代码, 是 read-only 还是 read/write 以及 section 的数据是否由所有进程共享.

一般来说, section 中的所有代码和数据在某种形式上都是逻辑相关的. 一般一个 PE 文件都有两个 section: 一个用于代码,一个用于数据. 通常, PE 文件中还要有另外一类的数据, 我将在第二部分介绍.

每个 section 都有一个名字. 起名是为了告知此 section 的作用. 例如, 名为 .rdata 说明是个 read-only 的 data section. Section 的名字就只是给人看的,对操作系统并不重要. 名为 FOOBAR 的 section 和叫 .text 的是一样合法的 Microsoft 常在section名前加一个点, 但并不是必需的. 多少年来, Borland 的 linker 一直使用像 CODE 和 DATA 这样的 section 名.

compilers 生成的一组标准的 section 也没什么可希奇的, 你也可以自己创建 sections 并为之命名, linker 会将其链接进 executable 中. 在 Visual C++ 中, 可以用 #pragma 语句让 compiler 向你命名的 section 插入代码或数据.例如, 语句:

#pragma data_seg( \"MY_DATA\" )

会让 Visual C++ 将生成的数据写入一个叫 MY_DATA 的 section 而不是默认的 .data section. 大多数 programs 只使用 compiler 生成的默认的 sections 就行了, 但也没准儿需要将代码或数据放入另外的section.

Sections 并非完全由 linker 生成,在 OBJ 文件里就有了, 通常是由 compiler 生成的. linker 的工作就是将 OBJ 文件和库文件中的所有所需的同类型 sections 合并成 PE 文件的相应的 section. 例如, 工程中的每个OBJ 文件可能至少都有一个包含代码的.text section. linker 从多个OBJ 文件中取出名为 .text 的 section 并将其合并进 PE 文件的一个单个的.text section. 类似地, 各 OBJ 中的所有名为 .data 的 section 也被合并进一个 .data section. .LIB文件的代码和数据一般也被包含进 executable 中, 但这个问题超出了本文的讨论范围.

Sections 有两类对齐,一是在 disk 文件里一是在 memory 中. PE 文件 header 中指定了这两个值, 这两个值可以不同. 每个 section 起始于对齐值的整数倍的偏移上. 例如, PE 文件中, 对齐值一般为 0x200. 因此每个 section 起始的文件偏移都是 0x200 的整倍数. 一旦映射入内存, sections 总是位于至少一个 page boundary 上也就是说, 当 PE section 映射入内存后,每个 section 的第一个字节对应于一个 memory page. 在 x86 CPU上, pages 为 4KB 对齐, 而在 IA-64 上, 则为 8KB 对齐. 以下代码为 Windows XP KERNEL32.DLL 的 .text 和 .data section 的 PEDUMP output.


Section Table
  01 .text     VirtSize: 00074658  VirtAddr:  00001000
    raw data offs:   00000400  raw data size: 00074800
???
  02 .data     VirtSize: 000028CA  VirtAddr:  00076000
    raw data offs:   00074C00  raw data size: 00002400

.text section 位于 PE 文件的文件偏移0x400, 在内存中则加载在以 KERNEL32 首地址为基址的 0x1000 字节偏移处. 类似地, .data section 位于文件偏移 0x74C00, 会被加载在 KERNEL32 的偏移 0x76000 字节处. 文件偏移可以与内存偏移相同. 这使得 executable 较大, 但在 Windows 9x 或 Windows 下可提高加载速度. 默认的 /OPT:WIN98 linker option (introduced in Visual Studio 6.0) 可创建此类 PE 文件. 在Visual Studio.NET 中, linker 是否使用 /OPT:NOWIN98 依赖于文件是否足够小. 一个有趣的linker feature 就是它能合并多个 sections. 两个 sections 相似, 属性相兼容, 则在 link 时可将其并入一个 section. 这是用 linker 的 /merge switch来完成的. 例如下面这个 linker option 将 .rdata 和 .text sections 结合到一个叫 .text 的 section 中:

/MERGE:.rdata=.text

合并 sections 的优点是节省空间, 包括磁盘上的和内存里的. 按最少算, 每个 section 在 memory 里占一页. 能将 executable 中的section 的数目从四个减到三个, 就可能少用一页内存. 当然这还依赖于两个 section 后面的未用空间加起来是否有一页. 将 .rdata 合并进 .text 中是可以的, 但不能将 .rsrc, .reloc 或 .pdata 合并进其他的 sections 里. 在Visual Studio.NET 以前可以将 .idata 合并进其它的 sections 里. 而在 Visual Studio.NET 里就不行了, 但在 release build 下, linker 经常将部分 .idata 入其它 sections, 如 .rdata 里. 既然 imports data 部分是在载入内存时由 Windows loader 写入的, 那它们是怎么被放进 read-only section 里的呢? 在载入时系统可以将包含 imports data 的页的属性临时设为 read/write. imports table 初始化后, 再将这些页恢复为原来的属性.


Relative Virtual Addresses

在 executable 中, 有许多地方需要为其指定内存中的地址. 例如, 当引用全局变量时就会需要其地址. PE 文件可以加载在进程地址空间的任意地方. 它们确实都有默认的加载地址, 但不能因此认为该 executable 就加载在该地址上. 由于这个原因, 指定一个不依赖于 executable 加载地址的寻址方法就很重要了. 为了避免在 PE 文件里硬编码内存地址就引入了 RVA . RVA 就是内存中相对于 PE 文件加载地址的偏移. 例如, 考虑一个加载在地址 0x400000 的一个 EXE 文件, 其代码 section 的地址是 0x401000. 则该代码 section 的 RVA 为:

(target address) 0x401000 - (load address)0x400000  = (RVA)0x1000.

要将 RVA 转换为实际地址,只需要一个逆过程: 向实际载入地址上加上 RVA 来找到实际地址.这个实际地址称为 PE 意义上的虚拟地址 VA. 还可以认为VA是默认加载地址加上RVA.别忘了前面我曾提到加载地址与 HMODULE 相同的. 想探索内存中任意DLL的数据结构吗?这里告诉你个方法. 用DLL为参数调用 GetModuleHandle. 返回的 HMODULE 就是加载地址. 你可以用你的 PE 文件 structures 的知识在 module 里找到任何你想要的东西.


The Data Directory

executable中有许多需要快速定位的数据结构. 显然的例子有 imports, exports, resources 和 base relocations. 所有这些熟知的数据结构有着一致的形式, 其 location 被称为 DataDirectory. DataDirectory 是一个16个 structures 的数组. 每个 entry 都有一个预定义的意义. IMAGE_DIRECTORY_ENTRY_ xxx #defines 为 DataDirectory 的数组索引(from 0 to 15). Figure 2 描述了每个 IMAGE_DATA_DIRECTORY_xxx 的意义. 对所指向的数据结构的更为详细的描述见本文的第二部分.

Figure 2 IMAGE_DATA_DIRECTORY Values

Value Description

IMAGE_DIRECTORY_ENTRY_EXPORT Points to the exports
    (an IMAGE_EXPORT_DIRECTORY structure).

IMAGE_DIRECTORY_ENTRY_IMPORT Points to the imports
                                 (an array of IMAGE_IMPORT_DESCRIPTOR structures).

IMAGE_DIRECTORY_ENTRY_RESOURCE Points to the resources
                                 (an IMAGE_RESOURCE_DIRECTORY structure).
                                
IMAGE_DIRECTORY_ENTRY_EXCEPTION Points to the exception handler table
(an array of IMAGE_RUNTIME_FUNCTION_ENTRY structures).
CPU-specific and for table-based exception handling.
Used on every CPU except the x86.

IMAGE_DIRECTORY_ENTRY_SECURITY Points to a list of WIN_CERTIFICATE structures,
defined in WinTrust.H. Not mapped into memory as part
of the image.Therefore, the VirtualAddress field is a
file offset, rather than an RVA.

IMAGE_DIRECTORY_ENTRY_BASERELOC Points to the base relocation information.

IMAGE_DIRECTORY_ENTRY_DEBUG Points to an array of IMAGE_DEBUG_DIRECTORY structures,
each describing some debug information for the image.
Early Borland linkers set the Size field of this
IMAGE_DATA_DIRECTORY entry to the number of structures,
rather than the size in bytes. To get the number of
IMAGE_DEBUG_DIRECTORYs, divide the Size field by the
size of an IMAGE_DEBUG_DIRECTORY.

IMAGE_DIRECTORY_ENTRY_ARCHITECTURE Points to architecture-specific data, which is an array
of IMAGE_ARCHITECTURE_HEADER structures.
Not used for x86 or IA-64, but appears to have been used
for DEC/Compaq Alpha.

IMAGE_DIRECTORY_ENTRY_GLOBALPTR The VirtualAddress field is the RVA to be used as the
global pointer (gp) on certain architectures.
Not used on x86, but is used on IA-64. The Size field
isn\'t used.
See the November 2000 Under The Hood column for more
information on the IA-64 gp.

IMAGE_DIRECTORY_ENTRY_TLS Points to the Thread Local Storage initialization
section.

IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG Points to an IMAGE_LOAD_CONFIG_DIRECTORY structure.
The information in an IMAGE_LOAD_CONFIG_DIRECTORY is
specific to Windows NT, Windows 2000, and Windows XP
(for example, the GlobalFlag value). To put this
structure in your executable, you need to define a
global structure with the name __load_config_used, and
of type IMAGE_LOAD_CONFIG_DIRECTORY. For non-x86
architectures, the symbol name needs to be
_load_config_used (with a single underscore). If you do
try to include an IMAGE_LOAD_CONFIG_DIRECTORY, it can be
tricky to get the name right in your C++ code. The
symbol name that the linker sees must be exactly:
__load_config_used (with two underscores). The C++
compiler adds an underscore to global symbols. In
addition, it decorates global symbols with type
information. So, to get everything right, in your C++
code, you\'d have something like this:
extern \"C\"
IMAGE_LOAD_CONFIG_DIRECTORY _load_config_used = {...}

IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT Points to an array of IMAGE_BOUND_IMPORT_DESCRIPTORs,
one for each DLL that this image has bound against. The
timestamps in the array entries allow the loader to
quickly determine whether the binding is fresh. If stale,
the loader ignores the binding information and resolves
the imported APIs normally.                                                                                                    
IMAGE_DIRECTORY_ENTRY_IAT Points to the beginning of the first Import Address
Table (IAT). The IATs for each imported DLL appear
sequentially in memory. The Size field indicates the
total size of all the IATs. The loader uses this address
and size to temporarily mark the IATs as read-write
during import resolution.

IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT Points to the delayload information, which is an array
of CImgDelayDescr structures, defined in DELAYIMP.H from
Visual C++. Delayloaded DLLs aren\'t loaded until the
first call to an API in them occurs. It\'s important to
note that Windows has no implicit knowledge of delay
loading DLLs. The delayload feature is completely
implemented by the linker and runtime library.

IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR This value has been renamed to
IMAGE_DIRECTORY_ENTRY_COMHEADER in more recent updates
to the system header files. It points to the top-level
information for .NET information in the executable,
including metadata. This information is in the form of
an IMAGE_COR20_HEADER structure.

Importing Functions

使用其它 DLL 中的代码或数据就是在 importing. 当加载 PE 文件时, Windows loader 的任务之一就是定位所有的 imported的函数和数据并使其地址对要加载的文件可用. 我会在本文的第二部分详细讨论用于实现此功能的这些数据结构, 但在这里大概了解一下这些概念还是值得的. 直接链接另一 DLL 的 code 和 data 是在隐式地链接此 DLL. 你无需为代码用到的 imported APIs 的地址操心, loader 会完全负责此事. 另外的方法是显式链接. 即显式地加载目标 DLL 再查找 APIs 的地址. 这几乎全是靠的 LoadLibrary 和 GetProcAddress APIs.

当隐式链接 API 时, 也会执行类似于 LoadLibrary 和 GetProcAddress的代码, 但都是 loader 自动做的. loader还
要保证 PE 文件所需的其它 DLLs 也被加载. 例如, 用 Visual C++ 创建的程序一般都要链接 KERNEL32.DLL. 而 KERNEL32.DLL 又从 NTDLL.DLL imports functions. 类似地, 若从 GDI32.DLL import,它又依赖 USER32, ADVAPI32, NTDLL 和 KERNEL32 DLLs, loader 要确保它们都已加载并且所有的 imports 都已 resolved. (Visual Basic 6.0 和 Microsoft .NET 的 executable 直接链接到一个不同的 DLL, 但道理相同)

当隐式链接时, 对主 EXE 文件和所有所依赖的 DLLs 的 resolution 的过程是在程序启动时进行的. 若出现问题(如, 一个 DLL 没找到), 就放弃此过程. Visual C++ 6.0 还加入了 delayload feature, 这是一种介于显式与隐式之间的方法. 当 delayload 一个 DLL 时, linker 生成一些看上去与一般的 imported DLL 的数据相似的数据. 然而 operating system 会忽略此数据.当首次调用 delayloaded APIs 时, linker 先添加入特殊的 stubs 来加载 DLL(若尚未载入内存), 再调用 GetProcAddress 来定位 API. 为了使对API的调用就像 API 是被正常 imported 一样高效,这里还需要其它的技术.

在 PE 文件内部有一个数据结构数组, 每个 imported DLL 一个. 每个结构给出 imported DLL 的名字并指向一个函数指针数组. 该函数指针数组即 import address table (IAT). 在 IAT 中每个 imported API 都有其自己保留的位置. Windows loader 会将 imported function 写入这些地址. 这最后一点犹为重要: 一旦载入一个module, IAT 就包含了 imported APIs 的地址. IAT 的优美之处就在于 PE 文件中只有一处保存着 imported API 的地址. 不管源程序中分散着多少对某 API 的调用, 所有的调用都会使用 IAT 中的相同的函数指针.

来看一下对 imported API 的调用是什么样的. 这里有两种情况: 高效的和低效的. 最好的情况, 对importedAPI的调用如下:

CALL DWORD PTR [0x00405030]

在 x86 汇编语言中, 这是用 function pointer 进行调用. 不管 0x405030 处的 DWORD-sized 的值为何, CALL指令都将控制送到那里. 在前例中,地址 0x405030 位于 IAT 中.

较低效的调用如下:

CALL 0x0040100C
???
0x0040100C:
JMP       DWORD PTR [0x00405030]

此时, CALL 将控制传给一个小 stub. 这个 stub 为向另一个地址的跳转,该地址保存在 0x405030. 再一次提醒 0x405030 是 IAT 中的一个 entry. 简言之, 较低效的 imported API 调用只用5字节的代码, 但因执行额外的 JMP, 执行时间稍长.

你可能会奇怪为什么还要用低效的办法. 这里有很好的解释. compiler 不能识别同一 module 中的 imported API 调用和一般的函数调用. 像这里, compiler 生成这样一个 CALL 指令:

CALL XXXXXXXX

其中 XXXXXXXX 为一实际代码地址, 之后会被 inker 填充. 注意这个 CALL 指令并非通过函数指针而是进入实际的代码. linker 还需要代码来替换 XXXXXXXX. 最简单的方法就是使调用点指向一个 JMP stub, 正如你所看到的.

JMP stub 从那里来呢? 令人惊奇的是, 它来自于 imported function 的 import library. 你若想查看 import library, 并查看与 imported API 名相对应的代码, 你就会发现那段代码就是一个像前面所示那样的 JMP stub. 这就意味着, 在默认情况下, imported API 会使用那个较低效的办法. 逻辑上, 下一个要问的问题就是如何得到最优的形式. 答案是给 compiler 一个提示. __declspec(dllimport) 函数修饰符告诉 compiler 函数位于另外的DLL中而且 compiler 应生成如下指令:

CALL DWORD PTR [XXXXXXXX]

而不是这个:

CALL XXXXXXXX

另外, compiler 生成信息告诉 inker 来 resolve 指令指向的一个符号名为 __imp_functionname 的函数指针. 例如, 若调用 MyFunction, 符号名就会是 __imp_MyFunction. 查看 import library, 就会发现除一般的符号名外, 还有一个__imp__ 前缀的符号名. 这个 __imp__ 符号直接进入 IAT entry, 而不是指向 JMP stub. 因此若想写
exported functions 并为其提供一个 .H 文件, 记着在函数前加上__declspec(dllimport):

__declspec(dllimport) void Foo(void);

若察看 Windows 的系统头文件, 就会发现其对所有 Windows APIs 都使用了 __declspec(dllimport). 尽管并不是直接作用于函数声明, 但若在 WINNT.H 中找一下 DECLSPEC_IMPORT 宏的定义, 就会知道是如何在系统 API声明前加的 __declspec(dllimport).


PE File Structure

现在来挖掘 PE 文件的实际格式. 我会从文件的首部开始,描述每个 PE 文件共同拥有的数据结构. 之后, 再描述 PE 的 sections 里面更为专门的数据结构(如 imports 或 esources). 除非特别指明, 否则所讨论的所有的数据结构都在 WINNT.H 中定义.

一般32位和64位的数据结构都是成对的――如, IMAGE_NT_HEADERS32 和 IMAGE_NT_HEADERS64. 除了某些域扩展到64位之外,这些结构基本上都是相同的. 为了代码的可移植性, WINNT.H 中使用 #defines 来选择是用32位的还是64位的, 并起了个与位数无关的别名(在前例中就是 IMAGE_NT_HEADERS). 选择哪种结构依赖于所使用的编译模式(即是否定义了_WIN64). 当 PE 文件的长度特征与编译平台的不同时,只需指定是使用32位的还是64位的.


The MS-DOS Header

每个 PE 文件的开头都是一个小 MS-DOS executable. 需要这个可执行的 stub 的原因要追溯到早期的 Windows, 当时使用 Windows 的人还不像这样多. 当在没有 Windows 的机器上运行时, 程序至少会最终显示一条消息告诉你运行此程序需要 Windows.

PE 文件开头的起始部分是传统的 MS-DOS header, 叫做 IMAGE_DOS_HEADER. 重要的值仅有两个: e_magic 和 e_lfanew. e_lfanew 域为 PE header 的文件偏移. e_magic 域 (一个WORD) 需设为值 0x5A4D. 对这个值有一个 #define, 名字是 IMAGE_DOS_SIGNATURE. 用 ASCII 表示, 0x5A4D 就是 MZ, Mark Zbikowski 的首字母缩略词, Mark Zbikowski 是 MS-DOS 缔造者之一.


The IMAGE_NT_HEADERS Header

IMAGE_NT_HEADERS结构是保存 PE 文件特征的主要位置. 此结构的偏移由文件开头的 IMAGE_DOS_HEADER 的 e_lfanew 域给出. IMAGE_NT_HEADER 实际有两个版本一个用于 32-bit executable, 另一个用于 64-bit 版本. 两个版本间差别很小, 从本文的讨论来看可认为是相同的. 区别两者的唯一正确的, Microsoft-approved 的方法就是通过 IMAGE_OPTIONAL_HEADER (后面介绍) 的 Magic 域的值.

IMAGE_NT_HEADER由三个域构成:

typedef struct _IMAGE_NT_HEADERS {
    DWORD Signature;
    IMAGE_FILE_HEADER FileHeader;
    IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

在合法的 PE 文件中, Signature 域要设为 0x00004550, 其 ASCII 为 \"PE00\" 并用 #define 定义为 IMAGE_NT_SIGNATURE. 第二个域, 一个 IMAGE_FILE_HEADER 类型的 struct, predates PE 文件. 其包含了文件的基本信息;最重要的是其后的描述可选数据大小的域. 在 PE 文件里, 尽管这个可选的数据是非常必要的, 但却依然叫做 IMAGE_OPTIONAL_HEADER.

Figure 3 为 IMAGE_FILE_HEADER 结构的各个域及其作用. COFF OBJ 文件的开头也可找到此结构. Figure 4 列出了 IMAGE_FILE_xxx 的常见值. Figure 5 为 IMAGE_OPTIONAL_HEADER 结构的各个成员.

IMAGE_OPTIONAL_HEADERs 末尾的 DataDirectory 数组是 executable 中重要位置的地址表. 每一 DataDirector entry 的形式如下:

typedef struct _IMAGE_DATA_DIRECTORY {
    DWORD   VirtualAddress;     // RVA of the data
    DWORD   Size;               // Size of the data
};


The Section Table

紧随 IMAGE_NT_HEADERS 之后的是 section table. section table 就是一个 IMAGE_SECTION_HEADERs 结构的数组. IMAGE_SECTION_HEADER 提供相应 section 的信息, 包括位置, 长度和特征. Figure 6 为对 IMAGE_SECTION_HEADER 各域的描述. IMAGE_SECTION_HEADER structures 的数目由 IMAGE_NT_HEADERS.FileHeader.NumberOfSections 域给出.

在 executable 中, sections 的文件对齐值对文件大小有着巨大的影响. 在 Visual Studio 6.0 中, linker 默认的 section 对齐值为4KB,除非使用了 /OPT:NOWIN98 或 /ALIGN. Visual Studio .NET 的linker, 尽管仍然默认使用 /OPT:WIN98, 却可自己确定 executable 长度是否小于某值以及这是否是由使用 0x200-byte 对齐引起的.

另一有趣的对齐值来源于 .NET 文件规范. 规范称 .NET executable 应有一个8KB的 in-memory 的对齐值, 而不是用于 x86 binaries 的4KB. 这是为了使使用 x86 入口点代码构建的 .NET executable 在IA-64 下仍然能够运行. 倘若 in-memory section 的对齐值为4KB, IA-64 loader 就不能载入文件, 因为 64-bit Windows 下的页是8KB.

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

Figure 3 IMAGE_FILE_HEADER

Size Field Description
 
WORD Machine The target CPU for this executable. Common values are:
                         IMAGE_FILE_MACHINE_I386    0x014c // Intel 386        
                         IMAGE_FILE_MACHINE_IA64    0x0200 // Intel 64
                        
WORD NumberOfSections Indicates how many sections are in the section table.
The section table immediately follows the IMAGE_NT_HEADERS.  

DWORD TimeDateStamp Indicates the time when the file was created. This value is
the number of seconds since January 1, 1970, Greenwich Mean Time
(GMT). This value is a more accurate indicator of when the file
was created than is the file system date/time. An easy way to
translate this value into a human-readable string is with the
_ctime function (which is time-zone-sensitive!). Another useful
function for working with this field is gmtime.

DWORD PointerToSymbolTable The file offset of the COFF symbol table, described in
section 5.4 of the Microsoft specification. COFF symbol tables
are relatively rare in PE files, as newer debug formats have
taken over. Prior to Visual Studio .NET, a COFF symbol table
could be created by specifying the linker
switch /DEBUGTYPE:COFF. COFF symbol tables are almost always
found in OBJ files. Set to 0 if no symbol table is present.

DWORD NumberOfSymbols Number of symbols in the COFF symbol table, if present. COFF
symbols are a fixed size structure, and this field is needed to
find the end of the COFF symbols. Immediately following the
COFF symbols is a string table used to hold longer symbol names.

WORD SizeOfOptionalHeader The size of the optional data that follows the IMAGE_FILE_HEADER.
In PE files, this data is the IMAGE_OPTIONAL_HEADER. This size
is different depending on whether it\'s a 32 or 64-bit file.
For 32-bit PE files, this field is usually 224. For 64-bit
PE32+ files, it\'s usually 240. However, these sizes are just
minimum values, and larger values could appear.

WORD Characteristics A set of bit flags indicating attributes of the file.
Valid values of these flags are the IMAGE_FILE_xxx values
defined in WINNT.H. Some of the more common values include
those listed in Figure 4.

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

Figure 4 IMAGE_FILE_XXX

Value Description

IMAGE_FILE_RELOCS_STRIPPED             Relocation information stripped from a file.

IMAGE_FILE_EXECUTABLE_IMAGE             The file is executable.

IMAGE_FILE_AGGRESIVE_WS_TRIM           Lets the OS aggressively trim the working set.

IMAGE_FILE_LARGE_ADDRESS_AWARE         The application can handle addresses greater than two
gigabytes.

IMAGE_FILE_32BIT_MACHINE               This requires a 32-bit word machine.

IMAGE_FILE_DEBUG_STRIPPED               Debug information is stripped to a .DBG file.

IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP     If the image is on removable media, copy to and run from

IMAGE_FILE_NET_RUN_FROM_SWAP           If the image is on a network, copy to and run from the

IMAGE_FILE_DLL                         The file is a DLL.

IMAGE_FILE_UP_SYSTEM_ONLY               The file should only be run on single-processor machines.                                                                                                                      
                                                                                                                      
--------------------------------------------------------------------------------

Figure 5 IMAGE_OPTIONAL_HEADER

Size Structure Member Description

WORD Magic                           A signature WORD, identifying what type of header this
is. The two most common values are
IMAGE_NT_OPTIONAL_HDR32_MAGIC 0x10b and
IMAGE_NT_OPTIONAL_HDR64_MAGIC 0x20b.

BYTE    MajorLinkerVersion              The major version of the linker used to build this
executable. For PE files from the Microsoft linker,
this version number corresponds to the Visual Studio
version number (for example, version 6 for
Visual Studio 6.0).
              
BYTE    MinorLinkerVersion              The minor version of the linker used to build this
executable.
      
DWORD   SizeOfCode                      The combined total size of all sections with the
IMAGE_SCN_CNT_CODE attribute.
              
DWORD   SizeOfInitializedData           The combined size of all initialized data sections.
      
DWORD   SizeOfUninitializedData         The size of all sections with the uninitialized data
attributes. This field will often be 0, since the
linker can append uninitialized data to the end of
regular data sections.
              
DWORD   AddressOfEntryPoint             The RVA of the first code byte in the file that will be
executed. For DLLs, this entrypoint is called during
process initialization and shutdown and during thread
creations/destructions. In most executables, this
address doesn\'t directly point to main, WinMain, or
DllMain. Rather, it points to runtime library code that
calls the aforementioned functions. This field can be
set to 0 in DLLs, and none of the previous notifications
will be received. The linker /NOENTRY switch sets this
field to 0.
      
DWORD   BaseOfCode                      The RVA of the first byte of code when loaded in memory.

DWORD   BaseOfData                      Theoretically, the RVA of the first byte of data when
loaded into memory. However, the values for this field
are inconsistent with different versions of the
Microsoft linker. This field is not present in 64-bit
executables.
              
DWORD   ImageBase                       The preferred load address of this file in memory. The
loader attempts to load the PE file at this address if
possible (that is, if nothing else currently occupies
that memory, it\'s aligned properly and at a legal
address, and so on). If the executable loads at this
address, the loader can skip the step of applying base
relocations (described in Part 2 of this article). For
EXEs, the default ImageBase is 0x400000. For DLLs, it\'s
0x10000000. The ImageBase can be set at link time with
the /BASE switch.
              
DWORD   SectionAlignment                The alignment of sections when loaded into memory.
The alignment must be greater or equal to the file
alignment field (mentioned next). The default alignment
is the page size of the target CPU. For user mode
executables to run under Windows 9x or Windows Me, the
minimum alignment size is a page (4KB). This field can
be set with the linker /ALIGN switch.

DWORD   FileAlignment                   The alignment of sections within the PE file. For x86
executables, this value is usually either 0x200 or
0x1000. The default has changed with different versions
of the Microsoft linker. This value must be a power
of 2, and if the SectionAlignment is less than the
CPU\'s page size, this field must match the
SectionAlignment. The linker switch /OPT:WIN98 sets the
file alignment on x86 executables to 0x1000, while
/OPT:NOWIN98 sets the alignment to 0x200.

WORD    MajorOperatingSystemVersion     The major version number of the required operating
system. With the advent of so many versions of Windows,
this field has effectively become irrelevant.
            
WORD    MinorOperatingSystemVersion     The minor version number of the required OS.
              
WORD    MajorImageVersion               The major version number of this file. Unused by the
system and can be 0. It can be set with the linker
/VERSION switch.
              
WORD    MinorImageVersion               The minor version number of this file.
              
WORD    MajorSubsystemVersion           The major version of the operating subsystem needed for
this executable. At one time, it was used to indicate
that the newer Windows 95 or Windows NT 4.0 user
interface was required, as opposed to older versions of
the Windows NT interface. Today, because of the
proliferation of the various versions of Windows, this
field is effectively unused by the system and is
typically set to the value 4. Set with the linker
/SUBSYSTEM switch.
              
WORD    MinorSubsystemVersion           The minor version of the operating subsystem needed for
this executable.
              
DWORD   Win32VersionValue               Another field that never took off. Typically set to 0.

DWORD   SizeOfImage                     SizeOfImage contains the RVA that would be assigned to
the section following the last section if it existed.
This is effectively the amount of memory that the system
needs to reserve when loading this file into memory.
This field must be a multiple of the section alignment.  

WORD   SizeOfHeaders                    The combined size of the MS-DOS header, PE headers, and
section table. All of these items will occur before any
code or data sections in the PE file. The value of this
field is rounded up to a multiple of the file alignment.

DWORD   CheckSum                        The checksum of the image. The CheckSumMappedFile API
in IMAGEHLP.DLL can calculate this value. Checksums are
required for kernel-mode drivers and some system DLLs.
Otherwise, this field can be 0. The checksum is placed
in the file when the /RELEASE linker switch is used.
      
WORD    Subsystem                       An enum value indicating what subsystem (user interface type) the executable expects. This field is only important for EXEs. Important values include:            
IMAGE_SUBSYSTEM_NATIVE       // Image doesn\'t require a subsystem                
IMAGE_SUBSYSTEM_WINDOWS_GUI  // Use the Windows GUI      
IMAGE_SUBSYSTEM_WINDOWS_CUI  // Run as a console mode application               // When run, the OS creates a console  
// window for it, and provides stdin,
// stdout, and stderr file handles

WORD    DllCharacteristics              Flags indicating characteristics of this DLL.
These correspond to the IMAGE_DLLCHARACTERISTICS_xxx
fields #defines. Current values are:

IMAGE_DLLCHARACTERISTICS_NO_BIND
// Do not bind this image                 IMAGE_DLLCHARACTERISTICS_WDM_DRIVER
// Driver uses WDM model                   IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE
// When the terminal server loads         // an application that is not
//  Terminal- Services-aware, it                             // also loads a DLL that contains
// compatibility code

DWORD   SizeOfStackReserve              In EXE files, the maximum size the initial thread in
the process can grow to. This is 1MB by default. Not
all this memory is committed initially.          

DWORD   SizeOfStackCommit               In EXE files, the amount of memory initially committed
to the stack. By default, this field is 4KB.          
DWORD   SizeOfHeapReserve               In EXE files, the initial reserved size of the default
process heap. This is 1MB by default. In current
versions of Windows, however, the heap can grow beyond
this size without intervention by the user.          
DWORD   SizeOfHeapCommit                In EXE files, the size of memory committed to the heap.
By default, this is 4KB.                          

DWORD   LoaderFlags                     This is obsolete.

DWORD   NumberOfRvaAndSizes             At the end of the IMAGE_NT_HEADERS structure is an
array of IMAGE_DATA_DIRECTORY structures. This field
contains the number of entries in the array. This field
has been 16 since the earliest releases of Windows NT.  

IMAGE_  DataDirectory[16]               An array of IMAGE_DATA_DIRECTORY structures. Each
structure contains the RVA and size of some important
part of the executable (for instance, imports, exports,
resources).

--------------------------------------------------------------------------------                                              
Figure 6 The IMAGE_SECTION_HEADER
 
Size Field Description                                                      
BYTE Name[8] The ASCII name of the section. A section name is not guaranteed
to be null-terminated. If you specify a section name longer than
eight characters, the linker truncates it to eight characters in
the executable. A mechanism exists for allowing longer section
names in OBJ files. Section names often start with a period, but
this is not a requirement. Section names with a $ in the name get
special treatment from the linker. Sections with identical names
prior to the $ character are merged. The characters following the
$ provide an alphabetic ordering for how the merged sections
appear in the final section. There\'s quite a bit more to the
subject of sections with $ in the name and how they\'re combined,
but the details are outside the scope of this article

DWORD Misc.VirtualSize Indicates the actual, used size of the section. This field may
be larger or smaller than the SizeOfRawData field. If the
VirtualSize is larger, the SizeOfRawData field is the size of
the initialized data from the executable, and the remaining
bytes up to the VirtualSize should be zero-padded. This field
is set to 0 in OBJ files.

DWORD VirtualAddress In executables, indicates the RVA where the section begins in
memory. Should be set to 0 in OBJs.

DWORD SizeOfRawData The size (in bytes) of data stored for the section in the
executable or OBJ. For executables, this must be a multiple of
the file alignment given in the PE header. If set to 0, the
section is uninitialized data.

DWORD PointerToRawData The file offset where the data for the section begins. For
executables, this value must be a multiple of the file alignment
given in the PE header.

DWORD PointerToRelocations The file offset of relocations for this section. This is only
used in OBJs and set to zero for executables. In OBJs, it
points to an array of IMAGE_RELOCATION structures if non-zero.

DWORD PointerToLinenumbers The file offset for COFF-style line numbers for this section.
Points to an array of IMAGE_LINENUMBER structures if non-zero.
Only used when COFF line numbers are emitted.

WORD NumberOfRelocations The number of relocations pointed to by the PointerToRelocations
field. Should be 0 in executables.

WORD NumberOfLinenumbers The number of line numbers pointed to by the NumberOfRelocations
field. Only used when COFF line numbers are emitted.

DWORD Characteristics Flags OR\'ed together, indicating the attributes of this section.
Many of these flags can be set with the linker\'s /SECTION option.
Common values include those listed in Figure 7.

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

Figure 7

Flags Value Description

IMAGE_SCN_CNT_CODE The section contains code.

IMAGE_SCN_MEM_EXECUTE The section is executable.

IMAGE_SCN_CNT_INITIALIZED_DATA The section contains initialized data.

IMAGE_SCN_CNT_UNINITIALIZED_DATA The section contains uninitialized data.

IMAGE_SCN_MEM_DISCARDABLE The section can be discarded from the final executable.
Used to hold information for the linker\'s use, including
the .debug$ sections.

IMAGE_SCN_MEM_NOT_PAGED The section is not pageable, so it should always be
physically present in memory. Often used for kernel-mode
drivers.

IMAGE_SCN_MEM_SHARED The physical pages containing this section\'s data will
be shared between all processes that have this
executable loaded. Thus, every process will see the
exact same values for data in this section. Useful for
making global variables shared between all instances of
a process. To make a section shared, use
the /section:name,S linker switch.

IMAGE_SCN_MEM_READ The section is readable. Almost always set.

IMAGE_SCN_MEM_WRITE The section is writeable.

IMAGE_SCN_LNK_INFO The section contains information for use by the linker.
Only exists in OBJs.

IMAGE_SCN_LNK_REMOVE The section contents will not become part of the image.
This only appears in OBJ files.

IMAGE_SCN_LNK_COMDAT The section contents is communal data (comdat). Communal
data is data (or code) that can be defined in multiple
OBJs. The linker will select one copy to include in the
executable. Comdats are vital for support of C++ template
functions and function-level linking. Comdat sections
only appear in OBJ files.
                                 
IMAGE_SCN_ALIGN_XBYTES The alignment of this section\'s data in the resultant
executable. There are a variety of these
values ( _4BYTES, _8BYTES, _16BYTES, and so on).
The default, if not specified, is 16 bytes. These flags
will only be set in OBJ files.                                                                                                              
bmyyyud
驱动老牛
驱动老牛
  • 注册日期2002-02-22
  • 最后登录2010-01-21
  • 粉丝0
  • 关注0
  • 积分1000分
  • 威望130点
  • 贡献值0点
  • 好评度106点
  • 原创分0分
  • 专家分0分
沙发#
发布于:2005-03-12 09:56
有没有办法自己做个ntoskrnl.lib呢,将Undocumented 函数加上呢?
滚滚长江东逝水 浪花淘尽英雄 是非成败转头空 青山依旧在 几度夕阳红 白发渔樵江渚上 惯看秋月春风 一壶浊酒喜相逢 古今多少事 尽付笑谈中
游客

返回顶部