比較兩款c#的本地代碼加密軟體)

來源:互聯網
上載者:User
標題】比較兩款c#的本地代碼加密軟體
——Remotesoft Protector和MaxtoCode
【作者】henryouly
【聲明】本文純粹技術探討性質,轉載時請保留作者資訊。本人才疏學淺,最近才初步接觸破解和.NET平台,對當前的最新技術瞭解也不充分,分析難免有失偏頗,請大家指教

上幾篇文章對C#的加密技術作了詳細探討,其中主要研究了混淆器保護.NET程式的一般原理以及破解方法。顯然IL語言本身的特點使得單純在IL層面上做的保護十分蒼白無力。於是另外一類軟體保護的產品產生了——本地代碼編譯。

Remotesoft Protector是國外某軟體公司的加密產品,MaxtoCode則是國人Jason.NET的力作。這兩款產品共有的特點都是,a)需要帶一個本地編譯的dll檔案發布,b)功能代碼不再在類裡面直接實現,用任何IL反組譯碼器看到的都是空的方法。


[函數體哪裡去了?]

當然,程式執行的時候,函數體還是要被變戲法一樣把內容填充回去的。下來我們具體研究到底這類保護軟體耍了什麼把戲。

先看看Remotesoft Protecter加密過的WebGrid.NET 3.5。這個ASP.NET程式需要ISNet.WebUI.WebGrid.dll和rscoree.dll一起放到bin下才能運行。先用Reflector開啟ISNet.WebUI.WebGrid.dll,留意到這些地方
internal class <PrivateImplementationDetails>
{
// Methods
[MethodImpl(MethodImplOptions.NoInlining)]
internal static void $$method-1();
[MethodImpl(MethodImplOptions.NoInlining)]
internal static void $$method-2();
[MethodImpl(MethodImplOptions.ForwardRef), DllImport("rscoree.dll", CharSet=CharSet.Ansi, ExactSpelling=true)]
private static extern void _RSEEStartup(int A_0);
[MethodImpl(MethodImplOptions.ForwardRef), DllImport("rscoree.dll", CharSet=CharSet.Ansi, ExactSpelling=true)]
private static extern void _RSEEUpdate(IntPtr A_0);

// Fields
private static bool $$started-1;
private static bool $$started-2;
}

[MethodImpl(MethodImplOptions.NoInlining)]
internal static unsafe void $$method-1()
{
if (!<PrivateImplementationDetails>.$$started-1)//保證初始化只進行一次
{
fixed (char* local1 = "")
{
<PrivateImplementationDetails>._RSEEStartup((int) local1);
//這裡用到一個小技巧,通過local1來得到程式碼片段在記憶體中的RVA,傳給_RSEEStartup
}
<PrivateImplementationDetails>.$$started-1 = true;
}
}

[MethodImpl(MethodImplOptions.NoInlining)]
internal static void $$method-2()
//註:這個類原來是混淆過的(而且非流程混淆,是構造特殊堆棧結構來阻止Reflector解釋成C#語句
//是Remotesoft Protector作者有意手動編寫的,用前幾篇文章介紹的方法反混淆出C#代碼
{
if (!<PrivateImplementationDetails>.$$started-2) //保證初始化只進行一次
{
StackTrace trace1 = new StackTrace();
if (trace1.FrameCount > 2)
{
<PrivateImplementationDetails>._RSEEUpdate(trace1.GetFrame(2).GetMethod().MethodHandle.Value);
//獲得調用棧中的方法名字,作為參數傳遞給_RSEEUpdate,稍後解釋為什麼
<PrivateImplementationDetails>.$$started-2 = true;
}
}
}

另外一個特別的地方是,我們留意到不少類當中都加入了一個靜態建構函式,內容均一樣,如下:
[MethodImpl(MethodImplOptions.NoInlining)]
static WebGrid()
{
<PrivateImplementationDetails>.$$method-1();
<PrivateImplementationDetails>.$$method-2();
}
這個建構函式僅僅在該類第一次被使用的時候調用。作用就是把該類被抽走的部分回填到記憶體中的assembly當中。

看到這裡已經很明顯了,每個類在第一次使用前,靜態建構函式首先被調用,用於調用method-1()和method-2(),method-1的作用是定位當前的代碼在記憶體中的位置,而method-2中的GetFrame(2)是取出該類(比如WebGrid)的建構函式的控制代碼。這兩個功能聯合起來即可實作類別似Win32 PE檔案的stolen byte的工作原理,在使用類以前修改建構函式,利用建構函式來把其他代碼填回去。

我稍微嘗試了一下,發現把記憶體中的assembly提取出來並不容易。.NET架構沒有提供直接存取記憶體中的IL code的辦法,所以不可能得到函數體。而嘗試用LordPE把記憶體dump出來,發現函數體內依然是空的,顯然Remotesoft並不直接填充該記憶體地區,而是採用類似ResolveHandler的辦法來實現動態加入的代碼和原Assembly的連結。

另外,通過查看rscoree.dll和ISNet.WebUI.WebGrid.dll,發現兩者修改日期一樣,相信函數功能是被靜態編譯到rscoree.dll當中去。ISNet.WebUI.WebGrid.dll純粹是一個不包含任何功能的軀殼。

再來分析一下MaxtoCode的原理。上網下載了MaxtoCode 2.0試用版。MaxtoCode的下載頁面說明運行依賴於ilasm和ildasm,明顯感覺到MaxtoCode需要先把assembly檔案dasm成IL檔案,然後加入自己的功能,再重新組裝為assembly。

簡單分析了一下,MaxtoCode的構造原理應該為:
1、 調用ildasm把assembly檔案反編譯成IL代碼
2、 清除IL檔案中所有注釋
3、 在IL檔案中增加一個新的entry point
.method private hidebysig static void
_1() cil managed
{
.entrypoint
.custom instance void [mscorlib]System.STAThreadAttribute::.ctor() = ( 01 00 00 00 )
.maxstack 5
.locals init (string Jason_0)
IL_0000: ldsfld int32 'Reflector'.'Application'::Locate__Assembly__Images__1
IL_0007: ldc.i4.0
IL_0008: bne.un.s IL_0021
IL_000a: call class [mscorlib]System.Reflection.Assembly [mscorlib]System.Reflection.Assembly::GetExecutingAssembly()
IL_000f: callvirt instance string [mscorlib]System.Reflection.Assembly::get_Location()
IL_0014: stloc.s Jason_0
IL_0015: ldloca.s Jason_0
IL_0017: call int32 'Reflector'.'Application'::AABBCCDDEE12345(string&)
IL_001c: stsfld int32 'Reflector'.'Application'::Locate__Assembly__Images__1
IL_0021: ldsfld int32 'Reflector'.'Application'::Locate__Assembly__Images__1
IL_0026: call bool 'Reflector'.'Application'::JasonIsGood_Actions(int32)
IL_0028: pop
IL_0029: ldsfld int32 'Reflector'.'Application'::Locate__Assembly__Images__1
IL_002d: ldc.i4 0x86da
IL_0032: ldc.i4.2
IL_0036: ldc.i4.0
IL_0037: ldc.i4.1
IL_0038: call bool 'Reflector'.'Application'::JasonIsGood_Actions_1(int32,
int32,
int32,
int32,
int32)
IL_003d: pop
IL_0042: call void 'Reflector'.'Application'::MTC___000086DA()
IL_0043: ret
}

4、 原entry point改名為MTC___000086DA,並增加如下內容
.method private hidebysig static void
MTC___000086DA() cil managed
{
.locals init (class Reflector.Application V_0,string Jason_0)
IL_0000: ldsfld int32 'Reflector'.'Application'::Locate__Assembly__Images__1
IL_0007: ldc.i4.0
IL_0008: bne.un.s IL_0021
IL_000a: call class [mscorlib]System.Reflection.Assembly [mscorlib]System.Reflection.Assembly::GetExecutingAssembly()
IL_000f: callvirt instance string [mscorlib]System.Reflection.Assembly::get_Location()
IL_0014: stloc.s Jason_0
IL_0015: ldloca.s Jason_0
IL_0017: call int32 'Reflector'.'Application'::AABBCCDDEE12345(string&)
IL_001c: stsfld int32 'Reflector'.'Application'::Locate__Assembly__Images__1
IL_0021: nop
IL_0028: ldsfld int32 'Reflector'.'Application'::Locate__Assembly__Images__1
IL_002d: ldc.i4 0x86da
IL_0032: ldc.i4.2
IL_0036: ldc.i4.0
IL_0037: ldc.i4.0
IL_0038: call bool 'Reflector'.'Application'::JasonIsGood_Actions_1(int32,
int32,
int32,
int32,
int32)
IL_003d: stsfld bool 'Reflector'.'Application'::MTC___000086DA_field
……Main原來的內容……

5、 在待加密的類當中加入:
.field private static int32 Locate__Assembly__Images__1
.field private static bool Locate__Assembly__Images__2
.method private hidebysig static pinvokeimpl("kernel32" as "GetModuleHandleA" nomangle ansi lasterr winapi)
int32 'AABBCCDDEE12345'(string& marshal( byvalstr) 'lpModuleName') cil managed preservesig
{
}
.method private hidebysig static pinvokeimpl("MShare.dll" as "EC1DB9C1620C48588C4701045B242FA9" nomangle ansi lasterr winapi)
bool 'JasonIsGood_Actions'(int32 'a') cil managed preservesig
{
}
.method private hidebysig static pinvokeimpl("MShare.dll" as "F1B0C9B05CF2496c8873B60602A22743" nomangle ansi lasterr winapi)
bool 'JasonIsGood_Actions_1'(int32 'a',int32 'b',int32 'c',int32 'd',int32 'e') cil managed preservesig
{
}

6、 加入PrivateImplementationDetails,後面詳細講。
7、 混淆,把類名、變數名、方法名替換為不可見字元(如後面的’\r\n’)
8、 重新編譯回去,並把安裝目錄下的Attick.dll 複製為MShare.dll

前5步是直接用Reflector的主程式所分析出來的,還記得Reflector會使得ildasm崩潰嗎?哈哈,就是利用它的崩潰等待按確定的時間慢慢翻查臨時檔案。

因為Reflector會引起崩潰,不可能得到最後的exe檔案,所以我又另外寫了一個小demo檔案用作測試。用Reflector查看加密後的結果:

internal class <PrivateImplementationDetails>
{
// Methods
[DllImport("kernel32", EntryPoint="GetModuleHandleA", CharSet=CharSet.Ansi, SetLastError=true, ExactSpelling=true)]
private static extern int //這個函數名字是”\r\n”,下面的名字均是這種情況導致換行
([MarshalAs(UnmanagedType.VBByRefStr)] ref string lpModuleName);
[DllImport("MShare.dll", EntryPoint="EC1DB9C1620C48588C4701045B242FA9", CharSet=CharSet.Ansi, SetLastError=true, ExactSpelling=true)]
private static extern bool //同上
(int a);
[DllImport("MShare.dll", EntryPoint="F1B0C9B05CF2496c8873B60602A22743", CharSet=CharSet.Ansi, SetLastError=true, ExactSpelling=true)]
private static extern bool
(int a, int b, int c, int d, int e);

// Fields
private static bool
;
private static int
;
internal static $$struct0x6000001-1 $$method0x6000001-1 = { 0x35, 0x46, 0x42, 0x32, 0x38, 0x31, 0x46, 0x36 };

// Nested Types
[StructLayout(LayoutKind.Explicit, Size=8, Pack=1)]
private struct $$struct0x6000001-1
{
}
}

internal class Class1
{
// Methods
private static void
();
[DllImport("kernel32", EntryPoint="GetModuleHandleA", CharSet=CharSet.Ansi, SetLastError=true, ExactSpelling=true)]
private static extern int
([MarshalAs(UnmanagedType.VBByRefStr)] ref string lpModuleName);
[DllImport("MShare.dll", EntryPoint="EC1DB9C1620C48588C4701045B242FA9", CharSet=CharSet.Ansi, SetLastError=true, ExactSpelling=true)]
private static extern bool
(int a);
[DllImport("MShare.dll", EntryPoint="F1B0C9B05CF2496c8873B60602A22743", CharSet=CharSet.Ansi, SetLastError=true, ExactSpelling=true)]
private static extern bool
(int a, int b, int c, int d, int e);
public Class1();
[STAThread]
private static void Main();

// Fields
private static bool
;
private static bool
;
private static int
;
}

[STAThread]
private static void Main()
{
if (Class1.
== 0)
{
Class1.
= Class1.
(ref Assembly.GetExecutingAssembly().Location);
}
Class1.
(Class1.
);
Class1.
(Class1.
, 0x2e, 2, 0, 1);
Class1.
();
}

其中method0x6000001-1應該就是加密後的函數體了。MaxtoCode思想上和Remotesoft是類似的,不過實現手法上有所不同。MaxtoCode的Mshare.dll檔案是MaxtoCode作者預先寫好的,不包含任何被加密軟體的功能,僅僅是確定解密的演算法並進行解密(據作者的介紹,企業版共有7種不同的密碼編譯演算法或變體)。被抽取的功能(即stolen byte)是經過加密變換後用靜態方式直接儲存在assembly當中。於是,MaxtoCode的加密強度在於分析出函數體的解密演算法的困難性。

對這兩種加密辦法,目前我並未找到方便的破解辦法(但並不代表一定沒有,也許只是我不知道)。.NET架構並不提供把記憶體中的Assembly內容轉換到本地exe/dll的辦法。如果是.NET的exe檔案,還可以通過OD進行調試(當然此時IL code已經經過CLR自動編譯,變成本地代碼了),如果是ASP.NET的dll檔案,似乎就無法從exe調用進去,只能通過靜態分析加密軟體產生的對應dll檔案。也許.NET的動態跟蹤技術發展成熟後,就能出現破解這類本地代碼編譯的保護殼的通用辦法。

【後記】

在JasonNET兄的提醒下,發現Remotesoft Protector把類的內容儲存在rscoree.dll,這個說法太武斷了。經過進一步分析,確實很有可能類的內容還是儲存在原dll中,依據如下
1)查看ISNet.WebUI.WebGrid.dll的節區表,發現比正常編譯出來的dll多了.rdata一節,大小為100多k
2)用ildasm反編譯後,再用ilasm重新編譯,檔案大小明顯減少,顯然.NET的ildasm直接忽略處理.rdata的內容
3)查看rscoree.dll,所有模組間調用,發現ImageRvaToVa,ImageNtHeader,VirtualAlloc等API
4)__abstract.__abstract當中有不明含義的加密欄位以及大量和程式結構有關的Hashtable

各種依據表明,被抽調函數內容很可能依然儲存在IsNet.WebUI.WebGrid.dll當中,特別進行加入此後記,加以更正。

_____________________________@
軟體四大重點:資料結構與演算法是靈魂。軟體工程是骨架。組合語言是七大系統。物件導向設計思想是血和肉。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.