Ofuscar una app desplegada con ClickOnce: por qué el orden de las operaciones decide si siquiera arranca
Los manifiestos de ClickOnce guardan un hash de cada archivo. Si ofuscas después de generarlos, la app se niega a arrancar con un error de validación. Este es el orden de compilación exacto que hace convivir la ofuscación y ClickOnce.
Hay una forma concreta en que una app de ClickOnce falla tras añadir ofuscación que lleva a la gente por el camino equivocado: la app se instala bien, luego se niega a arrancar con un error de validación de archivo o de hash, y nada en tu código cambió. El instinto es sospechar que el ofuscador rompió el ensamblado. No lo hizo. ClickOnce hace exactamente lo que está diseñado para hacer — negarse a ejecutar un archivo cuyos bytes no coinciden con el hash que registró — y el error real es el orden en que se ejecutaron tus pasos de compilación. Acierta con el orden y la ofuscación y ClickOnce conviven sin dramas.
Por qué ClickOnce es inusualmente sensible
La mayoría de los mecanismos de despliegue copian archivos y los ejecutan. ClickOnce hace más: su manifiesto de aplicación registra, para cada archivo del despliegue, un hash criptográfico y un tamaño, y el runtime de ClickOnce verifica cada archivo contra ese registro antes de arrancar. Es una función de detección de manipulación y corrupción: garantiza que el usuario ejecuta exactamente lo que publicaste.
Esa garantía es justo con lo que choca la ofuscación. La ofuscación reescribe los bytes de tus ensamblados — renombrado, aplanamiento de flujo de control y cifrado de cadenas cambian todos el archivo. Si el hash del manifiesto para MyApp.dll se calculó sobre la DLL anterior a la ofuscación, la DLL posterior a la ofuscación que realmente publicas ya no coincide, y ClickOnce se detiene con un error de validación antes de que se ejecute una línea de tu código.
El orden de compilación correcto
La regla cabe en una frase: toda transformación de los ensamblados debe ocurrir antes de generar los manifiestos, y la generación de manifiestos debe ser lo último. En concreto:
- Compila el proyecto.
- Ofusca los ensamblados de salida.
- Vuelve a aplicar el nombre seguro si usaste delay-sign (una ofuscación que reemite el ensamblado debe preservar o reestablecer el nombre seguro — véase volver a firmar ensamblados ofuscados).
- Firma con Authenticode los binarios, si los firmas.
- Genera los manifiestos de aplicación y de implementación con
mage/mageUIo los targets de ClickOnce de MSBuild — aquí es donde se calculan los hashes, sobre los archivos ya finales. - Firma los manifiestos.
Si usas la Publicación de un clic de Visual Studio, la trampa es que la publicación hace el paso 5 por ti justo tras compilar, antes de que cualquier hook de ofuscación post-compilación se haya ejecutado. La solución es sacar la generación de manifiestos del paso de Publicación y ejecutar mage tú mismo tras la ofuscación (un pequeño script de CI), o ejecutar la ofuscación como parte de la compilación de modo que termine antes de que se ejecuten los targets de ClickOnce.
Un esquema mínimo de CI
# 1-2: build then obfuscate the output
dotnet build -c Release -o publish
nebula protect publish/MyApp.dll # (plus other assemblies)
# 3-4: re-sign as needed
sn -R publish/MyApp.dll keypair.snk # if delay-signed
signtool sign /f cert.pfx publish/MyApp.dll # Authenticode (optional)
# 5: generate manifests over the FINAL files (hashes computed now)
mage -New Application -Processor msil -ToFile publish/MyApp.exe.manifest \
-Name "MyApp" -Version 1.2.0.0 -FromDirectory publish
mage -New Deployment -Processor msil -Install true \
-ToFile MyApp.application -AppManifest publish/MyApp.exe.manifest \
-providerUrl "https://downloads.example.com/MyApp/"
# 6: sign the manifests
mage -Sign MyApp.application -CertFile cert.pfx
Como los hashes del paso 5 se toman sobre los ensamblados ofuscados y firmados, la verificación del runtime de ClickOnce pasa y la app arranca.
Cómo hacerlo con Nebula
Nebula encaja en el paso de ofuscación anterior y, como se ejecuta como etapa de compilación/CI (véase ofuscar solo en el servidor de compilación y las guías de Azure DevOps / GitHub Actions), es sencillo situarlo antes de la generación de manifiestos de ClickOnce. Preserva la firma con nombre seguro para que el paso de volver a firmar sea limpio, y sus reglas de conservación manejan cualquier reflexión de WinForms/WPF que haga tu app. La única regla específica de ClickOnce es el orden — acierta con eso y nada más cambia.
Empieza con la edición gratuita, o consulta los planes.
Prueba Nebula.NET
Endurece tu código .NET en minutos — empieza con la edición gratuita.