.NET平台PE结构分析之Metadata(一)
.NET平臺(tái)PE結(jié)構(gòu)分析之Metadata(一)
強(qiáng)命名及其去除
?
?????? 首先,這不是一篇完整的參考,所以并沒(méi)有涉及Metadata的各個(gè)方面,而只是討論了與強(qiáng)命名有關(guān)的部分。所以,在開(kāi)始前,先列出一些參考文獻(xiàn),在閱讀過(guò)程中若遇到問(wèn)題,可以直接從中查閱。
?
兩本書(shū):The Common Language Infrastructure Annotated Standard(Addison Wesley)
????????????? Inside Microsoft .NET IL Assembler(Microsoft Corporation)
文章:? MS.Net CLR擴(kuò)展PE結(jié)構(gòu)分析? (作者:Flier Lu)
????????????? The .NET File Format?????????? (來(lái)自:codeproject.com)
當(dāng)然,還有最權(quán)威的Framework SDK的文檔。
?
本文的例子文件:
?
1、什么是強(qiáng)命名(StrongName)
?????? 我的理解,強(qiáng)命名類(lèi)似win32平臺(tái)下PE文件的checksum,用來(lái)對(duì)原始文件完整性進(jìn)行驗(yàn)證的。一般有兩個(gè)作用:一是不同版本的相同文件,Strongname是不一樣的,因此可以將其區(qū)分開(kāi);二是防止文件被修改,被修改的文件是無(wú)法運(yùn)行的。第一點(diǎn)我們不關(guān)心,但是第二點(diǎn)就應(yīng)該引起cracker們的注意了。一個(gè)加密非常簡(jiǎn)單的文件,只要改動(dòng)一個(gè)字節(jié)的數(shù)據(jù)(比如je變jne)就可以破解,結(jié)果修改了過(guò)后卻無(wú)法運(yùn)行。郁悶啊!因此,在對(duì)有強(qiáng)命名的PE文件修改前,必須去掉其強(qiáng)命名。(太累了,下面一律簡(jiǎn)稱(chēng)SN。)
?
2、怎么給程序加上SN
?????? 這和cracker關(guān)系不大,但是了解一下有好處,至少有個(gè)感性的印象。
?????? 第一步,是生成一個(gè)key文件,命令是:sdk安裝目錄\bin\sn.exe –k strong.snk。
?????? 第二步,把strong.snk文件的信息加入到你需要加密的Module當(dāng)中,通常在AssemblyInfo.cs的文件中添加:
?????? [assembly: AssemblyKeyFile(@"完整路徑\strong.snk")]
?????? 然后編譯生成就OK了。這樣,一個(gè)含有SN的文件便生成。可以用我寫(xiě)的工具snView看一下(文件見(jiàn)末尾的pskill.exe,這是我N年前寫(xiě)的一個(gè)查殺進(jìn)程的程序,已被加上SN):
?????? 看一下它的保護(hù)效果,用UD打開(kāi)文件,把末尾的一個(gè)字節(jié)從00改為01,再運(yùn)行。報(bào)錯(cuò),如下:
?
3、去除強(qiáng)命名的兩種方法
?????? 下面介紹去除SN的兩種方法,第一種手動(dòng),第二種自動(dòng)。
?
3.1、反匯編成il代碼,修改后再編譯成exe文件
?????? 這個(gè)方法不多講了,codeproject上有幾篇文章詳細(xì)說(shuō)過(guò):Building Security Awareness in .NET Assemblies : Part 3 - Learn to break strong name .NET Assemblies?,只大概說(shuō)一下過(guò)程。
?
1、? 用ildasm反匯編
2、? 在.assembly 這個(gè)assembly的name塊中尋找.publickey,如圖:
?????? 注意,會(huì)搜索到很多.publickeytoken,而且長(zhǎng)度較短。這些都不是該文件(assembly,又叫裝配件)的SN,而不過(guò)是其中的方法/類(lèi)等等的唯一性標(biāo)志。
3、? 刪除選定的部分
包含兩個(gè),一個(gè)是key的值,一個(gè)是.hash algorithm,這是計(jì)算該key的算法。
4、? 再用ilasm進(jìn)行編譯
ilasm /resource=psill.res pskill.il
?
這時(shí)就可以對(duì)這個(gè)文件進(jìn)行修改了。
BUT,這種方法有兩個(gè)缺點(diǎn):一是麻煩,二是某些文件沒(méi)法反匯編,或反匯編不完全,或反匯編后就無(wú)法再次匯編成功。(特別是混淆過(guò)的程序)
?
3.2、直接在文件上修改
這樣最方便,但是,方便的前提是你知道.NET判斷SN的數(shù)據(jù)及修改方法,這就要牽涉到Metadata了。
原先網(wǎng)上有一個(gè)工具,叫snRemove,不過(guò)不好用,修改完了運(yùn)行不了。這里先紹一個(gè)偶寫(xiě)的工具:snRemover,可以自動(dòng)去除程序中的SN。下載請(qǐng)到http://vxer.cn/hmx
?
?
下面介紹snRemover的原理。什么是Metadata?我們都知道,.NET下運(yùn)行的PE文件類(lèi)似JAVA,不是將指令編譯成機(jī)器代碼,而是編譯成il中間代碼,再在運(yùn)行時(shí)進(jìn)行既時(shí)編譯(JIT)。這樣,用一些軟件可以直接打開(kāi)PE文件,看到類(lèi)名、方法名、指令等等。所有的這些東東,都是Metadata。我們的任務(wù),就是在Metadata中,找到標(biāo)識(shí)SN的地方并修改之。
下面假定你已經(jīng)對(duì)win32平臺(tái)下PE結(jié)構(gòu)有些了解了,講述從簡(jiǎn)。
在PE文件中緊跟PE Header的是16個(gè)Data Directory Table,最常見(jiàn)的是第1個(gè)輸出表和第2個(gè)輸入表。而.NET擴(kuò)展的PE結(jié)構(gòu)則由倒數(shù)第二個(gè)表指向,也就是Common Language Runtime header address and size(簡(jiǎn)稱(chēng)CLI),根據(jù)他,我們找到了CLI Header。以pskill.exe為例,CLI Header的RVA是2008,大小是48,算出物理偏移是1008。你現(xiàn)在就可以用UD打開(kāi)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的結(jié)構(gòu)如下:
| RVA
| Field
| Contents
|
| 0x2008 | Cb(結(jié)構(gòu)的大小) | 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 |
?
這里,出現(xiàn)了兩處和SN有關(guān)的標(biāo)識(shí)。一處是FLAGS,另一處是StrongNameSignature。對(duì)于FLAGS,有這個(gè)標(biāo)志:
COMIMAGE_FLAGS_STRONGNAMESIGNED (0x00000008)?
如果這處標(biāo)志被置位,則認(rèn)為有SN。第二處則指出了SN數(shù)據(jù)的RVA和大小,也就是最開(kāi)始用snView看到的。
修改時(shí),FLAGS標(biāo)志位減去0x00000008,然后把StrongNameSignature的RVA和SIZE 均填0。運(yùn)行一下試試,還是出錯(cuò)。當(dāng)然,還有一處最重要的地方要修改,我們繼續(xù)。
注意第四項(xiàng)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 ; ................
?
看一下文檔中對(duì)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 |
?
第一項(xiàng),Metadata根部的標(biāo)識(shí),ASC碼“BSJB”。這樣,以后我們?cè)趯ふ宜鼤r(shí)就可以直接搜索“BSJB”既可。這里有一點(diǎn)注意,就是ASC碼串VersionString是可變長(zhǎng)度的,結(jié)束后再加一個(gè)fFlags,然后要和4字節(jié)對(duì)齊,也就是padding。這里,我們的版本號(hào)是v2.0.50727,前面iLength指出了長(zhǎng)度是0C(十進(jìn)制的12,已經(jīng)是和4對(duì)齊的了,能整除),因此fFlags的地址就是00003220+0C=0000322C,后一個(gè)字節(jié)為空,又是padding。最后,05 00指出了Number of streams,共有幾個(gè)數(shù)據(jù)流。
Metadata中的數(shù)據(jù)都是存放在各種數(shù)據(jù)流stream里,比較重要的是“#~”和“#Strings”,后者保存了各種名稱(chēng)(比較混淆或者反混淆,就要從這個(gè)流著手,如果有機(jī)會(huì),下次再講),而與SN相關(guān)的則是#~流。它也是所有當(dāng)中最復(fù)雜的。
緊接著上面的數(shù)據(jù),就是各個(gè)流的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.......
?
這個(gè)結(jié)構(gòu)不難,如下:
| 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,相對(duì)于Metadata Root的,藍(lán)色部分是大小,而黑色斜體就是“#~”的ASC碼了。那為什么237E后要加兩個(gè)字節(jié)的0呢?又忘了?因?yàn)樽址c4字節(jié)對(duì)齊。我們來(lái)計(jì)算#~流的實(shí)際物理地址: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?????????????
?
對(duì)應(yīng)的結(jié)構(gòu)如下:
| 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). |
?
這里要講一下#~流中各種數(shù)據(jù)的保存形式了。該流中保存的主要是各種表,這些表又定義了Metadata中其它的各種數(shù)據(jù),所以才說(shuō)它重要啊。現(xiàn)在微軟已經(jīng)定義的表有
注意結(jié)構(gòu)中的MaskValid數(shù)據(jù),它是8字節(jié)的,對(duì)應(yīng)2進(jìn)制數(shù)有64位。從最低位開(kāi)始,如果這個(gè)位為1,代表#~流中該表被定義了,如果為0,代表沒(méi)有該表。我們看一下pskill的數(shù)據(jù),為57 15 02 00 09 01 00 00,翻譯為2進(jìn)制為
2進(jìn)制:0000 0000 0000 0000 0000 0001 0000 1001 0000 0000 0000 0010 0001 0101 0101 0111
16進(jìn)制: 0?? 0??? 0?? 0??? 0?? 1??? 0?? 9??? 0?? 0??? 0?? 2??? 1?? 5?? 5??? 7
這樣我們就知道了一共有C個(gè)表被定義了,pskill中存在的表可以用Spices .Net看一下,再與上表對(duì)應(yīng)一下,看看是不是相等:
?
?
同時(shí),我們點(diǎn)擊了第20個(gè)表,AssemblyDef,看到了右邊的數(shù)據(jù)顯示出了PublicKey,那不正是我們要找的SN嗎。
????????????? 接下來(lái)的工作就是計(jì)算AssemblyDef前面表的大小,然后直到找到AssemblyDef為止。剩下的不多講了,可以看codeproject的那篇THE .NET File Format。但是這個(gè)過(guò)程是非常煩索的,我寫(xiě)的強(qiáng)命名去除工具snRemover也沒(méi)有說(shuō)細(xì)的計(jì)算,而是選擇一個(gè)比較偷懶的方法。下面再說(shuō)。我們先來(lái)到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....
?
來(lái)看一下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項(xiàng),其中Flags項(xiàng)有一個(gè)常數(shù)為
afPublicKey = 0x0001,
// The assembly ref holds the full (unhashed) public key.
也就是說(shuō),如果Flags(數(shù)據(jù)中藍(lán)色部分)的第一位被置1,則認(rèn)為它有SN。因此,我們將Flags減1,然后將.PublicKey項(xiàng)(黑色斜體部分,指向BLOG中的指針)置0。現(xiàn)在才徹底修改完成。運(yùn)行一下,OK。??????
????????????? 偶是怎么定義AssemblyDef的地方的呢?因?yàn)樵摫淼牡谝豁?xiàng)為HashAlgId,目前只有三種可能:00008004,00008003和0。如果是0,代表沒(méi)有SN。因此直接從#~開(kāi)始,搜索00008004或者00008003,定義既可。但是有失敗的可能,因?yàn)椴荒鼙WCAssemblyDef之前的表中沒(méi)有00008004或00008003,那樣的話(huà)就玩完了。不過(guò)我試了那么多程序,暫時(shí)沒(méi)有發(fā)現(xiàn)不能用。等回頭有空再把snRemover改成精確定位吧!
?
?
????????????? 要是你能堅(jiān)持看到這,真得感謝你了,頭暈了吧!我打字都不行了。那就休息一下,下次再講講簡(jiǎn)單的,因?yàn)樽铍y的部分已經(jīng)講完了。
?
?
By:tankaiha [NE365]
2006.04.28
Any bug, report to http://vxer.cn/
?
轉(zhuǎn)載于:https://www.cnblogs.com/cxd4321/archive/2008/06/04/1213313.html
與50位技術(shù)專(zhuān)家面對(duì)面20年技術(shù)見(jiàn)證,附贈(zèng)技術(shù)全景圖總結(jié)
以上是生活随笔為你收集整理的.NET平台PE结构分析之Metadata(一)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 让你的程序只能运行一个实例
- 下一篇: 黯淡的蓝点