Bocchi Games

Individual Game Developer in Japan

【Unity】ソースコードを難読化して改造対策をする「Obfuscator」を使ってみる

ソースコードの難読化にObfuscatorを購入したのですが、導入するのになかなか苦戦したので、試して分かったことを書いていきます。

ちなみにAndroidのIL2CPPビルドで試しています。Monoでは試していません。

ソースコード難読化の必要性

いろんなサイトで詳しく説明されているので、そちらで確認を。

baba-s.hatenablog.com

www.slideshare.net

engineering.linecorp.com

「IL2CPPビルドにしておけば機械語になるんだし、大丈夫だろ」と最初は思ってたんですが、そうじゃないんですね、IL2CPPビルドでもメタデータにクラスのデータ構造がばっちり残っていました。

global-metadata.datの解析結果
global-metadata.datの解析結果。クラスのデータ構造丸見え!

名前とアドレス値がわかれば、どういう動作がどの場所で処理されてるか分かってしまってやばいので、この名前の方をObfuscatorで難読化(リネーム、フェイク関数を追加)します。

解析のヒントになる情報を片っ端からつぶしていく感じです。

Obfuscatorの有効化

パッケージをインポートすると、そのままObfuscatorが有効化されます。

設定ファイルはEditor/Beebyte/Obfuscator/ObfuscatorOptionsにあります。

Obfuscatorの設定ファイル
Obfuscation Enabledで有効化・無効化を切り替えられる

Obfuscatorを有効化すると必要な名前までリネームしてアプリが動かなくなる可能性があるので、要注意です。

リフレクションとかで必要な名前をリネームしてしまうと、実行時にアプリがクラッシュします。

後からObfuscatorを導入するとアプリが動かなくなった時の問題部分の切り出しが困難になるので、開発初期から導入しておいた方がよさげ。

「Il2CppDumper」で難読化の結果を確認する

Il2CppDumperを使うと難読化した結果を確認することができます。

Il2CppDumperの使い方は、以下のサイト参照。

baba-s.hatenablog.com

public class NewBehaviourScript
{
    private int a;

    void Hoge()
    {
    }
}

SkipRename
Il2CppDumperで難読化の確認

難読化の対象を変更する

難読化の対象はAssembly-CSharp.dllに設定してあります。

Assemblies
Assembly-CSharp.dllが難読化対象

Assembly-CSharp.dllには特殊フォルダ(PluginsとかStandard Assetsとか)以外にあるスクリプトファイルが含まれています。

なので、一般公開されてて難読化するメリットがあまりない外部ライブラリやUnityアセットも、Assets直下に置いてある場合、問答無用で難読化されるので注意です。

自分は、外部ライブラリやUniyアセットはPluginsに移動させて、難読化の対象外にしています。

名前空間で難読化をスキップする指定もできますが、管理が面倒くさくなると思って今のところ使っていません。

Skip Namespaces
難読化をスキップする対象を名前空間で指定する。スキップする名前空間はあらかじめいくつか定義されている

難読化されないクラスがあるんだけど

MonoBehaviourなクラスでリネームされない

MonoBehaviourを継承しているクラスのクラス名、公開関数、公開フィールドは設定でリネームされないようになっています。

また、AwakeやUpdate等のUnity固有のイベント関数もリネームされないようになっています。

using UnityEngine;

public class NewBehaviourScript : MonoBehaviour
{
    public string Name;

    private int a;

    private void Awake()
    {        
    }

    public void Hoge()
    {
    }

    private void Fuga()
    {
    }
}

MonoBehaviourの難読化
リネームされない箇所があるのが確認できる

これらの名前も設定変更でリネームするようにできますが、エンジン側で参照する名前の可能性大なので、要注意です。

基本はそのままの設定でいいかと思います。

特定の属性をつけるとリネームされなくなる

たとえば、[SerializeField]をつけると名前がリネームされなくなるようです。

他にもリネームされなくなる属性があるかもしれません。

using UnityEngine;

public class NewBehaviourScript
{
    [SerializeField]
    private int a;
}

特定の属性でリネームされなくなる
[SerializeFiled]な名前はリネームされない

設定ファイルに、これらのリネームされない属性が表示されてる項目はないっぽい。まじか。

「SkipRename」属性で特定の名前をリネームしない

SkipRename属性を使うと、クラス名、関数名やフィールド名をリネームしないようにできます。

using Beebyte.Obfuscator;

[SkipRename]
public class NewBehaviourScript
{
    [SkipRename]
    private int a;

    [SkipRename]
    void Hoge()
    {
    }
}

SkipRename
SkipRenameで名前をリネームしない

コンパイラが生成したコードを難読化しない

ラムダ式やUnityコルーチンやasyncを使うと、コンパイル時にコンパイラがコードを生成するのですが、これにも難読化がかかります。

コンパイラが生成したコードを難読化したくない場合は、Equivalent Attributes For SkipにSystem.Runtime.CompilerServices.CompilerGeneratedを設定します。

Equivalent Attributes For Skip Rename
System.Runtime.CompilerServices.CompilerGeneratedを設定

using System.Collections;
using UnityEngine;

public class NewBehaviourScript
{
    IEnumerator Hoge()
    {
        yield return new WaitForEndOfFrame();
    }
}

CompilerGenerated
コンパイラが生成したコードは難読化されないようになった

難読化してるとUniTaskを使っているときに例外が発生したことがあったので、自分はコンパイラの生成コードは難読化しないようにしています。

おわりに

上記の内容で自分がやりたいことは大体できたのですが、もし他にわからないことがあったら、Assets/Editor/Obfuscator.pdfを確認してみてください。(ただし、英語)

個人アプリで難読化が必要かと言うと、うーんな感じですが、やっとくに越したことはないんじゃないかな。

ちなみにMonoビルドは多分リスクしかないのと思うので、公開するアプリに使うのはやめといたほうがいいと思います。(某ひまつぶゲームが改造されて配布されたのを見かけましたし)