Viele Linuxer wissen nicht um die Gefährlichkeit von Kernelmodulen, deren Herkunft und Integrität nicht 100-prozentig gesichert ist. Noch weniger bekannt ist, dass sich eigene Systeme mit signierten Kernelmodulen gegen Codemanipulationen schützen lassen – vorausgesetzt man macht es richtig.
Der beste Aufenthaltsort für einen Linux-Trojaner ist ein Kernelmodul. Von dort darf er auf alle Ressourcen zugreifen, Kernelfunktionen auf sich umbiegen und sich optimal vor Enttarnung schützen: Da sein Code mit Kernelprivilegien läuft, kann er Schutzfunktionen wie SE Linux oder Dateisignaturen-Scanner abschalten, Code nachladen und starten, Tasks vor »ps« verbergen oder Netzwerktools beliebige Dinge vorgaukeln. Eine Schadsoftware als Kernelmodul besitzt faktisch mehr Rechte als Root, weshalb der nicht mal mehr auf die Vollständigkeit eines »ls -a« in seinem Homeverzeichnis zu trauen braucht.
Die Gefahr ist nicht nur theoretischer Natur. Es reicht der Tipp in einem Forum, nach dem es gut sei, ein alternatives Repository einzubinden, um irgendein Grafikproblem zu lösen – und schon kommt ein bösartiges Kernelmodul geflogen.
Aber auch reguläre Repositories sind bereits Ziel gelungener Crackerangriffe geworden. Allein die NSA und der GCHQ sollten auch Linuxer gelehrt haben, dass vieles weitaus schlimmer ist als befürchtet. Sicherheit auf einem akzeptablen Niveau ist nur mit Misstrauen herzustellen. Darum: Traue nur dem Code, den du selbst kompiliert hast – besonders wenn die Software mit weitreichenden Privilegien läuft.
Der Linux-Kernel ist seit der Version 3.7 im Jahr 2012 offiziell auf Misstrauen vorbereitet. Linus Torvalds hatte damals signierte Kernelmodule eingeführt, ein Sicherheitskonzept, das Microsoft mit der ersten 64-Bit-Version von Windows Vista schon ins Gespräch gebracht hatte. Aber anders als bei Windows – und zentral für die eigene Sicherheitsstrategie – darf der Linux-User selbst bestimmen, welchen digitalen Unterschriften er vertraut!
Es gibt also Unterschiede: Nur weil ein System signierte Kernelmodule verwendet, ist es nicht gleich sicherer und besser vor Trojanercode geschützt. Das hat der Stuxnet-Angriff auf die iranischen Atomanlagen eindrucksvoll gezeigt. Stuxnet hat sich dank gültiger Signatur als Treiber im Windows-Kernel eingenistet und so an zentraler Stelle die Kontrolle über das System übernommen [1].
An eine gültige Signatur durch Unterwanderung der ausstellenden Instanz zu kommen scheint nicht schwer zu sein – mit politischem Druck oder genug Geld. Alternativ lassen sich Zertifikate auch stehlen. Und da es keinen fehlerfreien Code gibt, hebelt ein Angreifer unter Umständen die Überprüfung auf eine gültige Unterschrift mit einer Zero-Day-Attacke aus. Wer möchte mit Bestimmtheit behaupten, dass sich im Zertifikatsspeicher seines Rechners kein eingeschmuggeltes Zertifikat befindet? Geheimdienste fordern diese schon seit Jahren.
Trumpfkarte Linux
Signierte Kernelmodule sind so vertrauenswürdig wie der eingesetzte Quellcode und die Zertifikat-ausstellende Instanz (CA, Certification Authority). Genau an dieser Stelle spielt Linux seine Trümpfe aus: Der offene Quellcode, aus dem der eigene Kernel entsteht (Kernel und Binary passen damit zusammen), erlaubt es, auf Hintertüren zu checken.
Von allen Zertifikat-ausstellenden Instanzen ist nur eine einzige vertrauenswürdig – die eigene. Der Fall Diginotar [2] ist nur einer von vielen Vorfällen der letzten Jahre, die die Sinnlosigkeit kommerzieller, zentraler PKIs demonstrierten. Um es klar auszudrücken: Wer heute einer nicht selbst betriebenen PKI den eigenen Rechner ernsthaft anvertraut, hat nichts mehr zu verlieren. Wer jedoch seine Schlüssel und Zertifikate selbst erzeugt und das System eigenständig zusammenbaut, fährt durch signierte Kernelmodule einen erheblichen Sicherheitsvorteil ein.
Ist ein Kernel mit der Option »Module signature verification« übersetzt, verlangt er beim Laden eines Kernelmoduls eine digitale Unterschrift [3]. Ist außerdem die Option »Require modules to be validly signed« ausgewählt, lädt er das Modul nur, wenn die digitale Unterschrift des Moduls mit einer im Kernel hinterlegten Unterschriftenprobe (Zertifikat) übereinstimmt (Abbildung 1). Hat die Konfiguration die Option nicht gesetzt, werden Module ohne digitale Unterschrift oder Module, für die es keine Unterschriftenprobe gibt, zwar geladen, aber als »tainted« , also als “verschmutzt” markiert. Erst wenn das Modul eine für den Kernel nicht identifizierbare Unterschrift trägt, lädt Linux es überhaupt nicht.
Die digitale Unterschrift stellt zudem mit Hilfe eines Hashalgorithmus die Integrität des Moduls sicher. Damit wird nachvollziehbar, ob der Modulcode seit dem Zeitpunkt, an dem er digital unterschrieben wurde, unverändert blieb. Der Schutz der Integrität und der Authentizität kann (muss aber nicht) die Sicherheit erhöhen.
Zertifikate im Kernel
Asymmetrisch signierte Kernelmodule brauchen ein Schlüsselpaar, also einen öffentlichen und einen privaten Schlüssel (Abbildung 2). Einerseits sind dann im Betriebssystemkern eine oder mehrere Unterschriftenproben in Zertifikaten hinterlegt. Andererseits unterschreibt der Kernelmodul-Übersetzungsvorgang die Binaries kryptographisch.
Gewöhnlich liegen beide Schlüssel im Kernelquellcode-Verzeichnis als »signing _key.priv« und »signing_key.x509« bereit. Andernfalls generiert Linux das Schlüsselpaar selbstständig mit Hilfe eines Perl-Programms im Linux-Quellcode: »scripts/sign-file« (Abbildung 3). Die Informationen zum Aussteller und zur Verwendung entnimmt das Kernel-Buildsystem der Datei »x509.genkey« . Falls auch die fehlt, legt sie der Kernel ebenfalls an.
Linus Torvalds hatte übrigens im Git-Repository seines Quellcodes vor der Version 4.1 als ausstellende Instanz den Planetendesigner Slartibartfast vom Douglas-Adams-Planeten Magrathea angegeben (»slartibartfast@magrathea.h2g2« ). Jetzt motiviert er eher irdisch mit »unspecified.user@unspecified.company« jeden Kernelbäcker, individuelle Daten vor dem Kompilieren in die Datei »x509.genkey« einzutragen. Insbesondere betrifft dies die Angaben unterhalb des Abschnitts »[ req_distinguished_name ]« .
Wer möchte, kompiliert in den Kernel gleich mehrere Unterschriftenproben ein. Dazu legt er die Zertifikate in das Linux-Quellcodeverzeichnis. Sie müssen die Datei-Erweiterung ».x509« tragen und vom Typ X.509 sein [4]. Den Versuch, Unterschriften nach dem Microsoft-Standard Authenticode [5] zu erlauben, hat Linus Torvalds vor einiger Zeit kategorisch abgelehnt. Der Superuser kann durch Aufruf von »cat /proc/keys« prüfen, welche Zertifikate sich im Kernel befinden (Abbildung 4).
Der Kernel sortiert seine Schlüssel über Schlüsselbünde, so genannte Keyrings. Auch ist es möglich, nachträglich über das Programm »keyctl« weitere Schlüssel dem Kernel hinzuzufügen:
keyctl padd asymmetric "" System-Keyring-ID < Keyfile
Zu beachten ist die Randbedingung, dass der zu importierende Schlüssel mit einem bereits im Kernel befindlichen Schlüssel unterschrieben sein muss. Zudem ist es möglich, Schlüssel aus einem Hardware-Store zur Prüfung von Modul-Integrität und Authentizität heranzuziehen.
Um Kernelmodule zu signieren, braucht man die Kernelquellen. Wer sie nicht per »git« , sondern über http://www.kernel.org lädt, überprüft unbedingt die digitale Unterschrift zum Kernelquellcode! Die Unterschriftenprobe (Zertifikat) von Linus Torvalds respektive von Greg Kroah-Hartmann gibt es über das Kommando »gpg –recv-keys« . Dann packt man das Archiv aus.
Für Produktivsysteme ist die Art des Kernelbezugs natürlich ungünstig, da sie am Paketmanagement vorbei passiert. Daraus erwächst auf Dauer ein Problem: Mit dem nächsten Kernel-Sicherheitsupdate kommt ein neuer Binärkernel ohne Signatur aus dem Repository der Distribution, der die selbst hergestellte Version außer Kraft setzt. Wer seinem Paketmanagement das untersagt, um der Nutznießer signierter Module zu bleiben, betreibt sein System mit mehr und mehr ungestopften Sicherheitslöchern.
Zwei Wege führen aus diesem Dilemma: Entweder baut sich der Admin eine Toolchain, die stets aktuelle Kernelquellen ins System holt und sie entsprechend der gleich folgenden Beschreibung automatisch übersetzt. Oder der Verantwortliche benutzt die Distributions-offiziellen Kernelquellen in Paketform.
Kernel passend machen
Ob von Kernel.org oder von seinem Distributor: Der Modul-Bäcker kopiert sich eine gültige Kernelkonfiguration zum Beispiel aus »/boot/« ins Kernelquellcode-Verzeichnis und ruft »make menuconfig« auf. Bei der Option »Enable loadable module support« wählt er »Module signature verification« und »Require modules to be validly signed« aus. Wer mag, ändert den Hashalgorithmus, die Vorauswahl zeigt auf die stärkste Variante SHA-512.
Dann legt der Admin die Datei »x509.genkey« an, wofür er beispielsweise die aus dem Linux-Quellcode stammende Variante aus Listing 1 als Basis nutzt. Er ergänzt die Parameter unter »req_distinguished_name« , die Organisation, den Common Name und eine sinnvolle E-Mail-Adresse, über die er den Aussteller des Zertifikats (sich selbst, sein Unternehmen) identifizierbar macht.
Listing 1
x509.keygen mit Meta-Informationen
01 [ req ] 02 default_bits = 4096 03 distinguished_name = req_distinguished_name 04 prompt = no 05 string_mask = utf8only 06 x509_extensions = myexts 07 08 [ req_distinguished_name ] 09 O = Magrathea 10 CN = Glacier signing key 11 emailAddress = slartibartfast@magrathea.h2g2 12 13 [ myexts ] 14 basicConstraints=critical,CA:FALSE 15 keyUsage=digitalSignature 16 subjectKeyIdentifier=hash 17 authorityKeyIdentifier=keyid
Nicht ins Netz gegangen
Bevor es gleich mit dem Übersetzungsvorgang losgeht, kappt der Linuxer dem Rechner alle LAN- und WLAN-Verbindungen, um nicht in letzter Sekunde digital überfallen zu werden. Danach reicht ein »make -j6« aus, um den Compilerlauf zu starten. Ein paar Tassen Tee später liegen Kernel und Module fertig da zum Installieren. Ein »make modules-install« installiert die Module. Dann kopiert Root den Kernel in das Verzeichnis »/boot/« , ebenso die aktuelle Config, generiert das Initram-FS und legt es ebenfalls im Verzeichnis »/boot/« ab.
Ein weiterer wichtiger Schritt: Den privaten Schlüssel vor fremdem Zugriff sichern! Gemäß der Maxime “Security by Isolation” verschiebt der Admin den privaten Schlüssel, also die Datei »signing_key.priv« , auf einen nur dafür verwendeten USB-Stick und stellt sicher, dass sich nach der Aktion keine Kopie mehr im Verzeichnis »/usr/src/linux/« befindet! Dann hängt er den externen Speicher aus. Danach darf nur noch der, der den Stick in der Hand hat, Module für den Rechner generieren.
Jetzt kann sich das Gerät wieder mit dem Internet verbinden. Zuletzt steht auf einem Ubuntu 14.04 noch ein »update-grub« -Lauf an, damit der Bootloader den frisch generierten Kernel bootet. Der ist mit dem nächsten Reboot aktiv. Listing 2 fasst noch mal alle Befehle zusammen, die Pfade sind anhand der lokalen Gegebenheiten anzupassen.
Listing 2
Befehle unter Ubuntu 14.04, um einen Kernel mit signierten Modulen herzustellen
01 # Kernel-Download und Konsistenzcheck 02 cd /usr/src/ 03 wget https://www.kernel.org/pub/linux/kernel/v4.x/linux-4.1.1.tar.xz 04 wget https://www.kernel.org/pub/linux/kernel/v4.x/linux-4.1.1.tar.sign 05 xz -d linux-4.1.1.tar.xz 06 gpg --keyserver hkp://keys.gnupg.net --recv-keys 00411886 6092693E 07 gpg --verify linux-4.1.1.tar.sign 08 tar xvf linux-4.1.1.tar 09 10 # alte Konfiguration als Basis 11 cp /boot/config-`uname -r` /usr/src/linux-4.1.1/.config 12 make menuconfig 13 [Im Menü einstellen:] 14 [Enable loadable module supprt][Module signature verification] 15 [Enable loadable module supprt][Require modules to be validly signed] 16 [Enable loadable module supprt][Automatically sign all modules] 17 # 18 Konfiguration für die Zertifikatserstellung anlegen 19 vi x509.genkey 20 [...] 21 22 # Netzverbindung kappen!!! 23 make -j6 24 sudo make -j6 modules-install 25 sudo cp arch/x86/boot/bzImage /boot/vmlinuz-4.1.1+ 26 sudo cp .config /boot/config-4.1.1+ 27 sudo mkinitramfs -o /boot/initrd.img-4.1.1+ 4.1.1+ 28 29 # Privaten Key auf einem externen Speichermedium (USB-Stick) sichern 30 # Pfad zum USB-Stick muss angepasst werden 31 sudo mv signing_key.priv /media/quade/usb-stick/ 32 33 # Externes Speichermedium sicher entfernen, Rechner wieder mit dem Netz verbinden, Bootloader aktualisieren 34 sudo umount /media/quade/usb-stick 35 sudo update-grub
Eigene Kernelmodule
Beim Aufruf von »make modules-install« signiert das Kernel-Buildsystem nun Module. Wer eines außerhalb der Quellen pflegt, ruft nach der Generierung »scripts/sign-file« auf. Das Kommando bekommt vier Parameter übergeben: Den Hashalgorithmus, die Schlüssel »signing_key.priv« und »signing_key.x509« und den Modulnamen. Ein Makefile lässt sich um die Signierfunktionalität ergänzen, Listing 3 zeigt ein Beispiel. (Die dort benutzte Demo-Anwendung gibt es auf [6].)
Der Admin darf nicht vergessen zuvor die Netzwerkverbindung wieder zu kappen, den Schlüssel zu kopieren und nach dem Signieren die Kopie des Schlüssels zu löschen, bevor er die Netzwerkverbindung wiederherstellt. Statt des Kopierens und Löschens lässt sich beim Aufruf von »scripts/sign-file« noch besser direkt auf das externe Speichermedium referenzieren, das vor der Verbindung mit dem Netz wieder ausgehängt wird.
Abbildung 4 veranschaulicht den Vorgang: Zunächst erzeugt »make« das Modul und Root versucht es zu laden. Letzteres misslingt, da der benötigte Schlüssel fehlt. Das Auslesen von »cat /proc/keys« zeigt die im Kernel abgelegten Unterschriftenproben. Nachdem das Modul aber signiert ist, lässt es sich problemlos laden. Das Kommando »modinfo« zeigt die Unterschrift und den verwendeten Hashalgorithmus an.
Einen Kernel mit signierten Modulen zu backen ist recht einfach, wenn der Bäcker als Zutat ein selbst erzeugtes Schlüsselpaar nimmt und den privaten Key sorgfältig isoliert. Die Signaturen sind plattformunabhängig und funktionieren deshalb auch auf eingebetteten Systemen und dem Raspberry Pi. So gewappnet werden die Geheimdienste wohl länger benötigen, um ihre Kuckuckseier ins Linux-Nest zu legen.
Listing 3
Makefile für ein signiertes Kernelmodul hello.ko
01 ifneq ($(KERNELRELEASE),) 02 obj-m := hello.o 03 else 04 KDIR := /lib/modules/$(shell uname -r)/build 05 PWD := $(shell pwd) 06 07 default: 08 $(MAKE) -C $(KDIR) M=$(PWD) modules 09 cd $(KDIR) && \ 10 ./scripts/sign-file sha512 ./signing_key.priv \ 11 ./signing_key.x509 $(PWD)/hello.ko 12 endif 13 14 clean: 15 rm -rf .*.cmd *.mod.o modules.order Module.symvers *.ko *.mod.c 16 rm -rf *.o
Infos
- Stuxnet: https://de.wikipedia.org/wiki/Stuxnet
- Diginotar: https://de.wikipedia.org/wiki/DigiNotar
- “Kernel Module Signing Facility”, Kernel-Dokumentation: https://www.kernel.org/doc/Documentation/module-signing.txt
- X.509: https://de.wikipedia.org/wiki/X.509
- Authenticode, Microsoft Developer Network: https://msdn.microsoft.com/en-us/library/ms537359%28v=vs.85%29.aspx
- Listings des Artikels und die Demo-Anwendung zu Listing 3: ftp://www.linux-magazin.de/pub/listings/magazin/2015/09/Kern-Technik










