最近一段时间不忙,闲下来的空闲时间,重读了一下CLR的原理,回味一下有关程序集的的知识,顺便练了一下手,学习致用,破解了若干个.NET平台的软件。以此来反观.NET程序开发中,需要注意的一些问题。

基本原理

.NET平台的编译格式是依靠MSIL中间语言,运行时即时编译(JIT)成CPU指令,对Win 32 的PE格式进行了扩展。程序集是自描述的,本身蕴藏了丰富的元数据信息。MSDN中有一段代码例子,请参考下面的程序

using System;
using System.Reflection; public class Example
{
public static void Main()
{
// Get method body information.
MethodInfo mi = typeof(Example).GetMethod("MethodBodyExample");
MethodBody mb = mi.GetMethodBody();
Console.WriteLine("\r\nMethod: {0}", mi); // Display the general information included in the
// MethodBody object.
Console.WriteLine(" Local variables are initialized: {0}",
mb.InitLocals);
Console.WriteLine(" Maximum number of items on the operand stack: {0}",
mb.MaxStackSize); // Display information about the local variables in the
// method body.
Console.WriteLine();
foreach (LocalVariableInfo lvi in mb.LocalVariables)
{
Console.WriteLine("Local variable: {0}", lvi);
} // Display exception handling clauses.
Console.WriteLine();
foreach (ExceptionHandlingClause ehc in mb.ExceptionHandlingClauses)
{
Console.WriteLine(ehc.Flags.ToString()); // The FilterOffset property is meaningful only for Filter
// clauses. The CatchType property is not meaningful for
// Filter or Finally clauses.
switch (ehc.Flags)
{
case ExceptionHandlingClauseOptions.Filter:
Console.WriteLine(" Filter Offset: {0}",
ehc.FilterOffset);
break;
case ExceptionHandlingClauseOptions.Finally:
break;
default:
Console.WriteLine(" Type of exception: {0}",
ehc.CatchType);
break;
} Console.WriteLine(" Handler Length: {0}", ehc.HandlerLength);
Console.WriteLine(" Handler Offset: {0}", ehc.HandlerOffset);
Console.WriteLine(" Try Block Length: {0}", ehc.TryLength);
Console.WriteLine(" Try Block Offset: {0}", ehc.TryOffset);
}
} // The Main method contains code to analyze this method, using
// the properties and methods of the MethodBody class.
public void MethodBodyExample(object arg)
{
// Define some local variables. In addition to these variables,
// the local variable list includes the variables scoped to
// the catch clauses.
int var1 = 42;
string var2 = "Forty-two"; try
{
// Depending on the input value, throw an ArgumentException or
// an ArgumentNullException to test the Catch clauses.
if (arg == null)
{
throw new ArgumentNullException("The argument cannot be null.");
}
if (arg.GetType() == typeof(string))
{
throw new ArgumentException("The argument cannot be a string.");
}
} // There is no Filter clause in this code example. See the Visual
// Basic code for an example of a Filter clause. // This catch clause handles the ArgumentException class, and
// any other class derived from Exception.
catch(Exception ex)
{
Console.WriteLine("Ordinary exception-handling clause caught: {0}",
ex.GetType());
}
finally
{
var1 = 3033;
var2 = "Another string.";
}
}
} // This code example produces output similar to the following:
//
//Method: Void MethodBodyExample(System.Object)
// Local variables are initialized: True
// Maximum number of items on the operand stack: 2
//
//Local variable: System.Int32 (0)
//Local variable: System.String (1)
//Local variable: System.Exception (2)
//Local variable: System.Boolean (3)
//
//Clause
// Type of exception: System.Exception
// Handler Length: 21
// Handler Offset: 70
// Try Block Length: 61
// Try Block Offset: 9
//Finally
// Handler Length: 14
// Handler Offset: 94
// Try Block Length: 85
// Try Block Offset: 9
 
 

重头戏在这一行: MethodBody mb = mi.GetMethodBody(); 它返回方法的元数据的字节流。说通俗一点,它返回的是方法的源代码。把这个返回的字节流转换成MSIL指令,不是一件难事。MSDN中有描述,CodeProject上面有一篇文章,讲解如何写一个解释方法,把MethodBody传化为方法的MSIL代码,几乎就是一个反编译器的模型。

再去理解那句话:.NET程序集是自描述的,是不是理解更深刻了一些。

 

基本方法

熟悉MSIL语言。对照文档手册,边读边看边学。我的办法是,想知道高级语言编译时如何翻译成MSIL的,找一本高级语言(C#,VB.NET)的入门手册书,把代码敲进去,编译成程序集,再用反编译器.NET Reflector一行一行对比看,进步神速。之所以要找入门书,是因为它会讲解到高级语言的各个特性,反编译时看到的MSIL代码会更全面。

 

两个软件破解的实战经验

软件A

软件A采用的保护措施是根据用户购买的许可数量,分成个人版,专业版,企业版。版本越高,能拥有的功能更高级。

比如个人版只能一天只能下载50个文档,专业版一天可以下载1000份文档,企业版则不受限制。

方法:跟踪软件启动时,加载的程序集和执行的动作。.NET程序一般有两个地方放程序文件,一是GAC,另一个是当前目录,或是当前目录的子目录(需要在配置文件中指定)。找到GAC中的文件,先把它拷贝到普通文件夹。

再拿.NET Reflector打开看看,如果能打开,看是否有strong name,如打不开,则用IL DASM反编它,看生成的IL文件中,是否有strong name的值。再开一下软件,看看哪些地方会显示注册/未注册,试用期等信息。一般有几个地方会暴露软件的保护方法:

1  直接在主窗体的标题栏中显示,“软件已注册”,”软件未注册,还有29天试用“

2  在关于对话框中显示软件是否注册,剩余许可天数或次数。

再从IL代码中追查,看它在哪些地方,保存当前的使用天数的数据。以我的追查经验,多半是保存在注册表中。于是开一个注册表写入监控程序,一下就知道它写到什么去了,再来解码就容易很多。

 

软件B

我的软件注册方式也是这样做的,所以我对这种方式非常熟悉。是运用Xml 签名文件,生成一个只读的许可文件,看起来是文本文件,但是你不能修改,一有任何的修改,重新计算Hash值会,会验证失败。这种方式,我的QQ群中的朋友都知道,用下面的代码,重新生成一套密钥匙对,替换程序集中的公匙,再用私匙生成一个注册文件让它验证。

[TestMethod]
public void SolutionValidationTest()
{
string publickey = RSACryptionHelper.GeneratePublickKey(false);
string privateKey = RSACryptionHelper.GeneratePublickKey(true);
}

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

 

基本思路与对策

1 替换策略  当程序集中有写死一些基本信息,比如strong name的public token,xml signature的public key,这时,只有替换程序集中的这些元数据,才能破解成功。因为这些信息是全球唯一的,就像GUID字符串的值一样,全世界再没有任何一台电脑能生成和他一样的数据(public token,public/private key),应用替换方法。

2 重签名策略。strong name一般都会配合代码进行检查,让它不会轻易被破解。遇到这种情况,可以考虑移除现在的签名,用本机重新生成的key给它签名。如果能保证程序集和它引用的程序都是一样的签名,则匹配成功。到目前为止,还没有看到一套程序,会有几个不同的strong name同时存在于不同的程序集中。

3  rouding-trip策略。根据程序,生成MSIL,修改MSIL,再生成程序集。这里涉及到修改MSIL代码指令,可以让程序直接绕过验证,或是不验证,这种方式威慑力最大。根本不用考虑验证这一回事,直接跳过,把验证方法方法体全部删除,第一句IL代码为nop,或是ret,直接返回。

4  代码与反编译结合策略。有时候面对程序集中五花八门的字符,完全不理解它的含义,无从下手。

遇到这种情况,它是应用了字符串加密技术。以其人之道,还其人之身,下面的几行简单的方法,破解文中的不可理解的字符:

Assembly assembly=Assembly.GetExecutingAssembly();
Type type=assembly.GetType("Class64");
MethodInfo mi=type.GetMethod("smethod_0");
mi.Invoke(null,new object [] { " ᓏᓒᓕᓎᒹᓊᓝᓑ "} );

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

再把这个方法做成一个GUI程序,依此对照文中乱码字符,全部解码。

5  直接编辑策略。程序集文件也是一个文件,编译器以生成目标格式的方式生成这个文件,而我们用到的文本,则是手工敲入字符生成,你可以用十六进制文件去编辑它的值,依照规律即可。.NET 反编译时,经常遇到的一个错误是的

This assembly does not contain a CLI header,This assembly is not a PE.NET format。借助于PE格式知识,把添加进去的错误的元数据删除即可。这里有一个典型的例子,Visual Studio本身是不可以生成多Module的程序集,一个程序集只能有一个Module,但是加密程序通常会给它加上多余的Module,对照PE.NET格式标准,删除多余的节即可。

6 利用Mono.Ceil.dll主动修改程序集策略。第四个策略中我提到字符串有加密,反其道行之,我把所有调用该方法的地方,再运算一次,重新生成一次,即可破解字符串,再把重新生成的代码写成一个程序集文件。

7 应用密码学算法策略。如果应用对称加密,试着给一些随机的错误的序列号给它,看看它是如何验证序列号的,再将此方向反向,导出如何生成它可以验证的字符中。举例说明,有一个软件,它的序列号是这样的

1234567890 =》 12  34 56 78 90 => 18 52 86 120 144

序列号1234567890,它把这个序列号以两个为一组,前后相邻的2个,转化为10进制数,再运算一次DES对称算法解密,看是是符合要求的密码。破解它的方法,就是学会如何生成一个字符串,让它通过验证,也就是理解这个流程。

也有应用MD5加密算法的软件。这样,整个软件只有一个序列号可用。把给你的序列号,验证时生成MD5哈希值,与它保存在当前程序中的密码匹配,验证错误则失败,否则通过。这种方式的好处是,你完全没有办法应用密码学知识去破解它,要么用前面提到的,把验证方法改成nop直接返回,别无它法。

8 跟踪策略。.NET时代是开创绿色软件时代,一个.NET Runtime,所有程序共用,再调用这个公共类库。但是,我发现几乎所有的加密方法中,都会涉及把注册信息或是机密信息,写到注册表中去。写到注册表中去的键或值,肯定不会是明文,至少也要用个ToBase64把它变成一堆乱码。键值不可读,一般要还原到验证,你要知道自己在哪个地方写入了UserName,哪个地方写入LicenseKey,一般会用可逆的算法,就像第六条中所说的。也有软件应用可不可逆的算法,比如直接用MD5加密,这时,生成键值UserName或LicenseKey的变量,肯定是不变的,否则,无法再次生成键值,去对比验证。找一个合适的注册表跟踪软件,如Reg Monitor为你的破解之路添加一线希望。

与此相对应的,Process Explorer, Dependency Walker也都应当应用到实际中,以发现珠丝马迹。

要做Web方面的破解,Findder,Http Watch可以很好的帮忙你分析服务器与IE客户端之间,有哪些数据交互来往。

有的追踪是死路(dead end),比如你想知道在网上买东西,网银付款时,在自己的打开的IE中,输入的钱数,是如何提交到银行,被银行扣走的。但是大部分程序或是网站,没有做到这么高的安全级别,可以考虑尝试。

深入理解.NET程序的原理 谈一谈破解.NET软件的工具和方法的更多相关文章

  1. Atitit 深入理解耦合Coupling的原理与attilax总结

    Atitit 深入理解耦合Coupling的原理与attilax总结     耦合是指两个或两个以上的电路元件或电网络等的输入与输出之间存在紧密配合与相互影响,并通过相互作用从一侧向另一侧传输能量的现 ...

  2. ch01.深入理解C#委托及原理(转)

    ch01..深入理解C#委托及原理_<没有控件的ASPDONET> 一.委托 设想,如果我们写了一个厨师做菜方法用来做菜,里面有 拿菜.切菜.配菜.炒菜 四个环节,但编写此方法代码的人想让 ...

  3. Java进阶(七)正确理解Thread Local的原理与适用场景

    原创文章,始自发作者个人博客,转载请务必将下面这段话置于文章开头处(保留超链接). 本文转发自技术世界,原文链接 http://www.jasongj.com/java/threadlocal/ Th ...

  4. 通俗化理解Spring3 IoC的原理和主要组件(spring系列知识二总结)

    ♣什么是IoC? ♣通俗化理解IoC原理 ♣IoC好处 ♣工厂模式 ♣IoC的主要组件 ♣IoC的应用实例 ♣附:实例代码 1.什么是IoC(控制反转)? Spring3框架的核心是实现控制反转(Io ...

  5. 微信小程序底层原理与运行机制类文章学习

    参考文档 小程序底层实现原理及一些思考 为了安全和管控, 双线程执行 Web Worker执行用户的代码; UI线程执行大部分的功能. 微信小程序架构原理 只通过mvvm模板语法动态改变页面, 不支持 ...

  6. 第16 章 : 深入理解 etcd:基于原理解析

    深入理解 etcd:基于原理解析 本文将主要分享以下三方面的内容: 第一部分,会为大家介绍 etcd 项目发展的整个历程,从诞生至今 etcd 经历的那些重要的时刻: 第二部分,会为大家介绍 etcd ...

  7. 菜鸡的Java笔记 国际化程序实现原理

    国际化程序实现原理 Lnternationalization        1. Locale 类的使用        2.国家化程序的实现,资源读取                所谓的国际化的程序 ...

  8. [diango]理解django视图工作原理

    前言:正确理解django视图view,模型model,模板的概念及其之间的关联关系,才能快速学习并上手使用django制作网页 本文主要讲解自己在学习django后对视图view的理解 在进入正文之 ...

  9. iOS app 程序启动原理

    iOS app 程序启动原理 Info.plist: 常见设置     建立一个工程后,会在Supporting files文件夹下看到一个"工程名-Info.plist"的文件, ...

随机推荐

  1. WKWebView与Js (OC版)

    OC如何给JS注入对象及JS如何给IOS发送数据 JS调用alert.confirm.prompt时,不采用JS原生提示,而是使用iOS原生来实现 如何监听web内容加载进度.是否加载完成 如何处理去 ...

  2. 谈谈Linux下动态库查找路径的问题

    学习到了一个阶段之后,就需要不断的总结.沉淀.清零,然后才能继续"上路".回想起自己当年刚接触Linux时,不管是用源码包编译程序,还是程序运行时出现的和动态库的各种恩恩怨怨,心里 ...

  3. 10个你必须掌握的Linux超酷VI命令技巧

    大部分Linux开发者对vi命令相当熟悉,可是遗憾的是,大部分开发者都只能掌握一些最常用的Linux vi命令,下面介绍的10个vi命令虽然很多不为人知,但是在实际应用中又能让你大大提高效率. 在使用 ...

  4. struts2 struts1.x 区别

    此文转于http://www.blogjava.net/sterning/archive/2007/07/17/130892.html Struts作为MVC 2的Web框架,自推出以来不断受到开发者 ...

  5. 新浪微博SDK的使用

    花了两天时间研究了一下新浪微博SDK,遇到了不少问题,有必要整理一下 1.首先下载下weiboSdk,导入weiboSDKD和weiboSDKDemo两个项目,这时发现导入的weiboSDKDemo项 ...

  6. iOS开发 multipart 上传多张图片

    - (void)uploade:(NSDictionary *)dic pic:(NSArray *)picArray {    NSString *hyphens = @"--" ...

  7. Unity3D研究院之使用Animation编辑器编辑动画(五十四)

     Unity提供了Animation编辑器,它可以为我们编辑物理动画.举个例子比如场景中有一个来回摇动的秋千,这个秋千在项目中完全只起到衬托作用,它不会与别的游戏对象有任何交互.如果这个秋千也用代码来 ...

  8. linux tcp协议重传定时器

    RTO:重传超时时间 RTT:往返时间

  9. sql语句对数据库表进行加锁和解锁

    锁是数据库中的一个非常重要的概念,它主要用于多用户环境下保证数据库完整性和一致性. 我们知道,多个用户能够同时操纵同一个数据库中的数据,会发生数据不一致现象.即如果没有锁定且多个用户同时访问一个数据库 ...

  10. Coins(HDU 2844):一个会超时的多重背包

    Coins  HDU 2844 不能用最基础的多重背包模板:会超时的!!! 之后看了二进制优化了的多重背包. 就是把多重转变成01背包: 具体思路见:http://www.cnblogs.com/tt ...