https://mp.weixin.qq.com/s/ttqbSjjbJPnW1DMJojONXw
前言
首先,对于.NET/C#生成的托管的代码,没有100%的保护措施,因为它们的程序执行最终会到二进制/汇编这个层面。而二进制/汇编层面可以针对性的对于指令集(Risc-v/Arm64/x64/Loongarch64)进行反汇编逆向重构。所以,对于.NET/C#生成的托管的代码,能做的只能是有限度的保护、增加理解难度。
软件保护
20年前,.Net刚诞生的时候有个笑话。有人嘲讽微软做托管代码,是为了方便别人破解。事实证明,托管确实比非托管更容易破解和篡改。MSIL遵循了一整套的ECMA-335的规范,对于这个规范了如指掌的程序开发人员来说,托管代码就像纸糊的一样,戳哪儿哪儿就破。
但绝大多数99%的软件工程师是在做业务开发、为商业服务,他们往往只需要懂.NET/C#这种高级语言就够了,很少有人会去研究&也没必要关心MSIL以及汇编这类低级语言(往往只有黑客、做编译器、驱动开发的那1%的程序员需要懂低级语言)。
所以,只需要对.NET/C#托管代码进行混淆加密,就能防范住99%的破解。
.Net加密工具
目前市面上流行的.Net加壳软件大致有如下:
Dotfuscator:Visual Stuido自带的,你可以自己去安装使用。混淆后的程序改变了方法名和一些变量名,降低了代码可读性。
dotNET Reactor:主要功能:NecroBit IL(转为非托管代码)、反 ILDASM(反编译器)、混淆代码、合并、压缩源码等。效果比VS自带的Dotfuscator要好。
ILProtector:可保护您的 .NET 代码免受逆向工程、反编译和修改。
Xenocode Fox:反编译工具,混淆工具,只能针对早期.NET Framework代码使用。
SmartAssembly:一款.NET代码加密保护软件,里面内置的选项方面是蛮多的,能够往程序里面注入一些没用的方法和类来混淆代码结构。使用该软件加密混淆后效果比.NET Reactor好一些。
DNGuard HVM:加密功能很强大,混淆后的程序很难被破解,效果更好。
商业授权
我们做的商业软件需要进行售卖,为了收取费用,一般需要一个软件使用许可证,然后输入这个许可到软件里就能够使用软件(比如,一串序列码或者一个许可证文件)。于是有的小伙伴就开始好奇这个许可是怎么实现的。
实现许可证的关键点1. 如何控制只在指定设备上使用
如果不控制指定设备,那么下发了许可证,只要把软件复制多份安装则可到处使用,不利于版权维护。但我们想想,有什么办法唯一标识一台电脑?答案就是:mac地址,ip地址,主板序列号等。在许可证中指定这些唯一标识即可实现指定设备使用。
实现许可证的关键点2. 如何控制软件使用期限
为了版权可持续性收益,对软件使用设置期限,到期续费等,则需要在许可证中配置使用起止日期。
其实,在前述的.Net加密工具中,往往就附带有制作许可证实现商业授权的功能。但我想各位小伙伴可能更想知道具体的实现原理、以及后期自己开发此类功能?下面就把关键的【核心源码】展现给大家参考:
【核心源码】
1.1 电脑信息获取
主要通过ManagementClass进行获取客户端电脑硬件相关配置信息,如下所示:
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Management;
using System.Net.NetworkInformation;
using System.Text;
using System.Threading.Tasks;
namespace DemoLicence.Common
{
public class ComputerHelper
{
public static Dictionary<string,string> GetComputerInfo()
{
var info = new Dictionary<string,string>();
string cpu = GetCPUInfo();
string baseBoard = GetBaseBoardInfo();
string bios = GetBIOSInfo();
string mac = GetMACInfo();
info.Add("cpu", cpu);
info.Add("baseBoard", baseBoard);
info.Add("bios", bios);
info.Add("mac", mac);
return info;
}
private static string GetCPUInfo()
{
string info = string.Empty;
info = GetHardWareInfo("Win32_Processor", "ProcessorId");
return info;
}
private static string GetBIOSInfo()
{
string info = string.Empty;
info = GetHardWareInfo("Win32_BIOS", "SerialNumber");
return info;
}
private static string GetBaseBoardInfo()
{
string info = string.Empty;
info = GetHardWareInfo("Win32_BaseBoard", "SerialNumber");
return info;
}
private static string GetMACInfo()
{
string info = string.Empty;
info = GetMacAddress();//GetHardWareInfo("Win32_NetworkAdapterConfiguration", "MACAddress");
return info;
}
private static string GetMacAddress()
{
var mac = "";
var mc = new ManagementClass("Win32_NetworkAdapterConfiguration");
var moc = mc.GetInstances();
foreach (var o in moc)
{
var mo = (ManagementObject)o;
if (!(bool)mo["IPEnabled"]) continue;
mac = mo["MacAddress"].ToString();
break;
}
return mac;
}
private static string GetHardWareInfo(string typePath, string key)
{
try
{
ManagementClass managementClass = new ManagementClass(typePath);
ManagementObjectCollection mn = managementClass.GetInstances();
PropertyDataCollection properties = managementClass.Properties;
foreach (PropertyData property in properties)
{
if (property.Name == key)
{
foreach (ManagementObject m in mn)
{
return m.Properties[property.Name].Value.ToString();
}
}
}
}
catch (Exception ex)
{
//这里写异常的处理
}
return string.Empty;
}
}
}
1.2 RSA非对称加密
主要对客户端提供的电脑信息及有效期等内容,进行非对称加密,如下所示:
public class RSAHelper
{
private static string keyContainerName = "star";
private static string m_PriKey = string.Empty;
private static string m_PubKey = string.Empty;
public static string PriKey
{
get
{
return m_PriKey;
}
set
{
m_PriKey = value;
}
}
public static string PubKey
{
get
{
return m_PubKey;
}
set
{
m_PubKey = value;
}
}
public static string Encrypto(string source)
{
if (string.IsNullOrEmpty(m_PubKey) && string.IsNullOrEmpty(m_PriKey))
{
generateKey();
}
return getEncryptoInfoByRSA(source);
}
public static string Decrypto(string dest)
{
if (string.IsNullOrEmpty(m_PubKey) && string.IsNullOrEmpty(m_PriKey))
{
generateKey();
}
return getDecryptoInfoByRSA(dest);
}
public static void generateKey()
{
CspParameters m_CspParameters;
m_CspParameters = new CspParameters();
m_CspParameters.KeyContainerName = keyContainerName;
RSACryptoServiceProvider asym = new RSACryptoServiceProvider(m_CspParameters);
m_PriKey = asym.ToXmlString(true);
m_PubKey = asym.ToXmlString(false);
asym.PersistKeyInCsp = false;
asym.Clear();
}
private static string getEncryptoInfoByRSA(string source)
{
byte[] plainByte = Encoding.ASCII.GetBytes(source);
//初始化参数
RSACryptoServiceProvider asym = new RSACryptoServiceProvider();
asym.FromXmlString(m_PubKey);
int keySize = asym.KeySize / 8;//非对称加密,每次的长度不能太长,否则会报异常
int bufferSize = keySize - 11;
if (plainByte.Length > bufferSize)
{
throw new Exception("非对称加密最多支持【" + bufferSize + "】字节,实际长度【" + plainByte.Length + "】字节。");
}
byte[] cryptoByte = asym.Encrypt(plainByte, false);
return Convert.ToBase64String(cryptoByte);
}
private static string getDecryptoInfoByRSA(string dest)
{
byte[] btDest = Convert.FromBase64String(dest);
//初始化参数
RSACryptoServiceProvider asym = new RSACryptoServiceProvider();
asym.FromXmlString(m_PriKey);
int keySize = asym.KeySize / 8;//非对称加密,每次的长度不能太长,否则会报异常
//int bufferSize = keySize - 11;
if (btDest.Length > keySize)
{
throw new Exception("非对称解密最多支持【" + keySize + "】字节,实际长度【" + btDest.Length + "】字节。");
}
byte[] cryptoByte = asym.Decrypt(btDest, false);
return Encoding.ASCII.GetString(cryptoByte);
}
}
1.3 生成文件
主要是加密后的信息,和解密秘钥等内容,保存到文件中,如下所示:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DemoLicence.Common
{
public class RegistFileHelper
{
public static string ComputerInfofile = "ComputerInfo.key";
public static string RegistInfofile = "Licence.key";
public static void WriteRegistFile(string info,string keyFile)
{
string tmp = string.IsNullOrEmpty(keyFile)?RegistInfofile:keyFile;
WriteFile(info, tmp);
}
public static void WriteComputerInfoFile(string info)
{
WriteFile(info, ComputerInfofile);
}
public static string ReadRegistFile(string keyFile)
{
string tmp = string.IsNullOrEmpty(keyFile) ? RegistInfofile : keyFile;
return ReadFile(tmp);
}
public static string ReadComputerInfoFile(string file)
{
string tmp = string.IsNullOrEmpty(file) ? ComputerInfofile : file;
return ReadFile(tmp);
}
private static void WriteFile(string info, string fileName)
{
try
{
using (StreamWriter sw = new StreamWriter(fileName, false))
{
sw.Write(info);
sw.Close();
}
}
catch (Exception ex)
{
}
}
private static string ReadFile(string fileName)
{
string info = string.Empty;
try
{
using (StreamReader sr = new StreamReader(fileName))
{
info = sr.ReadToEnd();
sr.Close();
}
}
catch (Exception ex)
{
}
return info;
}
}
}
以上这三部分,各个功能相互独立,通过LicenceHelper相互调用,如下所示:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace DemoLicence.Common
{
public class LicenceHelper
{
/// <summary>
/// 获取电脑信息,并生成文件
/// </summary>
public static string GetComputerInfoAndGenerateFile()
{
string computerKeyFile = string.Empty;
try
{
var info = GetComputerInfo();
if (info != null && info.Count > 0)
{
//获取到电脑信息
var strInfo = new StringBuilder();
foreach (var computer in info)
{
strInfo.AppendLine($"{computer.Key}={computer.Value}");
}
RegistFileHelper.WriteComputerInfoFile(strInfo.ToString());
computerKeyFile = RegistFileHelper.ComputerInfofile;
}
}catch(Exception ex)
{
throw ex;
}
return computerKeyFile;
}
public static Dictionary<string,string> GetComputerInfo()
{
var info = ComputerHelper.GetComputerInfo();
return info;
}
public static bool CheckLicenceKeyIsExists()
{
var keyFile = RegistFileHelper.RegistInfofile;
if (File.Exists(keyFile))
{
return true;
}
else
{
return false;
}
}
public static string GetComputerInfo(string computerInfoFile)
{
return RegistFileHelper.ReadComputerInfoFile(computerInfoFile);
}
public static void GenerateLicenceKey(string info,string keyfile)
{
string encrypto = RSAHelper.Encrypto(info);
string priKey = RSAHelper.PriKey;//公钥加密,私钥解密
byte[] priKeyBytes = Encoding.ASCII.GetBytes(priKey);
string priKeyBase64=Convert.ToBase64String(priKeyBytes);
StringBuilder keyInfo= new StringBuilder();
keyInfo.AppendLine($"prikey={priKeyBase64}");
keyInfo.AppendLine($"encrypto={encrypto}");
RegistFileHelper.WriteRegistFile(keyInfo.ToString(), keyfile);
}
public static string ReadLicenceKey(string keyfile)
{
var keyInfo = RegistFileHelper.ReadRegistFile(keyfile);
if (keyInfo == null)
{
return string.Empty;
}
string[] keyInfoArr = keyInfo.Split("\r\n");
var priKeyBase64 = keyInfoArr[0].Substring(keyInfoArr[0].IndexOf("=")+1);
var encrypto = keyInfoArr[1].Substring(keyInfoArr[1].IndexOf("=")+1);
var priKeyByte= Convert.FromBase64String(priKeyBase64);
var priKey = Encoding.ASCII.GetString(priKeyByte);
RSAHelper.PriKey= priKey;
var info = RSAHelper.Decrypto(encrypto);
return info;
}
public static string GetDefaultRegisterFileName()
{
return RegistFileHelper.RegistInfofile;
}
public static string GetDefaultComputerFileName()
{
return RegistFileHelper.ComputerInfofile;
}
public static string GetPublicKey()
{
if (string.IsNullOrEmpty(RSAHelper.PubKey))
{
RSAHelper.generateKey();
}
return RSAHelper.PubKey;
}
public static string GetPrivateKey()
{
if (string.IsNullOrEmpty(RSAHelper.PriKey))
{
RSAHelper.generateKey();
}
return RSAHelper.PriKey;
}
}
}