.NET平台PE结构分析之Metadata(一)
.NET平臺PE結構分析之Metadata(一)
強命名及其去除
?
?????? 首先,這不是一篇完整的參考,所以并沒有涉及Metadata的各個方面,而只是討論了與強命名有關的部分。所以,在開始前,先列出一些參考文獻,在閱讀過程中若遇到問題,可以直接從中查閱。
?
兩本書:The Common Language Infrastructure Annotated Standard(Addison Wesley)
????????????? Inside Microsoft .NET IL Assembler(Microsoft Corporation)
文章:? MS.Net CLR擴展PE結構分析? (作者:Flier Lu)
????????????? The .NET File Format?????????? (來自:codeproject.com)
當然,還有最權威的Framework SDK的文檔。
?
本文的例子文件:
?
1、什么是強命名(StrongName)
?????? 我的理解,強命名類似win32平臺下PE文件的checksum,用來對原始文件完整性進行驗證的。一般有兩個作用:一是不同版本的相同文件,Strongname是不一樣的,因此可以將其區分開;二是防止文件被修改,被修改的文件是無法運行的。第一點我們不關心,但是第二點就應該引起cracker們的注意了。一個加密非常簡單的文件,只要改動一個字節的數據(比如je變jne)就可以破解,結果修改了過后卻無法運行。郁悶啊!因此,在對有強命名的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 這個assembly的name塊中尋找.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的是16個Data Directory Table,最常見的是第1個輸出表和第2個輸入表。而.NET擴展的PE結構則由倒數第二個表指向,也就是Common Language Runtime header address and size(簡稱CLI),根據他,我們找到了CLI Header。以pskill.exe為例,CLI Header的RVA是2008,大小是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,然后把StrongNameSignature的RVA和SIZE 均填0。運行一下試試,還是出錯。當然,還有一處最重要的地方要修改,我們繼續。
注意第四項Metadata,他指出了Metadata表的RVA和大小。看一下,pskill的Metadata在RVA=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 6C6F 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 6C6F 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。因此,我們將Flags減1,然后將.PublicKey項(黑色斜體部分,指向BLOG中的指針)置0。現在才徹底修改完成。運行一下,OK。??????
????????????? 偶是怎么定義AssemblyDef的地方的呢?因為該表的第一項為HashAlgId,目前只有三種可能:00008004,00008003和0。如果是0,代表沒有SN。因此直接從#~開始,搜索00008004或者00008003,定義既可。但是有失敗的可能,因為不能保證AssemblyDef之前的表中沒有00008004或00008003,那樣的話就玩完了。不過我試了那么多程序,暫時沒有發現不能用。等回頭有空再把snRemover改成精確定位吧!
?
?
????????????? 要是你能堅持看到這,真得感謝你了,頭暈了吧!我打字都不行了。那就休息一下,下次再講講簡單的,因為最難的部分已經講完了。
?
?
By:tankaiha [NE365]
2006.04.28
Any bug, report to http://vxer.cn/
?
轉載于:https://www.cnblogs.com/cxd4321/archive/2008/06/04/1213313.html
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的.NET平台PE结构分析之Metadata(一)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 让你的程序只能运行一个实例
- 下一篇: server 2008 IIS 7下as