Virenalarm. Go! Go! Go!
en

In der Webentwicklung kommt man mit dem Thema eher weniger in Berührung. Die Umgebung ist bekannt und in eigener Hand, am besten über virtuelle Maschinen oder Container in Ketten gelegt. Ein Virenscanner kommt einem kaum bis gar nicht in die Quere. Mit unserem neuen Produkt kairo/m für Arztpraxen sieht das allerdings anders aus und wir sahen uns vor neue (alte) Probleme gestellt. Teilweise mit enormem Kopfschütteln unsererseits.

Die Entwicklung erfolgte in zwei Schritten, ein Prototyp und die finale Umsetzung. Den Prototypen hatten wir in C umgesetzt, um ohne Umwege Win32-API nutzen zu können. Installation auf den Zielrechnern klappte problemlos, die Funktionen, die wir ausprobieren wollten, taten in ungefähr das, was sie sollten.

Der nächste Schritt war also die Umsetzung des Produkts. Go erschien uns als beste Basis für die Entwicklung, was auch der schnelle Fortschritt unterstrich, aber vor allem Go's Cross-Compiling-Features schaffen die besten Voraussetzungen für Multi-Platform-Software. Entwicklung startete und das erste Inkrement war fertig. Als es aber zur Installation beim Kunden kam, schlug dessen Virenscanner Alarm. Natürlich haben wir sofort alle weiteren Aktionen eingestellt, um den Virus zu beseitigen. Wir fanden aber keinen. Zur Sicherheit wurden die Entwicklungsmaschinen neu aufgesetzt und der CI-Prozess aufgesetzt (was eigentlich für später geplant war), damit Builds separiert vom Rest in Containern ablaufen, Cross-Compiled auf Linux für Windows-Targets. Nachdem alles sauber erschien, sind wir wieder zum Kunden... und der Virenscanner schlug Alarm. Zur Überprüfung gaben wir die Binaries an VirusTotal, mit dem Ergebnis, dass ca. 20 Virenscanner Alarm schlugen, aber mit verschiedensten, vermeintlichen Ursachen. Die Vermutung lag nahe, dass es sich um einen Fehlalarm handeln musste.

Nach tiefergehender Recherche, was den Alarm auslösen könnte, stießen wir auf den Artikel A Big Problem in Go That No One Talks About, in dem Marvin Wendt über seine Erfahrungen berichtet. Ums kurz zu machen:

The process of how Go compiles this binary is relatively unique, and often confuses antivirus scanners, resulting in the binary being mistakenly detected as a virus.
Das Problem liegt also an der Art und Weise wie Go Programme kompiliert, und demgegenüber Virenscanner, die die Entwicklung von Go verschlafen haben. Was sind aber die Alternativen? Neu schreiben in einer anderen Programmiersprache? Jeden Virenscanner-Hersteller einzeln kontaktieren und davon überzeugen, dass unsere Software ok ist? Bei jeder neuen Version wieder?

Glücklicherweise gibt es Code-Signing, das nebenbei gesagt nicht nur für diesen Fall eine gute Idee ist. Ein Binary wird mit einer Signatur versehen, die auf einem verfizierbaren Zertifikat beruht. Eine CA (Certificate Authority) stellt dazu ein spezielles SSL-Zertifikat (nebst dazugehörigen privatem Schlüssel) aus, mit dem das Binary signiert wird. Wird das Binary auf einen anderen Rechner übertragen/installiert, kann das Zielsystem anhand der Signatur überprüfen, ob es tatsächlich vom "guten" Hersteller stammt und ob es manipuliert wurde. Das macht natürlich nicht nur das System, sondern auch darauf installierte Virenscanner. Sind sie mit der Überprüfung zufrieden, kann das Programm ausgeführt werden.

Woher bekommt man ein solches Zertifikat zur Code-Signierung? Das stellen alle großen CAs aus, natürlich für einen leider nicht allzu kleinen Obulus. Um vorerst Kosten zu sparen haben wir uns angesehen, wie weit ein paar auserwählte Virenscanner gehen, um eine Signatur zu überprüfen.

Unser Program, das für diesen Test geeignet ist und damit eine Virenwarnung auslöst:


package main

func main() {
  println("Hello Virus!")
}
      
Unsere Schritte bei diesem Test:
  1. selbstsigniertes CA-Zertifikat erstellen
  2. Zertifikat für Code-Signierung aus CA ableiten
  3. Programm signieren
  4. Programm auf Zielrechner laden
  5. Programm auf Zielrechner ausführen
Dabei sollte gleich das Detail auffallen, dass der Zielrechner unsere selbsignierte CA überhaupt nicht kennt. Er kann also gar nicht prüfen, ob die Signatur echt und nicht manipulert ist. Und damit kommt das erste Kopfschütteln: sieben von acht Virenscanners haben das Programm akzeptiert, nur weil eine Signatur existierte, eine Überprüfung der Signatur fand augenscheinlich gar nicht statt. Aus Ungläubigkeit vor diesem Ergebnis haben wir nochmal die Umkehrrechnung gemacht und das unsignierte Programm ausprobiert, diesmal mit dem Ergebnis, dass alle Virenscanner erwartungsgemäß Alarm schlugen.

Bleibt die Frage, was macht der letzte im Bunde anders, der richtigerweise immer noch von einem Virusbefall ausging? Das haben wir überprüft, indem wir unser CA-Zertifikat auf dem Zielsystem bekannt machten. Und siehe da, auch der letzte Virenscanner hat daraufhin das Programm akzeptiert. Signatur ist vorhanden und validiert, genau so, wie das Konzept Code-Signierung gedacht ist. Code-Signierung ist also genau die Lösung für unser Problem.

Trotzdem hinterlässt dieses Experiment ein ungutes Gefühl bei mir. Denn was bringt die Überprüfung einer Signatur auf einem infizierten Rechner, wenn genau die CA-Zertifikate eben dieses befallenen Rechners zur Verifizierung herangezogen werden?. Wie erkennt ein Viren-Scanner, ob die Zertifikate auch nach einer Infektion noch vertrauenswürdig sind?