CR的代码文本

all for learning about the world
  订阅 订阅  :: 管理

如何判断exe或dll的目标平台及是否是.NET?

Posted on 2014-10-29 15:12  mumuliang  阅读(2504)  评论(1编辑  收藏  举报

1.

COFF文件头中偏移0处的Machine指示目标机器类型(IMAGE_FILE_MACHINE_AMD64等),偏移18处的Characteristics位指示文件属性(IMAGE_FILE_32BIT_MACHINE0x0100,IMAGE_FILE_LARGE_ADDRESS_AWARE0x0020)。

但我们判断dll或exe支持的目标平台并不使用COFF头,而使用可选文件头(PE32,PE32+即位于此处),因为可选文件头用于为加载器提供信息

可选文件头分为3个部分:标准域,windows特定域和数据目录。PE32/PE32+,由位于标准域处的首个标识幻数(Magic),长度为2,它的可能值和含义为:

  • 0x10b  PE32可执行文件
  • 0x107 一个ROM镜像
  • 0x20b PE32+可执行文件

位于可选文件头标准域的magic标志位的值,也确定了标准域和特定域的大小。

  • 标准域:PE32 28,PE32+24;(PE32比PE32+多了一个BaseOfData)
  • 特定域:PE32 68,PE32+ 88。
  • 数据目录:可变,可选文件头的总大小由COFF文件头中的SizeofOptionalHader指定。

数据目录的第15个即CLR Runtime Header,记录了CLR运行时头部的地址和大小。

从CLR运行时偏移16byte处的uint32即Corflags。

2.

VS2012生成的程序集使用的CORFLAGS版本是2.5; 早前版本都是2.0。

2.0版本的Corflags的标识值包含:

COMIMAGE_FLAGS_ILONLY               =0x00000001,
COMIMAGE_FLAGS_32BITREQUIRED        =0x00000002,
COMIMAGE_FLAGS_IL_LIBRARY           =0x00000004,
COMIMAGE_FLAGS_STRONGNAMESIGNED     =0x00000008,
COMIMAGE_FLAGS_NATIVE_ENTRYPOINT    =0x00000010,  
COMIMAGE_FLAGS_TRACKDEBUGDATA =0x00010000,

它们和目标平台的关系是:

Any CPU: PE = PE32 and 32BIT = 0
x86: PE = PE32 and 32BIT = 1
64-bit: PE = PE32+ and 32BIT = 0

2.5版本中多了一个32BITPREF,含义如下。暂时未找到32BITPREF存放在哪位上(4.节已找到)。

CorFlags  : Hexadecimal value, computed based on below 4 flags.
ILONLY    : 1 if MSIL otherwise 0
32BITREQ  : 1 if 32-bit x86 only assembly otherwise 0
32BITPREF : 1 if 32-bit x86 only preferred in Any CPU architecture otherwise 0
Signed    : 1 if signed with strong name otherwise 0 

 

3.

.NET 4.5(即CLR Header 2.5)的CorFlags新增的标志位32BITPREF貌似有点坑爹,引自msdn:

Sets the 32BITPREFERRED flag. The app runs as a 32-bit process even on 64-bit platforms. Set this flag only on EXE files. If the flag is set on a DLL, the DLL fails to load in 64-bit processes, and a BadImageigeFormatException exception is thrown. An EXE file with this flag can be loaded into a 64-bit process.

首先,它说设置了该标志的应用程序即使是在64位平台上也运行在32位环境下。

其次如果把DLL项目设置了该属性的话,会导致DLL无法被64位进程加载,并抛出异常。

再其次,设置了该标志位的EXE也可以被加载到一个64位进程中。

各种不确定。

 

4.

CorHdr.h记录了CLR文件头的结构。

用vs2012新建一个c++ win32 控制台项目,在ConsoleApplication1.cpp头部添加

#include <CorHdr.h>

然后使用Shift+Ctrl+G打开该H文件。

该文件中有一个CorPEKind枚举

typedef enum CorPEKind
{
    peNot       = 0x00000000,   // not a PE file
    peILonly    = 0x00000001,   // flag IL_ONLY is set in COR header
    pe32BitRequired=0x00000002,  // flag 32BITREQUIRED is set and 32BITPREFERRED is clear in COR header
    pe32Plus    = 0x00000004,   // PE32+ file (64 bit)
    pe32Unmanaged=0x00000008,    // PE32 without COR header
    pe32BitPreferred=0x00000010  // flags 32BITREQUIRED and 32BITPREFERRED are set in COR header
} CorPEKind;

这应该就是2.5版本的CORFLAGS位的含义。
注意这个CLR文件头结构体仍然叫IMAGE_COR20_HEADER,直接COPY出来如下:

// #ManagedHeader
// 
// A managed code EXE or DLL uses the same basic format that unmanaged executables use call the Portable
// Executable (PE) format. See http://en.wikipedia.org/wiki/Portable_Executable or
// http://msdn.microsoft.com/msdnmag/issues/02/02/PE/default.aspx for more on this format and RVAs.
// 
// PE files define fixed table of well known entry pointers call Directory entries. Each entry holds the
// relative virtual address (RVA) and length of a blob of data within the PE file. You can see these using
// the command
// 
// link /dump /headers <EXENAME>
//  
//  
// Managed code has defined one of these entries (the 14th see code:IMAGE_DIRECTORY_ENTRY_COMHEADER) and the RVA points
// that the IMAGE_COR20_HEADER.  This header shows up in the previous dump as the following line
// 
// // Managed code is identified by is following line
// 
//             2008 [      48] RVA [size] of COM Descriptor Directory
//
// The IMAGE_COR20_HEADER is mostly just RVA:Length pairs (pointers) to other interesting data structures. 
// The most important of these is the MetaData tables.   The easiest way of looking at meta-data is using
// the IlDasm.exe tool.   
// 
// MetaData holds most of the information in the IL image.  THe exceptions are resource blobs and the IL
// instructions streams for individual methods.  Intstead the Meta-data for a method holds an RVA to a 
// code:IMAGE_COR_ILMETHOD which holds all the IL stream (and exception handling information).  
// 
// Precompiled (NGEN) images use the same IMAGE_COR20_HEADER but also use the ManagedNativeHeader field to
// point at structures that only exist in precompiled images. 
//  
typedef struct IMAGE_COR20_HEADER
{
    // Header versioning
    DWORD                   cb;              
    WORD                    MajorRuntimeVersion;
    WORD                    MinorRuntimeVersion;
    
    // Symbol table and startup information
    IMAGE_DATA_DIRECTORY    MetaData;        
    DWORD                   Flags;           
  
    // The main program if it is an EXE (not used if a DLL?)
    // If COMIMAGE_FLAGS_NATIVE_ENTRYPOINT is not set, EntryPointToken represents a managed entrypoint.
    // If COMIMAGE_FLAGS_NATIVE_ENTRYPOINT is set, EntryPointRVA represents an RVA to a native entrypoint
    // (depricated for DLLs, use modules constructors intead). 
    union {
        DWORD               EntryPointToken;
        DWORD               EntryPointRVA;
    };
    
    // This is the blob of managed resources. Fetched using code:AssemblyNative.GetResource and
    // code:PEFile.GetResource and accessible from managed code from
    // System.Assembly.GetManifestResourceStream.  The meta data has a table that maps names to offsets into
    // this blob, so logically the blob is a set of resources. 
    IMAGE_DATA_DIRECTORY    Resources;
    // IL assemblies can be signed with a public-private key to validate who created it.  The signature goes
    // here if this feature is used. 
    IMAGE_DATA_DIRECTORY    StrongNameSignature;

    IMAGE_DATA_DIRECTORY    CodeManagerTable;            // Depricated, not used 
    // Used for manged codee that has unmaanaged code inside it (or exports methods as unmanaged entry points)
    IMAGE_DATA_DIRECTORY    VTableFixups;
    IMAGE_DATA_DIRECTORY    ExportAddressTableJumps;

    // null for ordinary IL images.  NGEN images it points at a code:CORCOMPILE_HEADER structure
    IMAGE_DATA_DIRECTORY    ManagedNativeHeader;
    
} IMAGE_COR20_HEADER, *PIMAGE_COR20_HEADER;

红色标出的Flags,即记录CLR信息的位。将该值和CorPEKind枚举进行逻辑运算就能得到dll/exe的目标平台属性。

 

5.

在4.打开的CorHdr.h位于C:\Program Files (x86)\Windows Kits\8.0\Include\um,是windows sdk 8.0使用的版本。

6.0,7.0,7.1中的CorPEKind枚举定义如下

// PE file kind bits, returned by IMetaDataImport2::GetPEKind()
typedef enum CorPEKind
{
    peNot       = 0x00000000,   // not a PE file
    peILonly    = 0x00000001,   // flag IL_ONLY is set in COR header
    pe32BitRequired=0x00000002, // flag 32BIT_REQUIRED is set in COR header
    pe32Plus    = 0x00000004,   // PE32+ file (64 bit)
    pe32Unmanaged=0x00000008    // PE32 without COR header
} CorPEKind;

另外在6.0的IMAGE_COR20_HEADER定义头部有一行注释称其为“COM+ 2.0 header structure”,后续版本都删掉了。

保存着.NET运行时信息的这个结构化数据,在Microsoft可执行文件和通用目标文件格式规范中称之为"CLR Runtime Header",在另一些地方又被成为 CLI Header,或者COR Header,这里又被称作COM+ 2.0 header,感觉也是醉了。

 

6. 32BitPre并不是0x10

在CorChr.h中定义了32BitPref的值是0x10

typedef enum CorPEKind
{
    peNot       = 0x00000000,   // not a PE file
    peILonly    = 0x00000001,   // flag IL_ONLY is set in COR header
    pe32BitRequired=0x00000002,  // flag 32BITREQUIRED is set and 32BITPREFERRED is clear in COR header
    pe32Plus    = 0x00000004,   // PE32+ file (64 bit)
    pe32Unmanaged=0x00000008,    // PE32 without COR header
    pe32BitPreferred=0x00000010  // flags 32BITREQUIRED and 32BITPREFERRED are set in COR header
} CorPEKind;

要注意的是,如果我们读取文件头获得了CorFlags的值corflags,如果对corflags和0x10逻辑求与,得到的并不是32BitPref。
也就是说虽然这里CorPEKind枚举定义的32BitPref是0x10,但corflags这个32位无符号整数的第9bit的含义并不是32BitPref。

可能它只是IMetaDataImport2::GetPEKind()方法的返回值,例如Assembly.ManifestModule.GetPEKind(..)返回的值之一。

通过把corflags的bit逐个打印出来,真正存放32BitPref的位像是第18位,即0x20000。试了试貌似是对的。

后来在CorChr.h中看到了COMIMAGE_FLAGS_32BITPREFERRED,也即是该值。

 

7.AnyCPU 32-bit Prefer 的条件不一定是PE==PE32 &&32BitReq==0&&32BitPref==1

使用VS提供的CorFlags工具查看AnyCPU 32bit Prefer的dll,得到的是:

PE:PE32
32BitReq:0
32BItPref:1

所以我们在代码中解析CorFlags位也是使用这个逻辑来判定目标平台是AnyCPU 32bit Prefer吗?

答案出人意料的为不是。通过解析CorFlags位需要使用如下逻辑:

PE==PE32 &&32BitReq==1&&32BitPref==1

这一点和CorFlags工具有冲突。之所以这样大约是为了COR2.5和COR2.0和的兼容,同时AnyCPU 32-bit Prefer的DLL又必须通知Loader是32优先。(笔者随时都在瞎猜)

结论来了:

通过读取文件头得到Corflag,判定AnyCPU 32-bit Prefer的条件是:

   PE==PE32 &&32BitReq==1&&32BitPref==1

使用Corflags工具查看各标识,判定AnyCPU 32-bit Prefer的条件是:

   PE==PE32 &&32BitReq==0&&32BitPref==1

 

8. 

 

 

参考目录:

http://stackoverflow.com/tags/corflags/info
http://illuminatedcomputing.com/posts/2010/02/sorting-out-the-confusion-32-vs-64-bit-clr-vs-native-cs-vs-cpp/
http://msdn.microsoft.com/en-us/library/ms164699.aspx
http://blog.csdn.net/breaksoftware/article/category/1294269
http://stackoverflow.com/questions/18608785/how-to-interpret-the-corflags-flags/23614024#23614024
http://www1.huachu.com.cn/read/readbookinfo.asp?sectionid=1000001727
http://msdn.microsoft.com/en-us/magazine/cc301805.aspx
http://weblog.ikvm.net/2011/11/14/ManagedPEFileTypes.aspx
http://blogs.microsoft.co.il/sasha/2012/04/04/what-anycpu-really-means-as-of-net-45-and-visual-studio-11/
http://www.ntcore.com/files/dotnetformat.htm
http://apichange.codeplex.com/SourceControl/changeset/view/76c98b8c7311
http://msdn.microsoft.com/library/windows/hardware/gg463119.aspx