zoukankan      html  css  js  c++  java
  • .NET平台PE结构分析之Metadata(一)

     

    .NET平台PE结构分析之Metadata(一)

    强命名及其去除

     

           首先,这不是一篇完整的参考,所以并没有涉及Metadata的各个方面,而只是讨论了与强命名有关的部分。所以,在开始前,先列出一些参考文献,在阅读过程中若遇到问题,可以直接从中查阅。

     

    两本书:The Common Language Infrastructure Annotated StandardAddison Wesley

                  Inside Microsoft .NET IL AssemblerMicrosoft Corporation

    文章:  MS.Net CLR扩展PE结构分析  (作者:Flier Lu

                  The .NET File Format             (来自:codeproject.com

    当然,还有最权威的Framework SDK的文档。

     

    本文的例子文件:

     

    1、什么是强命名(StrongName

           我的理解,强命名类似win32平台下PE文件的checksum,用来对原始文件完整性进行验证的。一般有两个作用:一是不同版本的相同文件,Strongname是不一样的,因此可以将其区分开;二是防止文件被修改,被修改的文件是无法运行的。第一点我们不关心,但是第二点就应该引起cracker们的注意了。一个加密非常简单的文件,只要改动一个字节的数据(比如jejne)就可以破解,结果修改了过后却无法运行。郁闷啊!因此,在对有强命名的PE文件修改前,必须去掉其强命名。(太累了,下面一律简称SN。)

     

    2、怎么给程序加上SN

           这和cracker关系不大,但是了解一下有好处,至少有个感性的印象。

           第一步,是生成一个key文件,命令是:sdk安装目录\bin\sn.exe –k strong.snk

           第二步,把strong.snk文件的信息加入到你需要加密的Module当中,通常在AssemblyInfo.cs的文件中添加:

           [assembly: AssemblyKeyFile(@"完整路径\strong.snk")]

           然后编译生成就OK了。这样,一个含有SN的文件便生成。可以用我写的工具snView看一下(文件见末尾的pskill.exe,这是我N年前写的一个查杀进程的程序,已被加上SN):

           看一下它的保护效果,用UD打开文件,把末尾的一个字节从00改为01,再运行。报错,如下:

     

    3、去除强命名的两种方法

           下面介绍去除SN的两种方法,第一种手动,第二种自动。

     

    3.1、反汇编成il代码,修改后再编译成exe文件

           这个方法不多讲了,codeproject上有几篇文章详细说过:Building Security Awareness in .NET Assemblies : Part 3 - Learn to break strong name .NET Assemblies ,只大概说一下过程。

     

    1、  ildasm反汇编

    2、  .assembly 这个assemblyname块中寻找.publickey,如图:

           注意,会搜索到很多.publickeytoken,而且长度较短。这些都不是该文件(assembly,又叫装配件)的SN,而不过是其中的方法/类等等的唯一性标志。

    3、  删除选定的部分

    包含两个,一个是key的值,一个是.hash algorithm,这是计算该key的算法。 

    4、  再用ilasm进行编译

    ilasm /resource=psill.res pskill.il

     

    这时就可以对这个文件进行修改了。

    BUT,这种方法有两个缺点:一是麻烦,二是某些文件没法反汇编,或反汇编不完全,或反汇编后就无法再次汇编成功。(特别是混淆过的程序)

     

    3.2、直接在文件上修改

    这样最方便,但是,方便的前提是你知道.NET判断SN的数据及修改方法,这就要牵涉到Metadata了。

    原先网上有一个工具,叫snRemove,不过不好用,修改完了运行不了。这里先绍一个偶写的工具:snRemover,可以自动去除程序中的SN。下载请到http://vxer.cn/hmx

     

     

    下面介绍snRemover的原理。什么是Metadata?我们都知道,.NET下运行的PE文件类似JAVA,不是将指令编译成机器代码,而是编译成il中间代码,再在运行时进行既时编译(JIT)。这样,用一些软件可以直接打开PE文件,看到类名、方法名、指令等等。所有的这些东东,都是Metadata。我们的任务,就是在Metadata中,找到标识SN的地方并修改之。

    下面假定你已经对win32平台下PE结构有些了解了,讲述从简。

    PE文件中紧跟PE Header的是16Data Directory Table,最常见的是第1个输出表和第2个输入表。而.NET扩展的PE结构则由倒数第二个表指向,也就是Common Language Runtime header address and size(简称CLI),根据他,我们找到了CLI Header。以pskill.exe为例,CLI HeaderRVA2008,大小是48,算出物理偏移是1008。你现在就可以用UD打开pskill.exe跟着我走了。

    00001008h: 48 00 00 00 02 00 05 00 10 42 00 00 60 11 00 00 ; H........B..`...

    00001018h: 09 00 00 00 04 00 00 06 A8 26 00 00 65 1B 00 00 ; ........?..e...

    00001028h: 50 20 00 00 80 00 00 00 00 00 00 00 00 00 00 00 ; P ..€...........

    00001038h: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ; ................

    00001048h: 00 00 00 00 00 00 00 00                         ; ........

     

    CLI Header的结构如下:

    RVA

    Field

    Contents

    0x2008

    Cb(结构的大小)

    0x48

    0x200C

    MajorRuntimeVersion

    2

    0x200E

    MinorRuntimeVersion

    0

    0x2010

    MetaData

    0x2060

    0x2014

    Size of the Metadata

    0x148 =(RVA of Import Table) – (RVA of MetaData)

    0x2018

    Flags

    1

    0x201C

    EntryPointToken

    0x06000001 (Method #1 in TypeDef table)

    0x2020

    Resources

    0

    0x2028

    StrongNameSignature

    0

    0x2030

    CodeManagerTable

    0

    0x2038

    VTableFixups

    0

    0x2040

    ExportAddressTableJumps

    0

    0x2048

    ManagedNativeHeader

    0

     

    这里,出现了两处和SN有关的标识。一处是FLAGS,另一处是StrongNameSignature。对于FLAGS,有这个标志:

    COMIMAGE_FLAGS_STRONGNAMESIGNED (0x00000008) 

    如果这处标志被置位,则认为有SN。第二处则指出了SN数据的RVA和大小,也就是最开始用snView看到的。

    修改时,FLAGS标志位减去0x00000008,然后把StrongNameSignatureRVASIZE 均填0。运行一下试试,还是出错。当然,还有一处最重要的地方要修改,我们继续。

    注意第四项Metadata,他指出了Metadata表的RVA和大小。看一下,pskillMetadataRVA=4210处,也就是物理地址3210处。

    00003210h: 42 53 4A 42 01 00 01 00 00 00 00 00 0C 00 00 00 ; BSJB............

    00003220h: 76 32 2E 30 2E 35 30 37 32 37 00 00 00 00 05 00 ; v2.0.50727......

    00003230h: 6C 00 00 00 7C 05 00 00 23 7E 00 00 E8 05 00 00 ; l...|...#~..?..

    00003240h: 80 07 00 00 23 53 74 72 69 6E 67 73 00 00 00 00 ; €...#Strings....

    00003250h: 68 0D 00 00 80 01 00 00 23 55 53 00 E8 0E 00 00 ; h...€...#US.?..

    00003260h: 10 00 00 00 23 47 55 49 44 00 00 00 F8 0E 00 00 ; ....#GUID...?..

    00003270h: 68 02 00 00 23 42 6C 6F 62 00 00 00 00 00 00 00 ; h...#Blob.......

    00003280h: 02 00 00 01 57 15 02 00 09 01 00 00 00 FA 01 33 ; ....W........?3

    00003290h: 00 16 00 00 01 00 00 00 33 00 00 00 02 00 00 00 ; ........3.......

    000032a0h: 09 00 00 00 0A 00 00 00 0C 00 00 00 53 00 00 00 ; ............S...

    000032b0h: 0D 00 00 00 04 00 00 00 01 00 00 00 05 00 00 00 ; ................

    000032c0h: 01 00 00 00 00 00 0A 00 01 00 00 00 00 00 06 00 ; ................

     

    看一下文档中对Metadata的定义:

    Type

    Field

    Description

    DWORD

    lSignature

    “Magic” signature for physical metadata, currently 0x424A5342

    WORD

    iMajorVersion

    Major version (1 for the first release of the common language runtime)

    WORD

    iMinorVersion

    Minor version (1 for the first release of the common language runtime)

    DWORD

    iExtraData

    Reserved; set to 0

    DWORD

    iLength

    Length of the version string

    BYTE[]

    iVersionString

    Version string

    BYTE

    fFlags

    Reserved; set to 0

    BYTE

     

    [padding]

    WORD

    iStreams

    Number of streams

     

    第一项,Metadata根部的标识,ASC码“BSJB”。这样,以后我们在寻找它时就可以直接搜索“BSJB”既可。这里有一点注意,就是ASC码串VersionString是可变长度的,结束后再加一个fFlags,然后要和4字节对齐,也就是padding。这里,我们的版本号是v2.0.50727,前面iLength指出了长度是0C(十进制的12,已经是和4对齐的了,能整除),因此fFlags的地址就是00003220+0C=0000322C,后一个字节为空,又是padding。最后,05 00指出了Number of streams,共有几个数据流。

    Metadata中的数据都是存放在各种数据流stream里,比较重要的是“#~”和“#Strings”,后者保存了各种名称(比较混淆或者反混淆,就要从这个流着手,如果有机会,下次再讲),而与SN相关的则是#~流。它也是所有当中最复杂的。

    紧接着上面的数据,就是各个流的Header了:

    00003230h: 6C 00 00 00 7C 05 00 00 23 7E 00 00 E8 05 00 00 ; l...|...#~..?..

    00003240h: 80 07 00 00 23 53 74 72 69 6E 67 73 00 00 00 00 ; €...#Strings....

    00003250h: 68 0D 00 00 80 01 00 00 23 55 53 00 E8 0E 00 00 ; h...€...#US.?..

    00003260h: 10 00 00 00 23 47 55 49 44 00 00 00 F8 0E 00 00 ; ....#GUID...?..

    00003270h: 68 02 00 00 23 42 6C 6F 62 00 00 00 00 00 00 00 ; h...#Blob.......

     

    这个结构不难,如下:

    Type

    Field

    Description

    DWORD

    iOffset

    Offset in the file for this stream

    DWORD

    iSize

    Size of the stream in bytes

    char[]

    rcName

    Name of the stream; a zero-terminated ANSI string no longer than seven characters

     

     

    我们以#~为例

    00003230h: 6C 00 00 00 7C 05 00 00 23 7E 00 00 E8 05 00 00 ; l...|...#~..?..

    红色部分是RVA,相对于Metadata Root的,蓝色部分是大小,而黑色斜体就是“#~”的ASC码了。那为什么237E后要加两个字节的0呢?又忘了?因为字符串要与4字节对齐。我们来计算#~流的实际物理地址:offset=root + RVA=00003210+6C=0000327C

    0000327ch: 00 00 00 00 02 00 00 01 57 15 02 00 09 01 00 00 ; ........W.......

    0000328ch: 00 FA 01 33 00 16 00             

     

    对应的结构如下:

    Size

    Field

    Description

    4 bytes

    Reserved

    Reserved; set to 0.

    1 byte

    Major

    Major version of the table schema (1 for the first release of the common language runtime).

    1 byte

    Minor

    Minor version of the table schema (0 for the first release of the common language runtime).

    1 byte

    Heaps

    Binary flags indicate the offset sizes to be used within the heaps.

    A 4-byte unsigned integer offset is indicated by 0x01 for a string heap, 0x02 for a GUID heap, and 0x04 for a blob heap.

    If a flag is not set, the respective heap offset is presumed to be a 2-byte unsigned integer.

     

     

    A # stream can also have special flags set: flag 0x20, indicating that the stream contains only changes made during an edit-and-continue session, and flag 0x80, indicating that the metadata might contain items marked as deleted.

    1 byte

    Rid

    Bit count of the maximal record index to all tables of the metadata; calculated at run time (during the metadata stream initialization).

    8 bytes

    MaskValid

    Bit vector of present tables, each bit representing one table (1 if present).

    8 bytes

    Sorted

    Bit vector of sorted tables, each bit representing a respective table (1 if sorted).

     

    这里要讲一下#~流中各种数据的保存形式了。该流中保存的主要是各种表,这些表又定义了Metadata中其它的各种数据,所以才说它重要啊。现在微软已经定义的表有

    注意结构中的MaskValid数据,它是8字节的,对应2进制数有64位。从最低位开始,如果这个位为1,代表#~流中该表被定义了,如果为0,代表没有该表。我们看一下pskill的数据,为57 15 02 00 09 01 00 00,翻译为2进制为

    2进制:0000 0000 0000 0000 0000 0001 0000 1001 0000 0000 0000 0010 0001 0101 0101 0111

    16进制: 0   0    0   0    0   1    0   9    0   0    0   2    1   5   5    7

    这样我们就知道了一共有C个表被定义了,pskill中存在的表可以用Spices .Net看一下,再与上表对应一下,看看是不是相等:

     

     

    同时,我们点击了第20个表,AssemblyDef,看到了右边的数据显示出了PublicKey,那不正是我们要找的SN吗。

                  接下来的工作就是计算AssemblyDef前面表的大小,然后直到找到AssemblyDef为止。剩下的不多讲了,可以看codeproject的那篇THE .NET File Format。但是这个过程是非常烦索的,我写的强命名去除工具snRemover也没有说细的计算,而是选择一个比较偷懒的方法。下面再说。我们先来到AssemblyDef处:

    0000376eh: 04 80 00 00 01 00 00 00 05 09 64 5F 01 00 00 00 ; .€........d_....

    0000377eh: 46 00 1B 00 00                                  ; F....

     

    来看一下AssemblyDef的定义:

     

    HashAlgId (a 4-byte constant of type AssemblyHashAlgorithm).

    MajorVersion, MinorVersion, BuildNumber, RevisionNumber

    (2-byte constants).

    Flags (a 4-byte bit mask of type AssemblyFlags).

    PublicKey (index into Blob heap).

    Name (index into String heap).

    Culture (index into String heap).

    一共有6项,其中Flags项有一个常数为

    afPublicKey = 0x0001,

    // The assembly ref holds the full (unhashed) public key.

    也就是说,如果Flags(数据中蓝色部分)的第一位被置1,则认为它有SN。因此,我们将Flags1,然后将.PublicKey项(黑色斜体部分,指向BLOG中的指针)置0。现在才彻底修改完成。运行一下,OK      

                  偶是怎么定义AssemblyDef的地方的呢?因为该表的第一项为HashAlgId,目前只有三种可能:00008004,000080030。如果是0,代表没有SN。因此直接从#~开始,搜索00008004或者00008003,定义既可。但是有失败的可能,因为不能保证AssemblyDef之前的表中没有0000800400008003,那样的话就玩完了。不过我试了那么多程序,暂时没有发现不能用。等回头有空再把snRemover改成精确定位吧!

     

     

                  要是你能坚持看到这,真得感谢你了,头晕了吧!我打字都不行了。那就休息一下,下次再讲讲简单的,因为最难的部分已经讲完了。

     

     

    By:tankaiha [NE365]

    2006.04.28

    Any bug, report to http://vxer.cn/

     

  • 相关阅读:
    pat 1123 Is It a Complete AVL Tree
    pat 1098 Insertion or Heap Sort
    pat 1147 Heaps
    Python中的Dict底层 | 字典类型删除数据为什么不直接删除?
    MySQL | 重置root密码
    MySQL | 安装后初次使用
    安装MySQL | 报缺失文件的错误
    IDEA | 不使用骨架创建Maven项目
    python | list.sort()和sorted(list)的区别
    python | 字符串不可修改
  • 原文地址:https://www.cnblogs.com/cxd4321/p/1213313.html
Copyright © 2011-2022 走看看