*****该文章转自https://msdn.microsoft.com/zh-tw/library/dd229210.aspx如果侵权或纠纷麻烦管理给删除,因此带来的不便深表歉意!*****
这几天想着破解我前几天没有完全破解的.net的编写的软件来着,到处找资料看,今天无意中看到了这篇文章,感触很深,虽然还没有帮我把那个软件破解了,但是有能看懂一些代码了。在找资料的过程中发现论坛里面有很多同学都在找.net脱壳及破解的方法,希望这篇文章会对你们有帮助的。
由于原文是繁体字,为了让大家看着方便,我转换了一下,但是个别的使用的习惯还是和咱们用惯了简体中文的有区别,但是大概意思是能看懂的。好啦,大家看吧,我得去研究那个软件去了,弄不出来我心理很是别扭哦,强迫症晚期了!!!
.NET中间语言(IL)
原作者:蔡学镛
以下为正文
.NET CLR 和 Java VM 都是堆叠式虚拟机器(Stack-Based VM),也就是说,它们的指令集(Instruction Set)都是採用堆叠运算的方式:执行时的资料都是先放在堆叠中,再进行运算。Java VM 有约 200 个指令(Instruction),每个指令都是 1 byte 的 opcode(操作码),后面接不等数目的参数;.NET CLR 有超过 220 个指令,但是有些指令使用相同的 opcode,所以 opcode 的数目比指令数略少。特别注意,.NET 的 opcode 长度并不固定,大部分的 opcode 长度是 1 byte,少部分是 2 byte。本文章以一个实际的例子,让你瞭解堆叠式 VM 的运作原理,并对 .NET IL(Intermediate Language)有最基本的领略。
下面是一个简单的 C# 原始码:
[C#] 纯文本查看 复制代码 using System;
public class Test {
public static void Main(String[] args) {
int i=1;
int j=2;
int k=3;
int answer = i+j+k;
Console.WriteLine("i+j+k="+answer);
}
}
将此原始码编译之后,可以得到一个 EXE 档案。我们可以透过 ILDASM.EXE 来反组译 EXE 以观察 IL。我将 Main() 的 IL 反组译条列如下,这裡共有十八道 IL 指令,有的指令(例如 ldstr 与 box)后面需要接参数,有的指令(例如 ldc.i4.1 与 add)后面不需要接参数。
[Visual Basic .NET] 纯文本查看 复制代码 ldc.i4.1
stloc.0
ldc.i4.2
stloc.1
ldc.i4.3
stloc.2
ldloc.0
ldloc.1
add
ldloc.2
add
stloc.3
ldstr "i+j+k="
ldloc.3
box [mscorlib]System.Int32
call string [mscorlib]System.String::Concat(object, object)
call void [mscorlib]System.Console::WriteLine(string)
ret
此程式执行时,关键的记忆体有三种,分别是: Managed Heap:这是动态配置(Dynamic Allocation)的记忆体,由 Garbage Collector(GC)在执行时自动管理,整个 Process 共用一个 Managed Heap。 Call Stack:这是由 .NET CLR 在执行时自动管理的记忆体,每个 Thread 都有自己专属的 Call Stack。每呼叫一次 method,就会使得 Call Stack 上多了一个 Record Frame;呼叫完毕之后,此 Record Frame 会被丢弃。一般来说,Record Frame 内纪录著method 参数(Parameter)、返回位址(Return Address)、以及区域变数(Local Variable)。Java VM 和 .NET CLR 都是使用 0, 1, 2… 编号的方式来识别区域变数。 Evaluation Stack:这是由 .NET CLR 在执行时自动管理的记忆体,每个 Thread 都有自己专属的 Evaluation Stack。前面所谓的堆叠式虚拟机器,指的就是这个堆叠。 后面有一连串的示意图,用来解说在执行时此三种记忆体的变化。首先,在进入 Main() 之后,尚未执行任何指令之前,记忆体的状况如图 1 所示: 图 1 接著要执行第一道指令 ldc.i4.1。此指令的意思是:在 Evaluation Stack 置入一个 4 byte 的常数,其值为 1。执行完此道指令之后,记忆体的变化如图 2 所示: 图 2 接著要执行第二道指令 stloc.0。此指令的意思是:从 Evaluation Stack 取出一个值,放到第 0 号变数(V0)中。这裡的第 0 号变数其实就是原始码中的 i。执行完此道指令之后,记忆体的变化如图 3 所示: 图 3 后面的第三道指令和第五道指令雷同于第一道指令,且第四道指令和第六道指令雷同于第二道指令。为了节省篇幅,我不在此一一赘述。提醒大家第 1 号变数(V1)其实就是原始码中的 j,且第 2 号变数(V2)其实就是源码中的 k。图 4~7 分别是执行完第三~六道指令之后,记忆体的变化图: 图 4 图 5 图 6 图 7 接著要执行第七道指令 ldloc.0 以及第八道指令 ldloc.1:分别将 V0(也就是 i)和 V1(也就是 j)的值放到 Evaluation Stack,这是相加前的准备动作。图 8 与图 9 分别是执行完第七、第八道指令之后,记忆体的变化图: 图 8 图 9 接著要执行第九道指令 add。此指令的意思是:从 Evaluation Stack 取出两个值(也就是 i 和 j),相加之后将结果放回 Evaluation Stack 中。执行完此道指令之后,记忆体的变化如图 10 所示: 图 10 接著要执行第十道指令 ldloc.2。此指令的意思是:分别将 V2(也就是 k)的值放到 Evaluation Stack,这是相加前的准备动作。执行完此道指令之后,记忆体的变化如图 11 所示: 图 11 接著要执行第十一道指令 add。从 Evaluation Stack 取出两个值,相加之后将结果放回 Evaluation Stack 中,此为 i+j+k 的值。执行完此道指令之后,记忆体的变化如图 12 所示: 图 12 接著要执行第十二道指令 stloc.3。从 Evaluation Stack 取出一个值,放到第 3 号变数(V3)中。这裡的第3号变数其实就是原始码中的 answer。执行完此道指令之后,记忆体的变化如图 13 所示: 图 13 接著要执行第十三道指令 ldstr "i+j+k="。此指令的意思是:将 "i+j+k=" 的 Reference 放进 Evaluation Stack。执行完此道指令之后,记忆体的变化如图 14 所示: 图 14 接著要执行第十四道指令 ldloc.3。将 V3 的值放进 Evaluation Stack。执行完此道指令之后,记忆体的变化如图 15 所示: 图 15 接著要执行第十五道指令 box [mscorlib]System.Int32。此指令的意思是:从 Evaluation Stack 中取出一个值,将此 Value Type 包装(box)成为 Reference Type。执行完此道指令之后,记忆体的变化如图 16 所示: 图 16 接著要执行第十六道指令 call string [mscorlib] System.String::Concat(object, object)。此指令的意思是:从 Evaluation Stack 中取出两个值,此二值皆为 Reference Type,下面的值当作第一个参数,上面的值当作第二个参数,呼叫 mscorlib.dll 所提供的 System.String.Concat() method 来将此二参数进行字串接合(String Concatenation),将接合出来的新字串放在 Managed Heap,将其 Reference 放进 Evaluation Stack。值得注意的是:由于 System.String.Concat() 是 static method,所以此处使用的指令是 call,而非 callvirt(呼叫虚拟)。执行完此道指令之后,记忆体的变化如图 17 所示: 图 17 请注意:此时 Managed Heap 中的 Int32(6) 以及 String("i+j+k=") 已经不再被参考到,所以变成垃圾,等待 GC 的回收。 接著要执行第十七道指令 call void [mscorlib] System.Console::WriteLine(string)。此指令的意思是:从 Evaluation Stack 中取出一个值,此值为 Reference Type,将此值当作参数,呼叫 mscorlib.dll 所提供的 System.Console.WriteLine() method 来将此字串显示在 Console 视窗上。System.Console.WriteLine() 也是 static method。执行完此道指令之后,记忆体的变化如图 18 所示: 图 18 接著要执行第十八道指令 ret。此指令的意思是:结束此次呼叫(也就是 Main 的呼叫)。此时会检查 Evaluation Stack 内剩下的资料,由于 Main() 宣告不需要传出值(void),所以 Evaluation Stack 内必须是空的,本范例符合这样的情况,所以此时可以顺利结束此次呼叫。而 Main 的呼叫一结束,程式也随之结束。执行完此道指令之后(且在程式结束前),记忆体的变化如图 19 所示: 图 19
透过此范例,读者应该可以对于 IL 有最基本的认识。对 IL 感兴趣的读者应该自行阅读 Serge Lidin 所著的《Inside Microsoft .NET IL Assembler》(Microsoft Press 出版)。我认为:熟知 IL 每道指令的作用,是 .NET 程式员必备的知识。.NET 程式员可以不会用 IL Assembly 写程式,但是至少要看得懂 ILDASM 反组译出来的 IL 组合码。
|