淺析MSIL中間語言——PE文件結構篇

轉自 https://www.cnblogs.com/dwlsxj/p/4052871.htmlhtml

1、開篇數組

  開篇我想講一下於本文無關的話題,其實我很想美化一下本身博客園一直沒時間弄,無心間找了博客園李寶亨的博客園裏面有一篇分享本身主題的文章,我就將這個模板暫時用做個人blog主題,我要講述一個關於PE文件結構的文章,這篇文章動手能力比較強,但願你們可以動手進行操做,這邊文章篇幅有可能會長一些,爲了方便你們閱讀我能夠將其分爲幾個部分進行講解,主要分爲如下幾個部分:數據結構

  ①  PE文件頭編輯器

  ②  導入表spa

  ③  導出表命令行

  ④  資源表3d

  下面我來說解下爲何要學PE文件結構,由於瞭解PE文件結構就會了解到數據字典中第十五存放的就是元數據經過這個能夠進一步研究元數據結構,至於.NET的 PE文件結構下一次進行分析版本控制

2、.NET的特殊之處

    這裏咱們不講普通程序的PE文件結構,咱們只針對當前.NET程序進行分析,瞭解普通的PE文件結構後,咱們會知道.NET的PE結構不一樣之處在於在PE頭中的IMAGE_OPTIONAL_HEARDER這個結構中的數據目錄DataDirectory這個包括了映像文件中的CLR頭的RVA和大小。這就使咱們可以很快的進行擴展.NET的PE文件結構,下面咱們就對文件進行分析,隨便找一個.NET的程序,我這裏有一個程序,咱們用16進制編輯器打開,找到數據目錄的第十五個,這個對應的2個字節的CLR頭RVA和2個字節的大小。code

  如今咱們來記錄下這個記錄:
  CLR頭:RVA:0x2008   size:0x48htm

  既然咱們知道了CLR頭的RVA和大小那麼咱們計算他在磁盤中的RVA也就是定位在磁盤中的位置,這裏咱們還須要其餘幾個區段的RVA和文件中的大小,這裏咱們就不在16進制編輯器中進行查找了咱們直接打開,CFF Explorer將程序載入後咱們查看區塊表信息:

  那麼咱們就開始進行定位,定位該區段在文件中的地址,這裏咱們來看這個CLR頭的RVA落在了那個區段上,CLR的RVA爲0x2008,咱們首先看的是第一個區段.text,該區段裝在在內存的地址是0x2000,而這個區段的大小事0x12A00,因此這個區段的範圍是0x2000-0x14A00,恰好0x2008落在這個區段上,那麼咱們來算出他在文件中的偏移,2008-2000=8,200+8=208;也就是0x208的位置是CLR在文件的RVA。下面實例圖將表述算出來的過程:

  這張圖已經很清楚的說明了這個RVA的換算公式,也是咱們在這裏標出來的?號處就是咱們要的東西,這裏內存中的CLR頭是內存裏面的地址0x2008而區段的開始RVA是0x2000這樣就是S=0x2000,R=.0x2008那麼差值=8,P=0x200這樣的話?=0x208這樣就算出了文件的RVA;

這樣咱們找到了CLR頭的RVA 咱們就來16進制編輯器中進行查看CLR頭,下面是CLR頭的結構:

 

複製代碼
typedef struct IMAGE_COR20_HEADER
{
    ULONG         cb;
    USHORT        MajorRuntimeVersion;
    USHORT        MinorRuntimeVersion;
    //符號表和開始信息
    IMAGE_DATA_DIRECTORY    MetaData;
    ULONG        Flags;
    union{
        DWORD    EntryPointToken;
        DWORD    EntryPointRVA;
               };
     //綁定信息
      IMAGE_DATA_DIRECTORY     Resource;
      IMAGE_DATA_DIRECTORY    StrongNameSignature;
      //常規的定位和綁定信息
      IMAGE_DATA_DIRECTORY    CodeMagagerTable;
      IMAGE_DATA_DIRECTORY    VTableFixups;
      IMAGE_DATA_DIRECTORY    ExprotAddressTableJumps;

      IMAGE_DATA_DIRECTORY    MagageNativeHeader;
}IMAGE_COR20_HEADER
複製代碼

   下面是對應字段的描述和對應的大小偏移量等等信息:

偏移量

大小

字段名

描述

0

4

Cb

頭的字節大小。

4

2

MajorRuntimeVersion

CLR須要運行程序的最小版本的主版本號。

6

2

MinorRuntimeVersion

CLR須要運行程序的最小版本的次版本號。

8

8

MetaData

RVA和元數據的大小。

16

4

Flags

二進制標記,在接下來的章節討論。在ILAsm中,你能夠經過顯示地使用指令.corflags <integer value>和/或命令行選項/FLAGS=<integer value>詳細指明這個值。這個命令行選項優先於指令。

20

4

EntryPointToken/EntryPointRVA

這個映像文件的入口點的元數據識別符(符號);對於DLL映像而言能夠是0。這個字段識別了屬於這個模塊的一個方法或包括這個入口點方法的一個模塊。在2.0或更新的版本中,這個字段可能包括內嵌的本地入口點方法的RVA

24

8

Resources

RVA和託管資源的大小。

32

8

StrongNameSignature

RVA和用於這個PE文件的哈希數據的大小,由加載器在綁定和版本控制中使用。

40

8

CodeManagerTable

RVA和代碼管理表的大小。在現有的CLR發佈版本中,這個字段是保留的,並被設置爲0。

48

8

VTableFixups

RVA和一個由虛擬表(v-表)修正組成的數組的字節大小。在當前託管的編譯器中,只有VC++鏈接器和IL編譯器可以生成這個數組。

56

8

ExportAddressTableJumps

RVA和由jump thunk的地址組成的數組的大小。在託管的編譯器中,只有8.0以前版本的VC++可以生成這種表,這將容許導出內嵌在託管PE文件中的非託管本地方法。在CLR的2.0版本中,這個入口是廢棄的而且必須被設置爲0。

64

8

ManagedNativeHeader

爲預編譯映像而保留的,被設置爲0。

  既然咱們已經知道了整個CLR頭的結構,那麼咱們就來對.NET的這個文件進行十六進制查找下:CTRL+G查找0x208

  對應這一塊就是CLR頭的數據,咱們能夠一步一步進行分析,好比cb佔2個字節那麼他就是00000048這個數據,以此進行分析能夠將全部數據進行分析出來。注意是這裏面是以小端的形式存放,也就是他要從後面的是高位,前面的是地位。

  那麼我能夠注意到這個字段StrongNameSignature這個字段就是強命名的字段,若是程序加了強命名咱們的一種手段就是將這個RVA和大小所有設置爲0就去除了強命名。還有就是Flags標誌位,標誌裏面去除COMIMAGE_FLAGEX_STRONGNAMESIGNED=0x00000008//此程序有強命名。

    這裏咱們要強調的是根據表中最重要的MetaData項,來查看元數據在PE文件中的存儲格式,咱們能夠在上圖中尋找到:

  其中元數據(MetaData)的RVA:0000B2D8,元數據的大小爲:00009534,經過這個RVA咱們能夠將其換算成文件地址,那麼這個RVA落在了第一個區段上也就是.text段上,這樣的話咱們就能夠換算出文件中的RVA:0x94D8,那麼咱們就能夠在16進制編輯器中查看元數據頭的結構。首先咱們先看一下總體結構是什麼:

        
  

類型

  
  

字段

  
  

描述

  

DWORD

lSignature

424A5342h,就是4個固定的ASCII碼,表明.NET四個創始人的首位字母縮寫

WORD

iMajorVersion

元數據的主版本,通常爲1

WORD

iMinorVersion

元數據的副版本,通常爲1

DWORD

iExtraData

保留,爲0

DWORD

iLength

接下來版本字符串的長度,包含尾部0,且按4字節對其

BYTE[ ]

iVersionString

UTF8格式的編譯環境版本號

BYTE

fFlags

保留爲0

BYTE

 [padding]

此字節無心義,對齊用

WORD

iStreams

NStream的個數(流的個數)

  既然咱們已經瞭解了元數據頭的結構以後咱們就對應的RVA看一下16進制編輯器裏面的內容:

  這裏咱們就不將全部的字段的值取出來咱們直接用CFF來看一下咱們查找的數據是否是正確的;

 

  其實這裏面最重要的就是咱們要看一下流到底有多少個,這裏面最後一個字段就是iStreams這裏面顯示的是5,那麼就說明有5個流數據,接下來就開始分析幾個流數據,緊接着元數據頭即是幾個流數據的頭,流按存儲結構的不一樣分爲堆(heap)和表(Table),在元數據中堆是用來存儲字符串和二進制對象。堆分爲如下三種:

  #Strings:UTF8格式的字符串堆,包含各類元數據的名稱(好比類名,方法名,成員名,參數等),以0開始以0結尾。

  #Blob:二進制數據堆,存儲程序中非字符串信息,好比常量值,方法的signature、pubicKey等。每一個數據的長度由該數據的前1-3爲決定:0表示長度1字節,10表示長度2字節,110表示長度4字節。

  #GUID:存儲全部的全局惟一標識

  #US:用戶自定義字符串

  #~:元數據表流,重要的流,幾乎全部元數據的信息都以表的形式存在

  上面咱們已經說起到了,MetaData Root緊接着就是流數據,那麼咱們先看一下流數據的結構,方便咱們對其進行分析:

        

  

大小

  
  

字段

  
  

描述

  

DWORD

iOffset

該流的存儲位置相對於MetaData   Root的偏移

DWORD

iSize

該流佔多少字節

char[]

rcName

流的名稱,與4字節對齊

 

    既然咱們看到流數據頭的結構咱們能夠發現iOffset這個字段是關於流存儲的位置,也就是流數據頭裏面存放的是真正流數據的位置,那麼咱們上面找到的元數據頭的地址是RVA:0x94D8這樣的話咱們就能夠找到真正的對應的流數據了!那麼咱們先看一下總體的流數據,咱們已經知道一共有5個流數據。

  其中的紅色「|」標示着下一個流數據結構的開始,相應對應的結果我用CFF更直觀的展示給你們看,這樣咱們就能夠進行一個詳細的對比;

通過咱們上下數據的比較數據徹底符合那麼,就說明咱們流數據頭找的是正確的。

既然咱們將流數據頭找出來,咱們就對這5個流數據進行分析,這裏咱們就單純的講一下#~流,由於這個是.NET都要存在的!上面咱們能夠看到#~流相對於MetaData的偏移量是0x6C,0x94D8+0x6C就是真正該流數據的存儲位置:0x9544,好的,既然已經尋找到了這個地址那麼先來了解下#~內部存儲結構是什麼樣的?請看下錶:

        
  

大小

  
  

字段

  
  

描述

  

4 bytes

Reserved

保留,爲0

1 byte

Major

元數據表的主版本號,於.NET主版本號一致

1 byte

Minor

元數據的副版本號,通常爲0

1 byte

Heaps

Heap中定義數據時的索引的大小,爲0表示16位索引值,若堆中數據超出16位數據表示範圍,則使用32位索引值。01表明strings堆,02h表明GUID堆04h表明blob堆

1 byte

Rid

全部元數據表中記錄最大索引值,在運行時有.NET計算,文件中一般爲1

8 bytes

MaskValid

8字節長度的掩碼,每一個爲表明一個表,爲1表示該表有效,爲0表示該表無效

8 bytes

Sorted

8字節長度的掩碼,每一個爲表明一個表,爲1表示該表已排序,反之爲0

  下面咱們來看一下該程序的#~元數據表流的存儲內容,將程序載入到16進制編輯器中,CTRL+G進行搜索0x9544,這個地址就是元數據表流的開始位置:以下所示:

  紅色地方表明的是Vaild,其中的數據是0XF0929B69D57,那麼將其換算成二進制,看一下哪一些表是有效的,二進制數據以下圖所示:

  其中紅色部分表示表數據是有效的一共有24個表,元數據中全部的表:

00-Module

01-TypeRef

02-TypeDef

03-FiledPtr

04-Filed

05-MethodPtr

06-MethodDef

07-ParamPtr

08-Param

09-MethodImpl

10-MemberRef

11-Constant

12-CustomAttribute

13-FieldMarshal

14-DeclSecurity

15-ClassLayout

16-FieldLayout

17-StandAloneSig

18-EventMap

19-EventPtr

20-Event

21-PropertyMap

22-PropertyPtr

23-Property

24-MethodSemantics

25-MethodImpl

26-ModuleRef

27-TypeSpec

28-ImplMap

29-FiledRVA

30-ENCLog

31-ENCMap

32-AssemblyRef

33-AssemblyProcessor

34-AssemblyOS

35-Assembly

36- AssemblyRefProcessor

37- AssemblyRefOS

38- File

39-ExportedType

40-ManifestResource

41- NestedClass

42-GenericParam

43-MethodSpec

44-GenericParamConstraint

  緊接着元數據表頭的是一串4字節數組,每一個雙字節表明該表中有多少項紀錄(record),本程序中存在24個表那麼就是,24*4=144個字節。那麼咱們就從元數據頭結尾處進行查找:

  咱們來驗證一下正確性使用CFF來看一下:

  通過咱們的驗證確實是Module裏面只有一條紀錄。點開就能夠看到內部結構是什麼!這裏咱們不去講全部表的結構。

這樣咱們已經知道了元數據是描述數據的數據,那麼這句話要怎麼理解呢?那麼就來用一個例子來解釋下這個說明的含義:好比該程序咱們將其反編譯成IL代碼,查看IL代碼的元數據.

  這裏我要不去講這個Token的由來,我只講一下這個Token怎麼去索引,前面好比這個02000002,前面的02表明在元數據表中的第二個表也就是TypeDef表,至於表內部的結構本身能夠再進行研究。那麼後面的02表明的是什麼呢?表明的是表裏面的第二條紀錄。截圖說明下:

  和IL圖中描述一致:

  至於剩下的#Strings堆都是一些二進制形式存在的數據。爲了節省篇幅就到此了!其餘的自行分析!

相關文章
相關標籤/搜索