轉(zhuǎn)帖|其它|編輯:郝浩|2010-11-29 16:33:20.000|閱讀 1876 次
概述:傳統(tǒng)的非托管程序,加殼的對(duì)象是匯編指令;對(duì).NET程序的加殼對(duì)象則是元數(shù)據(jù)和IL代碼。對(duì).NET程序的加殼,在理論和方式上并沒有什么創(chuàng)新,目前都是直接繼承與Windows程序的加殼理論和方法。大部分.NET加殼工具也是傳統(tǒng)的加殼工具在自身功能上提供了擴(kuò)展。純.NET實(shí)現(xiàn)的加殼工具還是很少。加殼的方式很多,我們這里以常見的托管壓縮殼為例進(jìn)行講解。
# 界面/圖表報(bào)表/文檔/IDE等千款熱門軟控件火熱銷售中 >>
加殼是一種常用的保護(hù)應(yīng)用程序的方式,確切的說是一種加密方式。取名為殼,意思是說這種對(duì)程序的保護(hù)方式就像植物種子的外殼,我們使用一段程序?qū)⑽覀兊闹鞒绦虬谄渲校荒茌p易被其他人看見。
被加殼的程序在運(yùn)行時(shí)先要運(yùn)行一段附加的指令,這段附加的指令完成相關(guān)操作后會(huì)啟動(dòng)主程序。
加殼的方法大致可分為壓縮和加密。
傳統(tǒng)的非托管程序,加殼的對(duì)象是匯編指令;對(duì).NET程序的加殼對(duì)象則是元數(shù)據(jù)和IL代碼。對(duì).NET程序的加殼,在理論和方式上并沒有什么創(chuàng)新,目前都是直接繼承與Windows程序的加殼理論和方法。大部分.NET加殼工具也是傳統(tǒng)的加殼工具在自身功能上提供了擴(kuò)展。純.NET實(shí)現(xiàn)的加殼工具還是很少。加殼的方式很多,我們這里以常見的托管壓縮殼為例進(jìn)行講解。
本節(jié)就.NET程序加殼的基本原理和方式做實(shí)踐性的分析。
為了探究其壓縮原理,我們先創(chuàng)建一段代碼用于實(shí)驗(yàn),該段代碼如代碼清單9-14所示。
代碼清單9-14 用于加殼的程序源碼
class Program
{
static void Main(string[] args)
{
DoSth();
}
public static void DoSth()
{
}
}
代碼清單9-14的代碼最終生成ForCompress.exe文件。使用Reflector查看其IL代碼,Main方法的如代碼清單9-15所示。
代碼清單9-15 Main方法的IL代碼 .method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
.maxstack 8
L_0000: nop
L_0001: call void ForCompress.Program::DoSth()
L_0006: nop
L_0007: ret
}
此時(shí),F(xiàn)orCompress.exe在Reflector中結(jié)果如圖9-19所示。
圖9-19 ForCompress.exe的結(jié)構(gòu)
此時(shí)ForCompress.exe
下面我們啟動(dòng)一款.NET壓縮工具,NETZ來(lái)對(duì)ForCompress.exe進(jìn)行加殼。加殼之后,我們?cè)俅螁?dòng)Reflector來(lái)查看加殼的文件。如圖9-20所示。
圖9-20 加殼之后的ForCompress.exe文件
對(duì)比圖9-19和圖9-20,我們發(fā)現(xiàn)名稱空間ForCompress變成了netz,類Progress變成了NetzStartter。程序集多多了個(gè)資源文件app.resources。下面我們展開NetzStartter類,來(lái)查看其下的方法。如圖9-21所示。
圖9-21 NetzStartter類的方法
從圖9-21中我們可以看出,NetzStartter類定義了一系列我們"不認(rèn)識(shí)"的方法,但是卻沒有代碼清單9-14中的DoSth方法。下面我們來(lái)分析一下加殼之后的exe文件的啟動(dòng)過程。
首先定位到Main方法,查看其源代碼,如代碼清單9-16所示。
代碼清單9-16 NetzStartter類的Main方法
[STAThread]
public static int Main(string[] args)
{
try
{
InitXR();
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(NetzStarter.NetzResolveEventHandler);
return StartApp(args);
}
catch (Exception exception)
{
string str = " .NET Runtime: ";
Log(string.Concat(new object[] { "#Error: ", exception.GetType().ToString(), Environment.NewLine, exception.Message, Environment.NewLine, exception.StackTrace, Environment.NewLine, exception.InnerException, Environment.NewLine, "Using", str, Environment.Version.ToString(), Environment.NewLine, "Created with", str, "2.0.50727.4927" }));
return -1;
}
}
代碼清單9-16中的Main方法中,首先調(diào)用了InitXR方法,然后為AppDomain.CurrentDomain.AssemblyResolve事件添加處理方法,最后調(diào)用StartApp方法。我們首先看看InitXR方法做了些什么事情。InitXR方法源碼如代碼清單9-17所示。
代碼清單9-17 InitXR方法源碼
private static void InitXR()
{
try
{
string str = @"file:\";
string str2 = "-netz.resources";
string directoryName = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
if (directoryName.StartsWith(str))
{
directoryName = directoryName.Substring(str.Length, directoryName.Length - str.Length);
}
string[] files = Directory.GetFiles(directoryName, "*" + str2);
if ((files != null) && (files.Length > 0))
{
xrRm = new ArrayList();
for (int i = 0; i < files.Length; i++)
{
string fileName = Path.GetFileName(files[i]);
ResourceManager manager = ResourceManager.CreateFileBasedResourceManager(fileName.Substring(0, fileName.Length - str2.Length) + "-netz", directoryName, null);
if (manager != null)
{
xrRm.Add(manager);
}
}
}
}
catch
{
}
}
代碼清單9-17的代碼很明了,在特定的文件路徑中搜索資源文件,然后添加到全局變量xrRm中。
Main方法中的AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(NetzStarter.NetzResolveEventHandler)一句代碼也無(wú)需多言,只是指定程序集解析失敗時(shí)的事件處理。
現(xiàn)在我們看StartApp方法的源碼,如代碼清單9-18所示。
代碼清單9-18 StartApp方法源碼
public static int StartApp(string[] args)
{
byte[] resource = GetResource("A6C24BF5-3690-4982-887E-11E1B159B249");
if (resource == null)
{
throw new Exception("application data cannot be found");
}
int num = InvokeApp(GetAssembly(resource), args);
resource = null;
return num;
}
StartApp方法,從名字上看,應(yīng)該是調(diào)用被加密的源程序。在方法體內(nèi),首先調(diào)用了GetResource方法,返回了指定的資源,然后調(diào)用InvokeApp方法進(jìn)入主程序。為了弄清楚來(lái)龍去脈,我們先看看GetResource方法到底做了什么?代碼清單9-19是GetResource方法的源碼。
代碼清單9-19 GetResource方法源碼
private static byte[] GetResource(string id)
{
byte[] buffer = null;
if (rm == null)
{
rm = new ResourceManager("app", Assembly.GetExecutingAssembly());
}
try
{
inResourceResolveFlag = true;
string name = MangleDllName(id);
if ((buffer == null) && (xrRm != null))
{
for (int i = 0; i < xrRm.Count; i++)
{
try
{
ResourceManager manager = (ResourceManager) xrRm[i];
if (manager != null)
{
buffer = (byte[]) manager.GetObject(name);
}
}
catch
{
}
if (buffer != null)
{
break;
}
}
}
if (buffer == null)
{
buffer = (byte[]) rm.GetObject(name);
}
}
finally
{
inResourceResolveFlag = false;
}
return buffer;
}
現(xiàn)在我們對(duì)代碼清單9-19的代碼做簡(jiǎn)要的分析。
if (rm == null)
{
rm = new ResourceManager("app", Assembly.GetExecutingAssembly());
}
上面這句代碼從當(dāng)前程序集中獲取名稱為app的資源文件,回到圖9-20,我們可以看到app. Resources文件是內(nèi)嵌在程序集中的,可以被獲取。接下來(lái)的代碼獲取指定名稱的資源,然后以byte數(shù)組的形式返回。返回的資源的用途是什么呢?我們繼續(xù)分析。
InvokeApp(GetAssembly(resource), args);
上面是StartApp方法最后的調(diào)用,GetAssembly方法,從名字上看是獲取程序集,其參數(shù)是GetResource方法返回的byte數(shù)組。我們到它的源碼中一探究竟。GetAssembly方法的源碼如代碼清單9-20所示。
代碼清單9-20 GetAssembly方法源碼
private static Assembly GetAssembly(byte[] data)
{
MemoryStream stream = null;
Assembly assembly = null;
try
{
stream = UnZip(data);
stream.Seek(0L, SeekOrigin.Begin);
assembly = Assembly.Load(stream.ToArray());
}
finally
{
if (stream != null)
{
stream.Close();
}
stream = null;
}
return assembly;
}
代碼清單9-20的代碼也很簡(jiǎn)單,從byte數(shù)組轉(zhuǎn)化到程序集。這里我們唯一需要注意的地方是下面這句代碼:
stream = UnZip(data);
UnZip方法對(duì)byte數(shù)組進(jìn)行解壓縮。這個(gè)方法是整個(gè)程序運(yùn)行的最關(guān)鍵的方法,但是解壓縮的具體實(shí)現(xiàn)我們不去關(guān)注。如果您感興趣的話可以自行研究。
得到程序集之后,才真正的開始執(zhí)行InvokeApp方法,我們看代碼清單9-21。
代碼清單9-21 InvokeApp源碼
private static int InvokeApp(Assembly assembly, string[] args)
{
MethodInfo entryPoint = assembly.EntryPoint;
ParameterInfo[] parameters = entryPoint.GetParameters();
object[] objArray = null;
if ((parameters != null) && (parameters.Length > 0))
{
objArray = new object[] { args };
}
object obj2 = entryPoint.Invoke(null, objArray);
if ((obj2 != null) && (obj2 is int))
{
return (int) obj2;
}
return 0;
}
從代碼清單9-21中我們看到,這段代碼首先獲取程序集的入口函數(shù),也就是Main方法,然后執(zhí)行。到這里,程序才真正的從外殼程序轉(zhuǎn)到真正的主程序。
結(jié)合上面的分析,我們總結(jié)一下一個(gè)純.NET壓縮殼程序的運(yùn)行流程:
1) 程序運(yùn)行時(shí)首先運(yùn)行外殼程序。
2) 外殼程序從其資源中讀取主程序的原始數(shù)據(jù)。
3) 對(duì)原始數(shù)據(jù)解壓縮,轉(zhuǎn)化成程序集。
4) 運(yùn)行主程序。
這種加殼方法的兩個(gè)關(guān)鍵點(diǎn),一個(gè)是主程序作為殼程序的資源文件存在,第二個(gè)是先對(duì)資源文件解密然后再反射執(zhí)行。
本站文章除注明轉(zhuǎn)載外,均為本站原創(chuàng)或翻譯。歡迎任何形式的轉(zhuǎn)載,但請(qǐng)務(wù)必注明出處、不得修改原文相關(guān)鏈接,如果存在內(nèi)容上的異議請(qǐng)郵件反饋至chenjj@fc6vip.cn
文章轉(zhuǎn)載自:博客轉(zhuǎn)載