Visual Studio for MacでXamarin.Androidの難読化を行う

## Xamarin.Androidにおける難読化
### Xamarin.AndroidでのR8コンパイラは一部のプロセスが実行されない
Android Studioを使用している場合、Androidアプリケーションの難読化はR8コンパイラによって行われます。
Android Studio3.4以降、またはAndroid Gradleプラグイン3.4.0以降の新規プロジェクト作成時ではこれを設定する必要があります。
（R8については[公式サイト](https://developer.android.com/studio/build/shrink-code?hl=ja)に詳しい説明がありますので割愛します。）

しかし、Xamarin.Androidでは[プロジェクトの設定でR8を使用するよう指定したとしても、難読化は行われない仕様](https://docs.microsoft.com/ja-jp/xamarin/android/deploy-test/release-prep/proguard?tabs=macos#proguard-in-xamarinandroid)です。
これは、Xamarin.AndroidのビルドがDEXファイルではなくIL(中間言語)ファイルを生成するものであるため、R8コンパイラによる難読化ができないからです。
![r8_setting.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283225/7c923e66-ccad-3022-9806-bedd067ef1a6.png)
↑の設定を行っても難読化は実施されない

ではどうするかというと、Xamarin.Androidにおける開発では、IL(Xamarin.AndroidではDLL形式)が生成された後、それに含まれるシンボルに対して後付で難読化を行うのが標準的な手法となっています。
また、その手法に則った難読化ツールも数多くのサードパーティが提供しています。

### iOSの場合は...
ちなみに、[Xamarin.iOSはAOTコンパイル](https://docs.microsoft.com/ja-jp/xamarin/ios/internals/architecture)(Ahead-Of-Time=事前)であるため、結果としてILを出力しません。
よって、Xamarin.iOSについては必ずしも難読化対象とする必要はなく、今回紹介する難読化方法の対象外となります。

## 難読化を行う
### 難読化ツールの選定
前述したとおり、ILに対する難読化ツールは数多く存在し、Xamarin.Androidに対してもそれは同様です。
しかし個人的に調べた限りでは、その殆どのツール、ドキュメント、その他情報がWindows向けであり、macOS向けはかなり少ない印象でした。
（Visual StudioのWindows版になら標準でバンドルされている.NET難読化ツールですら、結局macOS向けにGUIの提供は無く、どう使えばよいのか理解し辛い）

散々調べた末にObfuscarという良さげな難読化ツールを発見したので、これを使用して難読化を行うことにします。
スターの数、コントリビュータの数、更新頻度を加味して、これが良いと判断しました。

### Obfuscarの導入
Obfuscar
https://www.obfuscar.com/

ObfuscarはOSSの難読化ツールです(MITライセンス)。
Obfuscarも基本的にWindows向けのようですが、Githubの[issueのコメント](https://github.com/obfuscar/example/issues/4)にはmacOSでも動作するとあります。
ただし、そのまま例に則っただけでは動きませんので、まずは基本的な手順から説明します。


#### 難読化の手順と環境
難読化の環境を一気に作ろうとすると動かない時に原因がわかりづらくなるため、
本記事では、まず単純に難読化ができる環境をつくり、その後自動化するというステップを踏みます。

本記事を書くにあたって使用した環境は以下のとおりです。

|項目|名称|
|---|---|
|機種|M1 Mac 2020<br>Intel Mac 2017|
|OS|macOS Big Sur (M1 Mac)<br>macOS Catalina (Intel Mac)|
|IDE|Visual Studio 2019 for Mac Community|

基本M1 Mac + Big Surを使用しましたが、Intel Mac + macOS Catalinaでも動作確認済みです。

### ObfuscarのVisual Studio向けセットアップ

1. Visual Studioのメニュー > プロジェクト > NuGet パッケージの管理からObfuscarを検索する
1. Obfuscar 2.2.29(執筆時の最新の安定版)をAndroidプロジェクトに追加する(その他のプロジェクトには不要)
![nuget_obfuscar.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283225/e5b5aa8b-4f95-cc8f-012c-0e8c79a574f6.png)
1. Androidプロジェクトを右クリック > 追加 > 新しいファイルを選択し、以下の内容で`obfuscate.xml`を作成する（ファイル名は任意)

    ```xml
<?xml version="1.0" encoding="UTF-8" ?>
<Obfuscator>
  <Var name="InPath" value="./bin/Release" />
  <Var name="OutPath" value="./bin/Release_Obfuscated" />
  <AssemblySearchPath path="./obj/Release/android/assets" />
  <Module file="$(InPath)/プロジェクト名.Droid.dll" />
  <Module file="$(InPath)/プロジェクト名.dll" />
</Obfuscator>
```
各種パラーメタの詳細やその他パラメータについては、[公式ドキュメント](https://docs.obfuscar.com/getting-started/configuration.html)を参照して下さい。
場合によっては`KeepPublicApi` `HidePrivateApi` `HideStrings`の３つのフラグあたりは設定を変更することもあると思いますが、基本的に標準で良いです。
(ただ、バージョンによりデフォルト値の変更された経緯もあるので、それを踏まえこれらを定義しておくのは良策だとは思います。ちなみに`KeepPublicApi`を`false`とした場合はdllのI/Fが潰されて外からアクセスできないのか動かなくなります。スタンドアロンのexeなら`true`でも行けるのかも。試してないですが。)

    この設定では、以下がポイントとなります。
 * プロジェクトのリリースビルド出力フォルダ(Droid.dllが存在するフォルダ)を`InPath`として定義
 * Obfuscarを適用した.dllを出力するフォルダを`OutPath`として定義
 * 依存ファイルが存在するフォルダを`AssemblySearchPath`として定義
 * `<Module file="" />`で実際に難読化するファイルのパスを定義

    注意点として、`InPath`と`OutPath`を同じにすれば良いと考えるのは自然な流れだと思うのですが、
これだとファイルI/Oの問題でうまく行かないようです。
ですので、`InPath`と`OutPath`を被らないように設定しています。
（難読化前と後で別れているので検証もしやすいですし、良いかなと思います。）

1. Androidプロジェクトの設定のビルド > カスタム コマンドから構成を`Release`に指定します
1. 以下の内容でビルド後のカスタムコマンドを登録します

    |項目|設定値|
|---|---|
|コマンド(※)|mono ${SolutionDir}/packages/Obfuscar.2.2.29/tools/Obfuscar.Console.exe ./obfuscate.xml|
|作業ディレクトリ|${ProjectDir}|

    (※) macOSではmono経由でないとexeファイルを実行できない。
![vs_build_settings.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283225/4012608a-63aa-a568-acef-3786011b43a6.png)

以上の手順で`bin/Release`内の`プロジェクト名.Droid.dll`と`プロジェクト名.dll`が、`bin/ReleaseObfuscated/`に難読化された状態で出力されます。

本記事ではObfuscarの導入はAndroidプロジェクトにのみ行いましたが、
これは共有プロジェクトの出力である`プロジェクト名.dll`の方は、Androidプロジェクトをビルドすると、自動的にAndroidのビルド出力先にコピーされてくるためです。（PCLプロジェクトでも同じようにAndroidプロジェクトのビルド出力先にDLLがコピーされる）

つまり、Androidプロジェクトにのみ`obfuscate.xaml`を設置し、`<Module file="" />`を追加すれば、これらも問題なく難読化されるということです。

## 検証
### ILの逆アセンブル
難読化の確認を行うには、実際に難読化されているかどうかを見る他ありません。
これにはILの逆アセンブラで確認する必要があり、カジュアルなアプリケーション解析者はまずこの操作を行っていると思います。
Windowsであればそのようなツールはいくらでもありますし、そもそもMicrosoftからも提供されているので、特にセキュリティ的な問題は考えても意味がないと判断し、ILの逆アセンブルの手順を公開します。

本記事ではmacOSでILの逆アセンブルを行うにあたって、ILSpyというOSSの逆アセンブラを使用します。

### ILSpyのセットアップ
ILSpyも、Windows向けであれば簡単に情報が見つかるのですが、
macOS向けの情報は少なく、あっても古かったりという感じなので、これも手順を説明します。

1. ILSpyをmacOS向けにビルドするため[.NET Core SDKをインストール](https://dotnet.microsoft.com/learn/dotnet/hello-world-tutorial/install)(執筆時v5.0.101)

    ```
# dotnetコマンドを確認
$ dotnet
```

1. ILSpyをインストール

    ```zsh
# ILSpyをclone(執筆時v6.2.1)
$ git clone https://github.com/icsharpcode/ILSpy.git
# ILSpyのCLIディレクトリに移動して.Net Core 2.1でmacOS向けにビルド
$ cd ILSpy/ICSharpCode.Decompiler.Console/
$ dotnet publish -c release -r osx-x64 -f netcoreapp2.1
# 生成されたilspycmdをコマンドで呼び出せるようにする
$ mkdir /usr/local/bin /usr/local/opt # フォルダが存在しない場合のみ実行
$ cp -pr bin/release/netcoreapp2.1/osx-x64/publish /usr/local/opt/ilspycmd
$ ln -s /usr/local/opt/ilspycmd/ilspycmd /usr/local/bin
```
ディレクトリ周りで権限がないよと怒られる場合は必要に応じてsudoで、、
ポイントは`dotnet publish -c release -r osx-x64 -f netcoreapp2.1`です。
執筆時点のILSpy6.2.1では`-f netcoreapp2.1`の指定がないとpublishに失敗します。

### 差分で確認

ILSpyにてObfuscarの難読化が施されていることを、実際に逆アセンブルを行うことで確認します。
試しにDroid.dllを逆アセンブルして、その結果を確認します。
手順は以下のとおりです。

1. 難読化していないビルドを行った(本記事どおりであればReleaseフォルダ内にある)Droid.dllを逆アセンブル
1. 難読化したビルドを行った(本記事どおりであればRelease_Obfuscatedフォルダ内にある)Droid.dllを逆アセンブル
1. 2つの逆アセンブルの結果を、Diffツールで比較

逆アセンブルの結果はbashでリダイレクトすることで簡単にソース化できます。

```bash
# 例: 逆アセンブル結果をそのままcsファイルとしてホームディレクトリに出力
$ ilspycmd 逆アセンブル対象 > ~/逆アセンブル結果.cs
```

この逆アセンブル結果.cs同士をDiffツールで比較すれば、何がどう変わったのかが一目瞭然です。
手元にDiffツールが無ければ、XCode付属のFileMergeを使用しても良いと思います。
(Xcodeを起動 > Dockアイコンを右クリック > Open Developer Tool > FileMerge)


## 自動化
以上でDroid.dll等の難読化ができました。
しかし、この難読化された状態の.dllが実際にapkに組み込まれないと意味がありません。
そこで、.dllがビルド時に自動で組み込まれるように設定変更、およびスクリプトを追加していきます。

### obfuscate.xmlの修正
まずはobfuscate.xmlを以下のように書き換えます。

   ```diff
-  <Var name="InPath" value="./bin/Release" />
-  <Var name="OutPath" value="./bin/Release_Obfuscated" />
+  <Var name="InPath" value="./bin/ReleaseOriginal" />
+  <Var name="OutPath" value="./bin/Release" />
```

### obfuscate.shの追加
1. Androidプロジェクト直下にシェルスクリプト`obfuscate.sh`を以下の内容で作成します

    ```zsh
#/bin/zsh
cp -prf ./bin/Release ./bin/ReleaseOriginal
mono ../packages/Obfuscar.2.2.29/tools/Obfuscar.Console.exe ./obfuscate.xml
```

1. Androidプロジェクトの設定を開き、ビルド > カスタムコマンドを開きます
1. 構成を`Release`に指定し、以下の内容で前述したビルド後のカスタムコマンドを書き換えます
 
    |項目|設定値|
|---|---|
|コマンド|sh ./obfuscate.sh|
|作業ディレクトリ|${ProjectDir}|

設定の修正は以上になります。

少し話は逸れますが、シェルスクリプトを介すように変更したことにより、
Visual Studioからビルドの構成である`${ProjectConfigName}`変数(中身は'Release'とかその他独自の構成名の文字列)をビルドのカスタムコマンドからシェルスクリプトに受け渡し、
シェルスクリプト内でビルド構成に応じた難読化設定ファイルを使用してObfuscarを呼び出すようにするといったことも柔軟に行えるようになります。
Releaseの他に配布用のプロジェクト構成を持っている場合は、こういった設定が必要になることもあると思います。

### 自動化の確認
1. 通常通りAndroidプロジェクトのRleaseビルドを行います
1. ビルドすると`bin/Release`内の`プロジェクト名.Droid.dll`と`プロジェクト名.dll`が難読化された状態で出力されます
1. 実行するとビルド後に難読化されたdllを含むapkが生成され、そのままアプリが実行されます
    (執筆時点では、M1 Macの場合Androidエミューレータが使用できなかったため、実機でのみ確認)

以上でXamarin.Androidアプリの難読化がReleaseビルド時に自動的に行われるようになりました。

## あとがき
こんな記事が必要になるような業務をこなす皆様、お疲れさまです。
あと、Xamarin.Forms使ってないプロジェクトは辛いですよね。頑張って下さい。
（えっ使ってる？、、そうですか、、）
Xamarinで開発をするなら、素直にWindowsを使ったほうが早いんではないでしょうかって素直に思いました。
(でもiOSのビルドや動作確認で困る気はするし、個人的にはmacOSメインなのですが)
両方できるからmacOS、そりゃそうなんですけども。うーん辛い。

そういえば記事がXamarin.Android向けになっていますが、普通の.NETアプリでも同じことですね。
業務のアプリのexeを逆アセンブルしたらなんちゃらキーとかが丸見えでした！
などという事案が結構ありそうでなんか怖いなって思いました。
難読化はあくまで難読化、万能ではないですが、丸見えはちょっと。。

はー それにしても森に籠もりたい（虫は嫌い）

## 参考
難読化について

* Android StudioにおけるR8について https://developer.android.com/studio/build/shrink-code?hl=ja
* Xamarin.Androidにおける難読化について(ProGuard) https://docs.microsoft.com/ja-jp/xamarin/android/deploy-test/release-prep/proguard?tabs=macos

Obfuscarについて

* Obfuscar GitHub https://github.com/obfuscar
* Obfuscar Xamarin.Android向け ドキュメント https://docs.obfuscar.com/tutorials/xamarin.html
* Obfuscarの導入について https://docs.obfuscar.com/tutorials/xamarin.html
* Obfuscarがアセンブリの解決に失敗する問題の解決 https://docs.obfuscar.com/getting-started/configuration.html#assembly-search-path-2-2-5
* macOSでexeを実行する https://stackoverflow.com/questions/41313788/is-it-possible-to-compile-a-c-sharp-program-from-visual-studio-for-running-on-ma

ILSpyについて

* LSpy公式　https://github.com/icsharpcode/ILSpy#ilspy-------
* macOSでC#の逆コンパイル　https://qiita.com/tatsumack/items/b12ac42a9b29514953d5