通常、ファイルシステムにデプロイされたアプリケーションは次のようになります。

リフレクターやIlSpyなどのツールからは完全に保護されていませんが、次のようになったらどうなりますか?

初心者のハッカーには、少なくともわずかなst迷が保証されます。 見た目が良く、
まもなく
写真にはBooster.exeモジュールがあります。それについて話しましょう。これは非常に小さなプログラムで、次のことができます。
- dll'okのセットを画像のセットに圧縮し、圧縮および暗号化する
- 一連の画像からdllをロードし、Mainメソッドでタイプを見つけて実行します
これは、アセンブリが相互に使用できることを考慮します(このため、AppDomainのAssemblyResolveイベントが使用されます)。
すべてのチェックを削除すると、実行開始コードは次のようになります。
AssemblyProvider.LoadAssemblies(imagesFolder).CallMain();
このようなパッキングコード:
var config = CommandLineParser.Parse(args); AssemblyProvider.PackAssemblies(config["img"], config["imgout"], config["asm"]);
パッキング
パッケージ化から始めましょう。PackAssembliesメソッドは、画像を含むディレクトリへのパス、コードを含む画像を配置するディレクトリへのパス、およびアセンブリを含むディレクトリへのパスを取得します。 まず、写真に関する情報を収集します。写真に保存できるデータの量を知る必要があります。
Dictionary<string, long> imagesVolume = new Dictionary<string, long>(); var imageFiles = Directory.GetFiles(imagesFolder, "*.*").Where(file => file.ToLower().EndsWith("bmp") || file.ToLower().EndsWith("png") || file.ToLower().EndsWith("jpg") || file.ToLower().EndsWith("jpeg")) .ToList(); foreach (string file in imageFiles) imageSet.Append(file); //ImageSet internal void Append(string imageFile) { using (Image img = Image.FromFile(imageFile)) { _imagesVolume.Add(imageFile, img.Width * img.Height - 4); _images.Add(imageFile); } }
数式は単純で、幅に高さを掛け、データサイズから4バイトを引きます。 ImageSetはさらに、写真のループを監視し(異なるように)、アセンブリのサイズに対応できる写真を見つけます。
今では、アセンブリを調べ、それぞれをバイナリ形式でダウンロードし、データのサイズに対応できる画像とマージする必要があります。 簡単に言うと、このような別のアセンブリの場合:
byte[] packed = AssemblyPacker.Pack(file); string imageFile = imageSet.FindImage(packed.Length); if (imageFile != null) { using (Image img = Image.FromFile(imageFile)) { Bitmap bmp = SteganographyProvider.Injection(img, packed); //Save } }
AssemblyPackerは、 GZipStreamを使用してバイト配列を圧縮します。 アセンブリのサイズはブロック単位で調整されるため、サイズを大幅に削減できます。
ビルドファイルの終わりの例:

、ゼロで終了した部分が強調表示され、さらに200個のゼロの下で完全に圧縮されます。
その後、圧縮された配列は「暗号化」され、暗号化プロバイダーは使用しませんでした。配列は疑似乱数のセットで単純にxorされますが、配列のサイズはジェネレーターです。 つまり 暗号化操作を繰り返すと、元の配列が取得されます。 アルゴリズムは非常に単純ですが、ホワイトノイズに似た結果が得られます。
生データの割り当て:

処理後の分布は正常に近い

パックされたアセンブリを受け取ったら、最も興味深い部分に進み、写真とマージします。 最初の問題に出くわす場所。 画像を保存するとき、.NETはWinApi GdipSaveImageToFileメソッドを使用します ( こちらで確認できます )。これは、画像とファイル名へのリンクに加えて、アーカイバ、オプティマイザなどを含む画像ハンドラ(エンコーダ)へのリンクも受け入れます。 それぞれがピクセル値を変更する可能性があり、保存されたデータを損傷します。 最も簡単な解決策はハンドラーを渡さないことですが、 GdipSaveImageToFileメソッド自体は、転送されたハンドラーリストに加えてスマートであり、画像形式(clsid、メソッドの3番目のパラメーター)にも焦点を当てていますが、たとえば、pngの場合、通常、ハンドラのリストを作成し、自分で何を使用するかを決定します。 ロスレス画像圧縮が機能する組み合わせはまだ見つかりませんでしたので、次のソリューションを使用します。
Bitmap bmp = SteganographyProvider.Injection(img, packed); FieldInfo fi = bmp.GetType().GetField("nativeImage", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); object d = fi.GetValue(bmp); IntPtr nativeImage = (IntPtr)d; Guid clsid = FindEncoder(ImageFormat.Bmp.Guid).Clsid; GdipSaveImageToFile(new HandleRef(bmp, nativeImage), outImagefile, ref clsid, new HandleRef(null, IntPtr.Zero));
つまり どの入力形式であっても、保存するときは、BMPで作業していることを指摘しますが、最終画像はどのような方法でも最適化されていません。これは最終画像の重みからすぐにわかります。 ただし、書き込まれたピクセルを確実に読み取ることができます。
ストレージを扱った後、合併に進みます。 32ビット画像の場合、各ピクセルは4バイト(argb)でエンコードされ、24ビット画像の場合3(rgb)の場合、アルゴリズムは最初はアルファチャネルを使用して4バイトで書き込むことに焦点を当てていたため、元の画像とほとんど区別できなかった画像を取得できましたが、 bmpに来て、彼はアルファチャンネルを拒否しました。
したがって、配列の各バイトは1つのピクセルにマッピングされますが、バイトは3つの数、つまりユニット数、数十、数百に分割されます。 バイト158の場合、3つの数値(1、5、8)を取得します。その後、rgbコンポーネントの単位を結果の数値に置き換えます。たとえば、ピクセル(7,155,72)は(1、155、78)になります。
pix = MixByteAndPixel(pix, ByteDecomposition(data[index], false)); ((byte*)bmd.Scan0)[offset] = pix.blue; ((byte*)bmd.Scan0)[offset + 1] = pix.green; ((byte*)bmd.Scan0)[offset + 2] = pix.red;
マージアルゴリズムは次のとおりです。データのある配列の先頭に4バイトを追加します。データには、配列の長さが書き込まれます。 画像を調べてバイトをピクセルにマージします。画像に空きピクセルが残っている場合は、ランダムな値にマージします(そうしないと、CD / DVDディスクに空の領域が見えるため、データが終了した境界線が最終画像で明確にトレースされます)。 結果の画像を保存します。
オリジナル(左)とアセンブリが内部にある写真(右):

打ち上げ
開始するには、Booster.exeと結果の画像が必要です。 Booster.exeを写真のあるディレクトリに入れて実行するだけです。 または、パラメータimg = path_to_picturesで実行します。
同時に、逆の順序のすべてのパッキング操作が写真に適用されます。
- ピクセル値を読み取り、rgbコンポーネントからピクセルごとに単位を取り、元のバイトを復元します。
- 最初の4バイトの後、残りのデータのサイズを取得します。
- パックされ暗号化されたアセンブリでソース配列を読み取ります。 画像の残りは無視します。
- 元の配列を復号化します。
- 配列を解凍し、未加工の形式で直接アセンブリを取得します。
- アセンブリをダウンロードする
- 読み込みが成功した場合、アセンブリが相互に使用する場合に、アセンブリをAssemblyResolverに登録します
internal static AssembliesSet LoadAssemblies(string imagesFolder) { AssembliesSet set = new AssembliesSet(); foreach (string file in Directory.GetFiles(imagesFolder, "*.bmp")) { byte[] data = null; using (Image img = Image.FromFile(file)) { data = SteganographyProvider.Extraction(img); } data = AssemblyPacker.UnPack(data); set.TryAppendAssembly(data); } return set; } //AssembliesSet internal void TryAppendAssembly(byte[] rawAssembly) { Assembly asm; try { asm = Assembly.Load(rawAssembly); AssembliesResolver.Register(asm); _assemblies.Add(asm); } catch { } }
最後の部分では、ロードされたコレクションでMainメソッドに使用可能なタイプを探し、それを呼び出します。
internal void CallMain() { foreach (var type in CollectExportedTypes()) { MethodInfo main = type.GetMethod("Main"); if (main != null) { ParameterInfo[] paramsInfo = main.GetParameters(); object[] parameters = new object[paramsInfo.Length]; for (int i = 0; i < paramsInfo.Length; i++) { parameters[i] = GetDefaultValue(paramsInfo[i].ParameterType); } main.Invoke(null, parameters); } } }
テスト中
アセンブリCを使用して、AとBの3つのアセンブリA、BおよびCでプロジェクトを作成しましょう。ここに、たとえば、
/ *下のコメントの写真* /
写真を貼り付けて実行します:
/ *下のコメントの写真* /
結論からわかるように、アセンブリの相互依存関係(CommonClassクラスのRunメソッドの呼び出し)を含め、すべてのアセンブリがロードされ、コードが実行されました。
おわりに
当初、私は本当にpng形式を使用したかったのですが、何らかの理由でロスレス圧縮アルゴリズムを使用していると確信していましたが、画像には重要ではないがステガノグラフィーには重要な損失があることが判明しました。 誰かが損失なくビットマップをpngに保存する方法を知っている場合は、登録を解除してください。
面白かったと思います。 ここからダウンロードして再生できます 。
PS Habrの写真で奇妙なことが起こっており、着実にリンクを失っています。
UPD PNGについてコメントしてくれたmayorovp habruiserのおかげで、損失なく標準的な方法で保存できます。 アルゴリズムはアルファチャネルの存在を考慮し、存在する場合、各バイトを4つのコンポーネントに分解して出力の歪みを減らします。 158に等しいサンプルバイトの場合、分解は次のようになります。
最初のステップでは、100、10、および単位を分割し、値(1、5、8、0)を持つベクトル(a1、a2、a3、a4)を取得し、条件に応じてa4を見つけます。
if (a2 >= 5 && a3 >= 5) { a4 = 2; a2 -= 5; a3 -= 5; } else if (a2 >= 5) { a4 = 3; a2 -= 5; } else if (a3 >= 5) { a4 = 4; a3 -= 5; } else a4 = 5;
最終的なベクトルは次のようになります(1、0、3、2)。
したがって、すべての単位rgba単位は0〜5の範囲になり、理論上は画像が滑らかになります。 より最適なエンコードを考え出すことができます。