出閉環(huán))
簡(jiǎn)介面向.NET開(kāi)發(fā)者的Reflector 7.4.1.179綠色注冊(cè)版工具包定位于程序集反編譯、源碼導(dǎo)出與閉源組件研究適合需要分析DLL實(shí)現(xiàn)、學(xué)習(xí)控件設(shè)計(jì)或排查依賴問(wèn)題的中高級(jí)開(kāi)發(fā)者。壓縮包共21個(gè)文件約4.79MB包含主程序與命令行工具等3個(gè)exe、6個(gè)dll、4個(gè)txt及rtf說(shuō)明文檔、config配置文件等核心是真正集成的FileDisassembler與FileGenerator兩大常用插件可輔助批量導(dǎo)出源文件、查看程序集結(jié)構(gòu)。已有395人學(xué)習(xí)下載。其價(jià)值在于免安裝注冊(cè)即可使用省去單獨(dú)尋找和配置插件的麻煩對(duì)閉源DLL的源碼還原、控件改造參考、代碼審查與教學(xué)演示較有幫助也能為.NET逆向分析提供一套可直接解壓使用的工具組合。對(duì)于需要研究非開(kāi)源組件實(shí)現(xiàn)方式的開(kāi)發(fā)者導(dǎo)出后的源碼雖缺少注釋且變量名有所變化但整體結(jié)構(gòu)仍具參考意義便于在理解邏輯后補(bǔ)充說(shuō)明并做少量改造復(fù)用。1. 先別急著雙擊Reflector 7.4.1.179 綠色版最容易在插件這一步出錯(cuò)Reflector 7.4.1.179 綠色注冊(cè)版真正值錢的地方不是反編譯本身而是包里已經(jīng)把 FileDisassembler 和 FileGenerator 這兩個(gè)插件一起塞進(jìn)來(lái)了前者能把整個(gè)程序集一次性導(dǎo)出成按命名空間分目錄的源碼樹(shù)后者能把散落的 .cs 文件組裝成能直接打開(kāi)的工程。聽(tīng)起來(lái)解壓就能用可我見(jiàn)過(guò)太多人卡在同一處——主程序能啟動(dòng)Add-Ins 列表里空空如也或者勾上了插件、菜單就是不出現(xiàn)。綠色版省掉的是安裝向?qū)Р皇沁\(yùn)行庫(kù)和目錄約定.NET Framework 版本、插件 DLL 的位數(shù)、AddIns 目錄的掃描規(guī)則任何一項(xiàng)對(duì)不上這套東西就退化成只能看、不能導(dǎo)出的單機(jī)反編譯器。下面按我實(shí)際部署的順序走先列環(huán)境再掛插件再配導(dǎo)出參數(shù)最后用自己編譯的 Demo 把整條鏈路驗(yàn)一遍。2. 裝前環(huán)境對(duì)照.NET Framework 版本、目錄和權(quán)限三件事綠色版最容易讓人產(chǎn)生“不用裝”的錯(cuò)覺(jué)實(shí)際上它只是不寫(xiě)注冊(cè)表、不走安裝向?qū)н\(yùn)行時(shí)依賴一個(gè)都不少。Reflector 7.4.1.179 這個(gè)年代的版本主程序是托管程序啟動(dòng)第一件事就是找對(duì)應(yīng)版本的 .NET Framework找不到就直接彈一個(gè)“需要安裝 .NET Framework”的對(duì)話框然后退出連主界面都看不到。插件更挑剔它們是以反射方式被主程序加載的主程序能跑不代表插件能跑。所以裝之前我一般把三件事先定死運(yùn)行庫(kù)版本、解壓目錄、當(dāng)前用戶權(quán)限。2.1 運(yùn)行庫(kù)版本對(duì)照3.5 和 4.x 要分開(kāi)看這個(gè)版本的主程序?qū)?.NET Framework 的依賴常見(jiàn)做法是 3.5 SP1 起步、4.x 兼容運(yùn)行更穩(wěn)。注意 3.5 和 4.x 在注冊(cè)表里是兩條完全獨(dú)立的路徑不能只看一個(gè)v4\Full就認(rèn)為 3.5 也裝了。很多人機(jī)器上只有 4.x雙擊后報(bào)“應(yīng)用程序正常初始化失敗”根子就在這。檢查項(xiàng)注冊(cè)表位置判定標(biāo)準(zhǔn)缺失后的表現(xiàn).NET Framework 3.5HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v3.5Install 1主程序閃退或提示初始化失敗.NET Framework 4.xHKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full存在Release值且大于等于 378389主程序能起、插件加載報(bào)錯(cuò)系統(tǒng)位數(shù)$env:PROCESSOR_ARCHITECTUREx86 或 AMD64決定插件 DLL 該用哪個(gè)版本用一段 PowerShell 把這兩條路徑一次看完比在“啟用或關(guān)閉 Windows 功能”里翻半天快# 同時(shí)檢查 3.5 與 4.x兩條注冊(cè)表路徑互不覆蓋缺一條就說(shuō)明對(duì)應(yīng)運(yùn)行庫(kù)沒(méi)裝 $keys ( HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v3.5, HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full ) foreach ($k in $keys) { if (Test-Path $k) { $p Get-ItemProperty $k {0} Install{1}, Version{2}, Release{3} -f $k, $p.Install, $p.Version, $p.Release } else { 缺失: $k } }邏輯說(shuō)明v3.5這條路徑主要看Install是否為 1v4\Full這條路徑看Release378389 對(duì)應(yīng) 4.5數(shù)值越大版本越新只要不小于這個(gè)數(shù)就能滿足絕大多數(shù) 4.x 兼容場(chǎng)景。參數(shù)說(shuō)明Get-ItemProperty讀的是默認(rèn)值之外的命名值所以Install、Version、Release都要顯式取如果某條路徑不存在Test-Path直接返回假腳本不會(huì)中斷方便一次看全。補(bǔ)充一句32 位系統(tǒng)上 4.x 的路徑可能是NDP\v4\Client遇到Full不存在時(shí)補(bǔ)查Client。2.2 解壓目錄怎么定別放 Program Files綠色版最常見(jiàn)的錯(cuò)誤放置位置就是C:\Program Files或C:\Program Files (x86)。這兩個(gè)目錄受 UAC 保護(hù)主程序往自己目錄寫(xiě)配置、插件往 AddIns 目錄寫(xiě)緩存時(shí)會(huì)被系統(tǒng)重定向到VirtualStore表現(xiàn)出來(lái)就是“配置改了重啟又變回去”“插件明明放進(jìn)去了卻加載不到”。我一般的做法是單獨(dú)開(kāi)一個(gè)工具盤目錄路徑全英文、不帶空格# 統(tǒng)一放到 D:\Tools 下路徑不帶中文和空格避免插件解析路徑時(shí)出問(wèn)題 $src D:\Downloads\Reflector741.zip $root D:\Tools\Reflector741 New-Item -ItemType Directory -Path $root -Force | Out-Null Expand-Archive -Path $src -DestinationPath $root -Force Get-ChildItem $root | Select-Object Name, Length, LastWriteTime邏輯說(shuō)明先建目標(biāo)目錄再解壓-Force保證目錄已存在時(shí)不報(bào)錯(cuò)解壓后立刻列一遍文件用來(lái)確認(rèn)壓縮包沒(méi)有在傳輸過(guò)程中損壞。參數(shù)說(shuō)明-DestinationPath指向的目錄必須是當(dāng)前用戶有完全控制權(quán)的普通目錄Expand-Archive在 Windows PowerShell 5.1 上對(duì)大文件解壓較慢包體大時(shí)可以換 7-Zip但目標(biāo)路徑的命名規(guī)則不變。目錄定下來(lái)之后按下面的清單核一遍文件是否齊全缺哪個(gè)后面就會(huì)在哪一步出問(wèn)題文件或目錄作用缺失后的表現(xiàn)Reflector.exe主程序無(wú)法啟動(dòng)Reflector.exe.config運(yùn)行庫(kù)綁定與啟動(dòng)配置啟動(dòng)報(bào)配置節(jié)錯(cuò)誤AddIns\插件掃描目錄Add-Ins 列表為空插件 DLL 及依賴FileDisassembler、FileGenerator菜單項(xiàng)不出現(xiàn)版本與注冊(cè)說(shuō)明注冊(cè)信息與版本對(duì)應(yīng)關(guān)系換機(jī)器后提示未注冊(cè)2.3 裝前體檢三個(gè)動(dòng)作確認(rèn)“裸程序”能跑插件出問(wèn)題的時(shí)候最怕分不清是主程序壞了還是插件壞了。所以我習(xí)慣在掛插件之前先讓裸程序跑通一次把變量隔離出來(lái)。三個(gè)動(dòng)作確認(rèn)能啟動(dòng)、確認(rèn)能打開(kāi)一個(gè)程序集、確認(rèn)原生導(dǎo)出可用。第一個(gè)動(dòng)作是直接啟動(dòng)。雙擊沒(méi)反應(yīng)時(shí)不要反復(fù)點(diǎn)打開(kāi)事件查看器看 Windows 日志里的“.NET Runtime”條目錯(cuò)誤信息通常會(huì)寫(xiě)明是哪個(gè)程序集加載失敗。第二個(gè)動(dòng)作是找一個(gè)手邊的小程序集打開(kāi)。沒(méi)有現(xiàn)成的就自己編一個(gè)后面驗(yàn)證環(huán)節(jié)還要用它一舉兩得// 模擬項(xiàng)目X里的一個(gè)服務(wù)類只為了產(chǎn)出一個(gè)可反編譯的測(cè)試 DLL namespace DemoService { public class PingService { private readonly string _prefix; public PingService(string prefix) { _prefix prefix; } public string Ping(string name) { return _prefix : name; } } }邏輯說(shuō)明這個(gè)類故意包含字段、構(gòu)造函數(shù)和普通方法反編譯后能同時(shí)看到字段聲明、構(gòu)造函數(shù)體和字符串拼接方便判斷導(dǎo)出是否完整。參數(shù)說(shuō)明編譯時(shí)用csc /target:library產(chǎn)出 DLL不要用dotnet build因?yàn)樾?SDK 默認(rèn)產(chǎn)出的是 .NET Core 程序集老版本 Reflector 打開(kāi)會(huì)提示不支持的格式。第三個(gè)動(dòng)作是右鍵任意類型試一次原生的“導(dǎo)出源碼”。這一步能過(guò)說(shuō)明主程序和運(yùn)行庫(kù)這條鏈路沒(méi)問(wèn)題后面插件再不出現(xiàn)就只可能是插件本身的事。提示整個(gè)體檢過(guò)程都在沒(méi)有掛任何插件的干凈目錄里做。等確認(rèn)裸程序正常再把插件目錄拷進(jìn)來(lái)出問(wèn)題才能一眼定位。3. 插件掛載把 FileDisassembler 與 FileGenerator 裝進(jìn) Reflector 7.4.1.179主程序跑通之后才輪到插件。這一步是整個(gè)部署里最容易反復(fù)的地方因?yàn)椴寮募虞d方式在不同小版本、不同漢化包里菜單名會(huì)有差異但底層規(guī)則只有兩條插件 DLL 必須能被主程序找到且它的依賴必須和它待在同一目錄。搞清楚這兩條剩下的都是操作順序問(wèn)題。3.1 先把兩個(gè)插件的分工理清楚FileDisassembler 解決的是“批量”問(wèn)題。Reflector 原生只能選中一個(gè)類型導(dǎo)出源碼一個(gè)幾百個(gè)類型的程序集靠手點(diǎn)基本不現(xiàn)實(shí)FileDisassembler 在程序集節(jié)點(diǎn)上加了導(dǎo)出入口可以按命名空間建目錄把整個(gè)模塊的源碼一次性寫(xiě)到磁盤還能選擇導(dǎo)出語(yǔ)言和是否帶上資源文件。FileGenerator 解決的是“組裝”問(wèn)題。FileDisassembler 導(dǎo)出來(lái)的是散落的 .cs 文件沒(méi)有工程文件雙擊打不開(kāi)、編譯不了FileGenerator 讀取導(dǎo)出目錄生成對(duì)應(yīng)的.csproj或.vbproj把Compile Include項(xiàng)一條條寫(xiě)好省掉手工“添加現(xiàn)有項(xiàng)”的體力活。兩者的使用順序是固定的先用 FileDisassembler 導(dǎo)出源碼再用 FileGenerator 在導(dǎo)出目錄上生成工程。順序反了FileGenerator 找不到輸入文件會(huì)生成一個(gè)空工程或者直接報(bào)錯(cuò)這也是新手最常踩的坑之一。3.2 插件 DLL 的放置與 Add-Ins 加載我優(yōu)先用目錄方式掛載而不是在對(duì)話框里逐個(gè)添加原因是目錄方式能把依賴一起帶過(guò)去不會(huì)出現(xiàn)“主 DLL 加載成功、依賴 DLL 找不到”的半成功狀態(tài)。操作順序是先完全退出 Reflector再拷文件再啟動(dòng)。# 目錄方式掛載整目錄復(fù)制避免只拷主 DLL 漏掉同目錄的依賴 $root D:\Tools\Reflector741 $addins Join-Path $root AddIns New-Item -ItemType Directory -Path $addins -Force | Out-Null Copy-Item .\plugins\* -Destination $addins -Recurse -Force Get-ChildItem $addins -Filter *.dll | ForEach-Object { {0}t{1:N1} KB -f $_.Name, ($_.Length / 1KB) }邏輯說(shuō)明先確保AddIns目錄存在再把插件包里的全部?jī)?nèi)容遞歸復(fù)制進(jìn)去最后列出所有 DLL 并帶上大小用來(lái)確認(rèn)拷貝沒(méi)有中斷。參數(shù)說(shuō)明-Recurse保證子目錄里的依賴也一起過(guò)去-Force允許覆蓋舊版本插件升級(jí)插件時(shí)不用手工先刪。如果插件包里自帶readme說(shuō)明放置路徑以說(shuō)明為準(zhǔn)不同打包方式目錄名可能是AddIns也可能是Add-Ins大小寫(xiě)和連字符在 Windows 上不敏感但目錄名本身要對(duì)。拷完之后啟動(dòng)主程序打開(kāi) Add-Ins 對(duì)話框正常情況下能在列表里看到兩個(gè)插件條目勾選、關(guān)閉對(duì)話框、重啟主程序菜單欄或右鍵菜單里就會(huì)出現(xiàn)對(duì)應(yīng)入口。這里有個(gè)細(xì)節(jié)勾選后不重啟菜單是不會(huì)刷新的很多人以為沒(méi)生效其實(shí)是沒(méi)重啟。如果不想動(dòng)程序目錄也可以在 Add-Ins 對(duì)話框里手動(dòng)添加 DLL 路徑。這種方式適合臨時(shí)試用缺點(diǎn)是換機(jī)器或者移動(dòng)目錄后路徑失效需要重新添加一遍。我一般只在排查問(wèn)題時(shí)用這種方式做對(duì)照實(shí)驗(yàn)。3.3 加載失敗的判定與回退插件加載失敗不會(huì)彈很明確的錯(cuò)誤框多數(shù)時(shí)候只是“菜單沒(méi)出現(xiàn)”。想確認(rèn)到底加載沒(méi)加載可以看主程序的 Add-Ins 列表列表里有條目但勾選無(wú)效說(shuō)明 DLL 被識(shí)別了但初始化失敗列表里壓根沒(méi)有條目說(shuō)明 DLL 沒(méi)被掃描到或者位數(shù)不對(duì)。判斷位數(shù)可以用一行命令看 DLL 的機(jī)器類型PE 頭里Machine字段是0x014c就是 32 位0x8664就是 64 位# 讀 PE 頭判斷插件位數(shù)0x014cx860x8664x640x0200AnyCPU 之外的情況要留意 $dll D:\Tools\Reflector741\AddIns\FileDisassembler.dll $fs [System.IO.File]::OpenRead($dll) $br New-Object System.IO.BinaryReader($fs) $fs.Seek(0x3C, Begin) | Out-Null $pe $br.ReadInt32() $fs.Seek($pe 4, Begin) | Out-Null $machine $br.ReadUInt16() $br.Close(); $fs.Close() Machine 0x{0:X4} -f $machine邏輯說(shuō)明先定位到 DOS 頭的e_lfanew偏移0x3C讀出 PE 頭位置再跳過(guò)簽名讀Machine字段。參數(shù)說(shuō)明0x014c對(duì)應(yīng) x860x8664對(duì)應(yīng) x64如果主程序是 32 位而插件是 64 位加載時(shí)必然拋BadImageFormatException在事件查看器里能看到完整堆棧?;赝瞬呗晕夜潭ㄓ枚址ㄏ劝袮ddIns目錄清空確認(rèn)裸程序正常然后只放 FileDisassembler重啟驗(yàn)證通過(guò)后再放 FileGenerator。這樣任何一次失敗都能立刻定位到是哪個(gè)插件引入的不用在兩個(gè)插件之間來(lái)回猜。4. 導(dǎo)出參數(shù)配置FileDisassembler 批量反編譯與 FileGenerator 生成工程插件掛上只是“能用”離“好用”還差一輪參數(shù)配置。這一步?jīng)Q定導(dǎo)出的源碼目錄亂不亂、生成出來(lái)的工程能不能直接編譯。我的習(xí)慣是先定目錄規(guī)范再定導(dǎo)出參數(shù)最后用腳本把“導(dǎo)出—生成—編譯”串起來(lái)保證每次重跑結(jié)果一致。4.1 FileDisassembler 的輸出語(yǔ)言與目錄結(jié)構(gòu)在程序集節(jié)點(diǎn)上右鍵調(diào)出 FileDisassembler 的導(dǎo)出窗口后幾個(gè)參數(shù)值得逐一看參數(shù)建議值說(shuō)明輸出目錄獨(dú)立空目錄不要和原始程序集目錄混放避免覆蓋導(dǎo)出語(yǔ)言C#與后續(xù)工程文件類型保持一致按命名空間分目錄開(kāi)啟大型程序集不開(kāi)啟會(huì)全平鋪在一個(gè)目錄導(dǎo)出資源按需資源文件多時(shí)關(guān)閉先只導(dǎo)代碼覆蓋已有文件首次導(dǎo)出開(kāi)啟增量導(dǎo)出時(shí)關(guān)閉防止覆蓋手工修改語(yǔ)言選擇要和 FileGenerator 生成的工程類型一致導(dǎo)出 C# 卻生成.vbproj后面編譯必然一堆語(yǔ)法錯(cuò)誤。另外導(dǎo)出目錄必須是空的或者專用目錄我見(jiàn)過(guò)有人在原始 DLL 旁邊直接導(dǎo)出結(jié)果源碼和二進(jìn)制混在一起清理時(shí)誤刪了原始文件。導(dǎo)出前先把目錄準(zhǔn)備好導(dǎo)出后立刻統(tǒng)計(jì)文件分布能快速判斷是否導(dǎo)出完整# 導(dǎo)出前后都用這段清空輸出目錄導(dǎo)出后按擴(kuò)展名統(tǒng)計(jì)文件數(shù) $out D:\Work\decomp\DemoService\src Remove-Item $out -Recurse -Force -ErrorAction SilentlyContinue New-Item -ItemType Directory -Path $out -Force | Out-Null Get-ChildItem $out -Recurse -File | Group-Object Extension | Sort-Object Count -Descending | Select-Object Name, Count邏輯說(shuō)明先徹底清空再重建保證每次導(dǎo)出結(jié)果可對(duì)比導(dǎo)出完成后按擴(kuò)展名分組統(tǒng)計(jì).cs數(shù)量應(yīng)該和程序集里的類型數(shù)量大致對(duì)得上如果只有零星幾個(gè)文件多半是導(dǎo)出范圍選成了單個(gè)類型而不是整個(gè)程序集。參數(shù)說(shuō)明-ErrorAction SilentlyContinue用于目錄本來(lái)就不存在的情況避免腳本在清理階段中斷Group-Object會(huì)把無(wú)擴(kuò)展名的文件歸到空名稱分組資源文件通常就落在這里。大程序集要注意分批。一次導(dǎo)出上萬(wàn)個(gè)文件磁盤寫(xiě)入不是問(wèn)題問(wèn)題在于導(dǎo)出完成后資源管理器索引會(huì)卡很久后續(xù) FileGenerator 讀取目錄也會(huì)變慢。常見(jiàn)做法是按命名空間分批導(dǎo)出或者先只導(dǎo)主模塊依賴模塊單獨(dú)處理。4.2 FileGenerator 生成的工程文件怎么改FileGenerator 在導(dǎo)出目錄上生成工程文件但它只能根據(jù)目錄里現(xiàn)有的文件生成Compile Include不會(huì)替你補(bǔ)目標(biāo)框架和外部引用。生成出來(lái)的.csproj通常要手工改兩處目標(biāo)框架版本和引用列表。!-- FileGenerator 生成的工程一般要補(bǔ)這兩段目標(biāo)框架和基礎(chǔ)引用 -- PropertyGroup TargetFrameworkVersionv4.0/TargetFrameworkVersion OutputTypeLibrary/OutputType RootNamespaceDemoService/RootNamespace /PropertyGroup ItemGroup Reference IncludeSystem / Reference IncludeSystem.Core / /ItemGroup邏輯說(shuō)明TargetFrameworkVersion要和原始程序集的編譯目標(biāo)一致反編譯一個(gè) .NET 4.0 的程序集卻把工程設(shè)為 2.0一些新語(yǔ)法會(huì)直接編譯失敗OutputType按原始程序集類型選類庫(kù)選Library可執(zhí)行文件選Exe。參數(shù)說(shuō)明RootNamespace要和源碼里的命名空間對(duì)齊否則生成資源文件時(shí)會(huì)出現(xiàn)命名空間嵌套錯(cuò)誤Reference里至少要保留System和System.Core用到 LINQ、泛型集合的代碼沒(méi)有System.Core會(huì)報(bào)類型找不到。還有一點(diǎn)要提前有心理預(yù)期反編譯產(chǎn)物不是原樣源碼。迭代器方法會(huì)被展開(kāi)成狀態(tài)機(jī)lambda 會(huì)變成編譯器生成的類async方法會(huì)拆成MoveNext和一堆字段。這些代碼能編譯通過(guò)但不代表邏輯結(jié)構(gòu)和原作者寫(xiě)的一致。所以生成工程后先編譯編譯通過(guò)再對(duì)照 IL 或行為做驗(yàn)證不要拿反編譯結(jié)果直接改完就上線。4.3 用批處理把“導(dǎo)出—生成—編譯”串起來(lái)目錄我固定分四層raw放原始程序集src放導(dǎo)出源碼proj放生成的工程build放編譯輸出。這樣任何一步出問(wèn)題都能單獨(dú)回滾不會(huì)把干凈目錄污染掉。# 目錄約定固定四層編譯輸出單獨(dú)放 build出問(wèn)題只清 build 不動(dòng)源碼 msbuild D:\Work\decomp\DemoService\proj\DemoService.csproj ^ /t:Rebuild ^ /p:ConfigurationRelease ^ /p:OutputPathD:\Work\decomp\DemoService\build\邏輯說(shuō)明/t:Rebuild強(qiáng)制全量重編避免增量編譯殘留舊產(chǎn)物掩蓋問(wèn)題/p:OutputPath必須帶尾部反斜杠否則 MSBuild 會(huì)把輸出當(dāng)成文件名處理。參數(shù)說(shuō)明ConfigurationRelease用于接近真實(shí)發(fā)布環(huán)境的編譯驗(yàn)證排查問(wèn)題時(shí)可以臨時(shí)改成Debug保留符號(hào)如果機(jī)器上只有新版 MSBuild路徑要換成 Visual Studio 安裝目錄下的MSBuild.exe不要用 .NET SDK 自帶的dotnet build去編老式 csproj。這一步的產(chǎn)物是驗(yàn)證環(huán)節(jié)的基礎(chǔ)只有build目錄里出現(xiàn)了和原始程序集同名的 DLL才能說(shuō)明反編譯源碼在語(yǔ)法和引用層面是自洽的。5. 排查清單插件不顯示、導(dǎo)出缺文件、版本對(duì)不上前面四章是“照著裝”這一章是“裝壞了怎么救”。下面這幾條都是我在不同機(jī)器上真實(shí)遇到過(guò)的按“現(xiàn)象 → 原因 → 解決”記遇到問(wèn)題時(shí)按順序?qū)φ栈灸芨采w八成情況。5.1 現(xiàn)象主程序雙擊閃退連界面都不出原因最常見(jiàn)的是運(yùn)行庫(kù)缺失或者版本不夠。Reflector 7.x 啟動(dòng)時(shí)會(huì)加載對(duì)應(yīng)版本的 CLR機(jī)器上只有 4.x 而程序配置綁定的是 3.5就會(huì)在加載階段直接退出。第二種原因是Reflector.exe.config在解壓過(guò)程中損壞配置文件解析失敗同樣不彈窗。解決先按第 2 章的腳本確認(rèn) 3.5 和 4.x 都裝到位再打開(kāi)事件查看器看“.NET Runtime”錯(cuò)誤條目里面會(huì)寫(xiě)明是FileLoadException還是ConfigurationErrorsException。確認(rèn)是配置問(wèn)題就重新解壓一份覆蓋config文件不要手工改配置內(nèi)容綠色版的配置文件里可能有打包時(shí)寫(xiě)死的綁定信息。5.2 現(xiàn)象Add-Ins 列表里能看到插件勾選后菜單還是不出現(xiàn)原因插件被識(shí)別但初始化失敗多數(shù)是版本不匹配。插件是針對(duì)某個(gè)主程序版本編譯的接口簽名變了加載時(shí)反射調(diào)用失敗主程序會(huì)靜默跳過(guò)而不是崩潰。另一種情況是勾選后沒(méi)有重啟主程序菜單沒(méi)刷新。解決先重啟一次確認(rèn)不是刷新問(wèn)題仍然不出現(xiàn)就換插件版本插件包里的說(shuō)明文件通常會(huì)寫(xiě)適配的主程序版本范圍。判斷是否初始化失敗可以在事件查看器里找加載時(shí)間點(diǎn)附近的.NET Runtime警告堆棧里會(huì)帶插件程序集名。5.3 現(xiàn)象導(dǎo)出到一半報(bào)“無(wú)法加載程序集”目錄里只有部分文件原因目標(biāo)程序集引用了同目錄之外的依賴FileDisassembler 在遍歷類型時(shí)需要解析這些依賴解析不到就中斷當(dāng)前分支。大型項(xiàng)目里依賴散落在多個(gè)子目錄時(shí)特別常見(jiàn)。解決把被依賴的 DLL 復(fù)制到原始程序集同目錄或者在導(dǎo)出窗口里先把范圍限制在主模塊依賴模塊單獨(dú)導(dǎo)出。不要指望插件自動(dòng)去 GAC 里找綠色版沒(méi)有裝配探測(cè)路徑的配置能力。5.4 現(xiàn)象FileGenerator 生成的工程一打開(kāi)滿屏紅線原因目標(biāo)框架版本不對(duì)或者缺少基礎(chǔ)引用。反編譯出來(lái)的代碼里用了System.Linq、System.Threading.Tasks而工程文件里只有默認(rèn)的System引用。解決按 4.2 節(jié)補(bǔ)TargetFrameworkVersion和Reference補(bǔ)完先編譯一次看錯(cuò)誤數(shù)量是否明顯下降。如果還剩大量“找不到類型”檢查是不是導(dǎo)出時(shí)漏了某個(gè)依賴模塊回到 5.3 處理。5.5 現(xiàn)象換一臺(tái)機(jī)器后提示未注冊(cè)或者插件目錄被清空原因綠色版的注冊(cè)信息和使用痕跡通常落在用戶配置目錄不在程序目錄里。只拷程序目錄過(guò)去配置沒(méi)跟過(guò)來(lái)就會(huì)提示未注冊(cè)。另一種情況是殺毒軟件把插件 DLL 當(dāng)成可疑文件隔離了表現(xiàn)和“插件沒(méi)放進(jìn)去”一模一樣。解決遷移時(shí)把程序目錄和用戶配置目錄一起打包不要只拷 exe換機(jī)器后先看殺毒軟件的隔離區(qū)確認(rèn)插件 DLL 是否被移走必要時(shí)把工具目錄加入白名單。有條件還是走正版授權(quán)渠道省掉這類遷移問(wèn)題。6. 裝完怎么驗(yàn)證用一個(gè) Demo DLL 跑通反編譯閉環(huán)驗(yàn)證這一步很多人會(huì)跳過(guò)覺(jué)得主程序能打開(kāi)、菜單能點(diǎn)就算裝好了。我的標(biāo)準(zhǔn)是自己編一個(gè) DLL用這套環(huán)境完整走一遍“打開(kāi) → 反編譯 → 導(dǎo)出 → 生成工程 → 編譯通過(guò)”五步都過(guò)才算真正跑通。原因很簡(jiǎn)單拿別人的程序集驗(yàn)證你分不清失敗是環(huán)境問(wèn)題還是那個(gè)程序集本身的依賴問(wèn)題拿自己編的 DLL所有變量都可控。驗(yàn)證用的 Demo 就是第 2 章那段PingService編譯成DemoService.dll之后按下面的表逐項(xiàng)過(guò)驗(yàn)證項(xiàng)動(dòng)作通過(guò)標(biāo)準(zhǔn)主程序加載打開(kāi)DemoService.dll樹(shù)里能看到PingService類型和字段原生反編譯選中類型看反編譯窗格構(gòu)造函數(shù)和方法體完整顯示插件入口程序集節(jié)點(diǎn)右鍵出現(xiàn) FileDisassembler 與 FileGenerator 菜單批量導(dǎo)出用 FileDisassembler 導(dǎo)出到src目錄中出現(xiàn)對(duì)應(yīng)命名空間的.cs文件工程生成用 FileGenerator 生成到proj生成.csproj且Compile Include條目齊全編譯驗(yàn)證執(zhí)行 4.3 節(jié)的 msbuild 命令build目錄出現(xiàn)同名 DLL退出碼為 0導(dǎo)出之后我還會(huì)做一個(gè)動(dòng)作打開(kāi)導(dǎo)出的.cs文件對(duì)照原始源碼看方法簽名和字符串常量。反編譯工具偶爾會(huì)把屬性寫(xiě)成字段加get_/set_方法或者把自動(dòng)屬性展開(kāi)成帶私有字段的寫(xiě)法這些都屬于正常變形但如果方法體整個(gè)缺失、只剩一個(gè)throw new NotImplementedException()那說(shuō)明導(dǎo)出鏈路有問(wèn)題要回到 5.3 檢查依賴。# 驗(yàn)證產(chǎn)物是否齊全源碼文件數(shù)、工程文件、編譯輸出三層各查一次 $base D:\Work\decomp\DemoService 源碼文件: {0} -f (Get-ChildItem $base\src -Recurse -Filter *.cs | Measure-Object).Count 工程文件: {0} -f (Get-ChildItem $base\proj -Filter *.csproj | Measure-Object).Count 編譯產(chǎn)物: {0} -f (Get-ChildItem $base\build -Filter DemoService.dll | Measure-Object).Count邏輯說(shuō)明三個(gè)數(shù)字分別對(duì)應(yīng)導(dǎo)出、生成、編譯三個(gè)階段任何一個(gè)為 0 都說(shuō)明該階段沒(méi)走通不用繼續(xù)往下看。參數(shù)說(shuō)明統(tǒng)計(jì)源碼文件時(shí)限定*.cs避免把資源文件算進(jìn)去干擾判斷編譯產(chǎn)物按文件名精確匹配防止把依賴 DLL 也算進(jìn)來(lái)。這套驗(yàn)證我每次都走包括只換了一臺(tái)機(jī)器、只升級(jí)了一個(gè)插件的情況。有一次就是靠它發(fā)現(xiàn)新拷過(guò)去的插件包少了一個(gè)依賴 DLL主程序能啟動(dòng)、菜單也出現(xiàn)了但一點(diǎn)導(dǎo)出就靜默失敗源碼目錄里只有零個(gè)文件。從那以后我每次部署完都強(qiáng)制走一遍這個(gè)五步閉環(huán)寧可多花十分鐘也不在導(dǎo)出到一半的時(shí)候才發(fā)現(xiàn)問(wèn)題。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取