今回は Windows の AMSI(Windows Antimalware Scan Interface) の概要と動作について簡単なメモ書きとして書きました。
内容はすべて公式ドキュメントやその他 Web サイトに記載の情報、もしくは一般に公開されている書籍などの情報を元に作成しています。
もくじ
AMSI について
AMSI(Windows Antimalware Scan Interface) とは、アプリケーションが処理するデータが悪意のあるものか否かを判断するためにシステムに登録されている AV プロバイダを利用できるインターフェースを提供する機能です。
公開ドキュメントの記載によると、AMSI はファイルやメモリ、ストリームのスキャンや、URL/IP のレピュテーションチェックなどを可能にする呼び出し構造をサポートしているそうです。
参考:マルウェア対策スキャン インターフェイス (AMSI) - Win32 apps | Microsoft Learn
AMSI の機能は Windows の UAC や PowerShell、Office VBA などのいくつかのコンポーネントにも統合されており、これらのアプリケーションを保護しています。
このようなアプリケーションとの統合により、AMSI はスクリプトベースの脅威に対処する目的などで主に使用されます。
アプリケーションの開発者は、AMSI を利用して任意のアプリケーションから任意のコンテンツのスキャン要求を実装することができます。
また、AMSI 自体はアンチマルウェアベンダーに依存しないため、任意のアンチマルウェアベンダーが AMSI を通したスキャン要求を受け取ることができます。
AMSI による保護の詳細
AMSI は、特にスクリプトベースのマルウェアにおける文字列連結や難読化などの検出回避のテクニックへの対策として Windows 10 のリリースと同時に登場しました。
AMSI リリース時のブログポストのアーカイブなどを見てみると、PowerShell などを利用したスクリプトベースの脅威やファイルレス攻撃の増加と、その検出回避手法の多様さによる保護のギャップに対する解決策として AMSI が導入されたことがわかります。
PowerShell などのアプリケーションは難読化解除のプロセスを経た後のプレーンな攻撃コードを、その実行前に AMSI を通してシステムに登録されているアンチマルウェアエンジンにスキャン要求を行うことができます。
これにより、複雑な難読化が行われている攻撃スクリプトやメモリ内にのみ悪意のあるコードが存在するファイルレス脅威などの検出を効果的に行うことができます。
参考:AMSI がマルウェアからの防御にどのように役立つのか - Win32 apps | Microsoft Learn
PowerShell に統合された AMSI の動作
ここからは、PowerShell が AMSI を使用して悪意のあるスクリプトの実行を防止する一連の動作を確認していきます。
幸いなことに、PowerShell は OSS としてソースコードが公開されているため、AMSI との統合の詳細を詳しく確認することができます。
参考:PowerShell/PowerShell: PowerShell for every system!
AMSI の動作テストを行う
まずは、公式のドキュメントに記載の以下のサンプルコードを AMSIPoShscript.ps1 として保存して実行することで、AMSI による実行ブロックをテストしてみます。
# Save this sample AMSI powershell script as AMSI_PoSh_script.ps1
$testString = "AMSI Test Sample: " + "7e72c3ce-861b-4339-8740-0ac1484c1386"
Invoke-Expression $testString
これを実行すると、This script contains malicious content and has been blocked by your antivirus software.
のエラーとともにスクリプトの実行がブロックされることを確認できます。
続いて、clone したソースコードから独自にビルドした PowerShell でも AMSI の検出テストを実行してみます。(今回は release/v7.5
のコードを使用しています)
PowerShell のビルドは、専用のビルドツールを使用することで簡単に成功しました。(一部エラーは出力されていたものの、.\src\powershell-win-core\bin\Debug\net9.0\win7-x64\publish\pwsh.exe
は正常に作成されていました )
Import-Module .\build.psm1
Start-PSBuild -Clean -PSModuleRestore -UseNuGetOrg -Configuration Debug
参考:PowerShell/docs/building/windows-core.md at master · PowerShell/PowerShell
ここで作成したプログラムを含む .\src\powershell-win-core\bin\Debug\net9.0\win7-x64
をまとめてテスト端末にコピーし、再度テストスクリプトを実行してみると、こちらも同じく AMSI による実行ブロックが動作することを確認できました。
PowerShell 側の実装を調べる
公式のドキュメントに添付されていた以下の画像を見ると、PowerShell は AmsiScanBuffer または AmsiScanString を呼び出して AMSI を利用していることがわかります。
この関数の呼び出し箇所を辿っていくと、System.Management.Automation/engine/runtime
/CompiledScriptBlock.cs
の PerformSecurityChecks の関数からPerformSecurityChecks() -> ScanContent() -> WinScanContent() -> AmsiScanBuffer()
の順に呼び出されていることがわかりました。
PerformSecurityChecks 関数は、直接的にはスクリプトの実行前にコンパイル処理を行う System.Management.Automationの ReallyCompile 関数から呼び出されています。
PerformSecurityChecks 関数の実装を確認すると、AmsiUtils.ScanContent(scriptExtent.Text, scriptFile)
が AMSI_RESULT_DETECTED
を返した場合に ParseError による例外が返されるようです。
private void PerformSecurityChecks()
{
/* 省略 */
// Call the AMSI API to determine if the script block has malicious content
var amsiResult = AmsiUtils.ScanContent(scriptExtent.Text, scriptFile);
if (amsiResult == AmsiUtils.AmsiNativeMethods.AMSI_RESULT.AMSI_RESULT_DETECTED)
{
var parseError = new ParseError(
scriptExtent,
"ScriptContainedMaliciousContent",
ParserStrings.ScriptContainedMaliciousContent);
throw new ParseException(new[] { parseError });
}
/* 省略 */
}
PerformSecurityChecks では、初期化された AmsiUtils クラスの ScanContent を使用して AMSI にスキャン要求が行われます。
ScanContent から呼び出される WinScanContent では、最終的に amsi.dll からロードした AmsiScanBuffer 関数を使用してスキャンを行います。
AmsiNativeMethods.AMSI_RESULT result = AmsiNativeMethods.AMSI_RESULT.AMSI_RESULT_CLEAN;
// Run AMSI content scan
int hr;
unsafe
{
fixed (char* buffer = content)
{
var buffPtr = new IntPtr(buffer);
hr = AmsiNativeMethods.AmsiScanBuffer(
s_amsiContext,
buffPtr,
(uint)(content.Length * sizeof(char)),
sourceMetadata,
s_amsiSession,
ref result);
}
}
AmsiScanBuffer 関数は、スキャン対象のデータを読み取るためのバッファを受け取ります。
また、複数のスキャン要求を関連づける目的で使用されるセッション情報も amsiSession として受け取ります。
参考:AmsiScanBuffer function (amsi.h) - Win32 apps | Microsoft Learn
参考:Better know a data source: Antimalware Scan Interface
このセッション情報は、アンチマルウェア製品側で様々なスキャン要求を関連づける目的で使用できるように実装されています。
これにより、断片的なデータのみでは判断できない脅威も、各データを関連づけることで検出することができるようになる場合があります。
amsi.dll 側の動作について知る
幸いなことに、PowerShell がロードしている amsi.dll のシンボル情報は Microsoft のパブリックシンボルサーバにて配布されているので比較的容易にデバッグを行うことができます。
しかし、今回は一般に公開されている情報の範囲で記事を作成するため、Evading EDR の 10 章 ANTIMALWARE SCAN INTERFACE の記載を基に amsi.dll 側の動作をまとめることにします。
まず、amsi.dll は、レジストリに登録されている情報を参照して AMSI プロバイダの DLL を読み込みます。
アンチマルウェアベンダーは、レジストリキー HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\AMSI\Providers
に登録されている COM GUID を使用して AMSI プロバイダをレジストリキーに登録しています。
例えば、標準では HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\AMSI\Providers\{2781761E-28E0-4109-99FE-B9D127C57AFE}
が登録されていますが、この GUID と対応するレジストリキーを確認すると、Microsoft Defender のコンポーネントに関する情報が書き込まれており、InprocServer32 のキーの値から MpOav.dll のパスを参照できることが確認できます。
参考:Better know a data source: Antimalware Scan Interface
プロバイダーの DLL をロードし、初期化処理が完了して AMSI を使用可能な状態となると、PowerShell などのアプリケーションから AMSI セッションなどの情報と共に AmsiScanBuffer 関数などによるスキャン要求を受け取れるようになります。
この時、AMSI 側では入力として受け取った各パラメータの妥当性を検証し、パスした場合に amsi!CAmsiAntimalware::Scan を呼び出します。
デフォルトで登録されている Microsoft Defender の AMSI モジュール(MpOav.dll)を使用する場合、AMSI 側で初期化などを行った後、Microsoft Defender ウイルス対策のクライアントインターフェースである MpClient.dll に処理を依頼します。
この後、Microsoft Defender ウイルス対策側でのスキャン結果がアプリケーションに返却されることになり、AMSI_RESULT_DETECTED
が返却された場合には実行ブロックが行われます。
参考:Evading EDR | No Starch Press
まとめ
AMSI についての概要をまとめました。
続けて自作のアプリケーションから AMSI スキャン要求を発行する方法とカスタム AMSI プロバイダーを登録する方法について書こうと思っていたのですが、長くなってきたので別の記事に分けることにします。