.NET 組件保護技術經歷了混淆、整體加密、方法級保護,逐步邁向傳統 Win32 加密領域。對應地,破解工作也從 IL 代碼分析轉向 ASM 代碼分析。基於方法的保護是這一過渡的關鍵環節,但公開的實作範例稀少。本文將透過手動實踐,剖析其核心原理。
.NET 採用即時編譯(JIT):方法首次被呼叫時,虛擬機調用 JIT 編譯器生成原生代碼。編譯單位為方法,實現位於 MSCORJIT.DLL。呼叫流程如下:
getJit() 僅導出一個虛表指標,其第一項為 CILJit::compileMethod 函數指標。compileMethod 呼叫 jitNativeCode,進而呼叫 Compiler::compCompile 完成實際編譯。corinfo.h 與 corjit.h,Hook 時需參考。struct CORINFO_METHOD_INFO {
CORINFO_METHOD_HANDLE ftn;
CORINFO_MODULE_HANDLE scope;
BYTE * ILCode; // 指向方法 IL 代碼體
unsigned ILCodeSize; // IL 代碼大小(位元組)
unsigned short maxStack;
unsigned short EHcount;
CorInfoOptions options;
CORINFO_SIG_INFO args;
CORINFO_SIG_INFO locals;
};
其中 ILCode 與 ILCodeSize 是保護的核心:加密時將真實 IL 交由 JIT 編譯;解密時攔截並替換為原始 IL。
在目標程式(APPCALLDLL.exe)的 Program 類別與 Main 方法中,透過 P/Invoke 呼叫自訂 HookJitPrj.dll 的 HookJIT() 函數,確保啟動時掛鉤生效。
使用 CFF Explorer 開啟目標組件,定位 doAddFun、doDllTwoFun 兩方法,記錄其 IL 位元碼,再以 NOP (0x00) 填充原方法體。注意:不可直接用 Mono.Cecil 寫入 NOP,因其會重構 PE 導致資源索引變動,使提取的 IL 失效。
核心 Hook 代碼(C++):
extern "C" __declspec(dllexport) void HookJIT() {
if (bHooked) return;
LoadLibrary(_T("mscoree.dll"));
HMODULE hJitMod = LoadLibrary(_T("mscorjit.dll"));
if (!hJitMod) return;
p_getJit = (ULONG_PTR *(__stdcall *)()) GetProcAddress(hJitMod, "getJit");
if (p_getJit) {
JIT *pJit = (JIT *) *((ULONG_PTR *) p_getJit());
if (pJit) {
DWORD OldProtect;
VirtualProtect(pJit, sizeof(ULONG_PTR), PAGE_READWRITE, &OldProtect);
compileMethod = pJit->compileMethod; // 保存原函數指標
pJit->compileMethod = &my_compileMethod; // 替換為自訂函數
VirtualProtect(pJit, sizeof(ULONG_PTR), OldProtect, &OldProtect);
bHooked = TRUE;
}
}
}
自訂編譯函數 my_compileMethod:根據方法名稱判斷,將預存的原始 IL 位元碼指標賦值給 info->ILCode,再呼叫原 compileMethod 完成編譯。
將 HookJitPrj.dll 與修改後的 AppCallDll.exe 放置同目錄,程式正常執行;移除 DLL 則報錯(若保留 RET 指令 0x2A 則不報錯但無功能)。核心邏輯現集中於 Hook DLL,可進一步以 Win32 加密技術保護;原組件已無真實方法體,大幅提高逆向難度。
此為原理性實驗。實務上可將所有步驟自動化,真實 IL 加密存放於新節區,自訂結構描述每方法加密演算法,甚至在替換函數中多次呼叫原函數傳遞假值、僅在特定條件傳真值。但終究逃不脫 Hook 機制,故 IL 指令替換與自訂解碼器(不依賴 MS JIT)是更進階的保護路徑。