-- 作者:admin
-- 发布时间:11/9/2004 2:25:00 AM
-- MS.Net CLR 扩展PE结构分析(3)
发信人: flier (小海->找啊找工作 :)), 信区: DotNET 标 题: MS.Net CLR 扩展PE结构分析(3) 发信站: BBS 水木清华站 (Mon Mar 18 23:03:30 2002) MS.Net CLR 扩展PE结构分析 Flier Lu <flier_">lu@sina.com.cn> 注意:本系列文章在水木清华BBS(smth.org)之.Net版首发, 转载请保留以上信息,发表请与作者联系 Metadata 篇 第二章 Metadata在PE中的结构分析 上一章大概介绍了Metadata在CLR架构中的作用,以及其在PE 文件中的组织结构。本章将从实际的二进制流的角度来分析Metadata 流在PE中的结构。 2.1 Metadata Header 前言中我们分析了CLR Header的结构,里面有一个Metadata字段 IMAGE_DATA_DIRECTORY MetaData; 字段指向一个数据块,里面包含着Metadata Header,是关于 Metadata信息所在,结构定义伪代码如下 TClrMetadataHeader = packed record Signature: DWORD; // Magic signature for physical metadata : $424A5342. MajorVersion, // Major version, 1 MinorVersion: Word; // Minor version, 0 Reserved, // Reserved, always 0 Length: DWORD; // Length of version string in bytes, say m. Version: array[0..0] of Char; // UTF8-encoded version string of length m // Padding to next 4 byte boundary, say x. Version: array[0..((m+3) and (not $3))-1] of Char; Flags, // Reserved, always 0 Streams: Word; // Number of streams, say n. // Array of n StreamHdr structures. StreamHeaders: array[0..n-1] of TClrStreamHeader; end; 首先是Signature,是一个DWORD或4字符长的标记,$424A5342或'BSJB' 可以通过此标记判断Metadata是否存在,如 function TJclPeCLRHeader.GetHasMetadata: Boolean; const METADATA_SIGNATURE = $424A5342; begin with Header.MetaData do Result := (VirtualAddress <> 0) and (PDWORD(FImage.RvaToVa(VirtualAddress))^ = METADATA_SIGNATURE); end; 通过检测Metadata节是否存在,以及头一个DWORD是否为Metadata标记来判断 Metadata是否存在。 接着的MajorVersion和MinorVersion保存Metadata格式的版本号, 一般设置为1.1。(文档中说明是1.0,实际为1.1) 跟着的Length+Version指定了UTF8格式的编译环境版本号,这个和你的CLR的编译 版本相同,如.Net Framework工具的编译环境版本号为v1.x86ret,而你用它编译代码 生成的版本号则是诸如v1.0.3705类型。注意这里Version字符串是按四字节对齐的, 在分析二进制流时应按(m+3) and (not $3)来计算实际的长度。 Streams和StreamHeaders则是Metadata流的信息数组,保存有Metadata不同类型 流的Offset, Size, Name等信息,没记录结构如下 TClrStreamHeader = packed record Offset, // Memory offset to start of this stream from start of the me tadata root Size: DWORD; // Size of this stream in bytes, shall be a multiple of 4. // Name of the stream as null terminated variable length // array of ASCII characters, padded with \0 characters Name: array[0..MaxWord] of Char; end; 根据这些信息,我们可以从PE映像中读取相应流进行分析。 2.2 Metadata Stream 上一章我们曾经提过,Metadata Stream有五种常见类型,#String, #Blob, #Guid, #US(User String)和#~流,每种类型流最多只能出现一次。 2.2.1 #String 流 #String流是一个字符串堆,程序用到的字符串,如类名、函数名、参数名等字符串 保存在其中。字符串以UTF8编码保存,以#0字符分隔。流首总有一个#0字符代表一个 空字符串,分析示例代码如下。 constructor TJclClrStringsStream.Create( const AMetadata: TJclPeMetadata; const AHeader: PClrStreamHeader); var pch: PChar; off: DWORD; begin inherited; FStrings := TStringList.Create; pch := Data; off := 0; while off < Size do begin if pch^ <> #0 then FStrings.AddObject(pch, TObject(off)); pch := pch + StrLen(pch) + 1; off := DWORD(pch - Data); end; end; 如碰到#0字符,直接作为空字符串跳过之,否则,以StrLen取得字符串长度, 并将字符串保存到一个字符串列表中,使用时需要动态将UTF8解码为Unicode。 function TJclClrStringsStream.GetString(const Idx: Integer): WideString; begin Result := UTF8ToWideString(FStrings.Strings[Idx]); end; 因为流中数据是以堆形式存放,指向#String流的索引是字符串偏移值。 2.2.2 #Guid 流 #Guid流格式很简单,就是一个GUID结构的数组,以流长度除以SizeOf(TGuid)就是 数组元素个数。因为流中数据是以数组形式存放,指向#Guid流的索引是以1开始的索引值 , 如索引为0则表示此项索引不存在。(注意,CLR中所有索引都是以1开始的,0表示索引值 不存在) 示例代码如下: constructor TJclClrGuidStream.Create( const AMetadata: TJclPeMetadata; const AHeader: PClrStreamHeader); var I: Integer; pg: PGUID; begin inherited; SetLength(FGuids, Size div SizeOf(TGuid)); pg := Data; for I:=0 to GetGuidCount-1 do begin FGuids[I] := pg^; Inc(pg); end; end; 2.2.3 #Blob 和 #US 流 #Blob流是一个二进制数据堆,程序中的所有非字符串形式数据都堆放在这个流里面, 如常数的值,Public Key的值,方法的Signature等等。 在每个二进制数据块头,都有一个块长度数据,但为了节约存储空间,CLR使用了比较 麻烦的编码方法。 如果开始一个字节最高位为0,则此数据块长度为一个字节; 如果开始一个字节最高位为10,则此数据块长度为两个字节; 如果开始一个字节最高位为110,则此数据块长度为四个字节; 在屏蔽标志位后,通过移位运算即可计算出数据块的实际长度值,并依据此获得数据。 示例代码如下。 constructor TJclClrBlobRecord.Create(const AStream: TJclClrStream; const APtr: PByteArray); var b: Byte; AData: Pointer; ASize: DWORD; begin FPtr := APtr; FOffset := DWORD(FPtr) - DWORD(AStream.Data); b := FPtr[0]; if b = 0 then begin AData := @FPtr[1]; ASize := 0; end else if ((b and $C0) = $C0) and ((b and $20) = 0) then // 110bs begin AData := @FPtr[4]; ASize := ((b and $1F) shl 24) + (FPtr[1] shl 16) + (FPtr[2] shl 8) + FPtr[ 3]; end else if ((b and $80) = $80) and ((b and $40) = 0) then // 10bs begin AData := @FPtr[2]; ASize := ((b and $3F) shl 8) + FPtr[1]; end else begin AData := @FPtr[1]; ASize := b and $7F; end; Assert(not IsBadReadPtr(AData, ASize)); inherited Create(AData, ASize); end; constructor TJclClrBlobStream.Create( const AMetadata: TJclPeMetadata; const AHeader: PClrStreamHeader); var ABlob: TJclClrBlobRecord; begin inherited; FBlobs := TObjectList.Create; ABlob := TJclClrBlobRecord.Create(Self, Data); while Assigned(ABlob) do begin if ABlob.Size > 0 then FBlobs.Add(ABlob); if (Integer(ABlob.Memory) + ABlob.Size) < (Integer(Self.Data) + Integer(Se lf.Size)) then ABlob := TJclClrBlobRecord.Create(Self, Pointer(Integer(ABlob.Memory) + ABlob.Size)) else ABlob := nil; end; end; #US流是User String的存储空间,结构上和#Blob流相同,只是所有数据都是 Unicode格式的字符串。 #Blob和#US流的索引都是数据块偏移。 2.2.4 #~ 流 #~流是Metadata中最复杂也是最重要的信息所在,几乎所有Metadata信息 都以表的形式组织存放在#~流中。流起始处有一个#~ Stream Header如下 TClrTableStreamHeader = packed record Reserved: DWORD; // Reserved, always 0 MajorVersion, // Major version of table schemata, always 1 MinorVersion, // Minor version of table schemata, always 0 HeapSizes, // Bit vector for heap sizes. Reserved2: Byte; // Reserved, always 1 Valid, // Bit vector of present tables, let n be the number of b its that are 1. Sorted: Int64; // Bit vector of sorted tables. // Array of n four byte unsigned integers indicating the number of rows // for each present table. Rows: array[0..n-1] of DWORD; Tables: array[0..n-1] of ??? end; 字段MajorVersion.MinorVersion是表格式的版本号,一般设置为 1.0; 在介绍其他字段意义前,我们要先了解Metadata中表的表示方法。 在Metadata中,表最多有64种,每种表有一个唯一的确定的编号,如Assembly表的 编号为$20、Module表的编号为$00等等。目前的CLR只使用到了最多$2B(43)种表, 其间还有一些表编号是被保留或非公开的。对应表编号如下 TJclClrTableKind = ( ttModule, // $00 ttTypeRef, // $01 ttTypeDef, // $02 ttUnknown03, // $03 ttFieldDef, // $04 ttUnknown05, // $05 ttMethodDef, // $06 ttUnknown07, // $07 ttParamDef, // $08 ttInterfaceImpl, // $09 ttMemberRef, // $0a ttConstant, // $0b ttCustomAttribute, // $0c ttFieldMarshal, // $0d ttDeclSecurity, // $0e ttClassLayout, // $0f ttFieldLayout, // $10 ttSignature, // $11 ttEventMap, // $12 ttUnknown13, // $13 ttEvent, // $14 ttPropertyMap, // $15 ttUnknown16, // $16 ttProperty, // $17 ttMethodSemantics, // $18 ttMethodImpl, // $19 ttModuleRef, // $1a ttTypeSpec, // $1b ttImplMap, // $1c ttFieldRVA, // $1d ttUnknown1e, // $1e ttUnknown1f, // $1f ttAssembly, // $20 ttAssemblyProcessor, // $21 ttAssemblyOS, // $22 ttAssemblyRef, // $23 ttAssemblyRefProcessor, // $24 ttAssemblyRefOS, // $25 ttFile, // $26 ttExportedType, // $27 ttManifestResource, // $28 ttNestedClass, // $29 ttUnknown2a, // $2a ttUnknown2b); // $2b 在#~流头中,使用了两个64位位图表示这些表的状态,如Valid位图中第$20位为1 则Assembly表在当前Metadata中存在,Sorted位图中第$00位为1则Module表已排序等等。 这里就涉及到在Metadata中非常重要的一个概念Token。一个Token是在一个Metadata 中可以唯一性确定一个记录的标识符,他是一个32位无符号整型数,高8位表示表的编号 低24位表示记录在表中的索引。因此一个Token为$20000001实际上表示编号为$20的 Assembly表中第1项记录。Metadata Unmanaged API就是使用Token来表示每个记录。 对Valid位图进行从低到高位扫描,发现一个表存在,则可到Rows中获取此表中记录 的行数,Rows数组大小为Valid位图中设置的位的数量,也就是Metadata中存在的表 的数量。而Tables则是实际数据的开始。 这里还有一个HeapSizes,表示其他几个流中索引的大小,位$01表示#String流, $02表示#Guid流,#04表示Blob流。如相应位设置为1,则表示对应流大于MaxWord 也就是大于2^16,因此需要以四字节来做索引,否则缺省用两字节做索引。 我们可以看到,为了节省存储空间,Metadata中使用了相对多的编码方式来合并字段 缩减尺寸,如后面将提到的Coded Index亦是如此。 2.3 相关信息 关于Metadata结构的详细定义可以在.Net FrameworkSDK的Tool Developers Guide 目录下的Partition II Metadata.doc文档中找到。 示例代码可以参考mono工程对metadata的分析,http://go-mono.com/ 或者下载JCL(Delphi Jedi Code Library)项目最新代码, http://groups.yahoo.com/group/jedi-jcl,或到www.sourceforge.org 上查找JCL项目,里面有我的较新版本的CLR分析代码JclCLR和相关使用示例ClrDemo 或者使用FreeVCS工具到demos.href.com:2106以账号jcluser1:jcluser1下载 最新版本的JCL代码。 2.4 小结 这一章我们大概了解了Metadata在PE中实际的物理结构和分析方法,在对整体结构 有所了解,对一般概念如Token等有所了解后,我们将在下一章开始对实际的Metadata 表进行分析,看看在Reflection或者Metadata Unmanaged API后面到底隐藏了些什么。 -- . 生命的意义在于 /\ ____\ /\_ \ /\_\ . . 希望 \ \ \___/_\/\ \ \/_/__ __ _ _★ . . 工作 \ \ ____\\ \ \ /\ \ /'__`\ /\`'_\ . . 爱你的人 \ \ \___/ \ \ \___\ \ \/\ __//\ \ \/ . . 和你爱的人 \ \___\ \ \_____\ \__\ \____\ \ \_\ . . …… \/___/ \/_____/\/__/\/____/ \/_/ @126.com. ※ 修改:·flier 於 Mar 18 23:04:54 修改本文·[FROM: 202.114.32.216] ※ 来源:·BBS 水木清华站 smth.org·[FROM: 202.114.32.216] 上一篇 返回上一页 回到目录 回到页首 下一篇
|