.NETアプリケーション逆アセンブラ兼逆コンパイラ「NekoDisIL」
.NETアプリケーション逆アセンブラ兼逆コンパイラ「NekoDisIL」 目次
●概要と注意事項●参考スクリーンショット
●基本操作
●ILコード解析・書き換えのヒント
●UsaTest_DotNET.exeを用いた.NETアプリケーションの解析練習について
●CTF(セキュリティコンテスト)への「NekoDisIL」活用例
概要と注意事項
■機能概要と動作環境この当ソフトウェア『うさみみハリケーン』補助ツール(同梱NekoDisIL.exe)は、.NETアプリケーションの解析を行うツールです。.NETアプリケーションの実行ファイルへの、逆アセンブルや逆コンパイルおよびILコードの書き換えに対応しています。
「NekoDisIL」の動作環境は、.NET Framework 4.8がインストールされた64ビット環境です。Windows 10 バージョン1903以降ならば、.NET Framework 4.8がプレインストールされています。「NekoDisIL」では、Windows 10 バージョン1809で導入された等幅フォント「BIZ UDゴシック」を使用しているため、それより前のWindowsでは文字の表示が崩れます。
■当解説で使用する用語の簡単な説明
「ILコード」とは、逆アセンブル結果として表示される中間言語(IL:Intermediate Language、.NETではMSIL)のコードを指します。ILコードは専用ランタイムによって、実行環境のCPUで実行できるマシンコード(機械語)に変換されてから実行されます。このマシンコードへの変換処理は、「Just-In-Time コンパイル」あるいは「JIT処理」と呼ばれます。
「アセンブリ」は、.NETアプリケーションでの、EXEファイルやDLLファイルすなわち実行ファイルを指します。「メソッド」はILコードのかたまりで、C++などのプログラミング言語での「関数」に相当します。また、メソッドは「クラス」という上位構造に属しています。「NekoDisIL」では、ツリー形式でクラスおよび属するメソッドを一覧表示します。
「逆アセンブル」はアセンブリ内のバイト列を、人間が処理の流れを理解しやすいように中間言語のILコード列に翻訳する処理です。「逆コンパイル」はILコード列をさらに元のC#言語ソースコードの形へ当てはめていく処理です。
当解説での「PUSH」は、スタックにデータを積むことを意味します。ILコードでは、スタックにデータを積むならば、たいていオペコード「ld*」が使用されます。
■注意事項
使用中のアセンブリは書き換えできません。つまり、実行中のプロセスのEXEファイルや、プロセスが使用中のDLLファイルは、書き換えに失敗します。
「NekoDisIL」の逆コンパイル機能は、元のソースコードを完全に復元するものではありません。
解析対象であるアセンブリ内に整合性がない設定があると、逆コンパイルに失敗します。また、解析対象によっては、Windowsの仕様が原因で、.NET デスクトップ ランタイム (x64版) バージョン5以降をインストールしないと、逆コンパイルに失敗することがあります。
難読化といった解析対策が施されているアセンブリは適切に逆コンパイルできません。
■ILコードの読解について
「NekoDisIL」での逆アセンブル結果を理解するにあたり、x86やx64の逆アセンブルに係る知識があれば役立ちます。四則演算や論理演算、条件ジャンプあるいは無条件ジャンプ、スタックへのプッシュやポップといった処理は、x86等の逆アセンブルコードとILコードで類似しています。x86での「NOP」や「POP」命令は、ILコードでも同じ意味の処理になります。
参考:基礎用語 逆アセンブルコードリスト
なお、ILコードでは、変数の各種操作にはスタックを用い、レジスタといった特定CPUの仕様に依存しないようになっています。
ILコードを構成する各オペコードは、「System.Reflection.Emit.OpCodes」で検索すれば、Micorosoft等の詳細な解説が見つかります。
基本操作
■アセンブリを指定解析対象となるアセンブリを指定します。これには、以下の方法が使用できます。
1.「参照」ボタンでアセンブリを指定
2.コマンドラインオプションでアセンブリのパスを指定
3.アセンブリのパス表示欄にエクスプローラー等からアセンブリをドロップ(UIPI非対応)
4.Shiftキーを押しながら「参照」ボタンでクリップボードにあるアセンブリのパスを取得
■解析モードを指定
解析モードを「逆アセンブル」か「逆コンパイル」に指定します。この指定はメソッドの解析中に切り替えることもできます。モードを指定後に、画面左側ツリービューでクラスあるいはメソッドを選択すると、逆アセンブルあるいは逆コンパイルの結果を表示します。
■「うさみみハリケーン」に同梱する他のツールとの連携
「へきさにゃんで開く」ボタンで、解析対象アセンブリをヘキサエディタ「へきさにゃん」で開きます。同様に、「ResStrViewで開く」で、リソースビューア「ResStrView」で開きます。「へきさにゃん」など、外部ツールで解析対象アセンブリが書き換えられた場合に対処できるよう、「再読み込み」ボタンでアセンブリの再読み込みができるようにしています。なお、ヘキサエディタを用いたILコードの書き換え・移動・挿入時には、既存のジャンプ命令のジャンプ先がずれていないか確認してください(NekoDisILでの書き換え時にはジャンプ先を自動修正)。
■逆アセンブラでメソッドやコードを抽出して表示・逆アセンブル時表示設定
「指定文字列を含むメソッドを抽出して表示」を有効にすると、「抽出実行・表示」ボタンで、指定した文字列を含むメソッドを抽出します。
「指定文字列を含むコードを抽出して表示」を有効にすると、「抽出実行・表示」ボタンで、指定した文字列を含むILコードを抽出します。
「フィールド一覧も表示」は、有効にすれば、上記での抽出結果あるいは通常の逆アセンブル結果に併せて、抽出されたメソッドを含むクラスに関連する変数を一覧表示します。「プロパティ一覧も表示」も同様にクラスに関連する情報を一覧表示します。
「抽出結果をファイルに出力」が有効ならば、抽出結果をテキストファイルに自動で保存します。保存先は解析対象アセンブリがあるフォルダです。
■選択メソッド内でILコードを挿入・複製・削除・上書き
メソッドの逆アセンブル結果が表示されている状態で、同メソッドのILコード書き換え等を行います。まずILコードの位置を、#に続く10進数値で指定し、それから処理内容を選択、さらに必要に応じて数値や文字列など追加情報を入力し、「ILコード書き換え」ボタンです。
この機能では、各種演算、無条件キャンプ、メソッドのCALL、スタックトップへの各種PUSH処理および、引数とローカル変数やフィールドの操作といった処理を実現する、ILコードの挿入を簡単にできるようにしています。加えて、ILコードを複製して挿入、ILコードの削除やNOPで上書き、ILコードを指定バイト列で上書きなどにも対応しています。
ILコードの書き換えには相応の時間がかかるため、「ILコード書き換え」ボタンを高速で連打すると、エラーが生じ書き換えに失敗することがあります。
「自動バックアップ」が有効ならば、解析対象アセンブリがあるフォルダに操作前のアセンブリを時系列バックアップします。ILコードの書き換え結果が不適切な状態ならば、対象アセンブリが正常に動作できなくなる可能性があるため、「自動バックアップ」機能を実装しました。
ILコード挿入・書き換え時に、ILコードに明らかな異常があると、エラーが生じ書き換えが失敗する上に対象アセンブリが削除されてしまう仕様のため、「自動バックアップ」は初期設定で有効にしています。
あるメソッドで絶対に実行されないILコードが生じる書き換えを行うと、別のメソッドの適切な書き換え時に問題が表面化し上記エラーが生じることもあります。
■表示
画面左上「最前面表示」チェックボックスにより、「NekoDisIL」の画面の最前面表示と通常表示を切り替え可能です。
ILコード解析・書き換えのヒント
「指定文字列を含むメソッドを抽出して表示」で、文字列「.ctor」を指定すると、各種初期化処理(コンストラクタ)を抽出することができます。「指定文字列を含むコードを抽出して表示」で、文字列「ldstr」を指定すると、文字列参照箇所を抽出することができます。
ILコードの挿入や書き換え結果として、メソッドに設定されたスタック最大使用値「MaxStack」を超えないよう、PUSH処理に伴うスタックの状態の変化に注意してください。
条件ジャンプ命令のNOPで上書きによる無効化書き換えには、POPによるスタックトップ以降にある判定要素の削除が必要です。ジャンプ処理には、ジャンプ距離をゼロに書き換える、つまり次のIL位置にジャンプさせる無効化アプローチもあります。
四則演算や論理演算の結果はスタックにPUSHされるので、演算処理の書き換え時はスタックの状態に注意が必要です。演算処理の無効化ならば、演算処理と、演算処理に用いる数値のPUSH処理を併せてNOPで上書きする方法があります。
文字列の難読化の解析にあたり、難読化で使用するバイト配列を指定する処理は、文字列「newarr」や「xor」を含むILコードを抽出することで見つかる可能性があります。併せて、文字列「GetString」を含むILコードの抽出が役立つかもしれません。
UsaTest_DotNET.exeを用いた.NETアプリケーションの解析練習について
■UsaTest_DotNET.exeについて「うさみみハリケーン」同梱の「UsaTest_DotNET.exe」は、.NETアプリケーションの解析練習用に製作しました。数値の初期値設定、数値の操作、文字列参照、メソッド呼び出しおよび条件分岐といった、解析およびILコード書き換えの題材となる処理を実装しています。
解析初心者の方が、いきなり.NETアプリケーションのマルウェアを解析するのは困難であることを踏まえた、「.NETアプリケーションの解析の初歩を手軽に体験できるサンプル」がコンセプトです。本来とは異なる動作をさせるよう、色々とILコードを書き換えてみてください。
ILコードの書き換えを行う際には、「UsaTest_DotNET.exe」は必ず終了させておいてください。実行中だと書き換えに失敗します。
■UsaTest_DotNET.exeのILコード書き換え例
▲1.ILコードを挿入し変数の操作処理を追加
UsaTest_DotNET.Form1クラスの下にあるIntAddButton_Clickメソッドの逆アセンブル結果を表示し、「選択メソッド内でILコードを挿入・複製・削除・上書き」欄で以下の指定を行います。
IL位置:3
処理:スタックトップに入力値をAddする(int32)
追加情報(加算する数値):2000
上記指定後に「ILコード書き換え」ボタンで書き換えを実行します。UsaTest_DotNET.exeを起動して(整数の)「数値 増加」ボタンを押すと、本来数値1000が加算される処理が、数値3000が加算されるようになります。


▲2.ILコードを削除し特定処理を無効化
UsaTest_DotNET.Form1クラスの下にあるIntSubButton_Clickメソッドの逆アセンブル結果を表示し、「選択メソッド内でILコードを挿入・複製・削除・上書き」欄で以下の指定を行います。
IL位置:1
処理:ILコードを削除する
追加情報:なし
上記指定後に「ILコード書き換え」ボタンを2回押して削除を実行します。UsaTest_DotNET.exeを起動して(整数の)「数値 減少」ボタンを押すと、本来数値1000が減算される処理がスキップされるようになります。なお、このような特定処理の無効化は、処理「ILコードをNopで上書きして無効化する」でも可能です。
ILコードの削除やNOP上書きではなく、特定処理を無条件ジャンプで無効化すると、絶対に実行されないILコードが生じます。これはメソッドのスタック使用量を計算する上で障害となるため、別のメソッドの書き換え時に問題が表面化し、書き換えに失敗することがあります。そのため、特定処理の無効化は、ILコードの削除やNOP上書きを用いるのが無難といえます。
ただし、アセンブリに対し、1つのメソッド内だけのILコード書き換えを行い、その結果絶対に実行されないILコードが生じても、アセンブリとしてはたいてい問題なく動作します。例えば、以下のILコード挿入だけを行うケースです。
IL位置:1
処理:無条件ジャンプする(ジャンプ先のIL位置を入力)
追加情報(ジャンプ先位置):3


▲3.ILコードを挿入し参照文字列を変更する
UsaTest_DotNET.Form1クラスの下にあるShowMessageboxButton_Clickメソッドの逆アセンブル結果を表示し、「選択メソッド内でILコードを挿入・複製・削除・上書き」欄で以下の指定を行います。
IL位置:1
処理:スタックトップを入力文字列にする
追加情報(新しい文字列):任意の文字列
上記指定後に「ILコード書き換え」ボタンで書き換えを実行します。UsaTest_DotNET.exeを起動して「メッセージボックスを表示」ボタンを押すと、入力した文字列が表示されるようになります。

CTF(セキュリティコンテスト)への「NekoDisIL」活用例
中学生・高校生向けのセキュリティコンテスト「picoCTF」で、2023年に出題されたリバースエンジニアリングの問題「No way out」を「NekoDisIL」で解いています。picoCTF2023(再チャレンジ) No way out