←[第2章] [第4章]→

3 反映方法のガイドライン


3.1 反映について

DSプロテクトを用いる際は、不正利用の検出を行うだけでなく、その結果を反映しなければ意味がありません。その反映内容はプログラムの継続動作が困難になるものであるべきですが、不適切な反映方法を用いた場合はプロテクトの強度が著しく下がりますので、本章を熟読した上で慎重に反映方法を検討してください。

なお、本ガイドラインは市場でのトラブルを極力抑えることを目的として記していますが、確実に回避できる事を保証するものではありません。


3.2 基本事項


3.2.1 なぜ反映方法に気を使うべきなのか?

DSプロテクトはマジコン検出の手段を提供するものですが、その反映方法については対象となるプログラムに応じたものでなければなりません。そのため、反映処理については各自で作成していただくことになりますが、その作成には十分な検討が必要です。それは、攻撃者のアプローチに着目することで容易に理解することができます。

攻撃者はまず、液晶画面から目視できる反映結果を確認し、その反映を行う処理をROMイメージのバイナリから探し出します。反映処理の箇所の特定に成功した後は、逆アセンブルリストなどを基にし周辺のプログラムを精査して、反映処理を迂回するようにプログラムを書き換えるパッチ(※)の作成を試むでしょう。

ここで重要なことは、検出処理の詳細が分からなくても、反映処理に着目するだけでプロテクト解除に成功してしまうという点です。したがって、反映処理は最初に狙われてしまう部分であるため、不適切な反映方法を用いると、プロテクトの強度が著しく下がることになります。

※パッチ
プログラムの一部を修正するプログラムです。


3.2.2 反映するまでの時間

動作テストを正常に行えるようにするため、ゲーム起動から10〜20分以内に反映を確認できる設計にすることを推奨します。

ゲーム中盤での反映は、プロテクションとしての効果を見込めますが、動作テストが困難になります。そのリスクを理解したうえで検討してください。


3.2.3 反映を実行する確率について

不正利用を検出した際は、毎回反映を行うようにしてください。「乱数を用いて毎回反映を行うとは限らない」という設計はプロテクト回避の難易度が上がるため良い設計といえますが、動作テストが正常に行えない可能性があるため推奨しません。


3.2.4 反映処理の記録

「どのタイミングで検出を行うのか」「どのタイミングで反映を行うのか」「どうすれば反映を確認できるのか」を必ず記録に残すようにしてください。


3.2.5 単純な反映方法

単純な反映方法を用いたりすることは、短時間でプロテクトを破られる確率が飛躍的に高まるため決して推奨できません。次項から詳述します。


3.2.6 ARM9モジュールの圧縮の推奨

クラック対策(コード保護)のため、マジコン検出コードが展開されるARM9常駐・オーバーレイモジュールの圧縮を推奨しています。

「CW for NINTENDO DS」の場合はNitro TS ROM設定のリンカツリーのNitro Makerom Postlinker項目にある、call compstatic toolのチェックボックスにチェックを入れoptions:を「-c -9 -F main.sbin main_defs.sbin」にすることで有効になります。
「CW for NINTENDO DSi」の場合はTWL TS ROM設定のリンカツリーのTWL Makerom Postlinker項目にある、call compstatic toolのチェックボックスにチェックを入れoptions:を「-9 -c -a -e_LZ -f "%OUTPUT_FOLDER%\component.files" 」にすることで有効になります。

その他詳しいことは、NITRO-SDK マニュアルのビルドスイッチの項を参照してください(ファイル:NitroSDK/docs/SDKRules/Rule-Defines.html、キーワード:NITRO_COMPRESS)。

ビルド後のROMが正常に圧縮されているかどうかは、本ライブラリに同梱されているDS-CCCを使用することで確認することが可能です。詳しくは 5.4.1節を参照ください。


3.2.7 オーバーレイモジュールへの配置の推奨

クラック対策(コード保護)のため、マジコン検出コードおよび検出コードを呼び出す関数は、常にメインメモリに存在し続けるARM9常駐モジュールではなく、メモリ上の生存期間の短いオーバーレイモジュールに配置することを強く推奨します。

また、ARM9常駐モジュールに配置した場合、一部のマジコンにおいて検出が回避される事例も確認されています。詳しくは 1.2.1 節を参照ください。


3.3 反映方法の検討について

反映方法を検討するにあたっては、不適切な反映方法の理解が不可欠です。本項では不適切な反映方法と実装方法に分けた上で解説します。


3.3.1 不適切な反映例

ここでの不適切な反映方法とは「マジコン検出処理の存在が容易に推測できるような挙動」を示すものを指します。以下に例を示します。

1) 「マジコンを検出しました!」またはそれに準ずるメッセージが表示される
固有のエラーメッセージは決して使用するべきではありません。エラーメッセージといった表示情報は、ROMイメージ上では平文の文字データとして存在しているため、検索が非常に容易です。そのため、検出・反映のタイミングを特定されやすく攻撃の端緒となり得ます。エラーメッセージを画像として用意すれば検索は少しは難しくなりますが、それでも好ましい方法であるとはいえません。

2) メッセージ性のある反映
同様の理由で、プレイヤーにコピーを戒めるようなメッセージを表示することも推奨できません。マジコン検出処理の存在をアピールしてしまうだけでなく、その特徴的なメッセージの文字列データがプロテクト回避のためのヒントになってしまうからです。さらに、メッセージの内容によってはユーザーの反感を買ってしまうかもしれません。

3) 不自然なタイミングでゲームが停止する
例えば「起動から20分が経過すると画面にメッセージが表示されゲームが終了する」「1ステージ終了後にメッセージが表示されゲームが終了する」といった、通常の不具合として考えにくい挙動は「マジコン検出が組み込まれているのではないか?」という疑念を抱かせるには十分です。反映タイミングの例は後述します。

4) 反映までに長い時間を要するもの
DSプロテクトの趣旨は「タダでゲームを遊ばせない」ことであり、カジュアルコピー(※)を行うライトユーザーからコアゲーマーまで全ての不正ユーザーが対象となります。したがって、「ラストボスの直前で反映する」「二週目のゲームで反映する」というような、ゲームの大部分を遊ばせることを許すようなタイミングでの反映ではプロテクトの意味がありません。また、動作テストの観点でも推奨できません。ただし、序盤と中盤にチェックを設けるといった、複数回のチェックを実施するという方針は、一つの方法として評価されます。

※カジュアルコピー
「不正行為を行っている」という自覚のないユーザーによるコピー行為を指します。

5) 検出から反映までに時間がかかるもの
3.2.2 節で解説済みですが、動作テストを確実にするために、反映の確認まで時間がかかりすぎるような設計は推奨できません。

6) セーブが重要でないゲームにおけるセーブ時の反映
セーブが重要でない、あるいはセーブをしなくても容易にクリア可能なゲームにおけるセーブ時の反映は、プロテクトとして効果が薄いため実施するべきではありません。ユーザの操作次第でプロテクトを迂回できるような設計は推奨できません。

7) Wi-Fi コネクションなどを用いて、秘密裏にユーザーネーム・誕生日情報を送信する行為
個人情報の不正送信に限らず、マルウェア(※)の挙動に似た過剰防衛とも取れる反映は絶対に行わないようにしてください。このような機能の実装が発覚した際には、深刻な企業イメージの低下に繋がることは確実です。

※マルウェア
コンピュータウイルス・ワーム・スパイウェアなど、悪意のあるソフトウェアの総称です。


3.3.2 不適切な実装例

ここでの不適切な実装方法とは「プログラムの解析が容易になってしまう実装」を指します。以下に例を示します。

1) 検出結果を検出結果格納用に定義したグローバル変数で所持すること
反映処理を実行するには、検出結果をそれまでどこかに保持し続ける必要があります。グローバル変数を使用すると、メモリ領域が静的に割り当てられてしまうため、逆アセンブルにより参照箇所の特定が非常に容易になり、プロテクトの強度が下がってしまいます。後述する 3.4.43.4.5 のサンプルなどを参考にしてください。

2) 反映処理中でヒントとなりえる特徴的な文字列を使用・参照すること
ROMイメージに限らず、正体不明のバイナリから最も手っ取り早く入手できる情報はASCII文字列です。そのため、ほとんどの攻撃者はUNIXのstringsコマンドに代表されるようなユーティリティや、高機能の逆アセンブラが持つ文字列参照機能を用いて機能の推定を試みます。「マジコンを検出しました!」といったメッセージを使用してはならない、と繰り返し記述しているのはこのためです。ここでの文字列は、エラーメッセージやキャラクターが喋るセリフ情報に加え、"ERROR:%08d" のような sprintf() 関数が用いる出力フォーマット指定子なども含まれます。

3) 検出直後に反映結果がわかるような反映処理を記述すること
検出直後に反映結果がわかってしまうような処理は、検出→反映までのプログラム構造が単純になってしまうため、適切な方法であるとはいえません。なるべく無関係の処理と混在する形にしてください。


3.3.3 適切な実装例

ここで具体例を示すことは難しいですが、確実に言えることとして、

不正利用の検知で反映させる処理(不具合・バグの再現)は、(バイナリレベルでの)デバッグが困難なものほど効果が見込めます。

上記を踏まえて、反映方法を検討してください。


3.4 反映の基本例

ここではDSプロテクトを用いた基本的な検出処理の記述例を示します。

本マニュアルは非公開情報ですが、攻撃者もこのマニュアルを読んでいるものと考えておくべきです。プロテクトについて真剣に考えているのであれば、ここで取り上げる実装例の上を行かなければなりません。もちろん、ここで紹介した方法を参考にして構いませんが、これらをヒントにしてさらに工夫を施すことが望ましいです。

検出コードを繰り返し使用する場合は、できるだけ離して挿入してください。また、同じ検出関数を別の箇所でも使用することも有効です。ただし、すべての検出関数はスレッドセーフではありません。


3.4.1 即時実行(非推奨)

もっとも単純な方法であるため、この方法は推奨しませんが、関数の動作説明のためにこのような例を紹介しています。

この例は、マジコン検出時に即座に ProtectFunc() が呼び出されます。ProtectFunc() 内に反映処理を記述してください。

// マジコン検出時に実行される関数
void ProtectFunc()
{
    // 反映処理を記述する
}

// ...

void Func(void)
{
    // 検出部分 各検出処理は離して配置すること

    // ...

    // オーバーレイでライブラリを含むモジュールを呼び出す
    FS_LoadOverlay(MI_PROCESSOR_ARM9, FS_OVERLAY_ID(OVERLAY_PROTECT));

    // マジコン検出時に ProtectFunc() が呼び出される
    AM_IsMagiconA2(ProtectFunc);

    // ...

    // マジコン検出時に ProtectFunc() が呼び出される
    // ただし A3 はダミー関数であるため、常に検出されない
    AM_IsMagiconA3(ProtectFunc);

    // ...

    // マジコン検出時のみif内の命令が実行される
    if (!AM_IsNotMagiconA1(NULL))
    {
        ProtectFunc();
    }

    // オーバーレイとして読み出したライブラリをアンロード
    FS_UnloadOverlay(MI_PROCESSOR_ARM9, FS_OVERLAY_ID(OVERLAY_PROTECT));

    // ...

}

3.4.2 後で実行

この例では素数同士を掛けた値 (139 * 41) を初期値として設定し、マジコン検出時のみ値が書き換わる処理を記述しています。マジコンの検出に成功すると Magicon の値が変更され、その時のみ反映処理が実行されます。論理値(0か1の値)として使うべき値を論理値以外の用途で使用していることがポイントです。

void Func()
{
    // 素数同士を掛けた値を初期値として設定
    u32 Magicon = 139 * 41;    // この値は必ず変更すること

    // ..

    // オーバーレイでライブラリを含むモジュールを呼び出す
    FS_LoadOverlay(MI_PROCESSOR_ARM9, FS_OVERLAY_ID(OVERLAY_PROTECT));

    // マジコン上でのみ戻り値(u32型)に0以外が返るため初期値が変更される
    // 71 も素数。
    Magicon += 71 * AM_IsMagiconA1(NULL);

    // オーバーレイとして読み出したライブラリをアンロード
    FS_UnloadOverlay(MI_PROCESSOR_ARM9, FS_OVERLAY_ID(OVERLAY_PROTECT));

    // ..

    // マジコン上でのみ初期値が変わるため、反映処理が実行される
    if (Magicon % 139)
    {
        // 反映処理を記述する
    }

    // ..

    if (Magicon % 41)
    {
        // 反映処理を記述する
    }

    // ..

}

3.4.3 処理の分岐

ここではメインループ処理を分断させることで基本フローを分岐させています。

// 不正利用時のメインループ処理
void ProtectMainLoop()
{
    // マジコン検出時のメインループ処理を記述する
}

// 正規利用時のメインループ処理
void MainLoop()
{
    // 正規のメインループ処理を記述する
}

void Func()
{

    // ...

    // オーバーレイでライブラリを含むモジュールを呼び出す
    FS_LoadOverlay(MI_PROCESSOR_ARM9, FS_OVERLAY_ID(OVERLAY_PROTECT));

    // 正規利用時に正規の用メインループが呼び出される
    AM_IsNotMagiconA1(MainLoop);

    // ...

    // 不正利用時にマジコン用メインループが呼び出される
    AM_IsMagiconA1(ProtectMainLoop);

    // オーバーレイとして読み出したライブラリをアンロード
    FS_UnloadOverlay(MI_PROCESSOR_ARM9, FS_OVERLAY_ID(OVERLAY_PROTECT));

    // ...

}

3.4.4 正規利用時のみ初期化を実施する

本来行うべき初期化処理を、正規利用時のみ行う(=不正利用時に行わない)ようにすることでプログラムの継続動作を阻止する方法です。ここでの初期化とは、初期化関数の実行・変数の初期化の両方を含みます。

反映処理の記述が不要になるうえ、工夫次第で検出と反映のプログラム上の距離を広げることができるため、より破られにくいプロテクトになるでしょう。ただし「初期化を行わなかったことでどう挙動が変わるのか」を把握する必要があります。

// 初期化処理を実施する関数
// この関数を実行しないと正常に動作しないことが前提
void FuncInit()
{

    // 初期化処理を記述する

    // ・画面表示の初期化
    // ・キャラクタパラメータの初期化
    // ・イベントフラグの初期化
    // etc...

}

void Func()
{

    // ...

    // オーバーレイでライブラリを含むモジュールを呼び出す
    FS_LoadOverlay(MI_PROCESSOR_ARM9, FS_OVERLAY_ID(OVERLAY_PROTECT));

    // FuncInit の関数内部に初期化処理を記述する
    // 正規利用時のみ初期化処理が呼び出される
    AM_IsNotMagiconA1(FuncInit);

    // オーバーレイとして読み出したライブラリをアンロード
    FS_UnloadOverlay(MI_PROCESSOR_ARM9, FS_OVERLAY_ID(OVERLAY_PROTECT));

    // 初期化が行われないと異常動作するようにしなければならない
    // ・画面表示に異常をきたす
    // ・レベルが上がらない
    // ・イベントアイテムが手に入らない
    // ・イベントが進行しない
    // etc...

    // ...
}

3.4.5 不正利用時に誤った初期化を実施する

ここでは逆に、本来の初期化を行った後、不正利用時のみ不適切な初期化を行う方法です。

// 初期化処理を実施する関数
void FuncInit()
{

    // 初期化処理を記述する

}

void FuncProtectInit()
{

    // 誤った初期化処理を記述する
    // ・レベルが上がらなくなるようデータ改ざん
    // ・イベントアイテムが手に入らなくなるようデータ改ざん
    // ・イベントが進行しなくなるようデータ改ざん
    // etc...

}

void Func()
{

    // ...

    // 初期化処理の実行
    FuncInit();

    // ...

    // オーバーレイでライブラリを含むモジュールを呼び出す
    FS_LoadOverlay(MI_PROCESSOR_ARM9, FS_OVERLAY_ID(OVERLAY_PROTECT));

    // FuncProtectInit の関数内部に誤った初期化処理を記述する
    // 不正利用時のみ誤った初期化処理が呼び出される
    AM_IsMagiconA1(FuncProtectInit);

    // オーバーレイとして読み出したライブラリをアンロード
    FS_UnloadOverlay(MI_PROCESSOR_ARM9, FS_OVERLAY_ID(OVERLAY_PROTECT));

    // ...
}

3.4.6 反映パターンの組み合わせ

反映パターンは組み合わせて利用することで効果の向上を見込むことができます。

// 初期化処理を実施する関数
// この関数を実行しないと正常に動作しないことが前提
void FuncInit()
{

    // 初期化処理を記述する

    // ・画面表示の初期化
    // ・キャラクタパラメータの初期化
    // ・イベントフラグの初期化
    // etc...

}

// 不正利用時のメインループ処理
void ProtectMainLoop()
{
    // マジコン検出時のメインループ処理を記述する
}

// 正規利用時のメインループ処理
void MainLoop()
{
    // オーバーレイでライブラリを含むモジュールを呼び出す
    FS_LoadOverlay(MI_PROCESSOR_ARM9, FS_OVERLAY_ID(OVERLAY_PROTECT));

    AM_IsNotMagiconA1(FuncInit());

    // オーバーレイとして読み出したライブラリをアンロード
    FS_UnloadOverlay(MI_PROCESSOR_ARM9, FS_OVERLAY_ID(OVERLAY_PROTECT));

    // 初期化しないと正常動作しない処理を記述する

}

void Func()
{

    // ...

    // オーバーレイでライブラリを含むモジュールを呼び出す
    FS_LoadOverlay(MI_PROCESSOR_ARM9, FS_OVERLAY_ID(OVERLAY_PROTECT));

    // 不正利用時にマジコン用メインループが呼び出される
    AM_IsMagiconA1(ProtectMainLoop);

    // ...

    // 正規利用時に正規の用メインループが呼び出される
    AM_IsNotMagiconA2(MainLoop);

    // オーバーレイとして読み出したライブラリをアンロード
    FS_UnloadOverlay(MI_PROCESSOR_ARM9, FS_OVERLAY_ID(OVERLAY_PROTECT));

    // ...

}

3.5 反映タイミング例

反映処理のサンプルを示します。3.4 節も参考にして実装を行ってください。

繰り返しになりますが、

不正利用の検知で反映させる処理(不具合・バグの再現)は、デバッグが困難なものほど効果が見込めます。

自身のデバッグ経験を基にして、反映タイミングを考えてみてください。


3.5.1 セーブ型プロテクト

以前のマジコンは、セーブデータを正しく取り扱えないバグが頻発していました。現在、このようなバグは減少傾向にありますが、セーブデータ絡みのエラーを誘発させる方法は有意義であると考えられます。

1) コンティニューを選ぶことができない
2) ロードに失敗する
3) セーブに失敗する
4) セーブデータに不正利用フラグを書き込む

といった反映方法が考えられます。


3.5.1.1 バックアップデバイスの無効化例 [BD-01]

[BD-01] 不正利用時のみ、バックアップデバイス機能を無効化する実装例

不正利用時に限り、バックアップデバイスのアクセス機能を限定させ、かつ Continue(ロード画面)への画面遷移を阻止する例です。

ここではゲーム起動からタイトル・初期メニュー画面にかけて、検出・反映を行っています。フローに示す「実装要確認箇所」が新たに実装するべき箇所です。

3.5.1.1.1 フロー中の「実装要確認箇所1」での実装のヒント

ここでは、不正利用検出時にバックアップデバイスの機能を挿げ替える初期化を行うことで、バックアップデバイスの正常なアクセスを封じるようにしています。

ここでは、バックアップデバイスの機能の挿げ替えの方法として、オーバーロードを用いた例を示します。ロード&セーブの直前で検出結果をもとに分岐する実装は、プログラムの改ざんが容易となるため、あまり好ましくありません。

// バックアップデバイス管理クラス
class BackupDeviceManager {
public:
    void Read() {
        // バックアップデバイスからデータを読む関数

        // ここに処理を書く

    }

    void Write() {
        // バックアップデバイスにデータを書き込む関数

        // ここに処理を書く

    }
};

// マジコン使用時に継承されるバックアップデバイス管理クラス
class BackupDeviceManager_Dummy : public BackupDeviceManager {
public:
    void Read() {

        // 誤った操作をする処理でオーバーライドする

    }

    void Write() {

        // 誤った操作をする処理でオーバーライドする

    }
};
// バックアップデバイス管理クラス
BackupDeviceManager *gpBackupManager;

    // ...

// 正規利用時に行われる初期化処理
void InitObject() {

    // 正規のクラスで初期化
    *gpBackupManager = new BackupDeviceManager();

    }

// 不正利用時に行われる初期化処理
void InitObject_Dummy() {

    // マジコン使用時用の継承クラスで初期化
    *gpBackupManager = new BackupDeviceManager_Dummy();

}

// オブジェクト初期化処理
void Init() {

    // ...

    // オーバーレイでライブラリを含むモジュールを呼び出す
    FS_LoadOverlay(MI_PROCESSOR_ARM9, FS_OVERLAY_ID(OVERLAY_PROTECT));

    if (!AM_IsMagiconA1(InitObject_Dummy)) {
        InitObject();
    }

    // オーバーレイとして読み出したライブラリをアンロード
    FS_UnloadOverlay(MI_PROCESSOR_ARM9, FS_OVERLAY_ID(OVERLAY_PROTECT));

    // ...

}
3.5.1.1.2 フロー中の「実装要確認箇所2」での実装のヒント

3.5.2.1 不正利用時のみ、画面遷移を改変する実装例 を参照してください。





3.5.2 メニュー型プロテクト

正規利用と不正利用とでメニューの構成・動作を変える方法です。

1) ゲームスタートが選べない
2) コンティニューの項目が存在しない(選択時にエラーを出すのではない)
3) メニュー画面から遷移できない

といった反映方法が考えられます。選択時に条件分岐を行うと命令書き換えに弱いため、メニュー初期化時に画面遷移情報を書き換える方法が好ましいです。一例を示します。


3.5.2.1 不正利用時のみ、画面遷移を改変する実装例 [SC-01]

[SC-01] 不正利用時のみ、画面遷移を改変する実装例

不正利用時に、本来とは異なる(ゲーム本編に移行しないように)画面遷移をするように、遷移情報を書き換える処理を記述します。

以下のように、メニュー項目選択時に不正利用の判定を行う方法は推奨されませんisMagicon の分岐処理に着目し、条件を反転させるだけで対抗できてしまいます。

// 初期メニュー画面での項目選択時において、
// カーソル位置によって次画面を設定する処理
switch (cursor) {
case 0:
    //「Game Start」が選択された
    nextState = stateGameStart;
    break;
case 1:
    //「Continue」が選択された
    // この実装では、isMagicon の判定を反転するだけで
    // 対抗できてしまうので望ましくない(代替方法は後述)
    if (isMagicon) {
        // 不正利用検出時は「Game Start」を選択したことにする
        nextState = stateGameStart;
    } else {
        nextState = stateContinue;
    }
    break;
case 2:
    //「Configration」が選択された
    nextState = stateConfigration;
    break;
case 3:
    //「Wi-Fi Connection」が選択された
    nextState = stateWifiConnection;
    break;
}

ここでは、画面遷移情報の初期化を行う段階で反映したほうが良いでしょう。

// State* は、画面を定義するクラス State を継承したもの

// 通常利用時の初期化
void InitState(void) {

    // ...

    stateGameStart = new StateGameStart();
    // メニュー画面「Continue」を選択した時に
    // 正しく Continue 画面に遷移するようにする
    stateContinue = new StateContinue();
    stateConfigration = new StateConfigration();
    stateWifiConnection = new StateWifiConnection();

    // ...

}

// 不正利用時の初期化
void InitState_Magicon(void) {

    // ...

    stateGameStart = new StateGameStart();
    // メニュー画面「Continue」を選択した時でも
    // Game Start 画面に遷移するようにする
    stateContinue = new StateGameStart();
    stateConfigration = new StateConfigration();
    stateWifiConnection = new StateWifiConnection();

    // ...

}

// ...

void func(void) {

    // ...

    // オーバーレイでライブラリを含むモジュールを呼び出す
    FS_LoadOverlay(MI_PROCESSOR_ARM9, FS_OVERLAY_ID(OVERLAY_PROTECT));

    // 画面遷移の初期化を行う
    // 不正利用時は InitState_Magicon() が呼び出され
    // 正常利用時は InitState() が呼び出される
    if (!AM_IsMagiconA1(InitState_Magicon)) {
        InitState();
    }

    // オーバーレイとして読み出したライブラリをアンロード
    FS_UnloadOverlay(MI_PROCESSOR_ARM9, FS_OVERLAY_ID(OVERLAY_PROTECT));

    // ...

}



3.5.3 ネットワーク対戦型プロテクト

[NW-01] 不正利用時のみ、ネットワーク対戦を拒否する実装例

いわゆるチート行為はマジコン上からでも行えるため、ネットワーク対戦の制限についても考慮したほうがよいでしょう。

1) ネットワーク設定を行わずに接続を試みて通常の「ネットワーク対戦ができません」を表示させる
2) 無線・Wi-Fi接続を禁止、あるいはシャットダウンする

といった反映方法が考えられます。これらは検出の直後に反映処理を実行していますが、反映結果を目視確認できるのは接続を実施した後です。

一例として、不正利用時のみネットワーク対戦を拒否する実装例のフローを示します。あくまで接続エラーとしておくことが望ましいでしょう。




3.5.4 イベント型プロテクト

イベント進行に影響のある反映を用いる方法です。動作テストのため、反映内容をしっかり定義する必要はありますが、工夫次第で発覚までの時間を大幅に遅らせることができるでしょう。

1) 初期のイベントアイテムが見つからない
2) キャラクターのレベルが上がらない
3) 永久に始めのステージしか遊べない
4) ノルマを達成しても選択可能項目が増えず、かつタイトル画面に戻される

といった反映方法が考えられます。4) の「タイトル画面に戻される」のように「対策であるとは気づかれないような」些細な挙動の変化を設けることで、動作テストが容易になります。

void ResetParameter()
{

    // パラメータ・フラグ情報をリセット・改ざんする
    // ・敵がイベントアイテムを落とさなくなる
    // ・イベントアイテムの効果を無効化する
    // ・etc...

}

void Func()
{
    // ...

    // 本来の初期化処理
    InitParameter();

    // ...

    // オーバーレイでライブラリを含むモジュールを呼び出す
    FS_LoadOverlay(MI_PROCESSOR_ARM9, FS_OVERLAY_ID(OVERLAY_PROTECT));

    AM_IsMagiconA1(ResetParameter);

    // ...

    AM_IsMagiconA2(ResetParameter);

    // オーバーレイとして読み出したライブラリをアンロード
    FS_UnloadOverlay(MI_PROCESSOR_ARM9, FS_OVERLAY_ID(OVERLAY_PROTECT));

    // ...
}

3.5.5 バグ誘発型プロテクト

不正利用時に、意図的にバグを引き起こすタイプのプロテクトです。無限ループや無限再帰のようなものや画面が表示されないなどといった方法が考えられます。

・メモリリークを引き起こす
・初期化時のヒープ取得サイズを故意に縮める
・無限ループで始まらない
・プログラムが暴走する
・バグが発生したような画面を表示する

といった反映方法が考えられます。以下のコードは不正利用時のみメモリリークを誘発させる例です。

再度繰り返しますが、誘発させるバグは、(バイナリレベルの)デバッグが困難なものほど効果が見込めます。

自身のデバッグ経験を基にして、反映方法を検討してください。

void Func()
{

    u8 *buffer;

    // ...

    // オーバーレイでライブラリを含むモジュールを呼び出す
    FS_LoadOverlay(MI_PROCESSOR_ARM9, FS_OVERLAY_ID(OVERLAY_PROTECT));

    if (AM_IsMagiconA1(NULL)) {
        // メモリリークを故意に引き起こす。
        // ただしこれによりプログラムが停止することを確認する必要がある。
        buffer = new u8[0x10000];
    }

    // オーバーレイとして読み出したライブラリをアンロード
    FS_UnloadOverlay(MI_PROCESSOR_ARM9, FS_OVERLAY_ID(OVERLAY_PROTECT));

    // ...

}

 

←[第2章] [第4章]→