【Dotnet 工具箱】JIEJIE.NET

2023-04-28 12:01:22

你好,這裡是 Dotnet 工具箱,定期分享 Dotnet 有趣,實用的工具和元件,希望對您有用!

【JIEJIE.NET - 強大的 .NET 程式碼混淆工具】

JIEJIE.NET

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 管理器】

什麼是 Dots?

Dots 是一個用於管理 .NET SDK 的 GUI 工具,它使用 .NET MAUI 開發的,可用於 Windows 和 macOS(對不住了,Linux 使用者)。

為什麼會開發 Dots?

總所周知, .NET 的小版本更新很快,而我經常會試用 SDK 的最新預覽版。甚至嘗試自定義構建。我在我的機器上安裝了幾個不同版本的 SDK,只是方便能夠在它們之間進行切換。通常我會嘗試保留當前的穩定版本、最新的預覽版和 LTS 版本。除此之外,我可能需要特定專案的特定 SDK 版本。

當然有 dotnet cli 允許我檢查安裝的版本,dotnet --list-sdks 可以輸出安裝的版本資訊。

但我想更好地瞭解所有已安裝版本的一些細節,並能夠快速解除安裝它們。

於是,Dots - 更友好的 .NET SDK 管理器來了!歡迎大家嘗試使用!

專案地址: https://github.com/nor0x/Dots

【DotNetCorePlugins- 動態載入和解除安裝 .NET 程式外掛】

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)
  • assemblyFile = 外掛 .dll 的檔案路徑
  • sharedTypes = 載入程式的統一的型別列表
  • isUnloadable = 允許這個外掛在將來的某個時候從記憶體中解除安裝。

定義介面

這是一個範例,我們定義了一個介面,裡面包含了 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