Eine per ClickOnce bereitgestellte App obfuskieren: warum die Reihenfolge entscheidet, ob sie überhaupt startet
ClickOnce-Manifeste speichern einen Hash jeder Datei. Obfuskieren Sie nach deren Erzeugung, weigert sich die App mit einem Validierungsfehler zu starten. Hier steht die genaue Build-Reihenfolge, die Obfuskierung und ClickOnce koexistieren lässt.
Es gibt eine bestimmte Art, wie eine ClickOnce-App nach dem Hinzufügen von Obfuskierung versagt und die Leute auf eine völlig falsche Fährte lockt: Die App installiert sich problemlos, weigert sich dann mit einem Dateivalidierungs- oder Hash-Fehler zu starten, und an Ihrem Code hat sich nichts geändert. Der Reflex ist, den Obfuskator zu verdächtigen, die Assembly beschädigt zu haben. Hat er nicht. ClickOnce tut genau das, wofür es gebaut ist — die Ausführung einer Datei verweigern, deren Bytes nicht zum gespeicherten Hash passen — und der wahre Fehler ist die Reihenfolge, in der Ihre Build-Schritte liefen. Stimmt die Reihenfolge, koexistieren Obfuskierung und ClickOnce problemlos.
Warum ClickOnce ungewöhnlich empfindlich ist
Die meisten Bereitstellungsmechanismen kopieren Dateien und führen sie aus. ClickOnce tut mehr: Sein Anwendungsmanifest hält für jede Datei der Bereitstellung einen kryptografischen Hash und eine Größe fest, und die ClickOnce-Laufzeit prüft jede Datei vor dem Start gegen diesen Eintrag. Das ist eine Funktion zur Erkennung von Manipulation und Beschädigung: Sie garantiert, dass der Nutzer genau das ausführt, was Sie veröffentlicht haben.
Genau mit dieser Garantie kollidiert die Obfuskierung. Obfuskierung schreibt die Bytes Ihrer Assemblies um — Umbenennung, Kontrollfluss-Abflachung und Zeichenketten-Verschlüsselung ändern alle die Datei. Wurde der Manifest-Hash für MyApp.dll über die DLL vor der Obfuskierung berechnet, passt die nach der Obfuskierung tatsächlich ausgelieferte DLL nicht mehr, und ClickOnce stoppt mit einem Validierungsfehler, bevor eine Zeile Ihres Codes läuft.
Die richtige Build-Reihenfolge
Die Regel passt in einen Satz: Jede Transformation der Assemblies muss vor der Manifesterzeugung geschehen, und die Manifesterzeugung muss die letzte sein. Konkret:
- Kompilieren Sie das Projekt.
- Obfuskieren Sie die Ausgabe-Assemblies.
- Wenden Sie den starken Namen erneut an, falls Sie delay-signiert haben (eine Obfuskierung, die die Assembly neu erzeugt, muss den starken Namen bewahren oder wiederherstellen — siehe obfuskierte Assemblies neu signieren).
- Signieren Sie die Binärdateien per Authenticode, falls Sie signieren.
- Erzeugen Sie die Anwendungs- und Bereitstellungsmanifeste mit
mage/mageUIoder den ClickOnce-MSBuild-Zielen — hier werden die Hashes berechnet, über die nun finalen Dateien. - Signieren Sie die Manifeste.
Wenn Sie die Ein-Klick-Veröffentlichung von Visual Studio nutzen, ist die Falle, dass die Veröffentlichung Schritt 5 direkt nach dem Kompilieren für Sie erledigt, bevor ein etwaiger Post-Build-Obfuskierungs-Hook gelaufen ist. Der Fix ist, die Manifesterzeugung aus dem Veröffentlichungsschritt herauszunehmen und mage nach der Obfuskierung selbst auszuführen (ein kleines CI-Skript), oder die Obfuskierung so als Teil des Builds auszuführen, dass sie vor den ClickOnce-Zielen abgeschlossen ist.
Eine minimale CI-Skizze
# 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
Da die Hashes in Schritt 5 über die obfuskierten, signierten Assemblies gebildet werden, besteht die Prüfung der ClickOnce-Laufzeit und die App startet.
Mit Nebula umsetzen
Nebula fügt sich in den obigen Obfuskierungsschritt ein, und da es als Build-/CI-Stufe läuft (siehe nur auf dem Build-Server obfuskieren sowie die Anleitungen für Azure DevOps / GitHub Actions), lässt es sich unkompliziert vor die ClickOnce-Manifesterzeugung setzen. Es bewahrt die Signierung mit starkem Namen, damit der Neu-Signierschritt sauber ist, und seine Keep-Regeln bewältigen jede WinForms/WPF-Reflexion Ihrer App. Die einzige ClickOnce-spezifische Regel ist die Reihenfolge — stimmt die, ändert sich nichts weiter.
Beginnen Sie mit der kostenlosen Edition oder sehen Sie sich die Tarife an.
Nebula.NET testen
Härten Sie Ihren .NET-Code in wenigen Minuten — starten Sie mit der kostenlosen Edition.