.NET核心代码保护策略-隐藏核心程序集
经过之前那个道德指责风波过后也有一段时间没写博客了,当然不是我心怀内疚才这么久不写,纯粹是程序员的通病。。怎一个懒字了得,本来想写一些长篇大论反讽一下那些道德高人的。想想还是算了,那样估计会引来新一波攻势,没什么实际意义,影响风气,大家看了也不爽,这次写点有实际意义的,说说我是怎么保护我的代码的,个人心得,如有不妥的地方请见谅。
我们都知道.NET的代码容易被反编译出来,站在破解者的角度来看,破解一个软件情况可分为几种:
1.修改注册验证的代码,达到绕过注册的效果;
2.破解出注册核心算法,写出注册机;
3.完全复制代码,做成一个可编译修改的项目;
从危害是上来看,第三种是最严重的,如果你的软件被实力更强的竞争对手复制了,那简直是毁灭性的打击,理论上.NET没有什么软件是不能破解复制的,只要有高手死了心要破你的软件,保护再好也是无意义的,那时只能认命,当然一般高手也不屑于破解一些没意义的东西。我们虽然做不到完全保护代码,但是我们可以增加他们的破解难度,防不了高手,至少我们要防住像我这种普通的程序员,不能随便什么阿猫阿狗都能染指我们的代码,这简直是对程序员智商的侮辱嘛。
一般我们保护软件的几个重要过程:
1.设计机器码;
2.根据机器码生成注册码;
3.验证注册码(本地+远程验证);
4.给程序集加强命名;
5.对程序集进行代码混淆;
当然这么多步骤一次性也说不完,当前我主要就说说怎么隐藏自己的核心程序集,也就是.DLL动态库。我们平时写项目的时候,里面肯定会有各种各样的动态库出现,当我们编译的时候都会有一个*.DLL的动态库出现,DLL里包含了许多元数据,这就很容易被人用反编译软件完全看到里面的代码,所以隐藏程序集就很重要了,思路是:把程序集存入内存中,到该用的时候才会去用它,这样反编译软件就不能轻易的看到我们的代码了,但是一个比较大的项目,把全部程序集存入内存是不现实的,我们只能把几个比较核心的程序集,如注册验证过程,登陆过程,核心算法过程等隐藏,由于你隐藏了一些重要的程序集,一些人就算得到了你的其他代码,想要软件正确运行还是需要很大的工夫的。by Zengg
花了一点时间写了一个隐藏程序集的的小DEMO,如下图:
这个DEMO的主要功能是:
1.从外部打开某个DLL,并列出该DLL里一共有多少个类;
2.列出某个类里有多少个可执行的公开方法;
3.执行某个方法,会显示执行的返回结果,前提是该方法具有返回值;
4.把从外部打开的DLL转换成C#可用的代码,方便大家存入内存;
5.从内存里读取已经存入内存的程序集,并且执行它的某个类的方法;
下面我们看看项目的VS结构:
我们假设AssemblyTest这个类库是存放我们注册验证的地方,AssemblyWPFDemo就是我们的主程序,一般来说,我们如果要用到AssemblyTest类库的功能时,肯定要由主程序先引用该类库,才能使用这个类库的功能,这里面就存在一个问题,只要引用了类库,在本地就会生成该类库对应的DLL,这个就暴露了我们的代码,下面的过程就是解释,如何在不引用的情况下使用该类库的功能。by Zengg
AssemblyTest类库情况:
有两个测试类,和一个WPF窗体。
namespace AssemblyTest
{
public class AssemblyTest1
{
public string AssemblyTest1GetString()
{
return "这是Assembly1";
}
}
}
AssemblyTest1
namespace AssemblyTest
{
public class AssemblyTest2
{
public string AssemblyTest2GetString()
{
return "这是Assembly2";
}
}
}
AssemblyTest2
WPF窗体预览
这个窗体就是上面DEMO图里弹出的那个窗口。
下面我们看看主要代码是怎么实现的,在这之前你们可能得去了解C# Assembly的应用,里面我只写一些简单的注释,大家自行去看:
核心代码:
// ***********************************************************************
// Assembly : AssemblyWPFDemo
// Author : 曾意恒
// Created : 09-25-2013
//
// Last Modified By : 曾意恒
// Last Modified On : 09-25-2013
// ***********************************************************************
// <copyright file="AssemblyControl.cs" company="">
// Copyright 2013(c) . All rights reserved.
// </copyright>
// <summary>程序集操作类</summary>
// ***********************************************************************
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
namespace AssemblyWPFDemo
{
/// <summary>
/// Class AssemblyControl
/// </summary>
public class AssemblyControl
{
/// <summary>
/// 把指定程序集转化为二进制流
/// </summary>
/// <param name="assemblyFileName">路径程序集</param>
/// <returns>二进制源</returns>
public byte[] GetAssemblyToByte(string assemblyFileName)
{
byte[] assemblySource=null;
if (File.Exists(assemblyFileName))
{
FileStream fStream = new FileStream(assemblyFileName, FileMode.Open);
BinaryReader bReader = new BinaryReader(fStream);
assemblySource = bReader.ReadBytes(Convert.ToInt32(fStream.Length));
fStream.Close();
bReader.Close();
}
return assemblySource;
}
/// <summary>
/// 二进制源转换为C#代码,方便我们新建一个静态类存入数据
/// </summary>
/// <param name="memberNmae">字段名</param>
/// <param name="assemblySource">二进制源</param>
/// <returns>System.String.</returns>
public string ByteConverToString(string memberNmae, byte[] assemblySource)
{
string strAssemblySource = "public byte[] " + memberNmae + "={";
foreach (byte b in assemblySource)
{
strAssemblySource += b.ToString() + ",";
}
strAssemblySource = strAssemblySource.Substring(0, strAssemblySource.Length - 1);
strAssemblySource += "};
";
return strAssemblySource;
}
/// <summary>
/// 通过二进制数据源转换为程序集
/// </summary>
/// <param name="assemblySource">二进制源</param>
/// <returns>程序集</returns>
/// <exception cref="System.NullReferenceException">assembly为空,请检查二进制数据源</exception>
public Assembly GetAssemblyBySource(byte[] assemblySource)
{
Assembly assembly = Assembly.Load(assemblySource);
if (assembly == null)
throw new NullReferenceException("assembly为空,请检查二进制数据源");
return assembly;
}
/// <summary>
/// 获取该程序集里有多少个类
/// </summary>
/// <param name="assembly">程序集</param>
/// <returns>类列表</returns>
public Type[] GetTypesByAssembly(Assembly assembly)
{
if (assembly == null)
return null;
Type[] types = assembly.GetTypes();
return types;
}
/// <summary>
/// 获取类名
/// </summary>
/// <param name="type">类</param>
/// <returns>类名</returns>
public string GetTypeName(Type type)
{
if (type == null)
return "";
return type.Name;
}
/// <summary>
/// 获取类里的所有公开方法的信息
/// </summary>
/// <param name="type">The type.</param>
/// <returns>MemberInfo[][].</returns>
public MemberInfo[] GetMemberInfosByType(Type type)
{
if (type == null)
return null;
return type.GetMethods();
}
/// <summary>
/// 获取当前方法的名称
/// </summary>
/// <param name="menberInfo">The menber info.</param>
/// <returns>System.String.</returns>
public string GetMemberInfoName(MemberInfo menberInfo)
{
return menberInfo.Name;
}
/// <summary>
/// 执行无返回值的方法
/// </summary>
/// <param name="menberInfo">方法体</param>
/// <param name="obj">类实体</param>
public void InvokeMember(MemberInfo menberInfo,object obj)
{
obj.GetType().InvokeMember(menberInfo.Name, BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Instance, null, obj, null);
}
/// <summary>
/// 执行有返回值的方法
/// </summary>
/// <param name="menberInfo">方法体</param>
/// <param name="obj">类实体</param>
/// <returns>返回结果</returns>
public object InvokeMemberHaveResult(MemberInfo menberInfo, object obj)
{
return obj.GetType().InvokeMember(menberInfo.Name, BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Instance, null, obj, null);
}
}
}
AssemblyControl
AssemblyWPFDemo里的AssemblyTestSource类里存放的就是我们AssemblyTest类库的二进制流,这个二进制流是通过我写的这个DEMO转换来的,可看上图,里面有个“转换代码”的按钮,当你打开了一个DLL后就可以把它转换为C#代码,通过加载这个数据流就能获得AssemblyTest的程序集,有了程序集你就能使用程序集里的功能。由于AssemblyTestSource的代码比较多,贴上来会影响排版,大家自行下载DEMO来看。
其他的就是一些简单的逻辑处理了,不一一贴上来,我这里只是起着抛砖引玉的作用,写得比较粗糙,大家要注意的是,程序集的二进制流要存放的隐蔽,要有艺术,不然别人还是能把二进制流取出来存入文件,这就又变成DLL了,有心研究的可以去研究一下把这些二进制数据进行个加密,尽量增加反编译的难度。
这里我又要唠叨一下破解者和软件开发者的关系,我们写软件保护就是和破解者斗智斗勇的过程,这两个是良性的竞争关系,如果你输了只能说明你技不如人,回去回炉一下卷土再来,一味的指责并不能使你的技术提高,你得了解破解人的思路是怎么想的才能设计出更好的保护手段,我相信一个高明的破解者,也一定是一个高明的软件开发者,这两个关系并没有什么分明的界限。。。另提前祝大家国庆快乐。。。下篇博客不知道什么时候才能出来了。。。以后的几篇我可能会围绕着保护这个主题来写..
源码:http://pan.baidu.com/s/1xc9aW
如果您看了本篇博客,觉得对您有所收获,请点击右下角的 [推荐]
如果您想转载本博客,请注明出处
如果您对本文有意见或者建议,欢迎留言
感谢您的阅读,请关注我的后续博客 Zengg
作者:Zengg 出处:http://www.cnblogs.com/01codeworld/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
高并发服务端分布式系统设计概要(中)
上篇(链接)我们完成了在此分布式系统中,一个group的设计。那么接下来,我们设计系统的其他部分。如前文所述,我们的业务及其数据以group为单位,显然在此系统中将存在many many的groups(别告诉我你的网站总共有一个业务,像我们的“山推”,那业务是一堆一堆地),那么由谁来管理这些groups呢?由Web过来的请求,又将如何到达指定的group,并由该group处理它的请求呢?这就是我们要讨论的问题。
我们引入了一个新的角色——Global Master,顾名思义,它是管理全局的一个节点,它主要完成如下工作:(1)管理系统全局配置,发送全局控制信息;(2)监控各个group的工作状态,提供心跳服务,若发现宕机,通知该group发起分布式选举产生新的Group Master;(3)处理Client端首次到达的请求,找出负责处理该请求的group并将此group的信息(location)返回,则来自同一个前端请求源的该类业务请求自第二次起不需要再向Global Master查询group信息(缓存机制);(4)保持和Global Slave的强一致性同步,保持自身健康状态并向全局的“心跳”服务验证自身的状态。
现在我们结合图来逐条解释上述工作,显然,这个系统的完整轮廓已经初现。
首先要明确,不管我们的系统如何“分布式”,总之会有至少一个最主要的节点,术语可称为primary node,如图所示,我们的系统中,这个节点叫Global Master,也许读过GFS + Bigtable论文的同学知道,在GFS + Bigtable里,这样的节点叫Config Master,虽然名称不一样,但所做的事情却差不多。这个主要的Global Master可认为是系统状态健康的标志之一,只要它在正常工作,那么基本可以保证整个系统的状态是基本正常的(什么?group或其他结点会不正常不工作?前面已经说过,group内会通过“分布式选举”来保证自己组内的正常工作状态,不要告诉我group内所有机器都挂掉了,那个概率我想要忽略它),假如Global Master不正常了,挂掉了,怎么办?显然,图中的Global Slave就派上用场了,在我们设计的这个“山推”系统中,至少有一个Global Slave,和Global Master保持“强一致性”的完全同步,当然,如果有不止一个Global Slave,它们也都和Global Master保持强一致性完全同步,这样有个好处,假如Global Master挂掉,不用停写服务,不用进行分布式选举,更不会读服务,随便找一个Global Slave顶替Global Master工作即可。这就是强一致性最大的好处。那么有的同学就会问,为什么我们之前的group,不能这么搞,非要搞什么最终一致性,搞什么分布式选举(Paxos协议属于既难理解又难实现的坑爹一族)呢?我告诉你,还是压力,压力。我们的系统是面向日均千万级PV以上的网站(“山推”嘛,推特是亿级PV,我们千万级也不过分吧),但系统的压力主要在哪呢?细心的同学就会发现,系统的压力并不在Global Master,更不会在Global Slave,因为他们根本不提供数据的读写服务!是的,系统的压力正是在各个group,所以group的设计才是最关键的。同时,细心的同学也发现了,由于Global Master存放的是各个group的信息和状态,而不是用户存取的数据,所以它更新较少,也不能认为读>>写,这是不成立的,所以,Global Slave和Global Master保持强一致性完全同步,正是最好的选择。所以我们的系统,一台Global Master和一台Global Slave,暂时可以满足需求了。
好,我们继续。现在已经了解Global Master的大概用途,那么,一个来自Client端的请求,如何到达真正的业务group去呢?在这里,Global Master将提供“首次查询”服务,即,新请求首次请求指定的group时,通过Global Master获得相应的group的信息,以后,Client将使用该信息直接尝试访问对应的group并提交请求,如果group信息已过期或是不正确,group将拒绝处理该请求并让Client重新向Global Master请求新的group信息。显然,我们的系统要求Client端缓存group的信息,避免多次重复地向Global Master查询group信息。这里其实又挖了许多烂坑等着我们去跳,首先,这样的工作模式满足基本的Ddos攻击条件,这得通过其他安全性措施来解决,避免group总是收到不正确的Client请求而拒绝为其服务;其次,当出现大量“首次”访问时,Global Master尽管只提供查询group信息的读服务,仍有可能不堪重负而挂掉,所以,这里仍有很大的优化空间,比较容易想到的就是采用DNS负载均衡,因为Global Master和其Global Slave保持完全同步,所以DNS负载均衡可以有效地解决“首次”查询时Global Master的压力问题;再者,这个工作模式要求Client端缓存由Global Master查询得到的group的信息,万一Client不缓存怎么办?呵呵,不用担心,Client端的API也是由我们设计的,之后才面向Web前端。
之后要说的,就是图中的“Global Heartbeat”,这又是个什么东西呢?可认为这是一个管理Global Master和Global Slave的节点,Global Master和各个Global Slave都不停向Global Heartbeat竞争成为Global Master,如果Global Master正常工作,定期更新其状态并延期其获得的锁,否则由Global Slave替换之,原理和group内的“心跳”一样,但不同的是,此处Global Master和Global Slave是强一致性的完全同步,不需要分布式选举。有同学可能又要问了,假如Global Heartbeat挂掉了呢?我只能告诉你,这个很不常见,因为它没有任何压力,而且挂掉了必须人工干预才能修复。在GFS + Bigtable里,这个Global Heartbeat叫做Lock Service。
中篇就写到这里吧。下篇(链接)将给出完整的系统设计并完结。