你好,這裡是 Dotnet 工具箱,定期分享 Dotnet 有趣,實用的工具和元件,希望對您有用!
JIEJIE.NET 是一個使用 C# 開發的開源 .NET 程式碼加密工具。
很多 .NET 開發人員擔心他們的軟體被破解,版權受到侵犯,所以他們使用一些工具來混淆 IL 程式碼。比如 PreEmptive dotfuscator, 但有些場景的需求,是這些工具不能滿足的。
所以作者寫了 JieJie.NET,它可以深度加密.NET程式集,幫助大家保護版權。重要的是,這個工具是開源的。
1.型別和成員重新命名
舊程式碼:
public abstract class XTextDocumentContentElement : XTextContentElement
{
public override void AfterLoad(ElementLoadEventArgs args);
public override void Clear();
[Obfuscation(Exclude = true, ApplyToMembers = true)]
public override XTextElement Clone(bool Deeply);
[Obfuscation(Exclude = true, ApplyToMembers = true)]
public override XTextDocument CreateContentDocument(bool includeThis);
public XTextSelection CreateSelection(int startIndex, int length);
public override void Dispose();
public override void DrawContent(InnerDocumentPaintEventArgs args);
[Obfuscation(Exclude = true, ApplyToMembers = true)]
public override void EditorRefreshViewExt(bool fastMode);
public float FixPageLinePosition(int pos);
public override void Focus();
[Obfuscation(Exclude = true, ApplyToMembers = true)]
public XTextLineList GetAllLines();
[Obfuscation(Exclude = true, ApplyToMembers = true)]
public virtual XTextRange GetRange(int StartIndex, int EndIndex);
public void InnerGetSelectionBorderElement(ref XTextElement startElement, ref XTextElement endElement);
public void InvalidateSpecifyLayoutElements();
[Obfuscation(Exclude = true, ApplyToMembers = true)]
public virtual bool IsSelected(XTextElement element);
public void RefreshParagraphListState(bool checkFlag, bool updateListIndex);
public XTextParagraphFlagElement RootParagraphFlag();
[Obfuscation(Exclude = true, ApplyToMembers = true)]
public bool SetSelection(int startIndex, int length);
[Obfuscation(Exclude = true, ApplyToMembers = true)]
public bool SetSelectionRange(int firstIndex, int lastIndex);
}
加密後:
public abstract class XTextDocumentContentElement : XTextContentElement
{
public override void Clear();
public override XTextElement Clone(bool Deeply);
public override XTextDocument CreateContentDocument(bool includeThis);
public override void Dispose();
public override void EditorRefreshViewExt(bool fastMode);
public override void Focus();
public XTextLineList GetAllLines();
public virtual XTextRange GetRange(int StartIndex, int EndIndex);
public virtual bool IsSelected(XTextElement element);
public bool SetSelection(int startIndex, int length);
public bool SetSelectionRange(int firstIndex, int lastIndex);
public XTextParagraphFlagElement z0ZzZzbmm1mO001();
public XTextSelection z0ZzZzbmm1mO011(int startIndex, int length);
public void z0ZzZzbmm1mO01O();
public float z0ZzZzbmm1mOOm1(int pos);
public void z0ZzZzbmm1mOOmn(ref XTextElement startElement, ref XTextElement endElement);
public void z0ZzZzbmm1mOOmO(bool checkFlag, bool updateListIndex);
public override void z0ZzZzbmmOO11nn(z0ZzZzbm0mmlm1O args);
public override void z0ZzZzbmmOOl0nO(ElementLoadEventArgs args);
}
可以看到,一些 API 的名稱被混淆了。
2. 混淆程式碼流程
JieJie.NET 可以分析 IL 程式碼,並且在不丟失任何特性的情況下隨機混淆程式碼流程。它可以破壞 foreach/lock/using
, 讓程式碼很難閱讀,有時候還會導致破解工具錯誤。
舊程式碼:
public int RemoveByControl(object control)
{
if (control == null)
{
throw new ArgumentNullException("control");
}
if (CheckOwner() == false)
{
return -1;
}
int result = 0;
lock (this)
{
for (int iCount = _Tasks.Count - 1; iCount >= 0; iCount--)
{
if (_Tasks[iCount].Control == control)
{
_Tasks.RemoveAt(iCount);
result++;
}
}
if (_CurrentTask != null && _CurrentTask.Control == control)
{
_CurrentTask = null;
}
}
return result;
}
使用 JieJie.NET 後,在 ILSpy 中顯示的程式碼如下:
public int RemoveByControl(object control)
{
//Discarded unreachable code: IL_000b, IL_0073
//IL_000b: Incompatible stack heights: 1 vs 0
//IL_0073: Incompatible stack heights: 1 vs 0
int num = z0ZzZzgw.z0kh;
bool flag = default(bool);
int num4 = default(int);
int result = default(int);
while (true)
{
switch (num)
{
default:
{
if (control == null)
{
throw new ArgumentNullException(z0ZzZzow.z0rj);
}
if (!z0rk())
{
goto IL_0049;
}
int num2 = 0;
z0ZzZzjw.z0uk(this);
try
{
int num3 = z0ZzZzgw.z0ah;
while (true)
{
switch (num3)
{
default:
num2++;
goto IL_0097;
case 3:
if (flag)
{
z0ik = null;
}
break;
case 4:
case 5:
{
num4 = z0bk.Count - 1;
goto IL_009e;
}
IL_009e:
if (num4 < 0)
{
flag = z0ik != null && z0ik.Control == control;
num3 = z0ZzZzgw.z0wj;
continue;
}
if (z0bk[num4].Control == control)
{
z0bk.RemoveAt(num4);
num3 = z0ZzZzgw.z0sh;
continue;
}
goto IL_0097;
IL_0097:
num4--;
goto IL_009e;
}
break;
}
}
finally
{
Monitor.Exit(this);
}
result = num2;
break;
}
case 0:
case 1:
case 3:
break;
}
break;
IL_0049:
result = -1;
num = z0ZzZzgw.z0wj;
}
return result;
}
現在程式碼流程已經被破壞了。
3. 加密所有字串值
JieJie.NET 可以收集程式集中定義的所有字串值,然後把它們轉換為新類中的靜態唯讀欄位,並對它們的值進行加密。
舊程式碼:
private string GetLicenseMessage()
{
return "這是一個金鑰 :" + Environment.UserName;
}
加密後:
private string GetLicenseMessage()
{
string text = _0._6 + Environment.UserName;
return text;
}
// also create a new class, contains all string value in assembly in random order.
internal static class _0
{
public static readonly string _0;
public static readonly string _1;
public static readonly string _2;
public static readonly string _3;
public static readonly string _4;
public static readonly string _5;
public static readonly string _6;
public static readonly string _7;
public static readonly string _8;
public static readonly string _9;
public static readonly string _10;
public static readonly string _11;
public static readonly string _12;
public static readonly string _13;
public static readonly string _14;
public static readonly string _15;
public static readonly string _16;
public static readonly string _17;
public static readonly string _18;
public static readonly string _19;
public static readonly string _20;
public static readonly string _21;
static _0()
{
byte[] datas = _BytesContainer__._0();
_11 = GetStringByLong(datas, 151732605047602L);
_20 = GetStringByLong(datas, 450799767951810L);
_7 = GetStringByLong(datas, 101155071172227L);
_4 = GetStringByLong(datas, 47279000500949L);
_15 = GetStringByLong(datas, 415615395474299L);
_5 = GetStringByLong(datas, 54975582493063L);
_2 = GetStringByLong(datas, 17592187197342L);
_14 = GetStringByLong(datas, 206708198516324L);
_8 = GetStringByLong(datas, 124244814685054L);
_21 = GetStringByLong(datas, 459595860893446L);
_6 = GetStringByLong(datas, 72567769190975L);
_13 = GetStringByLong(datas, 182518931688172L);
_18 = GetStringByLong(datas, 433207581847376L);
_16 = GetStringByLong(datas, 417814419099513L);
_3 = GetStringByLong(datas, 36283884381871L);
_1 = GetStringByLong(datas, 9895605165436L);
_9 = GetStringByLong(datas, 136339442622330L);
_19 = GetStringByLong(datas, 440904163377248L);
_17 = GetStringByLong(datas, 426610511995160L);
_0 = GetStringByLong(datas, 598562L);
_10 = GetStringByLong(datas, 148434069970387L);
_12 = GetStringByLong(datas, 158329675868829L);
}
private static string GetStringByLong(byte[] datas, long key)
{
int num = (int)(key & 0xFFFF) ^ 0xEF83;
key >>= 16;
int num2 = (int)(key & 0xFFFFF);
key >>= 24;
int num3 = (int)key;
char[] array = new char[num2];
int num4 = 0;
while (num4 < num2)
{
int num5 = num4 + num3 << 1;
array[num4] = (char)(((datas[num5] << 8) + datas[num5 + 1]) ^ num);
num4++;
num++;
}
return new string(array);
}
}
專案地址: https://github.com/dcsoft-yyf/JIEJIE.NET
Dots 是一個用於管理 .NET SDK 的 GUI 工具,它使用 .NET MAUI 開發的,可用於 Windows 和 macOS(對不住了,Linux 使用者)。
總所周知, .NET 的小版本更新很快,而我經常會試用 SDK 的最新預覽版。甚至嘗試自定義構建。我在我的機器上安裝了幾個不同版本的 SDK,只是方便能夠在它們之間進行切換。通常我會嘗試保留當前的穩定版本、最新的預覽版和 LTS 版本。除此之外,我可能需要特定專案的特定 SDK 版本。
當然有 dotnet cli 允許我檢查安裝的版本,dotnet --list-sdks 可以輸出安裝的版本資訊。
但我想更好地瞭解所有已安裝版本的一些細節,並能夠快速解除安裝它們。
於是,Dots - 更友好的 .NET SDK 管理器來了!歡迎大家嘗試使用!
專案地址: https://github.com/nor0x/Dots
DotNetCorePlugins 是一個 .NET 的開源外掛專案,它提供了能夠動態載入程式集的 API,然後把它們作為 .NET 主程式的擴充套件程式執行。
這個庫主要用到了 AssemblyLoadContext
技術, System.Runtime.Loader.AssemblyLoadContext
,又名 ALC,提供了一些用於定義動態程式集載入行為的基本 API。這是 .NET Core 中我最喜歡但鮮為人知的 API 之一。
安裝 McMaster.NETCore.Plugins
NuGet 包。
dotnet add package McMaster.NETCore.Plugins
主要使用的 API 是 PluginLoader.CreateFromAssemblyFile
, 它允許從檔案中讀取並載入程式集。
PluginLoader.CreateFromAssemblyFile(
assemblyFile: "./plugins/MyPlugin/MyPlugin1.dll",
sharedTypes: new [] { typeof(IPlugin), typeof(IServiceCollection), typeof(ILogger) },
isUnloadable: true)
定義介面
這是一個範例,我們定義了一個介面,裡面包含了 GetName, 如下
public interface IPlugin
{
string GetName();
}
對於外掛,我們直接使用這個介面並進行實現,如下
internal class MyPlugin1 : IPlugin
{
public string GetName() => "My plugin v1";
}
對於主程式,我們可以使用 PluginLoader
API 來載入外掛,程式需要使用查詢磁碟中的外掛程式集。一種方式是基於約定的,比如
plugins/
$PluginName1/
$PluginName1.dll
(additional plugin files)
$PluginName2/
$PluginName2.dll
每個外掛都發布到一個單獨的目錄中,這樣可以避免外掛之間的爭用和重複的依賴問題。
以通過執行下面的命令,輸出外掛到資料夾中。
dotnet publish MyPlugin1.csproj --output plugins/MyPlugin1/
接下來,我們可以通過反射獲取所有的外掛,並進行載入, 程式碼如下
using McMaster.NETCore.Plugins;
var loaders = new List<PluginLoader>();
// create plugin loaders
var pluginsDir = Path.Combine(AppContext.BaseDirectory, "plugins");
foreach (var dir in Directory.GetDirectories(pluginsDir))
{
var dirName = Path.GetFileName(dir);
var pluginDll = Path.Combine(dir, dirName + ".dll");
if (File.Exists(pluginDll))
{
var loader = PluginLoader.CreateFromAssemblyFile(
pluginDll,
sharedTypes: new [] { typeof(IPlugin) });
loaders.Add(loader);
}
}
// Create an instance of plugin types
foreach (var loader in loaders)
{
foreach (var pluginType in loader
.LoadDefaultAssembly()
.GetTypes()
.Where(t => typeof(IPlugin).IsAssignableFrom(t) && !t.IsAbstract))
{
IPlugin plugin = (IPlugin)Activator.CreateInstance(pluginType);
Console.WriteLine($"Created plugin instance '{plugin.GetName()}'.");
}
}
支援 MVC 和 Razor
另外外掛還支援載入 MVC 的 Controller 和 Razor Pages。通過安裝下面的 Nuget 包。
dotnet add package McMaster.NETCore.Plugins.Mvc
載入程式集的方法如下:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
var pluginFile = Path.Combine(AppContext.BaseDirectory, "plugins/MyRazorPlugin/MyRazorPlugin.dll");
services
.AddMvc()
.AddPluginFromAssemblyFile(pluginFile);
}
}
更多外掛的使用方法,作者提供了一些範例專案,可以進行參考。
專案地址:https://github.com/natemcmaster/DotNetCorePlugins