Python.NETを用いた.NETバイナリのリソース抽出

はじめに

.NETマルウェアの解析を行っていると、自動化スクリプトのために、.NETバイナリ内に含まれるリソースを抽出したい時が出てくる。なぜならば、.NETマルウェアの多くは、次に実行するマルウェアの本体や設定情報などをリソースに格納しているケースがあるからだ。こうした状況において、基本的には.NET対応言語であるC#でプログラムを書くほうが良いが、個人的にはそこまでC#を習熟していないので、Pythonで書きたいと考える。そこで、本記事ではPythonを使って.NETバイナリに含まれるリソースの抽出のPoCを紹介する。

Python.NETとは

github.com

Python.NETは、Pythonから.NETのCLR(共通言語ランタイム)の機能を使うことができる外部ライブラリである。.NET Frameworkに対応したCLRだけでなく、.NET CoreのCoreCLRにも対応している。下記のようにpip経由でインストールすることができる。

pip install pythonnet

リソースを含む.NETバイナリのサンプル

目的のスクリプトを作る前に、適当なリソースを含む.NETバイナリを作成した。Visual Studio 2022で、下記のようにTestResourceという名前のリソースを含めてコンパイルした。

ターゲットとなるリソースを含んだ.NETバイナリ
なお、ソースコード自体は特筆すべきことはなく、当該リソースの値を標準出力するのとデバッグ用に標準入力を待ち受けているだけ。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Sample
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(Properties.Resource1.TestResource);
            Console.ReadLine();
        }
    }
}

.NETバイナリのリソース抽出するスクリプトの解説

初めにスクリプト(resource-viewer.py)の全体を記載する。

import sys

import clr
from System.Resources import ResourceReader
from System.Reflection import Assembly

def load_assembly(path: str) -> Assembly:
    buf = open(path, "rb").read()
    asm = Assembly.Load(buf)    
    return asm

def main():
    if len(sys.argv) != 2:
        print("Usage: resource-viewer.py <.NET Framework Assembly>")
        sys.exit(1)

    path = sys.argv[1]
    asm = load_assembly(path)
    for resource_name in asm.GetManifestResourceNames():
        print(f"Found resource: {resource_name}")
        stream = asm.GetManifestResourceStream(resource_name)
        res = ResourceReader(stream)
        for dict in res.GetEnumerator():
            print(f"{dict.Key}: {dict.Value}")

if __name__ == '__main__':
    main()

C#を書きたくないから、Pythonでやろうとしたものの、結局C#で抽出する処理を書いている。GetManifestResourceNamesでリソース名の一覧が取得できる。今回は、1つのリソースしかない。見つかったリソース名を用いて、リソース用のストリームを取得する。当該ストリームは、ResourceReaderに渡すことで、実際にリソースの中身を取得できるようになる。リソースの中にある値も今回は1つのキーバリューだが、走査するGetEnumeratorでループ処理を記載している。これにより、リソース名がわからなくても、とりあえずすべて抽出することができそうである。

スクリプトの実行結果は、下記の通りである。リソース一覧から、Key Value形式で意図したリソースを抽出することができている。

resource-viewer.pyの実行結果

おわりに

スクリプトを拡張することで、.NETマルウェア解析用のスクリプトPythonだけで書くことができるようになる。どうしてもPythonスクリプトをまとめておきたいなど、かなり母数が少ない人向けの内容だが、役立ったら幸いである。