.NET製のDLLデバッグの効率化のためにSimpleDllLoaderを作成した
はじめに
マルウェア解析を行っていると、たまにファイルレスなマルウェアの解析を行うことがある。多くは、PowerShellなどでダウンロードした.NET製のDLLなどをロードして実行するものだ。そこで、PowerShellのスクリプトなどを改変して、対象の.NETのDLLをディスク上に出力する。しかしながら、.NET製のDLLは、WindowsのネイティブなDLLのように、rundll32.exe hoge.dll,#1
のように実行することができない。そこで、普段どのようにこうした.NET製のDLLを分析しているのかの解説と、それを手軽に行うためのツールを作ったたので、紹介する。
.NET製DLLの分析手法
.NET製のバイナリであれば、まずはdnSpyを使うことが最も早い。しかしながら、.NET製のDLLの場合、単体で実行することができない。もし詳しい人がいたら、単体で動かす方法を教えてくれると嬉しい。そこで、多くのケースでは、対象の.NET製のDLLをロードする簡易なローダをC#などで記載して、それをdnSpyで読み込んで分析する。しかしながら、毎回そのようなコードを書くのも煩わしい。 そこで調査したところ、SharpDllLoaderと呼ばれるツールが結構使いやすそうだったので使ってみていた。 github.com
.NET製DLLで呼び出したいクラスやメソッド、またその名前空間を引数として設定することで、dnSpyでデバッグを行うことができるようになる。具体的な使い方はREADME.md
に記載されているので参考にしてほしい。dnSpyでデバッグをしていき、最終的に呼ばれるInvokeメソッドをStep Intoで入ると、目的の.NET製DLLにたどり着くといったところだ。
SharpDllLoaderの欠点
欠点と言ってしまうと失礼で後で回収するが、どうしてもこうしてしまったほうがツールが作りやすいというものではある。何が欠点かというと、本ツールでは対象とするクラスのコンストラクタに引数が無いケースのものしか扱えないようになっている。具体的にはコードの下記の部分だ。Activator.CreateInstance(type)
のような渡し方をするとこのような事象になってしまう。
private static object GetClass(Type type) { object classObj = null; if (type.IsAbstract == false) { classObj = Activator.CreateInstance(type); } return classObj; }
マルウェアによって、どうしても最初のバイナリで文字列を引数として渡す形で、.NET製DLLのメソッドを使うケースも出てくる。そうした際に本ツールだと扱えなくなってくる。
SimpleDllLoaderの紹介
ブログ記事として書いている割には、実は出来が甘くてまだ結構なケースでこけてしまう気がする。しかしながら、そうするといつまでたっても公開できなくなってしまいそうな気がしたので、いったん公開してしまう。ちなみに、こけやすいのは独自に実装しているコマンドラインパーサーの部分であって、あまり本質的ではない。 github.com
本ツールでは、クラスのコンストラクタやメソッドを探す際に、型情報も必要とする形としている。
C:\>SimpleDllLoader.exe -h usage: SimpleDllLoader v1.0.0 A Simple Loader for .NET DLL required arguments: -d, --dll DLL_PATH Specify a path for DLL -c, --class CLASS Specify a class optional arguments: -h, --help Show this help message and exit -ca, --cctor-args ARGS Specify constructor arguments -ct, --cctor-types TYPES Specify constructor argument types -n, --namespace NAMESPACE Specify a namespace -m, --method METHOD Specify a method name -ma, --method-args ARGS Specify method arguments -mt, --method-types TYPES Specify method argument types
例えば、コンストラクタにSystem.String
型の引数を取るメソッドを実行したい場合は下記のように行うことができる。
C:\>SimpleDllLoader.exe -d C:\FakeDll.dll -c FakeDll -ca Test -ct System.String
また、仕組みとしては同じなので、SharpDllLoaderと同様にdnSpy上で実行することで、dnSpyで当該メソッドのデバッグなどもできる。
実装のめんどくささの実情
今回SharpDllLoaderの欠点を解決するためにツールを作ってみたわけだが、作ってみて作りにくさを感じた。やはり、.NET製のDLLがどんなクラス・引数の型のものが入力としてされるかわからないため、汎用的に作るのが難しい。例えば、.NET製のDLLのクラスが、System.Byte
の配列を引数にとる場合を考えてみよう。この場合、引数から受け取った情報をその型に変換する必要があるが、その仕組みを実装するのも若干めんどくさい。また、ユーザ定義のクラスだったりしてしまったら、もはやそれを予測実装することは不可能だ。おそらくこうした点から、SharpDllLoaderでは引数なしのコンストラクタのクラスしか受け入れないようになっているのだと思われる。
つまり、汎用的にツールで解消するよりも、都度C#でミニマムなローダを書いたほうが早いと思われるというのが、作ってみての感想である。つきましては、SharpDllLoaderや拙作のSimpleDllLoaderの実装を参考に、.NET製のDLLをAssembly.LoadFile
などでロードしたり、TypeやMethodInfoなどのいわゆるC#のリフレクション機能を使うコードを学ぶのがおすすめであるという結論にして終わりたいと思う。
おわりに
.NET製DLLの分析のめんどくささと、それを解消するための既存ツールの紹介、そして既存ツールで足りない部分を解消するために作ったツールSimpleDllLoaderを紹介した。まとめとしては、都度作ったほうが楽で漏れがないという点である。そこまで大したコード量にもならないので、都度C#でローダを書いてしまうほうが楽だと思う。.NETバイナリの解析をしたかったら、とりあえずみんなC#を勉強しよう。