nativecompile保護的dotNet本地程式還原成dotNetIL程式集

瑞克-rick發表於2007-08-27

前面討論了 .Net 保護中的 native compile 方式 。
提到了 native compile的兩種方式 偽編譯 和 ngen 編譯。仍然沒有像C++那樣的完全native的編譯。

這裡要討論的就是 ngen編譯 生成的  ni 檔案。
前面討論時我們提到了fetion框架中的 fetionvm.srm檔案。
注意到它是使用了ngen編譯保護模式。
Remotesoft在評論中予以了證實,今回就以 fetionvm.srm這個檔案為例,嘗試ni檔案的還原。

首先用reflector直接開啟這個 srm檔案。
這個檔案裡面就只有一個
[STAThread]
public static void Main(string[] argv)
{
}

空函式 ,再沒有其它內容。很明顯這只是一個殼,而且後設資料和原始的也肯定不一樣。

但是它配合ni檔案能夠正常執行,顯然有一個地方應該會包含原始的後設資料。
srm檔案只有2k大小,應該沒有可能隱藏原始的後設資料。

在前面已經確認了原始後設資料就隱藏在 ni 檔案中。理論上說ni檔案應該有個地方記錄原始後設資料的rva和size,由於對 ni 檔案的格式不是完全清楚,所以這裡採用暴力搜尋的模式確定原始後設資料的rva。
用編輯器(如UE)開啟檔案
CWINDOWSassemblyNativeImages_v2.0.50727_32FetionVM6e39d95b1cb7d342a0ad2b892350dc65FetionVM.ni.exe
搜尋 BSJB ,
我們會發現有兩個結果,其中一個就是目前cli header中指定的metadata。推測另外一個就是原始的後設資料,在檔案偏移的0x3000處。
根據後設資料結構計算出這個原資料的大小是 0x0970。
把它提取出來檢視其結構為:
namespace FetionVM
{
    internal static class Program
    {       
        private static string GetTimeString(DateTime dateTime);
        private static void Log(string message);
        [STAThread]
        private static void Main(params string[] args);
    }
}
看到這樣的結構基本上可以肯定它就是原始的後設資料了。

後設資料有了,只能得到程式的型別結構,還是無法獲取到方法體的程式碼。

前面推測ni 檔案也包含了 原始的IL程式碼,今回就做個測試,
通過強制放法重新Jit編譯,然後在Jit層進行捕獲,得到了想要的IL程式碼。
強制方法重新Jit最簡單的方式是使用profile api。
這個程式集很簡單,經過手動還原,可以得到如下:
internal static class Program
{
    // Methods
    private static string GetTimeString(DateTime dateTime)
    {
        return (dateTime.ToLongTimeString() + “:” + dateTime.Millisecond);
    }

    private static void Log(string message)
    {
        try
        {
            File.AppendAllText(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, “VMDotNet.log”), “[” + (DateTime.Now.ToString() + “] ” + message + ”
“));
        }
        catch
        {
        }
    }

    [STAThread]
    private static void Main(params string[] args)
    {
        if (args.Length != 0)
        {
            string path = args[0];
            if (!File.Exists(path))
            {
                Log(“未找到要執行的程式 ” + path);
            }
            else
            {
                string assemblyFile = path;
                if (path.IndexOf(@””) > 0)
                {
                    Environment.CurrentDirectory = Path.GetDirectoryName(path);
                    assemblyFile = Path.GetFileName(path);
                }
                AppDomainSetup info = new AppDomainSetup();
                info.PrivateBinPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, “System”);
                AppDomain domain = AppDomain.CreateDomain(Path.GetFileNameWithoutExtension(path), AppDomain.CurrentDomain.Evidence, info);
                try
                {
                    string[] destinationArray = new string[args.Length – 1];
                    Array.Copy(args, 1, destinationArray, 0, destinationArray.Length);
                    domain.ExecuteAssembly(assemblyFile, AppDomain.CurrentDomain.Evidence, destinationArray);
                }
                catch (Exception exception)
                {
                    Log(“執行程式 ” + path + ” 出現錯誤!” + exception.Message);
                }
            }
        }
    }
}

到這裡我們可以確定,在ni 檔案中包含了原始的後設資料和原始的IL程式碼。所以理論上ni檔案是可以被還原的。

不知道除了Jit層捕獲,是否還可以使用net2.0中的反射獲取方法體程式碼?
有興趣可以自己試試。

需要注意的是這些測試需要在那個虛擬框架中進行,另外看Main函式的程式碼,我們會注意到程式中至少包含兩個應用程式域。


相關文章