原文链接:https://intezer.com/blog/incident-response/intro-to-malware-net-executable-file/#h-conclusions
欢迎来探讨.NET恶意软件逆向的世界。作为一名安全人员,你可能知道.NET 框架是一把双刃剑,该框架因其能够实现快速而强大的应用程序开发而受到普通开发者和恶意程序编写者的欢迎。
为什么要投入时间和精力来破解.NET恶意软件呢?简而言之,网络威胁环境中充满了使用 .NET 框架构建的恶意软件,应对这些威胁需要深入了解其底层代码结构并且拥有分析其复杂操作的能力。依靠逆向等技能,您将能够解开恶意软件的机制,发现其攻击特征,并加强对这些威胁的防御。
本文将介绍.NET框架以及.NET恶意文件的常见功能和行为。
.NET框架
.NET框架于2002年由微软发布,用于提供一个基于Windows系统的受控编程环境,以开发、安装和执行软件。目前,随着.NET更高版本的出现,该框架已支持Linux平台和macOS平台。另外,.NET实际并不是一种编程语言,而是一种支持多个编程语言的框架,涉及的语言包括C#、Visual Basic、F#。.NET包含一个大型类库,该类库被称为Framework类库(Framework Class Library,简称FCL),FCL为开发者提供了大量的即用型、经过测试与优化的函数,包括从数据访问到XML解析等多种功能。
.NET 威胁格局
恶意软件开发者更青睐于使用 .NET 框架而不是 C/C++,是因为.NET框架具有对用户更加友好的开发过程、丰富的功能集以及与Windows的平滑集成。然而,随着dnSpy(一款开源的. NET反编译器和调试器)之类的工具出现,安全人员对基于.NET的恶意软件进行逆向变得更加简单,从而促使恶意软件开发者采用混淆方法来加大分析难度。此外,由于.NET允许恶意软件改变其行为或者隐藏自身,导致安全检测和逆向变得更加困难。虽然C/C++ 允许对系统资源进行更精细的控制,并可能导致更谨慎、更高效的恶意软件,但它需要对系统内部工作原理有更深入的了解。对于那些寻求开发速度和易用性之间平衡的人来说,这使得 .NET 成为更有吸引力的选择。
随着.NET 威胁形势不断发展,攻击者经常利用适应性强且广泛采用的 .NET 框架来制作和部署各种复杂的威胁。该框架目前已被用于开发多种恶意程序,比如勒索软件Locky和Killnet、信息窃取程序RedLine Stealer、银行木马CryptoClippy、远控木马QuasarRAT和NanoCore等。此外,用 .NET 编写的数据擦除程序也开始出现,比如DoubleZero和最近披露的Hatef Wiper。
.NET编译和运行时
编译——托管代码
托管语言(C#、F#或VB.NET)的执行由通用语言运行时(CLR)控制。编译器编译源代码后输出的内容则被称为中间语言(IL)、MSIL(微软中间语言)、托管代码(Managed Code)或通用中间语言(CIL)。
例如,当在 .NET框架中编译C#代码时,C#编译器(csc.exe)的输出是一个.NET程序集(托管代码)。该程序集以可执行文件 (EXE) 或动态链接库 (DLL)的格式表现。(注:如果电脑上没有安装.NET框架,这种程序集将无法运行。)托管代码被打包到程序集中,并附有包含必要元数据的清单。
托管代码的优点在于它的可移植性和灵活性;相同的程序集可以在.NET支持的任何平台上运行而无需重新编译。此外,托管代码允许跨语言继承代码访问安全性(CAS,code-access security)。
下图演示了C#语言在.NET框架中的编译和执行过程。
运行时执行 – 通用语言运行时 (CLR)
通用语言运行时 (CLR)是Microsoft .NET框架的重要组件,用于管理.NET程序的执行。CLR本质上是一个执行引擎,提供运行.NET应用程序所需的各种服务。
当.NET二进制文件执行时,CLR会设置执行环境,但不会立即将所有托管代码转换为机器码。JIT编译器根据需要将托管代码转换为本地代码,并在方法被调用时对其进行编译。这确保了在特定硬件上的高效执行。此外,在.NET Core和.NET 5+的版本中,NGEN和AOT等技术可以在执行之前将托管代码预编译为本地代码,从而进一步提高性能。
CLR的功能超出了执行应用程序的范围,CLR提供多种关键服务,例如内存管理、异常处理、垃圾收集、类型安全检查、安全性等。由CLR协调的内存管理抽象化了开发人员手动分配和释放内存的需要,从而显着减少了内存泄漏和相关错误。提供的自动垃圾收集功能可管理对象的生命周期,通过释放应用程序不再使用的对象来回收内存。此外,CLR强制执行严格的类型安全,并且确保应用程序不会尝试执行不安全或未经验证的操作。它还在 .NET 的安全体系结构中发挥着重要作用,提供代码访问安全性 (CAS),该安全性根据分配给应用程序的信任级别控制程序可以访问哪些资源。总体而言,CLR创建了一个高级环境,可以减少传统编程语言所需的许多低级编程任务,从而实现更快的开发周期、提高生产力、产出更安全可靠的应用程序。这使得CLR成为.NET生态系统中不可或缺的组件。
非托管函数
.NET框架中的非托管函数是指在通用语言运行时(CLR)托管环境之外运行的代码。这些函数通常用C或C++之类的语言编写,并且被直接编译为机器代码,绕过了CLR的管理。这意味着.NET托管环境固有的自动垃圾收集、类型安全和异常处理等功能不适用于这些非托管函数。这类代码主要用于互操作性目的,允许.NET应用程序利用非.NET兼容语言编写的遗留代码或外部库。此功能在需要使用非.NET库或调用只能通过非托管代码访问的系统级API时至关重要。
不过,使用非托管函数会增加复杂性,开发人员必须手动进行内存管理以及错误处理,这将增加内存泄漏和安全漏洞等问题出现的可能性。在恶意软件分析的背景下,了解非托管函数至关重要,因为这类函数可被用于绕过托管环境的某些保护措施,从而给分析和检测带来独特的挑战。
示例 – 非托管函数
要创建使用非托管函数的简单.NET程序,可以使用C#中的平台调用服务 (PInvoke), PInvoke允许托管代码从动态链接库(DLL)调用非托管函数。
下面是一个示例,通过标准Windows库user32.dll调用MessageBox函数:
运行时执行 – 通用语言运行时 (CLR)
通用语言运行时 (CLR)是Microsoft .NET框架的重要组件,用于管理.NET程序的执行。CLR本质上是一个执行引擎,提供运行.NET应用程序所需的各种服务。
当.NET二进制文件执行时,CLR会设置执行环境,但不会立即将所有托管代码转换为机器码。JIT编译器根据需要将托管代码转换为本地代码,并在方法被调用时对其进行编译。这确保了在特定硬件上的高效执行。此外,在.NET Core和.NET 5+的版本中,NGEN和AOT等技术可以在执行之前将托管代码预编译为本地代码,从而进一步提高性能。
CLR的功能超出了执行应用程序的范围,CLR提供多种关键服务,例如内存管理、异常处理、垃圾收集、类型安全检查、安全性等。由CLR协调的内存管理抽象化了开发人员手动分配和释放内存的需要,从而显着减少了内存泄漏和相关错误。提供的自动垃圾收集功能可管理对象的生命周期,通过释放应用程序不再使用的对象来回收内存。此外,CLR强制执行严格的类型安全,并且确保应用程序不会尝试执行不安全或未经验证的操作。它还在 .NET 的安全体系结构中发挥着重要作用,提供代码访问安全性 (CAS),该安全性根据分配给应用程序的信任级别控制程序可以访问哪些资源。总体而言,CLR创建了一个高级环境,可以减少传统编程语言所需的许多低级编程任务,从而实现更快的开发周期、提高生产力、产出更安全可靠的应用程序。这使得CLR成为.NET生态系统中不可或缺的组件。
非托管函数
.NET框架中的非托管函数是指在通用语言运行时(CLR)托管环境之外运行的代码。这些函数通常用C或C++之类的语言编写,并且被直接编译为机器代码,绕过了CLR的管理。这意味着.NET托管环境固有的自动垃圾收集、类型安全和异常处理等功能不适用于这些非托管函数。这类代码主要用于互操作性目的,允许.NET应用程序利用非.NET兼容语言编写的遗留代码或外部库。此功能在需要使用非.NET库或调用只能通过非托管代码访问的系统级API时至关重要。
不过,使用非托管函数会增加复杂性,开发人员必须手动进行内存管理以及错误处理,这将增加内存泄漏和安全漏洞等问题出现的可能性。在恶意软件分析的背景下,了解非托管函数至关重要,因为这类函数可被用于绕过托管环境的某些保护措施,从而给分析和检测带来独特的挑战。
示例 – 非托管函数
要创建使用非托管函数的简单.NET程序,可以使用C#中的平台调用服务 (PInvoke), PInvoke允许托管代码从动态链接库(DLL)调用非托管函数。
下面是一个示例,通过标准Windows库user32.dll调用MessageBox函数:
using System;
using System.Runtime.InteropServices;
class Program
{
// 从user32.dll导入MessageBox函数
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern int MessageBox(IntPtr hWnd, String text, String caption, uint type);
static void Main(string[] args)
{
//进行非托管函数调用,调用MessageBox函数
MessageBox(new IntPtr(0), "Hello, World!", "Message Box", 0);
}
}
上方代码详解:
-
使用DllImport从user32.dll导入MessageBox函数。这个步骤可以告诉CLR该函数是外部函数并且不受.NET 运行时管理。
-
MessageBox函数使用与user32.dll中非托管函数相同的签名进行声明。
-
在Main方法中调用MessageBox函数。传递的参数包括窗口句柄(IntPtr(0)指无窗口句柄)、文本消息、标题和消息框的类型(0表示简单的“确定”按钮框)。
-
当这段代码运行后,程序将展示一个弹窗,弹窗中包含文本“Hello, World!”和标题“MessageBox”。
.NET程序集
.NET 程序集(.NET Assembly)是.NET应用程序的基本构建块,充当一个或多个代码模块或资源文件的集合。程序集内容包括:
-
中间语言 (IL) 代码:IL是一个与CPU无关的指令集,确保相同的程序集能够在.NET框架支持的不同平台上执行。
-
元数据:它描述由CLR管理的结构元素,例如程序集、类型(类、接口、枚举、结构)、方法等。元数据囊括了调试、垃圾收集、安全属性以及运行时管理代码所需的详细信息。
-
清单(Manifest):程序集清单是描述程序集本身的元数据的特定部分。它包括程序集的名称、版本、区域性以及用于唯一标识程序集的潜在强名称。虽然元数据描述了程序集中的内容,但清单提供了对整个程序集的更高级的概述,确保程序集与其所依赖的其他程序集的正确版本进行交互。
深入研究.NET可执行文件格式
在本节中,我们将深入了解.NET文件的内部结构。为了更好的阐释本节内容,本节将使用SolarWinds组织曾使用的样本,样本名为Sunburst,样本哈希值为:32519b85c0b422e4656de6e6c41878e95fd95026267daab4215ee59c107d6c77
读者可获取hash对应的样本,以跟着本文一起操作。(注:如果未找到样本的可联系本译者)
分析过程中使用的工具有dnSpy、ILSpy和PEStudio。
.NET程序集中的运行时头部是供CLR使用的PE文件中的重要元素。程序集中保存了CLR正确执行.NET程序集的元数据和关键细节。运行时头部是PE头中Optional Header中的第15个data directory条目,叫做CLR Runtime Header。
PE文件中的data directory是一个索引或目录,会列出重要的表并提供表位置和大小的信息。此结构提供对PE文件不同部分的访问地址,例如导入/导出表、资源表以及.NET程序集的CLR Runtime Header。
PE文件中CLR Runtime Header记录了CLR Runtime Header的相对虚拟地址 (RVA) 及其大小,从而将CLR引导至CLR Runtime Header,以便CLR在加载时管理.NET程序集的执行。
下面的屏幕截图显示了标记为.NET的第15个data directory。
在PeStudio中分析.NET文件。
双击.NET条目可跳转至CLR Runtime Header(元数据头),该头部在协调流中起着至关重要的作用。如下图,它提供了一个目录,列出了每个流、它的大小以及流的偏移量。当CLR或类似dnSpy、ILDasm的工具需要访问一段元数据时,它会查阅元数据标头以查找对应的流。然后,工具将被导航到该流的正确位置以读取数据。
接下来,我们来看一下.NET元数据标头内容中的一些关键字段:
-
signature:签名是不变的,均为BSJB (0x42534a42)。
-
GUID:一个128位长的唯一标识符。
-
IL-Only:表明指示程序集仅包含中间语言(IL)代码,不包含本地CPU特定代码。 PE文件可以包含托管代码和非托管代码。
-
32-Bit Required:设置此标志后,表明程序集需要32位运行时环境,哪怕程序运行在64位操作系统上。这一般被需要32位本机依赖项的程序集或者具有32位运行时特定行为的程序集使用。
-
强名称签名(Strong Name Signed):表明程序集是否已使用强名称进行签名。强命名涉及使用公钥/私钥对签名程序集,提供唯一的身份并确保程序集未被篡改。
-
流(Streams)指包含特定类型元数据的结构化数据段。 在.NET程序集元数据中的关键流包括以下几种:
- #~:主元数据流包含元数据表。这些表存储有关程序集中定义的类型、方法、字段、参数和其他元素的信息。#~流是根据CLI规范定义的元数据表架构构建的。
-
#Strings:该流存储元数据使用的字符串,例如类型名称、方法名称和字段名称。元数据表(在 #~流中)中的引用指向该流中实际字符串值的偏移量。
-
#US(用户字符串)流:此流保存程序集中使用的文字字符串值,例如代码中字符串变量或字符串常量的默认值。元数据引用这些字符串,特别是在加载字符串文字的说明中。
- #GUID:包含程序集使用的GUID(全局唯一标识符)。该流中的每个条目都是一个GUID,用于唯一标识元数据的某些方面,例如模块版本ID(MVID)。
- #Blob:BLOB(二进制大对象,Binary Large Object)流存储用于各种目的的二进制数据,例如字段的默认值、方法签名、属性签名和封送处理信息。元数据表中的项目引用此流以获取详细的二进制信息。
- #Pdb:此可选流包含将元数据和IL代码与源代码行和文件相关联的调试信息。
因为不同查看器的表示方式不同,在某些.NET程序集查看器中查看可执行文件的元数据可能很棘手。下面的屏幕截图展示了dnspy和ILspy之间的区别。
下图为ILspy的展示内容。
下图为dnspy的展示内容。
元数据(Metadata)
.NET中的元数据是一组描述编程元素及其特征的二进制信息。这包括有关代码中定义的类型(类、接口、枚举等)、成员定义(方法、属性、字段、事件)、对其他类型和成员的引用以及程序集本身的信息。
在存储描述性信息的PE文件中,元数据被分为几个表,统称为元数据表(Metadata Tables)。每个表都遵循特定的模式,该模式概述了它所包含的数据的结构和性质。以下是可在元数据表中找到的一些关键信息类型:
定义表(Definition Tables):包含有关当前程序集中定义的代码的信息。这包括:
-
TypeDef表:源代码中定义的每个类或接口的详细信息,包括其名称、可见性、基本类型及其包含的方法或属性。关键领域包括:
- TypeName:类型的名称。
- TypeNamespace:类型所属的命名空间。
- BaseType:TypeDef、TypeRef或TypeSpec表的索引,指示类型的基类。
- Flags:描述类型属性(可见性、抽象/密封状态等)。
-
MethodDef表:每个方法的详细信息,包括其名称、签名(参数和返回类型)以及与其关联的IL代码。关键领域包括:
- Name:方法的名称。
- Signature:指向方法签名的blob索引,其中包括其调用约定、返回类型和参数。
- RVA:相对虚拟地址,指向PE文件中方法的实现。
-
FieldDef表:每个字段(类变量)的详细信息,包括其名称和类型。关键字段包括:
- Name:字段的名称。
- Signature:指向字段类型签名的blob索引。
- Flags:指定字段属性,如可见性、静态/实例等。
引用表(Reference Tables):包含程序集引用的外部代码信息。这包括:
-
TypeRef表:当前程序集引用的其他程序集中定义的类型的信息。
-
MemberRef表:在另一个模块或程序集中定义的成员(方法、属性等)的描述。
清单元数据表(Manifest Metadata Table):描述程序集本身,包括:
-
Assembly表:有关程序集的信息,例如其名称、版本、区域性和强名称签名。
-
AssemblyRef表:该程序集所依赖的其他程序集的详细信息,包括它们的名称、版本和公钥(如果它们是强命名的)。
-
其他元数据表,除了上述之外,还有几个其他表存储信息,例如:
- Module Table:有关当前模块的信息,例如其名称和唯一标识它的GUID(全局唯一标识符)。
- 自定义属性表(CustomAttribute Table):包含应用于程序集中各种元素的自定义属性的详细信息。
- 事件表和属性表(Event Table and Property Table):描述类型中声明的事件和属性。
- 参数表(Param Table):有关方法参数的信息。
- StandAloneSig表:独立签名可用于封装类型或方法签名。
- 常量表(Constant Table):存储代码中定义的常量。
这些表对于CLR的操作至关重要,因为它们提供执行程序集所需的上下文信息。这些表格在运行时被读取以执行各种任务,例如类型实例化、方法调用、安全验证等。
这些元数据表以高度优化的二进制格式进行编码,可由运行时有效地处理。通过映射,还可以通过编程方式访问元数据,从而允许.NET应用程序在运行时检查自己的结构或其他程序集的结构。这种内省功能是.NET框架的强大功能之一,支持一系列动态编程场景。
#### 元数据唯一标识符 (ID)
元数据令牌(Metadata tokens)是CLR用于引用程序集元数据表中的元数据元素的唯一标识符。这些表中的每个条目都分配有一个元数据令牌,该令牌充当对该特定项目的稳定引用。
元数据令牌对于CLR与编译代码交互至关重要,因为它们为运行时提供了一种有效识别和访问元数据的方法。 PE 文件中的每个类型、成员、签名或其他元数据描述符都有一个相应的令牌。
在dnSpy中,每个方法都在其声明上方包含注释,信息包括令牌、RID、RVA和文件偏移量,如下面的屏幕截图所示。
我们来看看这些字段的含义:
-
令牌:元数据令牌的高字节(大端)指定元数据的类型,以便于运行时识别。它指示令牌引用元数据中的哪个表(TypeDef、TypeRef、MethodDef等)。有关令牌类型值的更多信息,请参阅附录 A。
-
行索引(RID):元数据令牌的剩余24位用于索引相关元数据表。它们指示可以找到该元素的实际元数据的行号。由于每个表可能有数百万个条目,因此24位可提供充足的索引范围。
-
RVA:RVA是方法主体(其编译的IL代码)相对于程序集加载到内存的基地址的地址。例如,0x00023B28表示该方法的IL代码从加载模块基地址的内存偏移量开始。 CLR使用RVA在运行时定位并执行方法的代码。在PE文件中,RVA在将文件加载到内存中时被广泛用于引用文件的各个部分。
-
文件偏移:该值表示方法的IL代码在磁盘上实际.NET程序集文件(.dll 或 .exe 文件)中的位置。 0x00021D28表示从文件开头到方法代码开始的位置之间的偏移量(以字节为单位)。这对于直接二进制分析或操作汇编文件非常有用,因为你可以通过文件偏移准确地找到该方法代码在文件中的位置。
元数据令牌的结构增强了CLR在运行时解析引用时的性能。例如,当JIT编译器需要将IL编译为本机代码时,它使用元数据令牌来查找方法签名、类型信息等。元数据令牌系统还支持CLR的动态功能,例如映射。它使得类型安全、安全检查、跨语言互操作等各种运行时服务能够有效地进行。
元数据表示例
以下是SolarWinds 恶意软件的启动函数。
令牌值的高位(0x6)对应于元数据表编号6,即方法(MethodDef)表。令牌的下半部分是0x5fa(1530),这是方法表中的条目号。
检查start方法的元数据的下半部分,值0x00058D66是距可执行文件开头的偏移量,偏移量0x1EC15则是#String流中的偏移量,其中将包含方法名称:Start。让我们看看 dnSpy 的十六进制编辑器中的样子:
途中为#String流中的偏移量值。 DnSpy自动检测字符串的值。
要查看 Strings 流中的数据,我们将执行以下操作:
在新窗口中,转到偏移量(相对于字符串流的开头 – RVA)并查看我们要查找的字符串 – Start。
清单
.NET清单是.NET程序集的关键组件,充当元数据中心,描述程序集中的元素如何相互关联。它嵌入在每个程序集(无论是静态程序集还是动态程序集)中,包含程序集操作所需的基本数据,包括其版本要求、安全标识、范围定义以及对资源和类的引用的解析。
.NET清单的主要功能是提供全面的元数据描述,以促进程序集的识别、版本控制和依赖项管理。它确保程序集是自述的,有助于解析类型引用以及将这些引用映射到包含其声明和实现的文件。这对于维护版本控制并确保不同程序集和依赖于它们的组件之间的兼容性尤其重要。
.NET清单的内容
清单包含对程序集的身份和操作至关重要的各种信息:
- 程序集名称:指定程序集名称的文本字符串。
- 版本号:包括主要和次要版本号,以及修订版和内部版本号,公共语言运行时使用它们来强制执行版本策略。
- 区域信息:指定程序集支持的文化或语言,对于包含特定于文化或语言的信息的附属程序集尤其重要。
- 程序集中的文件列表:包括程序集中包含的每个文件的哈希值及其名称,确保程序集的完整性。
- 类型引用信息:由运行时用来将类型引用映射到包含其声明和实现的文件,这对于类型安全性和正确性至关重要。
- 有关引用程序集的信息:列出当前程序集静态引用的其他程序集,包括它们的名称、元数据(如版本、区域性、操作系统)和公钥(如果它们是强名称)。
方法体结构
在.NET 中,方法主体结构可以用两种格式进行编码,称为“Tiny”格式和“Fat”格式,每种格式根据方法的复杂性和需求提供不同的目的。 Tiny和Fat标头之间的选择是由.NET编译器根据正在编译的方法的复杂性进行的。
Tiny标头是两种格式中较简单的一种。当方法满足以下条件时,将使用Tiny标头:
- 该方法小于 64 字节。
- 其堆栈深度不会超过8个槽(槽,slot,堆栈上的每个项目一个槽,无论项目的大小如何)。
- 它不包含局部变量或结构化异常处理程序(SEH)。
微小的标头更加紧凑,并针对小型方法进行了优化。
Tiny 头是一个单字节长,低2位设置为“0x2”(二进制“10”),表明它是一个Tiny头,其余6位表示方法体的大小(以字节为单位)。这种紧凑的格式允许高效存储小型方法体,从而减少简单方法的元数据开销。
场地 | 大小(位) | 描述 |
---|---|---|
标头标志 | 2 | 始终设置为“10”(b) 以指示 Tiny 标头。 |
方法大小 | 6 | 指定方法主体的大小(以字节为单位)(最大 63 字节)。 |
Fat 标头用于超出 Tiny 标头限制的更复杂的方法体:
- Fat 标头用于不符合Tiny标头标准的较大方法。
- 它提供了附加信息,例如方法的局部变量和SEH。
- 当一个方法超过Tiny标头的大小或复杂性限制时,它会使用Fat标头格式。
Fat 标头较大,由多个字段组成,其中包括一个标志字段,用于指示方法体的附加特征(例如异常处理子句或局部变量初始化)。
字段 | 大小(位) | 描述 |
---|---|---|
Flags | 2 | 指定方法体的属性,包括是否存在局部变量、init局部变量等。最低位设置为1时(即0x3)表示 Fat 标头。 |
Size | 2 | 标头的大小(以 4 字节字为单位),包括整个标头的大小,而不仅仅是方法主体。 |
MaxStack | 2 | 方法执行期间任意时刻操作数堆栈上的最大项目数。 |
CodeSize | 4 | 方法主体的IL代码的大小(以字节为单位)。 |
LocalVarSigTok | 4 | 局部变量签名的元数据令牌,仅当方法具有局部变量时才出现。 |
More sections | 可变的 | 如果在flag中指定,则存在诸如异常处理子句之类的附加部分。 |
有关这些标头结构的更多详细信息,可以查看此资源。
方法体示例
我们以函数DeleteDiscoveryProfileInternal为例:
单击偏移量(或 RVA)将引导我们到十六进制视图窗口中的方法的标题:
在这里,当我们将鼠标悬停在标头的字节上时,我们可以看到 DnSpy 突出显示标头字段。函数的内容 – 指令位于标题后面,并在 dnSpy 中表示为 image_core_ilmethod_fat.instruction[]。为了更好地理解和查看指令的值(操作码),我们将在 IDA 中打开恶意软件:
在左侧我们可以看到每个操作码的值,遵循dnSpy中的前2条指令:
结论
在对.NET可执行文件结构的初步探索中,我们深入研究了.NET框架的复杂性,强调了它对于合法开发和恶意软件创建的双重用途。通过剖析.NET编译过程、运行时执行以及元数据和程序集的复杂细节,我们为理解.NET应用程序如何运行以及如何出于恶意目的操纵它们奠定了基础。当你深入研究.NET恶意软件逆向工程技术时,这是让你开始获得有效分析和应对基于.NET的威胁的技能和知识的基础。
附录
令牌类型表
令牌类型 | 值(十六进制) | 描述 |
---|---|---|
模块 | 0x00 | 引用模块定义。 |
类型参考 | 0x01 | 引用另一个模块中的类型。 |
类型定义 | 0x02 | 定义模块内的类型。 |
场定义 | 0x04 | 定义类型中的字段。 |
方法定义 | 0x06 | 定义类型内的方法。 |
参数定义 | 0x08 | 定义方法的参数。 |
接口实现 | 0x09 | 定义类型的接口实现。 |
会员参考 | 0x0A | 引用另一个模块中的字段或方法。 |
自定义属性 | 0x0C | 定义自定义属性。 |
允许 | 0x0E | 定义声明性安全权限。 |
签名 | 0x11 | 定义独立签名。 |
事件 | 0x14 | 定义类型内的事件。 |
财产 | 0x17 | 定义类型内的属性。 |
模块参考 | 0x1A | 引用外部模块。 |
类型规格 | 0x1B | 使用签名指定类型。 |
集会 | 0x20 | 定义程序集元数据。 |
装配参考 | 0x23 | 引用另一个程序集。 |
文件 | 0x26 | 定义与程序集关联的外部文件。 |
导出类型 | 0x27 | 定义从另一个程序集导出的类型。 |
清单资源 | 0x28 | 定义嵌入资源。 |
通用参数 | 0x2A | 定义类型或方法的通用参数。 |
方法规范 | 0x2B | 指定泛型方法的方法实例化。 |
通用参数约束 | 0x2C | 指定对通用参数的约束。 |
文中部分内容的深入了解参考链接:
https://www.cnblogs.com/CarpenterLee/p/5994681.html
https://www.cnblogs.com/yeanzhi/archive/2013/02/24/2924687.html