本文將用簡單的示例讓你瞭解如何通過ILAsm與ILDasm修改你的exe、dll文件 ...
一、背景
最近項目組新上項目,交付的時間比較急迫,原本好的分支管理習慣沒有遵守好,於是出現下麵狀況:
- 多個小伙伴在不同的分支上開發。
- 原本QA環境也存在一個阻礙性的bug A
- 一位同事在QA環境發佈了新的代碼,引入了新bug B
- 回滾QA能修改bug B,但是對於bugA卻無能為力
- 同時,混亂的代碼管理已經導致無法確定原始發佈包對應的代碼版本。
最終陷入了兩難的地步,既不能發佈新包,回滾也無法解決問題。
好在之前瞭解到如何使用微軟官方工具ILAsm與ILDasm對dll文件進行修改,於是開始動手實現。下麵將會用示例代碼講解如何修改已經的.exe文件。
二、ILAsm與ILDasm
我們知道,.net是一個跨平臺的的開發平臺,其跨平臺則是由其編譯的中間語言(Intermediate Language, 簡稱IL或MSIL)實現,無論我們使用的是C#、VB.Net、還是F#或者C++, 最終都會被編譯成IL,由JIT(Just In Time)編譯成目標機器語言,在CLR(Commen Language Runtime, 公用語言運行時)上運行。
因此,理論上,我們可以跳過過平時使用的C#代碼,直接修改IL,然後生成相應的dll或者exe文件。
那麼如何查看與修改IL呢,這就是ILAsm與ILDasm的工作了。ILAsm (MSIL Assembler),用來從IL語言生成PE(Portable Executable),也就是.net中我們使用的.exe、.dll文件。ILDasm (MSIL Disassembler),則與ILAsm相反,從PE文件,生成.IL文件。那麼我們可以猜到,要修改dll,我們需要先用ILDasm反編譯.dll生成.il文件,再用ILAsm編譯修改後的.il文件生成.dll,最終替換.dll文件。
三、使用ILDasm生成IL
先看下示例代碼:
class Program
{
static void Main(string[] args)
{
var loginResult = Login("foo", "111111");
if (loginResult)
{
Console.WriteLine("登錄成功");
}
else
{
Console.WriteLine("登錄失敗,請重試");
}
Console.ReadLine();
}
private static bool Login(string userName, string password)
{
if (userName.Equals("johnny") && password.Equals("123456"))
{
return false;
}
return false;
}
}
顯然,上述代碼針對Login(string userName, string password)
的調用會返回false,導致最後Console中會輸出"登錄失敗,請重試", 我們的目的是通過直接修改.exe文件,讓它返回true, Console裡面輸出"登錄成功"。
ILDasm與ILAsm已經包含在Visual Studio發行包中中,無需另外下載安裝。按如下步驟執行即可:
1. 開Developer Command Prompt for VS 2017,在裡面輸入命令ILDasm
。
2. 在打開的IL Dasm視窗中找到需要修改的.exe文件。
3. 選擇菜單 File > Dump,彈出的新視窗中點擊確認,保存生成的.il文件。
整個過程如下麵gif所示:
最後會生成相應的.il與.res文件。
四、修改IL
打開.il文件,會看到如下代碼(節選)
.method private hidebysig static bool Login(string userName,
string password) cil managed
{
// Code size 43 (0x2b)
.maxstack 2
.locals init ([0] bool V_0,
[1] bool V_1)
IL_0000: nop
IL_0001: ldarg.0
IL_0002: ldstr "johnny"
IL_0007: callvirt instance bool [mscorlib]System.String::Equals(string)
IL_000c: brfalse.s IL_001b
IL_000e: ldarg.1
IL_000f: ldstr "123456"
IL_0014: callvirt instance bool [mscorlib]System.String::Equals(string)
IL_0019: br.s IL_001c
IL_001b: ldc.i4.0
IL_001c: stloc.0
IL_001d: ldloc.0
IL_001e: brfalse.s IL_0025
IL_0020: nop
IL_0021: ldc.i4.0
IL_0022: stloc.1
IL_0023: br.s IL_0029
IL_0025: ldc.i4.0
IL_0026: stloc.1
IL_0027: br.s IL_0029
IL_0029: ldloc.1
IL_002a: ret
} // end of method Program::Login
這個就是Login(string userName, string password)
所對應的IL代碼了。如果你瞭解IL語言,可以直接對其修改。
如果不想直接修改IL,我們可以重寫一個小的示例方法,直接return true
,如下:
private static bool Login()
{
return true;
}
然後使用ILDasm生成相應的IL代碼,替換我們想修改的方法。最終的IL如下:
.method private hidebysig static bool Login(string userName,
string password) cil managed
{
.maxstack 8
IL_0000: ldc.i4.1
IL_0001: ret
} // end of method Program::Login
五、使用ILAsm生成exe
修改保存完.il文件以後,接下來的工作就是利用 ILAsm 讓.il文件生成重新生成.exe可執行文件了,在Console中執行如下命令
ilasm ILAsmAndILDasmDemo.il /output:ILAsmAndILDasmDemo_1.exe
// 如果修改的是dll文件,需要加上參數 /dll
成功以後會生成一個ILAsmAndILDasmDemo_1.exe文件,執行這個文件,我們可以看到,現在已經顯示"登錄成功"了。
使用反編譯工具 dotPeekI查看新生成的ILAsmAndILDasmDemo_1.exe文件,我們能夠看到,Login(string userName, string password)
已經直接return true
了。如下圖,
六、總結
其它,上面所做的事情其實也是《CLR via C#》中提到強名能夠 防止代碼被不懷好意的人篡改 的一個反面教材了。通過這個例子,大家應該對代碼被篡改的風險也有一定的認識了,所以如果大家需要將自己的.dll(.exe)文件露給別人,最好還是打上強名,防止別人惡意篡改你的代碼,導致不必要的損失。
經過這次事件,也證明瞭多瞭解下底層還是很有必要的,說不定哪天就用上了。平時再忙,也不能只限於只業務和編程語言層面,還需要對底層有一定的瞭解,這樣才能知其然與知其所以然。
雖然此次在不修改c#代碼的情況下完美解決QA的環境問題,但是這種方法也只限於小範圍的改動,只試用於救急。所以給我的教訓是分支管理規範才是王道,要能做到隨時可發佈,隨時可回滾才行,這樣才能完全避免再次出現這樣尷尬的情況了。