アセンブリのパッケージング、圧縮および保護

記事の最新バージョンは、サイトmakeloft.xyzで入手できます。



この記事の資料では、 Visual Studio開発環境の標準ツールを使用して、レイアウト、圧縮、動的読み込みメカニズム、および.NETアセンブリの基本的な保護方法について説明します。 ただし、他のソフトウェアプラットフォームでは、以下がある程度当てはまる場合があります。



そのような目的のために、有料のものを含む多くのユーティリティが作成されましたが、それらはしばしばブラックボックスとして機能し、開発者はプロセスをほんの少ししか制御できません。 リンクした後、アプリケーションがまったく実行を拒否し、理由を特定するのがかなり難しい場合があります。



しかし、ある程度の知識があれば、すべてのステップを完全に制御して、プロセス全体を自分で完了することができ、既にパッケージ化されたアセンブリをデバッグすることができます...



画像



最初に、これらのメカニズムが適用されるアプリケーションの例を見てみましょう。保護に加えて、テキストエディターPoetです。 外部では、プログラムは単一の小さな.exeファイルですが、実際には複数のライブラリで構成されています。



明らかなマイナスがない場合、アセンブリのパッケージには多くのプラスがあることにすぐに注意してください。

•多くのアセンブリを1つに接着し、実行可能ファイルにすべてを含めることもできます。 その部分はどこにも失われないため、アプリケーションはモノリシックで安定します。

•これらのアセンブリは簡単に圧縮(アーカイブ)できます。その後、ファイルが占有するスペースははるかに少なくなります。

•アプリケーションの起動速度を著しく向上させます! 主観的に、 Poetはすべてのファイルを1つにコンパイルした後、さまざまなマシンで2〜4倍速く起動し始めました。

•pr索好きな目からの原始的な保護が表示されます。つまり、ファイルを分解すると、部外者はコード全体を見ることができなくなります。 彼はアセンブリを抽出する必要がありますが、これにはいくつかのスキルが必要です。

•より深刻な保護のための便利な機会がある...

•何かに必要な場合、アセンブリのパッケージングは​​、アンパックバージョンの同時使用を除外しません。

•アセンブリを非難する機会を節約します!



1.アプリケーションドメインでのアセンブリの動的読み込み



Assemblies.resxなどの別のリソースファイルにアセンブリファイルを追加する場合、それらは単なるバイトの配列になります。 以下に示す小さなAssemblyLoaderクラスを使用すると、それらをアプリケーションドメインにアップロードするのは非常に簡単です。 適切なタイミングで、通常はアプリケーションを起動するときに、呼び出します



AssemblyLoader.ResolveAssemblies<Assemblies>(AppDomain.CurrentDomain);
      
      







 using System; using System.Collections.Generic; using System.Linq; using System.Reflection; namespace Loader { public static class AssemblyLoader { public static void ResolveAssembly(this AppDomain domain, string assemblyFullName) { try { domain.CreateInstance(assemblyFullName, "AnyType"); } catch (Exception exception) { Console.WriteLine(exception.Message); } } public static void ResolveAssemblies<TResource>(this AppDomain domain, Func<byte[], byte[]> converter = null) { var assemblies = ResolveAssembliesFromStaticResource<TResource>(converter); ResolveAssemblies(domain, assemblies); } public static void ResolveAssemblies(this AppDomain domain, List<Assembly> assemblies) { ResolveEventHandler handler = (sender, args) => assemblies.Find(a => a.FullName == args.Name); domain.AssemblyResolve += handler; assemblies.ForEach(a => ResolveAssembly(domain, a.FullName)); domain.AssemblyResolve -= handler; } public static void ResolveAssembly(this AppDomain domain, Assembly assembly) { ResolveEventHandler handler = (sender, args) => assembly; domain.AssemblyResolve += handler; ResolveAssembly(domain, assembly.FullName); domain.AssemblyResolve -= handler; } public static List<Assembly> ResolveAssembliesFromStaticResource<TResource>(Func<byte[], byte[]> converter = null) { var assemblyDatyType = typeof (byte[]); var assemblyDataItems = typeof (TResource) .GetProperties(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) .Where(p => p.PropertyType == assemblyDatyType) .Select(p => p.GetValue(null, null)) .Cast<byte[]>() .ToList(); var assemblies = new List<Assembly>(); foreach (var assemblyData in assemblyDataItems) { try { var rawAssembly = converter == null ? assemblyData : converter(assemblyData); var assembly = Assembly.Load(rawAssembly); assemblies.Add(assembly); } catch (Exception exception) { Console.WriteLine(exception.Message); } } return assemblies; } } }
      
      





この記事の著者は長い間、ドメインにアセンブリをロードするプロセスを開始する方法を理解できませんでした。 domain.AssemblyResolve + = handler event ;を呼び出す代わりに、不明なタイプを作成しようとしたとき 実行は単純に落ち、イベント自体はトリガーされませんでした。 そして判明した主な魔法は、同じ行domain.CreateInstance(assemblyFullName、 "AnyType")にあります。MSDNを採用して裏庭でこの秘密を明らかにした例を見つけるのに数日かかった。



2.同時に、バイト配列の圧縮または暗号化を禁止する人はいません



非常に単純なCompressorクラスが圧縮に役立つ場合があります。 今だけアセンブリのダウンロードを開始するには、少し異なる必要があります

 AssemblyLoader.ResolveAssemblies<Resources>( AppDomain.CurrentDomain, bytes => Foundation.Compressor.ConvertBytes(bytes));
      
      







 using System.IO; using System.IO.Compression; namespace Foundation { public static class Compressor { public static void ConvertFile(string inputFileName, CompressionMode compressionMode, string outputFileName = null) { outputFileName = outputFileName ?? (compressionMode == CompressionMode.Compress ? inputFileName + ".compressed" : inputFileName.Replace(".compressed", string.Empty)); var inputFileBytes = File.ReadAllBytes(inputFileName); ConvertBytesToFile(inputFileBytes, outputFileName, compressionMode); } public static void ConvertBytesToFile(byte[] inputFileBytes, string outputFileName, CompressionMode compressionMode = CompressionMode.Decompress) { var bytes = ConvertBytes(inputFileBytes, compressionMode); File.WriteAllBytes(outputFileName, bytes); } public static byte[] ConvertBytes(byte[] inputFileBytes, CompressionMode mode = CompressionMode.Decompress) { using (var inputStream = new MemoryStream(inputFileBytes)) using (var outputStream = new MemoryStream()) { if (mode == CompressionMode.Compress) using (var convertStream = new GZipStream(outputStream, CompressionMode.Compress)) inputStream.CopyTo(convertStream); if (mode == CompressionMode.Decompress) using (var convertStream = new GZipStream(inputStream, CompressionMode.Decompress)) convertStream.CopyTo(outputStream); var bytes = outputStream.ToArray(); return bytes; } } } }
      
      





また、アーカイブ用の基本的なコンソールユーティリティも必要です。



 using System; using System.IO.Compression; using System.Linq; using Foundation; namespace FileCompressor { class Program { private static void Main(string[] args) { try { args.Where(n => !n.ToLower().Contains(".compressed")) .ToList() .ForEach(n => Compressor.ConvertFile(n, CompressionMode.Compress)); args.Where(n => n.ToLower().Contains(".compressed")) .ToList() .ForEach(n => Compressor.ConvertFile(n, CompressionMode.Decompress)); } catch (Exception exception) { Console.WriteLine(exception.Message); Console.ReadKey(); } } } }
      
      





Visual Studioでは、プロジェクトをコンパイルする前と後に、任意のコマンドラインを実行できます。 これは、プロジェクトのプロパティで構成されます。 つまり、ライブラリを構築した後、必要に応じてファイルを圧縮し(たとえば、アーカイバによって)、適切な場所に自動的にコピーする(リソースにアクセスする)ようにすべてを構成できます。 ドメインにアップロードされたバージョンはコンパイルされたバージョンに対応しているため、正しく設定すると、すべてが正常になります。 これは重要です。アプリケーションが実行を停止した後、または他の問題がある場合、デバッグを使用して簡単に原因を特定して修正できるためです。



この構成プロセスはどれほど複雑であっても、一度実行すれば十分であり、その後、再コンパイル時に開発者の関与なしにすべてが自動的に更新されます。



理想的には、デスクトップアプリケーションの場合、次のスキームを適用すると便利です。圧縮されていないすべてのアセンブリをメインの実行可能ファイルに含めてから、ローダーアプリケーションを作成します。 複数の接着ファイルを圧縮することは、各ファイルを個別に圧縮するよりも効果的です。



すべてのキーは記事に記載されており、プロジェクトの知識を理解して適用するだけです。 しかし、困難がある場合は、 詩人エディターのソースコードを購入する機会があります。 これらの例に加えて、それらには他の多くのユニークで有用なソリューションが含まれています。 おそらく、数日または数週間ソリューションを探して開発するよりも、誰かがそこで何かを覗く方が費用対効果が高いでしょう。



3.保護の可能性にスムーズにアプローチしました



アセンブリは、何でもできるバイトの配列です...そして、復号化アルゴリズムを可能な限り複雑にする場合、部外者がデコードされたアセンブリを引き出すことは容易ではありません。 復号化自体は、ネイティブC ++コードに取り込むことも、Webアプリケーションがサーバーから復号化キーを受け取ることもできます。 2つの問題が残っています-オペレーティングシステムプロセスの借方記入またはメモリダンプの削除が可能です。



デバッガから身を守るのは難しくありません



  static class AntiDebugger { private static int Fails { get; set; } private static DateTime TimeStamp { get; set; } public static void Run() { TimeStamp = DateTime.Now; new Thread(ProtectThread).Start(); //new Thread(ProtectThread).Start(); } private static void ProtectThread() { while (true) { var now = DateTime.Now; if (TimeStamp.AddSeconds(5) < now) Fails++; else { TimeStamp = now; Fails = 0; } // One fail may attend when pc wake up from the sleep mode if (Fails >= 2) InitProtection(); Thread.Sleep(TimeSpan.FromSeconds(3)); } } private static void InitProtection() { Fails = 0; Console.WriteLine("Debugger detected!"); //Process.GetCurrentProcess().Kill(); //Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High; //for (var i = 0; i < 100; i++) //{ // new Thread(() => { while (true) ; }) {Priority = ThreadPriority.Highest}.Start(); //} } }
      
      





指定された間隔で1つまたはいくつかのスレッドがチェックされ、TimeStamp変数の値が更新されます。 TimeStampの値が古すぎる場合、これは、コンピューターがスリープモードから復帰したか、クロックが移動したか、デバッグが試行されたことを示します。 エラーが2回以上繰り返される場合、最後のオプションが最も可能性が高いため、保護を有効にする必要があります。 無限ループと高い優先度で千以上のスレッドを起動するだけで、プロセスを単にクラッシュさせたり、オペレーティングシステムをクラッシュさせることさえできます。 このトリックは、強力なマルチコアコンピューターでもシステムを無効にするため、誤検知のオプションを提供し、注意する必要があります。



これは即時の操作ではないため、メモリダンプの削除を防ぐために同様の手法を適用してみることができます。 ここでのみ、スレッドの代わりに、互いに続く少なくとも2つのプロセスを使用する必要があり、そのうちの1つが停止するとすぐに保護アクションを実行します。



まとめ



この記事が開発者に役立つことを願っています。 私はもう一度、すべてのキーポイントがその中にうまく記述されていることを繰り返します。実際にそれらを適用することだけが残っています。 突然問題が発生した場合は、 makeloft.byからPoetエディターの完全なソースコードをいつでも購入できます。



PS現金寄付と感謝の情報



無料のFoundation FrameworkライブラリのPPSプレビューバージョン。



All Articles