‹‹‹ Übersicht Kolumne

Windows DLLs unter Linux, MacOS oder BSD nutzen
Pleiszenburg.de veröffentlicht quelloffenes Projekt 'zugbruecke'

zugbruecke ist ein experimentelles Python-Modul (gegenwärtiger Entwicklungsstatus 3/alpha). Es dient dazu, Routinen in Windows-DLLs aus Python-Code heraus aufzurufen, welcher auf Unix / Unix-ähnlichen Betriebssystemen wie Linux, MacOS oder BSD läuft. zugbruecke ist als hundertprozentiger Ersatz für ctypes aus Pythons Standardbibliothek ausgelegt. zugbruecke baut auf Wine auf. Ein eigenständiger Windows-Python-Interpreter, welcher mit Hilfe von Wine im Hintergrund automatisch startet, wird benutzt, um die aufgerufenen DLL-Routinen auszuführen. Die Kommunikation zwischen der Unix- und der Windows/Wine-Seite basiert auf Pythons eingebauter Fähigkeit für "multiprocessing connections" (Interprozesskommunikation zwischen Python-Interpretern). zugbruecke unterstützt Zeiger und struct-Typen - mit Einschränkungen. zugbruecke bietet umfassende Möglichkeiten zur Ausgabe von Statusnachrichten (Logs), welche es erlauben, Probleme mit sich selbst sowie mit Wine zu analysieren. zugbruecke wurde in Python-3-Syntax entwickelt und zielt primär auf die CPython-Implementierung von Python.

  • Interoperabilität
  • Linux
  • Python
  • Python 3
  • Python-Modul
  • Softwareentwicklung
  • Unix
  • Wine
  • ctypes

Hintergrund

Der Wunsch, Routinen aus DLLs von Linux-/Unix-Programmen aus aufzurufen, spiegelt sich in unzähligen Foren-Einträgen und Fragen auf Mailing-Listen wieder und reicht, so weit heute nachvollziehbar, weit über ein Jahrzehnt zurück. Allein die folgenden zwei Fragen auf StackOverflow wurden jeweils über rund 8 Jahre ca. 20.000 mal besucht, ohne jemals für die Fragesteller zufriedenstellend beantwortet zu werden:

Es kommt immer wieder vor, dass relevante Funktionen nur in geschlossenen oder propritären DLLs angeboten werden, zu welchen es kein Pendent in der Unix-Welt gibt. Andererseits kommt es auch nicht gerade selten vor, dass sich eine ältere oder hochkomplexe Codebasis nicht ohne Weiteres portieren lässt. Im allgemeinen wird an dieser Stelle dazu geraten, eine Anwendung basierend auf "winelib" zu bauen, einer Bibliothek, die zum Wine-Projekt gehört. Dieser Ansatz erlaubt es, eine für Windows geschriebene Codebasis ohne ernsthafte Modifikationen unter Unix zu übersetzen und gegen Windows-Bibliotheken sowie, aus der gleichen Anwendung heraus, gegen Unix-Bibliotheken zu linken. Auf diese Weise lässt sich eine Brücke zwischen beiden Welten konstruieren. In der Relatität stellt sich dieser Prozess jedoch alles andere als einfach dar und führt nicht unbedingt direkt zum gewünschten Resultat. Hat man beispielsweise nur eine einzelne DLL, welche man unter Unix nutzen möchte, dann muss man eine dazu passende "winelib"-Anwendung entwickeln und sich darüber hinaus noch etwas einfallen lassen, wie diese mit anderen "reinen" Unix-Prozessen kommunizieren kann. Dies stellt einen nicht unerheblichen (Lern-) Aufwand dar, wobei viele der dabei notwendigen Schritte nur maßig bis schlecht dokumentiert sind.

Über die Jahre immer wieder vor ähnliche Probleme gestellt habe ich immer wieder spezielle, angepasste Lösungen für Probleme dieser Art entwickelt beziehungsweise entwickeln müssen. Mein Wunsch war es jedoch, über eine generische Lösung zu verfügen, mit welcher ich direkt aus einer Unix-Anwendung heraus auf DLLs zugreifen kann, ohne mich im die Implementierung einer "winelib"-Anwendung und Interprozesskommunikation kümmern zu müssen. Da ich in vielen Fällen Code schreibe, der im Kern durch Python-Scripte gesteuert wird, bestand die logische Konsequenz darin, eine zu "ctypes" kompatible API zu entwickeln, welche meine bisherigen Entwicklungen vereint. Mit zugbruecke stelle ich das Resultat dieser Arbeit in einer geordneten, allgemeingültigen Form als quelloffenes Projekt unter der GNU Lesser General Public License v2.1 zur Verfügung.

Über das Projekt

zugbruecke verhält sich wie ein direkter Ersatz für ctypes, wobei ctypes intern weiterhin zum Einsatz kommt. Folgender Code, unter Linux, MacOS oder BSD ausgeführt, ruft beispielsweise die Funktion "pow" in Microsofts MSVCRT.dll auf:

from zugbruecke import cdll, c_double
dll_pow = cdll.msvcrt.pow
dll_pow.argtypes = (c_double, c_double)
dll_pow.restype = c_double
print('Hier erscheint "1024.0": "%.1f".' % dll_pow(2.0, 10.0))
Quelltext 1

Realisiert wird der Aufruf über einen zweiten Python-Interpreter für Windows, welcher automatisch auf Wine gestartet wird und sich um die Abwicklung der Funktionsaufrufe in DLLs kümmert. Um mit Zeigern umgehen zu können bietet zugbruecke ein spezielles "memsync"-Protokoll, welches analog zu den Parametern "argtypes" und "restype" für eine Funktion bestimmt, welche Speicherbereiche basierend auf welchen Parametern zwischen der Unix- und der Wine-Seite synchronisiert werden müssen. "memsync" ist dabei so gestaltet, dass es durch ctypes ignoriert wird, sollte der Code einmal direkt auf Windows ausgeführt werden müssen. Die Entwicklung von platformunabhöngiger Software ist somit relativ einfach möglich. Die Interprozesskommunikation zwischen der Unix- und der Wine-Seite wird durch "multiprocessing connections" aus der Python-Standardbibliothek umgesetzt.

Anwendungsfälle

Das primäre Augenmerk liegt auf dem fehlerfreien Aufruf von Funktionen und der verhältnismäßg guten Wartbarkeit sowie Erweiterbarkeit des Codes. Geschwindigkeit und Sicherheit sind im Moment sekundär. zugbruecke wurde mit dem Ziel entwickelt, in der Lage zu sein, beispielsweise komplexe numerische Berechnungen in DLLs aufzurufen oder exotische beziehungsweise veraltete Datenformate mit Hilfe entsprechender alter DLL-Dateien schnell und unkompliziert lesen zu können.

zugbruecke sollte nicht genutzt werden, wenn es um sicherheitskritische Funktionen wie Authentifizierung oder die Ver- beziehungsweise Entschlüsselung sensibler Daten geht. Ebenfalls ungeeignet ist es für Szenarien, in denen verhältnismäßig kleine Funktionen sehr oft aufgerufen werden müssen, da hierdurch ein nicht zu unterschätzender zeitlicher Nachteil entsteht. Zum gegenwärtigen Zeitpunkt schlägt die Abwicklung eines Funktionsaufrufs auf moderner Hardware durchschnittlich mit etwas weniger als 0,2 µs zu Buche.

Weiterführende Links

Alternative Ansätze

Neben "winelib"-Anwendungen gibt es nur noch einige wenige nennenswerte Ansätze, um auf DLLs unter Unix-System zugreifen zu können. Diese sollen an dieser Stelle Erwähnung finden.

Hauptsächlich durch die Unterstützung von Windows-Treibern unter Linux haben sich im letzten Jahrzehnt zwei Projekte einen Namen gemacht: DriverLoader von Linuxant sowie NDISwrapper von Pontus Fuchs, Giridhar Pemmasani und anderen. Während ersteres, propritäres Projekt durch Linuxant inzwischen für obsolet und beendet erklärt worden ist, existiert das zweite, quelloffen unter LGPL veröffentlichte Projekt immer noch, auch wenn es seit inzwischen 4 Jahren kaum noch gepflegt wurde. Nennenswert sind diese Projekte deshalb, weil sie demonstrieren, dass man prinzipiell sogar sehr systemnahe Windows-DLLs unter Unix-Systemen aufrufen und nutzen kann.

Letzterem Gedanken folgend erregte in jüngster Vergangenheit der Google-Entwickler und Sicherheitsanalyst Tavis Ormandy einiges Aufsehen, als er auf GitHub Werkzeuge veröffentlichte (lizensiert under der GNU General Public License v2.0), welche, abgeleitet von NDISwrapper, in der Lage sind, Windows-DLLs nahezu direkt und ohne komplexe Zwischenschicht wie Wine auf Linux auszuführen. Seine Entwicklung hatte die Motivation, unter Linux in der Lage zu sein, mit Fuzzing Windows-Antivirus-Software auf Schwachstellen hin zu untersuchen. Er fand dann auch gleich eine, inhaltlich zutreffend als "dramatisch" (technisch: wormable/wurmbar) eingestufte, Sicherheitslücke in Micosofts Virenscanner-Engine. Einige Kommentare auf GitHub lassen darauf schließen, dass sich das "loadlibrary" genannte Projekt auch zur Entwicklung von Linux-Software nutzen lässt, welche auf Windows-DLLs zugreifen kann. Ormandy weist jedoch klar darauf hin, dass sein Projekt keinen vollständigen Nachbau der Windows-API anstrebt wie das Wine-Projekt und damit nur für sehr rudimentäre DLLs mit wenigen Systemaufrufen in Frage kommt.

‹‹‹ Übersicht Kolumne