ソフトウェア, ソフトウェア開発

Visual Studio 2015には、Dotfuscator and Analytics CE(以下Dotfuscator)というソースコード難読化ツールが付属している。

通常はGUI画面を呼び出し、手作業で登録・処理を行う必要があるが、
ユーザー登録を行うことでコマンドラインからの実行ができるようになる。

64BitOSの場合、Dotfuscatorは下記ディレクトリにインストールされている。
C:Program Files (x86)Microsoft Visual Studio 14.0PreEmptive SolutionsDotfuscator and Analytics Community Edition

(2017/03/08 追記)
Visual Studio 2017では、Dotfuscatorは下記ディレクトリとなる。
C:Program Files (x86)Microsoft Visual Studio2017CommunityCommon7IDEExtensionsPreEmptiveSolutionsDotfuscatorCE

dotfuscator.exeがVisualStudioから呼び出されたGUI画面、dotfuscatorCLI.exeがコマンドライン版。

dotfuscatorCLI.exeのヘルプは以下の通り。

C:Program Files (x86)Microsoft Visual Studio 14.0PreEmptive SolutionsDotfuscator and Analytics Community Edition>dotfuscatorCLI.exe /?

使用法: dotfuscator [オプション] [構成ファイル]

オプション
/g : GUI モードで実行します。
/i : 調査のみ実行します。
/p=<プロパティ リスト> : /p=<プロパティ リスト>
(例: /p=projectdir=c: emp,projectname=MyApp)
/q : メッセージを表示しません。
/v : 詳細な情報を表示します。
/c:<カルチャ> : ユーザー インターフェイスの言語を設定します (適切な言語リソースが必要です)。引数は、小文字の言語コードです。:
(例: /c:de, /c:ja, /c:zh-CHS)
/? : このメッセージを表示します。
[構成ファイル] : ランタイム オプションを含む構成ファイルです。

拡張オプション
/in:[+|-]<ファイル>[,[+|-]<ファイル>] : 入力アセンブリを指定します。公開 (+) または秘密 (-) アセンブリとして入力を難読化するには、プレフィックスを使用します。また、パッケージの種類を指定するには、ファイル名の後に : を使用します。

使用可能なパッケージの種類

Appx
ClickOnce
Silverlight
Directory

/out: <ディレクトリ> : 出力ディレクトリを指定します。
/honor:[on*|off] : すべての入力アセンブリで見つかった難読化属性の使用指令を有効/無効にします。
/strip:[on*|off] : すべての入力アセンブリからの難読化属性の除去を有効/無効にします。
/smart:[on*|off] : 出力プログラムが正しく動作するために必要であると見なされるプログラム要素を自動的に処理します。
/soreport:[all*|warn|none] : スマート難読化レポートの詳細を指定します。
/makeconfig:<ファイル> : コマンド ライン オプションを構成ファイルに保存します。
/disable : すべての変換 (“-rena:off -prune:off -encr:off -cont:off -link:off" の省略形です) およびインストルメンテーションを無効にします。

拡張オプション (Professional Edition のみ)
/debu:[on|off*] : 難読化されたアセンブリ用にデバッグ シンボルを出力します。

名前の変更の拡張オプション
/rename:[on*:off] : 名前の変更を有効/無効にします。
/mapout:<ファイル> : 出力割り当てファイルを指定します。
/clobbermap:[on|off*] : 割り当て上書きモードを指定します。
/keep:[namespace|hierarchy|none*] : 型の名前の変更規則を指定します。

名前の変更の拡張オプション (Professional Edition のみ)
/naming:[loweralpha*|upperalpha|numeric|unprintable] : 識別子の名前の変更規則を指定します。
/pref:[on|off*] : 名前を変更した型にプレフィックスを付けます。
/mapin:<ファイル> : 入力割り当てファイルを指定します。
/enhancedOI:[on|off*] : Enhanced Overload Induction(TM) を使用します。
/refsrename:[on*|off] : 入力割り当てファイルにのみ定義されている、参照されるメタデータの名前を変更します。

制御フローの拡張オプション (Professional Edition のみ)
/controlflow:[high*|medium|low|off] : 制御フロー レベルを設定します。

文字列の暗号化の拡張オプション (Professional Edition のみ)
/encrypt:[on*|off] : 文字列の暗号化を有効/無効にします。

不要コードの除去の拡張オプション (Professional Edition のみ)
/prune:[on*|off] : 不要コードの除去を有効/無効にします。

リンクの拡張オプション (Professional Edition のみ)
/link:[[+]<名前>[,[+]<名前>],]out=<名前> : 名前を付けて出力アセンブリとしてリンクする複数のアセンブリを指定します。プライマリ アセンブリは '+’ で指定します。すべての入力をリンクし、最初のアセンブリをプライマリとする場合は、リストを省略できます。

ウォーターマークの拡張オプション (Professional Edition のみ)
/premark:[on|off*|only] : ウォーターマークを有効/無効にします。"Only" オプションは、他のすべての変換を無効にします。
/watermark:<文字列> : ウォーターマークに使用する文字列を指定します。二重引用符は省略可能です。既定では、すべての入力アセンブリにウォーターマークが設定されます。
/passphrase:<パスフレーズ> : 文字列の暗号化に使うパスフレーズを指定します (オプション)。
/charmap:<名前> : 使用する文字セットを指定します。

オプション キーは '-' または '/’ です。拡張オプションは先頭 4 文字によって識別されます。
拡張オプションは構成ファイルのオプションよりも優先されます (ただし入力ファイルをマージする場合は除きます)。

●VisualStudioのビルド後に、自動的にDotfuscatorを実行する

1.プロジェクトのビルドイベントのビルド後のコマンドラインにDotfuscatorのコマンドを記載する

記載例

if $(ConfigurationName) == Release (
	del /S /Q "$(TargetDir)Dotfuscated*.*"
	md "$(TargetDir)Dotfuscated"
	"C:Program Files (x86)Microsoft Visual Studio 14.0PreEmptive SolutionsDotfuscator and Analytics Community EditiondotfuscatorCLI.exe" /in:"$(TargetDir)*.dll" /out:"$(TargetDir)Dotfuscated"
)

記載例解説
if $(ConfigurationName) == Releaseを指定することにより、リリースビルド時のみ実行する設定となる。
ビルドしたディレクトリにあるすべてのDLLを、ビルドしたディレクトリ配下のDotfuscatedディレクトリに、難読化後に出力する。

ソフトウェア, ソフトウェア開発

ILMergeを用いることで、EXE内にDLLを格納する等、アセンブリファイルをまとめることができる。

 

1.ILMergeのダウンロード・インストール

下記サイトよりILMergeをダウンロードし、インストールを行う。

Download ILMerge from Official Microsoft Download Center

ここでは「C:Program Files (x86)MicrosoftILMerge」にインストールしたものとし、解説をする。

 

2.ILMergeの実行

ILMergeを実行すると、下記のヘルプが表示される。

C:Program Files (x86)MicrosoftILMerge>ILMerge.exe
Usage: ilmerge [/lib:directory]* [/log[:filename]] [/keyfile:filename [/delaysign]] [/internalize[:filename]] [/t[arget]:(library|exe|winexe)] [/closed] [/ndebug] [/ver:version] [/copyattrs [/allowMultiple] [/keepFirst]] [/xmldocs] [/attr:filename] [/targetplatform:<version>[,<platformdir>] | /v1 | /v1.1 | /v2 | /v4] [/useFullPublicKeyForReferences] [/wildcards] [/zeroPeKind] [/allowDup:type]* [/union] [/align:n] /out:filename <primary assembly> [<other assemblies>…]

基本的な使い方は、「/out:」にマージしたファイルの出力先パスを、「primary assembly」にメインとなるファイル、「other assemblies」にマージしたいファイルを指定する。

publicな修飾子をinternalに変更したいときは、「/internalize」を指定する。
しかし、これによりマージ後のアプリケーションが正常に動作しなくなる(参照エラー)恐れもあるため、テストは十分に行うこと。

pdbファイルを生成しないようにするには「/ndebug」を指定する。

.NetFrameworkを用いたアセンブリの場合は、「/targetplatform:」に.Net Frameworkのバージョン または プラットフォームディレクトリを指定する。
.NetFramework4.5.2の場合は下記のようになる。
/targetplatform:"v4,C:Program Files (x86)Reference AssembliesMicrosoftFramework.NETFrameworkv4.5.2″

「other assemblies」に、全てのファイルを列挙するのではなく、ワイルドカードを用いて指定することもできる。
その場合は「/wildcards」を指定する。

使用例

ILMerge.exe /internalize /ndebug /targetplatform:"v4,C:Program Files (x86)Reference AssembliesMicrosoftFramework.NETFrameworkv4.5.2" /wildcards /out:MargeProject.exe ReleaseProject1.exe Release*.dll

 

出力先を異なるディレクトリにする場合は、事前にディレクトリを用意しておくこと。
ディレクトリの自動生成はされず、エラーとなる。

 

3.VisualStudioのビルド後に、自動的にILMergeを実行する

3.1.各プロジェクトのビルド出力先を同一の場所にする

プロジェクトの初期値では、プロジェクトのフォルダ内にBinフォルダが作られ、ビルドされたアセンブリファイルが置かれる。
これを、各プロジェクトが同一のフォルダにアセンブリファイルを出力するように変更する。
注意:指定対象は全てのプロジェクト

 

3.2.メインプロジェクトのビルドイベントのビルド後のコマンドラインにILMergeのコマンドを記載する

注意:指定対象はメインのプロジェクトのみ

記載例

if $(ConfigurationName) == Release (
	del /S /Q "$(TargetDir)Marge*.*"
	md "$(TargetDir)Marge"
	"C:Program Files (x86)MicrosoftILMergeILMerge.exe" /internalize /ndebug /targetplatform:"v4,C:Program Files (x86)Reference AssembliesMicrosoftFramework.NETFrameworkv4.5.2" /wildcards /out:"$(TargetDir)Marge$(TargetName)$(TargetExt)" "$(TargetDir)$(TargetName)$(TargetExt)" "$(TargetDir)*.dll"
)

記載例解説
if $(ConfigurationName) == Releaseを指定することにより、リリースビルド時のみ実行する設定となる。
ILMergeは、3.1.にて指定したディレクトリ内のMargeディレクトリに、プロジェクトのEXEと同名の、DLLを全てマージしたEXEを生成する。
パスは全てダブルクォーテーションで囲う。

 

3.3.プロジェクトをビルドする

C#, VB.NET, ソフトウェア開発

返り値のないメソッドの場合

        private async void btnStart_Click(object sender, EventArgs e)
        {
            Console.WriteLine("Start:" + DateTime.Now.ToString("HH:mm:ss.ff"));

            await Count();

            Console.WriteLine("Finish:" + DateTime.Now.ToString("HH:mm:ss.ff"));
        }

        private async Task Count()
        {
            int intValue = 1;

            await Task.Run(() =>
            {
                for(int intCount = 0; intCount < 15; intCount++)
                {
                    intValue *= 2;
                    Console.WriteLine(intValue);

                    System.Threading.Thread.Sleep(100);
                }
            });
        }

 

別スレッドからキャンセル処理を行う場合は下記のようになる。

        // トークン
        private CancellationTokenSource objToken = null;

        private async void btnStart_Click(object sender, EventArgs e)
        {
            Console.WriteLine("Start:" + DateTime.Now.ToString("HH:mm:ss.ff"));

            await Count();

            Console.WriteLine("Finish:" + DateTime.Now.ToString("HH:mm:ss.ff"));
        }

        private async Task Count()
        {
            int intValue = 1;

            objToken = new CancellationTokenSource();

            await Task.Run(() =>
            {
                for(int intCount = 0; intCount < 15; intCount++)
                {
                    // キャンセルフラグが立っていたら処理を抜ける
                    if(objToken.IsCancellationRequested == true)
                    {
                        return;
                    }

                    intValue *= 2;
                    Console.WriteLine(intValue);

                    System.Threading.Thread.Sleep(100);
                }
            }, objToken.Token);
        }

        /// <summary>
        /// キャンセル処理
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnCancel_Click(object sender, EventArgs e)
        {
            if(objToken != null)
            {
                objToken.Cancel();
            }
        }

また、IsCancellationRequested ではなく ThrowIfCancellationRequested を用いることで、
例外として処理することもできる。

        // トークン
        private CancellationTokenSource objToken = null;

        private async void btnStart_Click(object sender, EventArgs e)
        {
            Console.WriteLine("Start:" + DateTime.Now.ToString("HH:mm:ss.ff"));

            try
            {
                await Count();
                Console.WriteLine("Finish:" + DateTime.Now.ToString("HH:mm:ss.ff"));
            }
            catch (OperationCanceledException exCanceled)
            {
                // キャンセル時
                Console.WriteLine("Canceled:" + DateTime.Now.ToString("HH:mm:ss.ff"));
            }
        }

        private async Task Count()
        {
            try
            {
                int intValue = 1;

                objToken = new CancellationTokenSource();

                await Task.Run(() =>
                {
                    try
                    {
                        for (int intCount = 0; intCount < 15; intCount++)
                        {
                            // キャンセルフラグが立っていたらOperationCanceledExceptionを発生させる
                            objToken.Token.ThrowIfCancellationRequested();

                            intValue *= 2;
                            Console.WriteLine(intValue);

                            System.Threading.Thread.Sleep(100);
                        }
                    }
                    catch (OperationCanceledException exCanceled)
                    {
                        // キャンセル時
                        throw (exCanceled);
                    }
                }, objToken.Token);
            }
            catch (Exception ex)
            {
                throw (ex);
            }
        }

        /// <summary>
        /// キャンセル処理
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnCancel_Click(object sender, EventArgs e)
        {
            if (objToken != null)
            {
                objToken.Cancel();
            }
        }

 

 

返り値のあるメソッドの場合

        private async void btnStart_Click(object sender, EventArgs e)
        {
            Console.WriteLine("Start:" + DateTime.Now.ToString("HH:mm:ss.ff"));

            int intResult = await Count();

            Console.WriteLine("Total:" + intResult);

            Console.WriteLine("Finish:" + DateTime.Now.ToString("HH:mm:ss.ff"));
        }

        private async Task<int> Count()
        {
            int intValue = 1;
            
            int intResult = await Task.Run(() =>
            {
                int intSum = 0;

                for(int intCount = 0; intCount < 15; intCount++)
                {
                    intValue *= 2;
                    Console.WriteLine(intValue);

                    intSum += intValue;

                    System.Threading.Thread.Sleep(100);
                }

                return intSum;
            });

            return intResult;
        }

 

また、VB.NETの場合は下記のようになる(返り値のないメソッド)

    Private Async Sub btnStart_Click(sender As Object, e As EventArgs) Handles btnStart.Click
        Debug.Print("Start:" + DateTime.Now.ToString("HH:mm:ss.ff"))

        Await Count()

        Debug.Print("Finish:" + DateTime.Now.ToString("HH:mm:ss.ff"))
    End Sub

    Private Async Function Count() As Task
        Dim intValue As Integer = 1

        Await Task.Run(Sub()
                           For intCount = 0 To 14
                               intValue *= 2
                               Debug.Print(intValue)

                               System.Threading.Thread.Sleep(100)
                           Next
                       End Sub)
    End Function

 

C#, ソフトウェア開発

        // サンプルデータ
        private List<string> lstData = new List<string> { "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K" };

        /// <summary>
        /// 通常処理
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnStart_Click(object sender, EventArgs e)
        {
            Console.WriteLine("Start:" + DateTime.Now.ToString("HH:mm:ss.ff"));
            Console.WriteLine("For文");
            for (int intCount = 0; intCount < lstData.Count; intCount++)
            {
                Console.WriteLine(lstData[intCount]);
                System.Threading.Thread.Sleep(300);
            }

            Console.WriteLine("ForEach文");
            foreach (string strData in lstData)
            {
                Console.WriteLine(strData);
                System.Threading.Thread.Sleep(300);
            }
            Console.WriteLine("Finish:" + DateTime.Now.ToString("HH:mm:ss.ff"));
        }
Start:12:00:32.75
For文
A
B
C
D
E
F
G
H
I
J
K
ForEach文
A
B
C
D
E
F
G
H
I
J
K
Finish:12:00:39.36

 

これにParallelを適用すると、下記のようになる。

        // サンプルデータ
        private List<string> lstData = new List<string> { "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K" };

        /// <summary>
        /// パラレル処理
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnParallelStart_Click(object sender, EventArgs e)
        {
            Console.WriteLine("Start:" + DateTime.Now.ToString("HH:mm:ss.ff"));
            Console.WriteLine("Parallel.For文");
            Parallel.For(0, lstData.Count, intCount =>
            {
                Console.WriteLine(lstData[intCount]);
                System.Threading.Thread.Sleep(300);
            });

            Console.WriteLine("Parallel.ForEach文");
            Parallel.ForEach(lstData, strData =>
            {
                Console.WriteLine(strData);
                System.Threading.Thread.Sleep(300);
            });
            Console.WriteLine("Finish:" + DateTime.Now.ToString("HH:mm:ss.ff"));
        }
Start:12:01:43.02
Parallel.For文
A
C
D
E
F
G
H
I
J
B
K
Parallel.ForEach文
A
E
D
F
B
G
H
C
I
J
K
Finish:12:01:44.24

並列に処理を行うため、全体の処理速度が向上している反面、処理の順番がバラバラになっていることに注意されたい。

 

並列処理内部でBreakを行いたい場合は下記のようになる。

        /// <summary>
        /// パラレル処理
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnParallelStart_Click(object sender, EventArgs e)
        {
            Console.WriteLine("Start:" + DateTime.Now.ToString("HH:mm:ss.ff"));
            Console.WriteLine("Parallel.For文");
            Parallel.For(0, lstData.Count, (intCount, objLoopState) =>
            {
                Console.WriteLine(lstData[intCount]);

                if (lstData[intCount] == "G")
                {
                    objLoopState.Break();
                }

                System.Threading.Thread.Sleep(300);
            });

            Console.WriteLine("Parallel.ForEach文");
            Parallel.ForEach(lstData, (strData, objLoopState) =>
            {
                Console.WriteLine(strData);

                if (strData == "G")
                {
                    objLoopState.Break();
                }

                System.Threading.Thread.Sleep(300);
            });
            Console.WriteLine("Finish:" + DateTime.Now.ToString("HH:mm:ss.ff"));
        }
Start:12:07:37.11
Parallel.For文
A
C
B
D
E
F
G
Parallel.ForEach文
A
F
D
E
G
B
C
Finish:12:07:37.73

Gが処理されたタイミングでBreakをかけているが、すでに並列処理が走ってしまっている分に関しては処理されてしまうので注意。

 

また、外部のスレッドから処理を中断する場合は下記のようになる。

        // トークン
        private CancellationTokenSource objToken = null;

        /// <summary>
        /// パラレル処理
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private async void btnParallelStart_Click(object sender, EventArgs e)
        {
            try
            {
                await Task.Run(() =>
                {
                    // ParallelOptionsの構築
                    objToken = new CancellationTokenSource();
                    ParallelOptions options = new ParallelOptions();
                    options.CancellationToken = objToken.Token;

                    Console.WriteLine("Start:" + DateTime.Now.ToString("HH:mm:ss.ff"));
                    Console.WriteLine("Parallel.For文");
                    Parallel.For(0, lstData.Count, options, intCount =>
                    {
                        Console.WriteLine(lstData[intCount]);
                        System.Threading.Thread.Sleep(300);
                    });

                    Console.WriteLine("Parallel.ForEach文");
                    Parallel.ForEach(lstData, options, strData =>
                    {
                        Console.WriteLine(strData);
                        System.Threading.Thread.Sleep(300);
                    });
                    Console.WriteLine("Finish:" + DateTime.Now.ToString("HH:mm:ss.ff"));
                });
            }
            catch (OperationCanceledException ex)
            {
                // キャンセルされた際に入る例外処理
                Console.WriteLine("Cancel:" + DateTime.Now.ToString("HH:mm:ss.ff"));
            }
        }

        /// <summary>
        /// パラレル処理のキャンセル
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnParallelBreak_Click(object sender, EventArgs e)
        {
            if (objToken != null)
            {
                objToken.Cancel();
            }
        }