一种对.net可执行程序进行保护的方法和系统的制作方法

文档序号:6542990阅读:153来源:国知局
一种对.net可执行程序进行保护的方法和系统的制作方法
【专利摘要】本发明公开了一种对.NET程序进行保护的方法及系统。该方法首先采用元数据API来分析.NET程序的结构,分析出程序中引用程序集、程序集、类和类中的方法,以及类中的方法的参数、返回值、IL指令等信息。其次由用户选择需要保护的方法,保护系统生成需要保护的方法的代理类,保护系统对需要保护的方法的IL指令进行加密。再次,保护系统创建保护后的方法,将加密后的IL指令以局部变量的形式存储在保护后的方法中,保护后的方法主要功能是创建动态方法、解密IL指令、设置动态方法的IL指令、创建动态方法的代理类、通过代理类调用动态方法,最后,将引用程序集、程序集、代理类及保护后的方法等信息注入到保护后的程序中。
【专利说明】一种对.NET可执行程序进行保护的方法和系统
【技术领域】
[0001]本发明涉及信息安全【技术领域】,特别涉及一种对.NET可执行程序进行保护的方法和系统。
【背景技术】
[0002].NET是微软公司用来实现XML、Web Services、SOA (面向服务的体系结构service-oriented architecture)和敏捷性的技术。技术人员一般将微软看成一个平台厂商。微软搭建技术平台,而技术人员在这个技术平台之上创建应用系统。从这个角度,.NET也可以如下来定义:.NET是微软的新一代技术平台,为敏捷商务构建互联互通的应用系统,这些系统是基于标准的、联通的、适应变化的、稳定的和高性能的。从技术的角度,一个.NET应用是一个运行于.NET Framework之上的应用程序。更精确的说,一个.NET应用是一个使用.NET Framework类库来编写,并运行于公共语言运行时Common LanguageRuntime之上的应用程序。如果一个应用程序跟.NET Framework无关,它就不能叫做.NET程序。比如,仅仅使用了 XML并不就是.NET应用,仅仅使用SOAP SDK调用一个Web Service也不是.NET应用。.NET是基于Windows操作系统运行的操作平台,应用于互联网的分布式。
[0003]在信息安全领域中,软件作为一种宝贵的财富,软件的安全性越来越受到单位、企业和个人的关注。.NET是作为微软的新一代技术平台,.NET统一了编程语言,无论程序使用C#,还是C++、VB编写,程序最终都会编译为.NET中间语言MSIL (MicrosoftIntermediate Language)。MSIL是将.NET代码转化为机器语言的一个中间过程,它是一种介于高级语言和基于Intel的汇编语言的伪汇编语言。当用户编译一个.NET程序时,编译器将源代码翻译成Microsoft中间语言(MSIL),它是一组可以有效地转换为本机代码且独立于CPU的指令。当执行这些指令时,实时(JIT)编译器将它们转化为CPU特定的代码。由于公共语言运行库支持多种实时编译器,因此同一段MSIL代码可以被不同的编译器实时编译并运行在不同的结构上。从理论上来说,MSIL将消除多年以来业界中不同语言之间的纷争。在.NET的世界中可能出现下面的情况,一部分代码可以用EFFIL实现,另一部分代码使用C#或VB.NET完成,但是最后这些代码都将被转换为中间语言。这给程序员提供了极大的灵活性,程序员可以选择自己熟悉的语言,并且再也不用为学习不断推出的新语言而烦恼了。
[0004]编译为托管代码时,编译器将源代码翻译为Microsoft中间语言(MSIL),这是一组可以有效地转换为本机代码且独立于CPU的指令。MSIL包括用于加载、存储和初始化对象以及对对象调用方法的指令,还包括用于算术和逻辑运算、控制流、直接内存访问、异常处理和其他操作的指令。要使代码可运行,必须先将MSIL转换为特定于CPU的代码,这通常是通过实时(JIT)编译器来完成的。由于公共语言运行库为它支持的每种计算机结构都提供了一种或多种JIT编译器,因此同一组MSIL可以在所支持的任何结构JIT上编译和运行。编译后的.NET运行时,由虚拟机执行引擎把中间语言代码转换为机器代码。
[0005]虽然.NET提供了便利,但.NET编译后的程序集非常容易被反编译,比如利用Reflector工具几乎可以生成.NET程序的源代码。Reflector是由微软员工Lutz Roeder编写的免费程序。Reflector的出现使.NET程序员眼前豁然开朗,因为这个免费工具可以将.NET程序集中的中间语言反编译成C#或者Visual Basic代码。除了能将IL(Intermediate Language,中间语言)转换为 C# 或 Visual Basic 以外,Reflector 还能够提供程序集中类及其成员的概要信息、提供查看程序集中IL的能力以及提供对第三方插件的支持。
[0006]目前对于.NET程序集的保护主要是基于纯软件的保护技术,常见的.NET保护软件有.NET Reactor、Dotfuscator等。基于纯软件的保护方法抗分析性较差。.Net自带工具Dotfuscator是领先的.NET模糊处理程序、压缩程序和水印处理程序,它有助于保护程序,使之免遭反向工程处理,同时使程序更小巧且更高效。Dotfuscator ProfessionalEdition 4.9旨在阻止各种反编译器生成有用的输出,即便是那些最好的反编译器也不例夕卜。它提供了全面、高效的.NET代码开发和部署过程。
[0007]元数据API是微软提供的一套非托管API,它的功能是可以读写程序集中的元数据,可操作的对象囊括了元数据的各个方面,比如可以是逻辑结构上的程序集、类、方法等。利用元数据API可以很方便的对.NET程序集进行分析和修改。元数据用于描述公共语言运行库(CLR)在JIT编译Microsoft中间语言(MSIL)、加载类、执行代码以及与COM传统型或本机领域进行交互时使用的运行库类型(类、接口和值类型)、字段、方法以及内部实现和布局信息。元数据包括在每个CLR组件中,并可供运行库、工具和服务使用。对元数据的所有操作都通过元数据API执行,这些API将客户端(工具和服务)与基础数据结构隔离,实现了一种可插入的持久性格式体系结构,这种体系结构允许运行库的二进制表示形式、COM传统型类型库和其他格式以透明方式出入内存。元数据API包括发出(即生成)和导入元数据的接口。客户端可通过下列方式发出或导入元数据:编译器和工具通过调用“发出”API发出元数据。元数据在编译和链接过程中发出。在生成组件或应用程序的过程中,RAD (Rapid Application Develop,快速应用开发)工具将发出元数据。API向内存中数据结构写入数据并从中读取数据。在保存时,这些内存中结构将被压缩并以二进制格式保存到目标编译单元(.0bj文件)、可执行文件(.exe文件)或独立元数据二进制文件中。在通过链接多个编译单元来形成EXE或DLL文件时,“发出”API将提供相应的方法,用于将每个编译单元中的元数据合并为一个集成的元数据二进制文件。在导入元数据时,加载程序及其他运行库工具和服务将调用“导入”API来获取有关组件的信息,以便可以完成加载和激活等任务。
[0008]基于硬件的.NET保护方法,主要是从.NET程序文件中提取部分代码到软件保护装置(下文也称为“保护装置”)中,这样一部分代码在计算机中执行,另外一部分在保护装置中模拟执行。基于硬件的保护方法可以移植的代码指令有限,受限于硬件的保护装置的空间限制,移植的代码的数量也有限。而软件保护装置是一种通过计算机接口(包括但不限于并口或者USB接口)连接到计算机主机上的硬件设备。该设备内部具有非易失性存储空间可供读写,通常还具有单片机或者微处理控制芯片等计算处理单元。软件开发者可以通过接口函数和软件保护装置进行数据交换(即对软件保护装置进行读写),来检查软件保护装置是否插在接口上;或者直接用软件保护装置附带的工具进行加密。这样,软件开发者可以在软件中设置多处软件锁,利用软件保护装置作为钥匙来打开这些锁;如果没插软件保护装置或软件保护装置不对应,软件将不能正常执行。
[0009]此外,软件保护装置内部包含特定的功能,例如一部分存储空间、一些密码算法或者一些用户自定义的算法或者功能。在软件发行之前,软件开发者修改自己的软件代码,使得软件在运行过程中需要使用到软件保护装置内部的一些功能,这样软件离开软件保护装置之后就会无法运行,而软件保护装置作为一种硬件设备复制的难度较大,从而起到防止盗版软件非法传播的作用。
[0010]当前市场上主要的软件保护装置包括:美国SafeNet公司的Sentinel Superpro>以色列Aladdin公司的Hasp HL、中国北京深思洛克软件股份有限公司的精锐系列、德国W1-Bu公司的WIBU-Key等。所有这些软件保护装置都提供了内置的存储空间、私有或公开的密码算法,在软件运行过程中可以调用这些功能来检验是否属于正版。这些软件保护装置采用了智能卡芯片作为硬件的基础,而且支持用户将自己定义的功能写入到软件保护装置内部,甚至可以直接将软件的部分功能移植到软件保护装置内部完成,从而大大提高了软件被盗版的难度,通常称这种将自己定义的功能或者软件的部分功能移植到软件保护装置内部的技术为代码移植。本发明人现在对应网站为http://www.sense, com.cn/,其中详细公开了本发明人开发的软件保护装置的具体参数性能和工作原理。

【发明内容】

[0011]有鉴于此,本发明对.NET程序实现了方法级的保护,只有插上合法的保护装置保护后的程序才能运行,保护后的程序运行时,保护后的方法动态生成保护前的方法并进行调用,动态生成的方法很难进行调试,可以对程序中的所有方法进行保护,从而达到了保护方法的目的。
[0012]本发明提供了一种.NET程序的保护方法及系统,以解决.NET程序的安全问题。
[0013]一种对.NET程序进行保护的方法,该方法具体步骤包括:
分析.NET程序中引用程序集信息、程序集信息、类及类中的方法,分析类中的方法中的返回值、参数、IL指令等信息;
用户选择需要保护的方法;
根据需要保护的方法的参数、返回值,生成保护后方法的代理类;
根据需要保护的方法的IL指令,对IL指令采用保护装置进行加密;
对需要保护的方法进行保护,具体包括:根据方法的参数、返回值创建一个动态方法对象,将加密后的IL指令作为一个数组存储在保护后的方法中,调用保护装置对加密后的IL指令进行解密,设置动态方法的IL指令,创建动态方法的代理类,通过代理类调用动态方法。
[0014]将程序中的引用程序集信息、程序集信息、类信息、保护后方法的代理类、保护后的方法等信息注入到保护后的.NET程序中。
[0015]本发明还提供了一种对.NET程序进行保护的系统,所述系统包括:保护装置、分析模块、保护模块、注入模块。保护装置可以是加密锁,分析模块、保护模块、注入模块可在计算机主机中。[0016]所述保护装置为具有智能卡芯片的信息安全设备,提供软件、数据保护功能。根据一个【具体实施方式】,所述保护装置包括但不限于加密锁。
[0017]所述分析模块,利用微软提供的元数据接口中的GetAssemblyRefProps函数分析.NET程序中的引用程序集信息,利用元数据接口中的GetAssemblyProps函数分析.NET程序中的程序集信息,利用元数据接口中的GetTypeDefProps函数分析.NET程序中的类,利用元数据接口的函数EnumMethods函数分析类中的方法,利用元数据接口中的GetMethodProps函数分析类中的方法的签名信息及IL指令所在的地址,得到方法的签名信息后,根据微软的方法签名的格式分析方法的参数、返回值,方法的签名的格式为调用约定、参数个数、返回值类型、参数信息;根据方法的IL指令所在的地址得到IL指令的详细信肩、O
[0018]所述保护模块,对需要保护的方法的IL指令利用保护装置进行加密,加密算法可以采用对称算法或者非对称算法,加密的密钥由保护装置生成;生成保护后方法的签名信息,根据保护前方法的签名信息(调用约定、参数、返回值等),设置保护后的方法的签名信息,使保护前后方法的签名信息保持一致;设置保护后方法的局部变量信息,局部变量主要有保护前的方法的参数类型数组变量、返回值类型变量、动态方法对象变量、方法的代理类变量、存储加密后的IL指令的数组变量等变量;设置保护后方法的IL指令,保护后方法的IL指令主要功能为:获取保护前的方法的参数的类型信息,并保存到参数类型数组中,获取保护前方法的返回值类型信息,并保存到返回值类型信息方法中,根据保护前方法的参数类型信息、返回值类型信息创建一个动态方法对象,将创建的动态方法对象保存到动态方法对象变量中,将加密后的IL指令依次存储到加密后的IL指令数组变量中,调用保护装置对加密后的IL指令进行解密,设置动态方法的IL指令,创建动态方法的代理类,并将代理类信息保存到代理类变量中,通过代理类调用动态方法,达到执行原方法逻辑的目的。
[0019]所述注入模块,利用分析模块中分析的引用程序集信息、程序集信息、类信息、类中没有保护的方法以及保护模块生成的代理类信息、保护后的方法,将它们都注入到保护后的.NET程序中。所谓注入指构建保护后.NET程序的过程,保护后的.NET程序保留了保护前.NET程序的大部分信息,比如引用程序集信息、程序集信息,类信息,保护后的.NET程序只是修改了需要保护的方法,增加了保护后方法需要使用的代理类。注入过程使用的具体方法为利用IMeataDataAssemblyEmit接口中的DefineAssemblyRef定义引用程序集信息,DefineAssembly方法定义程序集信息,利用IMetaDataEmit接口中的DefineTypeDef函数定义类型信息,利用DefineMethod定义方法。最后可以利用ICeeFileGen接口中的函数GenerateCeeFile创建一个.NET可执行程序。
【专利附图】

【附图说明】
[0020]图1为按照本发明的一优选实施例的对.NET程序保护过程的流程示意图。
[0021]图2为按照本发明的一优选实施例中的.NET程序保护系统的示意图。
【具体实施方式】
[0022]为使本发明的目的、技术方案及优点更加清楚明白,以下参照附图并举实施例,对本发明进一步详细说明。[0023]根据本发明的一个实施方式,提供.NET程序的保护方法,具体包括:
1.分析.NET程序中引用程序集信息、程序集信息、类及类中的方法,类中方法中的返回值、参数、IL指令等信息;
2.用户选择需要保护的方法;
3.根据需要保护的方法的参数、返回值,生成保护后方法的代理类;
4.分析需要保护的方法的IL指令,对IL指令采用保护装置进行加密;
5.对需要保护的方法进行保护,具体包括,根据需要保护的方法的参数、返回值创建一个动态方法对象,将加密后的IL指令作为一个数组存储在保护后的方法中,调用保护装置对加密后的IL指令进行解密,设置动态方法的IL指令,创建动态方法的代理类,通过代理类调用动态方法;
6.将程序中的引用程序集信息、程序集信息、类信息、方法的代理类、保护后的方法等信息注入到保护后的.NET程序中。
[0024]根据本发明的一个实施方式,本发明还提供了一种对.NET程序进行保护的系统,所述系统包括:
保护装置、分析模块、保护模块、注入模块。
[0025]所述保护装置为具有智能卡芯片的信息安全设备,提供软件、数据保护功能。根据一个【具体实施方式】,所述保护装置包括但不限于加密锁。
[0026]所述分析模块,利用微软提供的元数据接口中的GetAssemblyRefProps函数分析.NET程序中的引用程序集信息,利用元数据接口中的GetAssemblyProps函数分析.NET程序中的程序集信息,利用元数据接口中的GetTypeDefProps函数分析.NET程序中的类,利用元数据接口的函数EnumMethods函数分析类中的方法,利用元数据接口分析中的GetMethodProps函数分析类中的方法的签名信息及IL指令所在的地址,得到方法的签名信息后,根据微软的方法签名的格式分析方法的参数、返回值,方法的签名的格式为调用约定、参数个数、返回值类型、参数信息;根据方法的IL指令所在的地址得到IL指令的详细信肩、O
[0027]所述保护模块,对需要保护的方法的IL指令利用保护装置进行加密,加密算法可以采用对称算法或者非对称算法,加密的密钥由保护装置生成;生成保护后方法的签名信息,根据保护前方法的签名信息(调用约定、参数、返回值等),设置保护后的方法的签名信息,使保护前后方法的签名信息保持一致;设置保护后方法的局部变量信息,局部变量主要有保护前的方法的参数类型数组变量、返回值类型变量、动态方法对象变量、方法的代理类变量、存储加密后的IL指令的数组变量等变量;设置保护后方法的IL指令,保护后方法的IL指令主要功能为获取保护前的方法的参数的类型信息,并保存到参数类型数组中,获取保护前方法的返回值类型信息,并保存到返回值类型信息方法中,根据保护前方法的参数类型信息、返回值类型信息创建一个动态方法对象,将创建的动态方法对象保存到动态方法对象变量中,将加密后的IL指令依次存储到加密后的IL指令数组变量中,调用保护装置对加密后的IL指令进行解密,设置动态方法的IL指令,创建动态方法的代理类,并将代理类信息保存到代理类变量中,通过代理类调用动态方法,达到执行原方法逻辑的目的。
[0028]所述注入模块,利用分析模块中分析的引用程序集信息、程序集信息、类信息、类中没有保护的方法以及保护模块生成的代理类信息、保护后的方法,将它们都注入到保护后的.NET程序中。所谓注入指构建保护后.NET程序的过程,保护后的.NET程序保留了保护前.NET程序的大部分信息,比如引用程序集信息、程序集信息,类信息,保护后的.NET程序只是修改了需要保护的方法,增加了保护后方法需要使用的代理类。注入过程使用的具体方法为利用IMeataDataAssemblyEmit接口中的DefineAssemblyRef定义引用程序集信息,Def ineAssembly方法定义程序集信息,利用IMetaDataEmit接口中的DefineTypeDef函数定义类型信息,利用DefineMethod定义方法。最后可以利用ICeeFileGen接口中的函数GenerateCeeFile创建一个.NET可执行程序。
[0029]根据本发明的一个实施方式,下面给出一个实施例来说明本发明。
[0030]实施例1
该实施例以保护安装有微软Windows XP 32位操作系统的系统环境为例,描述根据本发明一个具体实施例实现.NET程序保护的具体过程。
[0031]为了方便说明,本实施例给出了一个具体的.NET程序,具体如下: using System;
class Program// 声明一个 Program 的类
{
/*
声明一个成员方法test,test方法有三个整型参数a、b、c,该方法完成的功能为对前两个参数a和b进行相加,将相加的结果和c相乘,最后将相乘的结果返回*/
static int test (int a, int b, int c)
{
return (a+b)氺c;
}
/*
Main方法为程序的入口方法,该方法中声明了一个变量整型变量d,d的值为函数test传入参数1,2,3的执行后的结果。
[0032]调用Console.WriteLine将d的值输出在屏幕上。
[0033]*/
public static void Main()
{
int d = test (I, 2, 3);
Console.WriteLine ("d = {0} ",d);
}
}
将上述C#代码保存在Classl.cs文件中,并采用C#编译器进行编译,生成的可执行文件为 classl.exe.保护装置为加密锁,为IL代码提供加密方法。
[0034]如图1所示,对.NET程序进行保护的详细步骤为:
1.插入保护装置,保护装置例如选用精锐IV加密锁;2.分析.NET可执行程序中引用程序集信息、程序集信息、类及类中的方法,类中的方法中的返回值、参数、IL指令;
对于本例来说,可执行程序中的程序集为Classl,引用程序集为mscorlib,
对于本例来说,可执行程序中包含Program类,Pr ο gram中包含.ctor、Main和test三个方法。.ctor方法为类Program的构造方法,该方法的作用是构造类的实例的实例化对象;test方法有三个整型参数a、b、C,该方法完成的功能为对前两个参数a和b进行相加,将相加的结果和c相乘,将结果返回;Main方法为程序的入口方法,该方法中声明了一个变量整型变量d,d的值为函数test传入参数1,2,3的执行后的结果。其中.ctor方法和Main方法中的返回值为void类型,两者参数个数为0,test方法包含3个int类型的参数,返回值也为int类型。分析的IL指令此处只列举test方法的IL指令,指令如下。
[0035]
【权利要求】
1.一种对.NET程序进行保护的方法,该方法的步骤包括: 分析.NET程序中引用程序集信息、程序集信息、类及类中的方法,分析类中的方法中的返回值、参数、IL指令等信息; 用户选择需要保护的方法; 根据需要保护的方法的参数、返回值,生成保护后方法的代理类; 根据需要保护的方法的IL指令,对IL指令采用保护装置进行加密; 对需要保护的方法进行保护; 将程序中的引用程序集信息、程序集信息、方法的代理类、保护后的方法等信息注入到保护后的.NET程序中。
2.如权利要求1所述的方法,其中,对需要保护的方法进行保护具体包括:根据方法的参数、返回值创建一个动态方法对象,将加密后的IL指令作为一个数组存储在保护后的方法中,调用保护装置对加密后的IL指令进行解密,设置动态方法的IL指令,创建动态方法的代理类,通过代理类调用动态方法。
3.—种对.NET程序进行保护的系统,所述系统包括:保护装置、分析模块、保护模块、注入模块;其中: 保护装置,提供软件、数据保护功能; 分析模块,对.NET程序进行分析; 保护模块,对需要保护的方法的IL指令利用保护装置进行加密,生成保护后方法的签名信息,设置保护后方法的局部变量信息和保护后方法的IL指令; 注入模块,将分析模块和保护模块得到的内容注入到保护后的.NET程序中。
4.如权利要求3所述的系统,其中所述保护装置为具有智能卡芯片的信息安全设备,提供软件、数据保护功能。
5.如权利要求3或4所述的系统,其中所述保护装置为加密锁。
6.如权利要求3-5之一所述的系统,其中分析模块、保护模块、注入模块设置在计算机主机中。
7.如权利要求3-6之一所述的系统,其中所述分析模块,利用微软提供的元数据接口中的GetAssemblyRefProps函数分析.NET程序中的引用程序集信息,利用元数据接口中的GetAssemblyProps函数分析.NET程序中的程序集信息,利用元数据接口中的GetTypeDefProps函数分析.NET程序中的类,利用元数据接口的函数EnumMethods函数分析类中的方法,利用元数据接口中的GetMethodProps函数分析类中的方法的签名信息及IL指令所在的地址,得到方法的签名信息后,根据微软的方法签名的格式分析方法的参数、返回值;根据方法的IL指令所在的地址得到IL指令的详细信息。
8.如权利要求7所述的系统,其中方法的签名的格式为:调用约定、参数个数、返回值类型、参数信息。
9.如权利要求3-8之一所述的系统,其中所述保护模块,对需要保护的方法的IL指令利用保护装置进行加密;生成保护后方法的签名信息,根据保护前方法的签名信息,设置保护后的方法的签名信息,使保护前后方法的签名信息保持一致;设置保护后方法的局部变量信息;设置保护后方法的IL指令,保护后方法的IL指令主要功能为:获取保护前的方法的参数的类型信息,并保存到参数类型数组中,获取保护前方法的返回值类型信息,并保存到返回值类型信息方法中,根据保护前方法的参数类型信息、返回值类型信息创建一个动态方法对象,将创建的动态方法对象保存到动态方法对象变量中,将加密后的IL指令依次存储到加密后的IL指令数组变量中,调用保护装置对加密后的IL指令进行解密,设置动态方法的IL指令,创建动态方法的代理类,并将代理类信息保存到代理变量中,通过代理类调用动态方法,达到执行原方法逻辑的目的。
10.如权利要求9所述的系统,其中对需要保护的方法的IL指令进行加密时,加密算法采用对称算法或者非对称算法,加密的密钥由保护装置生成。
11.如权利要求9或10所述的系统,其中保护前方法的签名信息包括调用约定、参数、返回值等。
12.如权利要求9-11之一所述的系统,其中局部变量主要有保护前的方法的参数类型数组变量、返回值类型变量、动态方法对象变量、方法的代理类变量、存储加密后的IL指令的数组变量等变量。
13.如权利要求9-12之一所述的系统,其中所述注入模块,利用分析模块中分析的引用程序集信息、程序集信息、类信息、类中没有保护的方法以及保护模块生成的代理类信息、保护后的方法,将它们都注入到保护后的.NET程序中。
14.如权利要求13所述的系统,其中注入的方式采用微软提供的元数据API来实现。
15.如权利要求13或14所述的系统,其中注入过程使用的方法为利用IMeataDataAssemblyEmit 接口中的 DefineAssemblyRef 定义引用程序集信息,DefineAssembly方法定义程序集信息,利用IMetaDataEmit接口中的DefineTypeDef函数定义类型信息,利用DefineMethod定义方法;最后可以利用ICeeFileGen接口中的函数GenerateCeeFile创建一个.NET可执行程序。
【文档编号】G06F21/12GK103955635SQ201410135281
【公开日】2014年7月30日 申请日期:2014年4月4日 优先权日:2014年4月4日
【发明者】孙吉平, 韩勇 申请人:北京深思数盾科技有限公司
网友询问留言 已有0条留言
  • 还没有人留言评论。精彩留言会获得点赞!
1