IronNetInjector: Turlaの新しいマルウェアローディングツール

By

Category: Unit 42

Tags: , , , , , ,

A conceptual image representing malware, such as IronNetInjector, discussed in this blog, Turla's new malware loading tool.

This post is also available in: English (英語)

概要

昨今、だれでも使える既製マルウェアが多数、ソフトウェア開発ホスティングサイトにリリースされています。これには脅威アクターも含まれます。このおかげで悪意のあるユーザーは開発時間を節約できるだけでなく、マルウェア検出防止の新たなアイデアを容易に見つけられるようになっています。

Unit 42のリサーチャーは、悪意のあるIronPythonスクリプトを複数発見したのですが、これらのスクリプトの目的は、Turlaのマルウェアツールを侵害した端末上にロードして実行することでした。悪意をもってIronPythonを使用すること自体はとく目新しいことではないのですが、Turlaのとった手法は斬新でした。その手法は一般に「Bring Your Own Interpreter (BYOI、自分のインタープリタは自分で持参せよ)」として知られています。BYOIは、実行したいマルウェアコードのプログラム言語やスクリプト言語用のインタープリタがデフォルトでシステムにない場合にそれを自前で用意することを指しています。

今回説明するツールの最初の悪意のあるIronPythonスクリプトは昨年FireEyeのセキュリティリサーチャーが発見したもので、今年はじめには別のDragosのセキュリティリサーチャーも、2人の異なる提出者からVirusTotalにアップロードされた同一脅威アクターによる新しいスクリプト複数について指摘しています。私たちは、その提出者の1人が他の2つのサンプルもアップロードしていたことを突き止めました。これらのサンプルはおそらくはこれらIronPythonスクリプトのうちの1つの埋め込みペイロードだろうと考えられます。これらのサンプルは、このツールがどのように機能し、どのマルウェアをロードし、どの脅威アクターがそれを使用するのかを理解するうえで役に立ちました。

なお、IronPythonスクリプトはツールの最初の部品にすぎず、マルウェアをロードする主たるタスクは、組み込みのプロセスインジェクタによって実行されます。私たちは、この一続きのツールをIronNetInjectorという名前で呼ぶことにしました。これは「IronPython」とインジェクタの内部プロジェクト名「NetInjector」とをあわせて作った名前です。本稿では、これらIronPythonスクリプトについて説明し、スクリプトがインジェクタの助けをかりてどのように1つ以上のペイロードをロードするかについても解説します。

パロアルトネットワークスのお客様は、この脅威からWildFireおよびCortex XDRによって保護されています。AutoFocus をお使いのお客様は、「IronNetInjector」というタグを使用してアクティビティを追跡できます。

IronPythonとは何か

まずはIronPythonとは何か、IronPythonがなぜローディングベクトルとして選ばれたのかを見ていきます。IronPythonチームによれば、IronPythonは次のように説明されています。

IronPython is an open-source implementation of the Python programming language which is tightly integrated with the .NET Framework. IronPython can use the .NET Framework and Python libraries, and other .NET languages can use Python code just as easily.

IronPythonは .NET Frameworkと緊密に統合されたPythonプログラミング言語のオープンソース実装である。NET FrameworkとPythonライブラリの両方が使えて、他の.NET言語からも同じように簡単にPythonコードを使用できる。

そしてさらに次のようにも説明しています。

IronPython’s sweet-spot is being able to use the .NET framework APIs directly from Python.

IronPythonのキモはPythonから直接.NET FrameworkのAPIを叩けることだ。

IronPythonを使用すれば.NET Framework APIをPythonスクリプトから直接利用することができます。IronPythonは完全にC#で記述されたPythonインタープリタで、本稿執筆時点ではPython 2を完全にサポートし、Python3のサポートはまだ開発中です。またIronPythonは以前Microsoftが開発していた2つの公式プロジェクトのうちの1つです。もう1つのプロジェクトはIronRubyで、IronRubyは動的言語ランタイム(DLR)を使用します。

以上の説明でIronPythonがマルウェア作成者にとっても魅力的である理由が明らかになったかと思います。つまり、.NETアセンブリをコンパイルしなくても.NET Framework APIを利用できるのです。もちろんそのためにはIronPythonインタープリタがシステムに存在していなくてはなりませんが、そこはなんとでも実現できます。なおコード内で.NET Framework APIを使用している場合、IronPythonスクリプトはもともとのPythonインタープリタでは実行できません。Pythonスクリプトをサポートするサンドボックスの場合、インタープリタは動的分析の結果を出すことなく、ただクラッシュしてしまいます。またIronPythonはC#で記述されていることから、実行時、そのプロセスには共通言語ランタイム(CLR)がすべて含まれており、追加のアセンブリを簡単にロードできます。

IronNetInjector

IronNetInjectorは、.NETインジェクタと1つ以上のペイロードを含むIronPythonスクリプトで構成されています。ペイロードは、.NETアセンブリ(x86/64)にもネイティブPE(x86/64)にもできます。IronPythonスクリプトが実行されると.NETインジェクタがロードされ、ロードされたインジェクタがペイロードを自身のプロセスないしリモートプロセスにインジェクトします。

悪意のあるIronPythonスクリプトの主な機能は次のとおりです。

  • 関数名と変数名は難読化されている
  • 文字列は暗号化されている
  • 暗号化された.NETインジェクタと1つ以上の暗号化されたPEペイロードが含まれる
  • 埋め込まれた.NETインジェクタとPEペイロードの復号キーとなる1つの引数を取る
  • 埋め込まれた.NETインジェクタとペイロードはBase64でエンコードされRijndaelで暗号化されている
  • ログメッセージが%PUBLIC%\Metadata.datに書き込まれる
  • エラーメッセージが%PUBLIC%\Metaclass.datに書き込まれる

次のスクリーンショットは、デコードされたIronPythonスクリプトの1つを示しています。

図1 デコードされたIronPythonスクリプト。.NETインジェクタとComRATペイロードが埋め込まれている(両方後半を省略)。
図1 デコードされたIronPythonスクリプト。.NETインジェクタとComRATペイロードが埋め込まれている(いずれも後半省略)

.NETインジェクタは2つのバージョンが見つかっています。2019年にコンパイルされ、内部的にNetInjectorという名前がつけられた新しい亜種と、それより古く2018年にコンパイルされたPeInjector_x64 という名前の亜種です。古い方の亜種は、2019年の亜種と比べ、ごく限られた機能しか持っていません。

どちらのバージョンもリモートプロセスにネイティブのx86/64ペイロードを反射型読み込みでロードさせることのできる本格的なPEインジェクションツールです。これは、アンマネージ関数と、C#で記述された公開済みPEパーサーライブラリであるPeNetによって実現しています。コードを逆コンパイルしてみると、コード全体に意味のある関数名やメソッド名、変数名が使われていて、内容のわかりやすいものになっています。さらにログメッセージやエラーメッセージも広く利用されています。

2018年の亜種のコードのほとんどは、PowerShell EmpireのReflectivePEInjectionスクリプトから拝借したものがC#に移植されていました。2018年の亜種は特定の用途に向けた書きかたがされていましたが、それと比べて2019年の亜種はより汎用的な書きかたをしたインジェクションツールでした。この新しいバージョンには、アンマネージドプロセスに.NETアセンブリをインジェクトする機能が追加で含まれていて、ペイロードを自身のプロセス空間であるIronPythonインタープリタのプロセスにロードすることができるようになっています。

新しいインジェクタには次のPDBパスが残されていました。

C:\Users\Devel\source\repos\c4\agent\build_tools\agent_dll_to_Python_loader\NetInjector\NetInjector\obj\Release\NetInjector.pdb

IronPythonスクリプトをアップロードしたのと同じ提出者がIronNetInjectorに直接関連する他のファイルも提出していました。IronPythonスクリプトに埋め込まれたペイロードのファイルサイズからは、その内容についていくつかの仮説を立てることができます。

次の表は、VirusTotalの提出者ごとに分類したIronPythonスクリプトを示しています。またこの表では、同じ提出者がほかにどんなサンプルをアップロードしたかや、他の提出者同士に繋がりがあるか、そして推定される埋め込みマルウェアがどんなものであるかも示しています。

提出者 アップロードされたIronPythonスクリプト 同じ提出者がアップロードした関連サンプル 推定されるペイロード
1 • prophile.py
• profilec.py
•IronPython-2.7.7z: 2つのIronPythonスクリプトとprofilec.pyを開始するためのWindowsタスクXMLを含む移植可能なIronPythonバージョン •prophile.py: .NETインジェクタ(亜種2018)+ RPCバックドア亜種
•profilec.py: .NETインジェクタ(亜種2019)+ ComRAT亜種
2 • profile.py •profile.py: .NETインジェクタ(亜種2019)+ ComRAT亜種
3 • 10profilec.py
• 120profilec.py
• 220profile.py
•10profilec.py: .NETインジェクタ(亜種2018)+ ComRAT亜種
•120profilec.py: .NETインジェクタ(亜種2019)+ ComRAT亜種
•220profile.py: .NETインジェクタ(亜種2018)+ 不明
4 • profilec.py •NetInjector.dll: .NETインジェクタ(亜種2019)、おそらく同じ提出者のprofilec.pyに埋め込まれた.NETインジェクタ
•payload.exe: ComRAT v4亜種(DLL)、おそらく同じ提出者のprofilec.pyに埋め込まれたもの
5 •part_1.data: .NETインジェクタ(亜種2018)、おそらく提出者1のprophile.pyに埋め込まれたもの
•part_2.data: RPCバックドア亜種、おそらく提供者1のprophile.pyに埋め込まれたもの
•part_3.data: RPCバックドア亜種、おそらく提供者1のprophile.pyに埋め込まれたもの

表1 VirusTotalの提出者と推定ペイロードをもとに分類したIronPythonサンプル

これでIronNetInjectorは主にComRATのロードに使用されるということが明らかになってきました。またあるケースではRPCバックドアの亜種が使用されており、また別のケースでは既知のマルウェアと関連付けらることのできないペイロードが使用されていました。

なお、IronPythonスクリプトが最初にどのように実行されるのかについては確認できませんでした。提出者の1人は、2011年のバージョン2.7.0.40のIronPython MSIファイルの内容を含む7-Zipアーカイブをアップロードしていましたが、このアーカイブには2つのIronPythonスクリプト(表を参照)とmssch.xmlという名前のWindowsタスクXMLファイルが含まれていました。この内容は次のようなものです。

図2 IronNetInjector用のWindowsタスクXMLファイル
図2 IronNetInjectorのWindowsタスクXMLファイル

このタスクは64ビット版インタープリタでIronPythonスクリプトを開始するのに使用されます。コマンドライン引数としてRijndael復号キーが渡されますが、このキーでは見つかったスクリプトの埋め込みファイルはどれも復号できませんでした。タスクの説明は「PythonUpdateSrvc」で、ユーザーのログイン時にWindowsスタートアップから実行されるか、2つのシステムイベント(後述)のいずれか1つが生成された場合に実行されるようになっています。

図3 IronNetInjectorタスクがトリガーされる
図3 IronNetInjectorタスクがトリガーされる

ID 8001のイベントは、Microsoft Internet Information Services(IIS)か、Microsoft Exchange Serverか、Windows Serverのいずれかのものです(出典: Netsurion EventTracker)。もう1つのイベント ID 5324は、Winlogonからのログオフに関連している可能性があります。いずれも、これらのイベントがMicrosoft-Windows-GroupPolicy(/Operational) のイベントログ内で生成された場合にのみトリガーされます。

7-Zipアーカイブ内のファイルがすべて同じディレクトリから取得されたものと仮定すると、いくつか仮定を立てられます。攻撃者はIronPython MSIを使用し、侵害したシステムのC:\ProgramData\IronPython-2.7にインタプリタをインストールしていた可能性があります。IronPythonスクリプトとWindowsタスクXMLが同じディレクトリに配置され、その後、タスクファイルでタスクを作成し、作成されたタスクがトリガーされるとスクリプトが開始されます。ただしこの提供者は、いろいろな場所からファイルを集めておいて、VirusTotalにスキャンさせるためにアーカイブにまとめただけである可能性もあります。また攻撃者がこのような古いバージョンのIronPythonを使用する理由も不明です。

実行フローをざっと見てみる

亜種2019のインジェクタとComRATの亜種(SHA256値 : 3aa37559ef282ee3ee67c4a61ce4786e38d5bbe19bdcbeae0ef504d79be752b6)を含むVirusTotal提出者4のスクリプトを1つ取り上げ、その実行フローをざっと追ってみます。

IronPythonスクリプトは、実行されるとIronPythonインタープリタにロードされます。IronPythonスクリプト内で、組み込みの.NETインジェクタ(SHA256: a56f69726a237455bac4c9ac7a20398ba1f50d2895e5b0a8ac7f1cdb288c32cc)とComRATのDLLペイロード(SHA256: a62e1a866bc248398b6abe48fdb44f482f91d19ccd52d9447cda9bc074617d56)がデコードされて復号されます。これを行うのがPythonのBase64モジュールとC#のSystem.Security.Cryptography名前空間のRijndaelManagedクラスです。復号キーはIronPythonスクリプトに引数として渡され、Rijndaelの初期化ベクトル(IV)はスクリプト内に格納されています。次に、.NETインジェクタがIronPythonプロセスにロードされますが、これにはAssembly.Load()というC#のSystem.Reflection名前空間のメソッドが使われます。それが可能な理由は、IronPython自体が.NETアセンブリで、もともとプロセス内にすべての.NETランタイムライブラリを含んでいるからです。

インジェクタのアセンブリがロードされると、ComRATのDLLをインジェクトする対象プロセスのIDが取得されます。このケースではexplorer.exeが選ばれていました。PIDを取得するこのルーチンは、私たちが見つけたIronPythonスクリプトのそれとは若干違っていて、一方のスクリプトではC#のメソッドGetProcessesByName()を使ってPIDを取得し、もう一方ではPythonのos.popen()関数の助けを借りてWindowsツールtasklist.exeを実行していました。その後はtasklistのフィルタを使って出力結果をパースし、対象のプロセスIDを絞り込みます。またスクリプトによってはWindowsサービスのサービス名をベースにPIDをフィルタリングする場合もありました。PIDが見つかると、インジェクタアセンブリのインスタンスが作成され、ComRATペイロードのバイトとPIDが渡されます。

図4 さまざまなIronPythonスクリプトのPID取得関数の違い
図4 さまざまなIronPythonスクリプトのPID取得関数の違い

最終的に、インジェクタのpublicメソッドInvoke()InvokeVoid()が呼び出されます。後者では、ComRATペイロードのエクスポートされた関数名VFEPが渡されます。この時点から.NETインジェクタがそれ以降の実行を制御します。

.NETインジェクタには、次の名前空間が含まれています。

  • DefaultSerializer
  • PeNet
  • PeNet.Parser
  • PeNet.Structures
  • PeNet.Structures.MetaDataTables
  • PeNet.Structures.MetaDataTables.Parsers
  • PeNet.Utilities

PeNetコードは当該プロジェクトからコピーしたものですが、名前空間DefaultSerializerにはインジェクタのコードが含まれており、次のクラスで構成されていました。

  • DefaultSerializer: インジェクタのコードを含む
  • NetBootstrapper: アセンブリをアンマネージドプロセスにロードするための32ビット/64ビットのブートストラップを含む
  • Win32: インポートされたアンマネージ関数の宣言とwin32構造体/定数を含む

DefaultSerializerクラスは次の4つのpublicメソッドを公開していました。

  • InjectAssembly
  • Invoke
  • InvokeAssemblyMethod
  • InvokeVoid

これらのメソッドはペアで使用されます。メソッドInjectAssemblyは .NETアセンブリをネイティブプロセス(または自身のプロセス)にインジェクトするために使用され、InvokeAssemblyMethodはインジェクトされたアセンブリの任意の選択済みメソッドを呼び出します。メソッドInvokeはネイティブPEをリモートプロセスにインジェクトするために使用され、InvokeVoidはインジェクトされたペイロードの任意のエクスポートされた関数を呼び出します。

図5 逆コンパイルされたNetInjectorコード
図5 逆コンパイルされたNetInjectorコード

作成時にDefaultSerializerに渡された引数の数に応じ、ペイロードは自身のプロセスかリモートプロセスにロードされます。ペイロードのバイトのみが渡された場合は自身のプロセス空間にロードされます。これとは別のオプションとして、ペイロードをインジェクトしたいリモートプロセスのIDかハンドルを渡すこともできます。

私たちのスクリプトでは2つ目のオプションが使われ、explorer.exeのPIDを渡して当該プロセスにComRATのペイロードを反射型読み込みでロードしていました。

このインジェクタについて1つ興味深いのは、アセンブリをアンマネージドプロセスにロードする機能がある点です。共通言語ランタイム(CLR)が存在していなければ、リモートプロセス内に単に.NETアセンブリをロードして実行することはできないので、このためのリモートプロセスでの準備が必要です。この準備を行うのがネイティブのブートストラップDLLで、このDLLがリモートプロセスにインジェクトされ、後で.NETアセンブリをインジェクトできるように準備します。

NetBootstrapperクラスには2つのブートストラップ(x86/64)が含まれています。それらは次のPDBパスを残していました。

F:\Dev\NetInjector\bin\Release\NetBootstrapper_Win32.pdb

F:\Dev\NetInjector\bin\Release\NetBootstrapper_x64.pdb

インジェクタ自体と同様に、ブートストラップには意味のある関数名(エクスポートされた関数)とログメッセージが含まれていました。ブートストラップは次のエクスポートされた関数を使用しています。

  • Bootstrap: CLRサービスをプロセスにロードする
  • GetMethodResult: InvokeMethodからメソッドの結果を取得する
  • InvokeMethod: パラメータとして渡されたインジェクト アセンブリのメソッドを呼び出す
  • LoadAssembly: パラメータとして渡された.NETアセンブリをロードする
  • StartClrRuntime: Bootstrapと同じ

これらの関数はインジェクタから呼び出され、IronPythonスクリプトからの.NETアセンブリペイロードを準備してリモートプロセスにロードします。

私たちが見つけたIronPythonスクリプトでは、ネイティブのペイロードからネイティブのリモートプロセスにインジェクションを行うオプションのみが使われていました。

結論

IronNetInjectorは兵力増強の続くTurlaをさらに強化する新たなツールセットの1つです。このツールセットはIronPythonスクリプトとインジェクタから構成され、その構造は、PowerShellスクリプトの助けを借りてマルウェアを実行する以前使用されていたインメモリロードのメカニズムと似ています。これらのスクリプトには、埋め込まれたマルウェアペイロードを実行するために埋め込まれたPEローダが含まれています。

本稿で説明したツールは、PowerShellから.NETに移行するために開発された可能性があります。Powershellベースの脅威の検出向上にあわせ、こうした全般的な傾向が近年見られるようになってきていますが、Microsoftが導入したAMSIのようなセキュリティメカニズムも、こうした傾向を後押ししているものと思われます。

.NETインジェクタとブートストラップには、クリーンなコードや意味のある関数名/メソッド名/変数名が含まれており、詳細なログやエラーメッセージも使われています。簡単に検出できないように難読化されているのは最初のIronPythonスクリプトだけです。

このツールについてはまだ答えのでていない疑問が複数残っています。ComRATやRPCバックドアの他にどのようなサンプルがロードされるのかや、IronPythonスクリプトはどのようにして実行されるのか、侵害されたシステムにどのようにしてインタープリタが展開されるのか、といった疑問です。

これらパズルの欠けたピースを見つけるため、今後も引き続き私たちはこのマルウェアローディングツールを監視していきます。

パロアルトネットワークスのお客様は、このマルウェアツールから保護されています。脅威防止プラットフォームWildFireが当該ツールを「malicious(悪意のあるもの)」として検出します。XDRプラットフォームCortex XDRは、マルウェアの実行を識別して阻止します。AutoFocusをお使いのお客様は、「IronNetInjector」というタグを使用してアクティビティを追跡できます。

IoC

IronPythonスクリプト

  • b641687696b66e6e820618acc4765162298ba3e9106df4ef44b2218086ce8040 (prophile.py, submitter 1)
  • c430ebab4bf827303bc4ad95d40eecc7988bdc17cc139c8f88466bc536755d4e (profilec.py, submitter 1)
  • c1b8ecce81cf4ff45d9032dc554efdc7a1ab776a2d24fdb34d1ffce15ef61aad (profile.py, submitter 2)
  • 8df0c705da0eab20ba977b608f5a19536e53e89b14e4a7863b7fd534bd75fd72 (10profilec.py, submitter 3)
  • b5b4d06e1668d11114b99dbd267cde784d33a3f546993d09ede8b9394d90ebb3 (120profilec.py, submitter 3)
  • b095fd3bd3ed8be178dafe47fc00c5821ea31d3f67d658910610a06a1252f47d (220profile.py, submitter 3)
  • 3aa37559ef282ee3ee67c4a61ce4786e38d5bbe19bdcbeae0ef504d79be752b6 (profilec.py, submitter 4)

インジェクタのサンプル

  • a56f69726a237455bac4c9ac7a20398ba1f50d2895e5b0a8ac7f1cdb288c32cc (2019 variant, submitter 4)
  • c59fadeb8f58bbdbd73d9a2ac0d889d1a0a06295f1b914c0bd5617cfb1a08ce9 (2018 variant, submitter 5)

ブートストラップのサンプル

  • 63d7695dabefb97aa30cbe522647c95395b44321e1a3b08b8028e4000d1be15e
  • ba17af72a9d90822eed447b8526fb68963f0cde78df07c16902dc5a0c44536c4

関連サンプル

  • 82333533f7f7cb4123bceee76358b36d4110e03c2219b80dced5a4d63424cc93 (IronPython-2.7.7z, submitter 1)
  • a62e1a866bc248398b6abe48fdb44f482f91d19ccd52d9447cda9bc074617d56 (ComRAT v4 variant, submitter 4)
  • 18c173433daafcc3aea17fc4f7792d0ff235f4075a00feda88aa1c9f8f6e1746 (RPC backdoor variant, submitter 5)
  • a64e79a81b5089084ff88e3f4130e9d5fa75e732a1d310a1ae8de767cbbab061 (RPC backdoor variant, submitter 5)