ビルドパイプライン
パッケージが MSBuild パイプラインのどこで Babel を実行するか、そのステップを移動する方法、フックできるターゲット、公開時の動作、ビルドツールが選択される仕組みを説明します。
難読化が実行される場所
既定では、パッケージは CoreCompile の直後に Babel を実行します。対象は、コンパイラーが中間フォルダー、つまり obj フォルダーに書き出したばかりのアセンブリ $(IntermediateOutputPath)$(TargetFileName) です。そのため、ビルドの以降のステップはすべて、難読化されたアセンブリを処理します。bin へのコピー、.deps.json の生成、サテライトアセンブリのほか、公開時のトリミング、単一ファイルへのバンドル、ReadyToRun または NativeAOT のコンパイル、APK やアプリバンドルへのパッケージ化がこれにあたります。
SDK スタイルのプロジェクトでパッケージを使用する理由は、この順序にあります。.NET SDK はコンパイル後に IL を書き換えるため、Babel はその前にアセンブリを処理する必要があります。bin や公開フォルダーから取り出したアセンブリは、すでにこれらのステップを経ています。CoreCompile の後にタスクを手作業で配置する方法は、ターゲットの正確な順序がプロジェクトの種類と SDK のバージョンに左右されるため、壊れやすくなります。パッケージは、この問題を代わりに解決します。
さらに、次の 2 つの動作によって IDE の応答性が保たれます。
- デザイン時ビルドはスキップされます。Visual Studio とコードアナライザーはバックグラウンドビルドを継続的に実行しますが、パッケージはそこに難読化ステップを挿入しません。
- 難読化は、コンパイラーが実行されたときにだけ実行されます。アセンブリが最新であると判断したインクリメンタルビルドは Babel もスキップするため、
objにあるすでに難読化されたアセンブリが 2 回処理されることはありません。
難読化ステップの移動
挿入位置は、3 つのプロパティで変更できます。
| プロパティ | 効果 |
|---|---|
RunBabelAfterBuild | true の場合、Babel はビルドの最後、出力が bin にコピーされた後に実行され、中間アセンブリの代わりに $(TargetPath) を処理します。 |
BabelAfterTargets | 指定した名前の MSBuild ターゲットの後に、難読化ステップを実行します。 |
BabelBeforeTargets | 指定した名前の MSBuild ターゲットの前に、難読化ステップを実行します。 |
いずれかを設定すると、CoreCompile の後という既定の配置は無効になります。BabelAfterTargets または BabelBeforeTargets を使用した場合、Babel は引き続き中間アセンブリを処理します。選択したターゲットが出力のコピー後に実行される場合は、BabelInputFile と BabelOutputFile を $(TargetPath) に向けてください。
典型的な用途は、ローカライズされたリソースを持つ Windows Forms アプリケーションです。この場合、Babel がサテライトアセンブリも処理できるように、サテライトアセンブリが生成された後で Babel を実行します。
<PropertyGroup>
<BabelAfterTargets>CoreGenerateSatelliteAssemblies</BabelAfterTargets>
</PropertyGroup>コンパイルから移動後の難読化ステップまでの間に実行される処理は、難読化されていないアセンブリを扱います。トリミング、単一ファイル、AOT で公開するプロジェクトや、SDK のステップがコンパイル後に IL を書き換えるプロジェクトでは、既定の配置のままにしてください。
ターゲットの順序と拡張ポイント
難読化ステップは ObfuscateBuild ターゲットです。このターゲットは、まず Babel タスクの構成を準備し、その後でタスクを実行します。
ObfuscateBuild
├─ ObfuscateSettings
│ ├─ ObfuscateDefaultSettings renaming defaults, license lookup, babelRules.xml, search directories
│ ├─ ObfuscateFrameworkSettings selects the assembly to process (obj, or $(TargetPath))
│ ├─ ObfuscateSetupBabelFiles BabelInputFile / BabelOutputFile, GenerateDebug when a PDB exists
│ ├─ ObfuscateCustomSettings empty, yours to redefine
│ ├─ SetupObfuscate empty, yours to redefine
│ └─ ConfigureBabel empty, yours to redefine
└─ Obfuscate
├─ BeforeObfuscate empty, yours to redefine
├─ CoreObfuscate runs the Babel task
└─ AfterObfuscate empty, yours to redefineその位置で独自のステップを実行するには、空のターゲットをプロジェクトファイルで再定義します。
| ターゲット | 実行のタイミング | 用途 |
|---|---|---|
ObfuscateCustomSettings、SetupObfuscate、ConfigureBabel | パッケージが既定値と入出力ファイルを計算した後、Babel が実行される前 | BabelInputFile、BabelOutputFile、GenerateDebug、検索ディレクトリなど、計算されたプロパティを上書きする。 |
BeforeObfuscate | Babel タスクの直前 | 依存関係を先に難読化する、解決済みの参照から設定を計算する、ルールを生成する。 |
AfterObfuscate | Babel タスクの直後 | マップファイルやログファイルを別の場所にコピーする、難読化されたアセンブリをチェックする。 |
CoreObfuscate は、AfterObfuscate で読み取れる 2 つの出力を提供します。BabelExitCode プロパティと、BabelProvideCommandLineArgs が true の場合にコマンドラインを保持する @(BabelCommandLineArgs) 項目です。
タスクの構成の上書き
パッケージは、プロジェクトの情報から Babel タスクを構成します。参照から得た検索ディレクトリ、厳密名キー、PDB、ルールファイルです。既定値が合わない場合は、BeforeObfuscate で調整します。次の例では、デバッグシンボルの生成を強制し、検索ディレクトリを出力フォルダーに置き換えます。
<Target Name="BeforeObfuscate">
<!-- Override computed properties -->
<PropertyGroup>
<GenerateDebug>true</GenerateDebug>
</PropertyGroup>
<!-- Replace the Babel search directories -->
<ItemGroup>
<BabelSearchDirectories Remove="@(BabelSearchDirectories)" />
<BabelSearchDirectories Include="$(TargetDir)" />
</ItemGroup>
</Target>BeforeObfuscate は、メインアセンブリを難読化する前に、依存関係に対して 2 つ目の Babel タスクを実行する場所でもあります。.NET アプリを公開するのサンプルでは、この方法でアセンブリ間のリネームを使い、NuGet パッケージのパブリックインターフェイスをリネームしています。
AfterObfuscate ターゲットでは、Babel が生成した成果物を収集できます。たとえば、単体テストのサンプルが使用する共有フォルダーに、マップファイルをコピーできます。
<PropertyGroup>
<GenerateMapOutFile>true</GenerateMapOutFile>
<BabelMapOutFile>$(IntermediateOutputPath)$(TargetFileName).map.xml</BabelMapOutFile>
</PropertyGroup>
<Target Name="AfterObfuscate">
<Copy SourceFiles="$(BabelMapOutFile)" DestinationFolder="$(SolutionDir)MapOut" />
</Target>公開
難読化は公開のステップより前に行われるため、dotnet publish と Visual Studio の公開プロファイルは、追加の構成なしで難読化された出力を生成します。パッケージは、これに加えて 3 つの調整を行います。
- マージされたアセンブリと埋め込みアセンブリは、公開対象から除外されます。
ComputeFilesToPublishの後に実行されるUpdateBabelFilesToPublishターゲットが、すべてのMergeAssembly項目とEmbedAssembly項目を、その.pdbファイルと.xmlファイルとともに、公開するファイルから取り除きます。これらを残すには、BabelPublishEnabledをfalseに設定します。 .deps.jsonファイルが更新されます。UpdateBabelBuildDependencyFileターゲットとUpdateBabelPublishDependencyFileターゲットが、マージされたアセンブリ、埋め込みアセンブリ、およびパッケージ自体を、ビルドと公開の依存関係マニフェストから削除します。これにより、ホストは、独立したファイルとしてはもう存在しないアセンブリを探さなくなります。マニフェストを変更しないようにするには、BabelUpdateDependencyFileをfalseに設定します。- Release ではデバッグシンボルは公開されません。プロジェクトで設定されていない限り、パッケージは Release 構成の
CopyOutputSymbolsToPublishDirectoryをfalseに設定します。PDB ファイルは、元のコードのファイル名と行番号を攻撃者に与えてしまうためです。それでも公開する場合は、trueに設定します。
単一ファイル、トリミング、AOT での公開は、難読化されたアセンブリでも動作しますが、注意点が 2 つあります。デスクトップの改ざんチェックは単一ファイルのイメージを検証できず、Babel はこれについて警告を出します(改ざん検出を参照)。また、.NET MAUI の iOS では、.NET MAUI を難読化するで説明しているとおり、リンカーを「Link SDK assemblies only」に設定する必要があります。
ビルドツールの選択
パッケージには、MSBuild ホストごとに Babel のビルドツール一式が含まれており、ビルドを実行しているホストに応じて選択されます。
| MSBuild ホスト | ツールフォルダー |
|---|---|
.NET SDK 6.0、7.0、8.0、9.0、10.0(dotnet build、Visual Studio) | tools\net6.0 … tools\net10.0(SDK のバージョンに対応) |
| その他のバージョンの .NET SDK | tools\net9.0 |
.NET Framework の MSBuild(MSBuild.exe) | tools\net472 |
| Mono の MSBuild | tools\Mono |
選択されたフォルダーは BabelTaskDir で参照でき、BabelPackageDir は展開されたパッケージのルートを指します。別のツール一式を強制するには、プロジェクトファイルで BabelTaskDir を設定します。
<PropertyGroup>
<BabelTaskDir>$(BabelPackageDir)tools\net8.0\</BabelTaskDir>
</PropertyGroup>この値は、Directory.Build.props ではなく、プロジェクトファイルまたは Directory.Build.targets で設定してください。パッケージ自体の .props ファイルは Directory.Build.props の後にインポートされるため、値が上書きされてしまいます。
パッケージの外部にインストールされた Babel、たとえば babel_net* zip パッケージから展開したコマンドラインツールを実行するには、BabelPath にそのフォルダーを設定し、必要に応じて BabelExe に実行可能ファイルの名前(babel.exe または babel.dll)を設定します。
ビルドサーバー
パッケージには特定のコンピューターに依存するものがないため、開発者の PC で難読化ビルドができるプロジェクトは、パッケージを復元でき、ライセンスにアクセスできるエージェントであれば、どこでも同じように難読化ビルドができます。サンプルでは、一般的なサービスを取り上げています。
- GitHub Actions:パッケージは GitHub Packages、ライセンスキーはリポジトリのシークレットから取得。
- Azure DevOps:パッケージは Azure Artifacts のフィード、ライセンスキーはパイプラインのシークレット変数から取得。
- AppVeyor:パッケージはアカウントのフィード、ライセンスキーは暗号化された変数から取得。
- 単体テスト:同じパイプラインでの難読化されたアセンブリのテスト。
Linux のエージェントとコンテナーは、Babel の tools\netX.0 ビルドを実行します。OpenSSL の FIPS 構成が壊れているホストには、FIPS 準拠で説明している設定が必要です。