Dynamic Mach overriding, dynamisches Überschreiben und Code Injection: Warum ein Angreifer davon nichts hat.
Im Jahr 2001 dachten einige Mac-Entwickler-Veteranen darüber nach, wie man wohl bei Mac OS X etwas den Systemerweiterungen von System 7-9 vergleichbares schaffen könnte, nämlich ausgewählte Verhaltensweisen von System- und Anwendungsprogrammen dynamisch zu überschreiben. Dynamisch bedeutet dabei, daß die Programme nur im Hauptspeicher und nicht auf der Festplatte verändert werden sollen.
Im Juni 2003 stellte der Mac-Entwickler mit dem schönen Namen Jonathan Wolfgang von Rentzsch, Rufname Wolf, eine Lösung dazu auf der MacHack 2003 vor, die dort zum besten Konferenz-Beitrag gewählt wurde.
Seine Lösung ist auch als Programmquelltext frei verfügbar und inzwischen weit verbreitet in vielen Mac-Programmen enthalten, beispielsweise in:
Um festzulegen, was gemeint ist, einige Definitionen, auf die ich mich weiter unten beziehe.
Ein Programm ist eine ausführbare Datei auf der Festplatte.
Ein Prozeß ist ein Programm, was gestartet wurde, sich im Hauptspeicher befindet und vom Prozessor ausgeführt wird. Also ein laufendes Programm.
Ein Thread ist ein Teil eines Prozesses. Ein Thread besteht aus einer Reihe von Anweisungen, die eine bestimmte Teilaufgabe erledigen. Ein Thread kann zeitgleich mit anderen Threads ausgeführt werden. Ein Prozeß besteht aus mindestens einem Thread.
Das dynamische Überschreiben besteht aus zwei Teilen.
Der mach_override-Teil ermöglicht es, eine vorhandene Funktion mit einer neuen Funktion zu ersetzen oder zu ergänzen. Das klappt sowohl mit Funktionen, die das System anbietet, als auch mit solchen, die man in eigenen Programmen definiert.
Der mach_inject-Teil erlaubt es einem Prozeß, Programm-Code zu laden und in einem anderen Prozeß auszuführen. Das passiert zur Laufzeit.
Man kann also Code schreiben und mit Hilfe von mach_inject in ein anderes Programm einschleusen, das gerade läuft. Und mit mach_override kann man dann eine Funktion des Wirtsprogrammes verändern, indem der eingeschleuste Code diese Funktion überschreibt oder ergänzt.
Das klingt zuerst alles ziemlich erschreckend, aber das ist es nicht, denn mach_inject benutzt eine Programmbibliothek, eine API, die die Grenzen zwischen den Benutzern des Betriebssystems respektiert; insbesondere Grenzen zwischen den Programmen und Prozessen, die unter den jeweiligen Benutzern ausgeführt werden. Ein Prozeß, der kein System-Prozeß ist (System bedeutet root), kann zum Beispiel nicht mit Hilfe von mach_inject Code in einen System-Prozeß einschleusen. Und ein Benutzer kann auch nicht die Prozesse eines anderen Benutzers beeinflussen hiermit. Allein seine eigenen Prozesse sind als Ziel für jeden Benutzer möglich. Nur root hat Zugriff auf alle Prozesse. In Leopard werden explizit POSIX-Rechte überprüft. Trotz Unterschieden im Detail bei den verschiedenen Versionen des Betriebssystems galt immer: Man kann maximal das tun, was die UNIX-Grenzen erlauben.
Es ist vollkommen legitim, daß ein Benutzer Zugriff auf seine eigenen Prozesse und deren Speicher hat. In diesem Fall verändert er innherhalb eines seiner Prozesse den Sprung zu einer Funktion so, daß der Sprung zu einer anderen Stelle im Programm erfolgt. An dieser anderen Stelle hat er zuvor zusätzliche Programmbefehle eingefügt, die nun ausgeführt werden. Um dies zu tun, bedient man sich der für auf dem jeweiligen Betriebssystem für Speicherzugriffe zuständigen API.
Wie dynamisches Überschreiben vorgeht.
An verschiedenen Stellen dieses Vorgehens müssen Speicherbereiche angefordert werden, beschreibbar und ausführbar gemacht werden. Das wird mit Hilfe der Mach-Programmbibliothek (API) möglich, die für die Verwaltung von Speicher und Prozessen zuständig ist als Teil des XNU -Kernels von Mac OS X.
Objective C
-Funktion sein, wenn man mit
relativ wenig Aufwand, viel Wirkung erzielen möchte. Die meisten
Funktionen von Mac OS X
sind jedoch nur als reine C
-Funktionen verfügbar, wozu alles
von Mach,
von BSD und von Carbon gehört.
Und selbst wenn diese C
-Funktionen indirekt durch eine
Objective C
-(Wrapper-)Funktion
verfügbar gemacht werden, nutzt das nicht viel, denn
es kann sein, daß das Zielprogramm die C
-Funktion direkt verwendet und
ein Überschreiben der Objective C
-Funktion daher keine Auswirkung auf das
Zielprogramm hätte. Man kann daher meistens nicht
die Originalfunktion überschreiben, sondern
bestenfalls eine Wrapper-Funktion, falls vorhanden.
Einfach formuliert funktioniert dies mit Hilfe der Mach-Programmbibliothek (siehe oben) so:
Thread
angelegt für den einzuschleusenden
Programm-Code.
Durch die kombinierte Nutzung von
mach_override und mach_inject
kann man neue Möglichkeiten
(Features) in vorhandene Programme einführen. Dazu
nutzt man die beiden Pakete, um Code mit den
gewünschten Features einzuschleusen.
Der eingefügte neue Thread
führt das Überschreiben durch.
Grundsätzlich verwendet das dynamische Überschreiben schlichtes C
und
Maschinensprache. Im Grunde könnte man auch damit zufrieden sein,
alle neuen Anweisungen an der Stelle abzulegen, wohin man den
Programmfluß aufgrund des ausgewechselten Sprungbefehls umgeleitet hat.
Diese Umleitung ist meist zweistufig, denn man fügt an der Zielstelle
des ersten Sprungs noch einen weiteren Sprung ein, um nicht
bezüglich der Sprungadresse eingeschränkt zu sein auf das, was die
Ersetzung des originalen absoluten Sprunges ermöglicht.
Einzige Bedingung beim Umleiten (Überschreiben) ist, daß hierfür ein absoluter Sprung
in Maschinensprache ersetzt wird. Alles Weitere baut darauf auf. Ob
die Funktion oder Methode, die zu dem ersetzten Sprung gehört, C
oder
Objective C
ist, ist für die Machbarkeit egal. Handelt es sich jedoch
um Objective C
, dann macht es das Leben einfacher, weil das
eingeschleuste Programmfragment (Ziel der Umleitung) dann zusätzlich noch
Objective C Categories
zum Ersetzen von weiteren Methoden zur Laufzeit oder
Objective C Posing
zum Ersetzen von Klassen zur Laufzeit verwenden kann,
indem es ein entsprechendes Objective C
-Programm-Bündel lädt.
Handelt es sich jedoch um normales C
, dann kann man zwar auch noch
weitere Programmteile nachladen, was aber die gleiche Wirkung hat, als hätte man
gleich mehr an die Umleitungs-Stelle eingeschleust. C
ermöglicht nicht
Funktionen zur Laufzeit umzudefinieren. Hier beschränkt sich die Veränderung dann
auf den einen auf Maschinen-Befehl-Ebene im Speicher umgeleiteten Funktions-Aufruf.
Der Unterschied bei den Sprachen liegt also darin, daß man im Falle von
C
nur die eine Funktion überschreiben (oder ergänzen) kann und
der eingeschleuste Code per CFBundle
weiteren Code laden kann, und daß im Falle von
Objective C
der per NSBundle
nachgeladene
Code zusätzliche eigene Überschreibungen vornehmen kann,
weil Objective C
das grundsätzlich ermöglicht.
Es ist nicht unbedingt notwendig, eine Objective C
-Methode als
Ziel zu nutzen, aber dies erleichtert die Arbeit, weil man zusätzliche
Programmteile einfacher laden und mehr Methoden ersetzen kann. Wenn man
ein Cocoa-Programm als Ziel hat, ermöglicht dies
mehr als nur die eine Funktion (mit dem absoluten Sprung) dynamisch
zu überschreiben. Der Unterschied zur normalen
Cocoa-Programmierung besteht dann
hier darin, daß das Nachladen nicht schon bei Programmerstellung vorgesehen
war. Dynamisches Laden und Ersetzen zur Laufzeit ist jedoch normal bei
Objective C
. Es eröffnet dem eingeschleusten Code
halt komfortablere Möglichkeiten für zusätzliche dynamische Änderungen.
Die System-Einstellungen als prominentestes
Beispiel in Mac OS X
laden ihre Module dynamisch, also zur Laufzeit, wenn die entsprechende
Einstellung angeklickt wird, nach.
Vergleiche dazu folgende Quellen:
Scott Knaster: Hacking Mac OS X Tiger, Kapitel 22 (geschrieben von Jonathan Rentzsch)
Behind the Red Shed, with Jonathan 'The Wolf' Rentzsch
Ende 2004 bis Anfang 2005 (also 1,5 Jahre nach Veröffentlichung dieser Technik) gab es Vorträge, Folien, Interviews und Hofhaltung in Foren durch Angelo Laub und andere selbsternannte "Hacker", die an dieser Technik und ihrer Entwicklung vollkommen unbeteiligt waren. Sie verbreiteten diverse Unrichtigkeiten über diese Technik und wollten mit Hilfe der dadurch erzeugten Besorgnis Aufmerksamkeit auf sich lenken.
Deren sachlich falsche Aussagen bedürfen einer Richtigstellung.
Angeblich sei mach_inject eine Schwachstelle für Mac OS X. Angelo glaubte, daß Schadprogramme damit das System negativ beeinflussen können, was allerdings nicht so ist, wegen der oben genannten Grenzen zwischen den Benutzerprozessen und im speziellen den Systemprozessen. Er glaubte, es seien keine Privilegien notwendig. Da irrte er sich im selben Punkt nochmals, da man nur jeweils seine eigenen Prozesse (sprich: die unter dem eigenen Benutzer laufenden Programme) beeinflussen kann. Und auch in diesem Fall prüft Mac OS X selbstverständlich, ob die Privilegien ausreichen für den Zugriff, wie ich weiter unten anhand des Betriebssystem-Quelltextes erläutere.
Er behauptete weiterhin, damit sei ein Rootkit ohne root machbar. Das würde bedeuten, man könnte Systemprozesse, ohne selbst root zu sein, beeinflussen. Wie oben beschrieben ist das mit mach_inject aufgrund der zugrundeliegenden Programmbibliothek und der Benutzergrenzen unter Unix nicht möglich.
Auch ein sogenanntes "Userland"-rootkit ist damit nicht machbar, weil User- oder Admin-Rechte zum Nutzen der benötigten Funktion nicht ausreichen. Auf die Einzelheiten gehe ich weiter unten ein.
Ebensowenig bietet mach_inject eine freundliche Umgebung für Viren. Ein Virus hängt sich immer an eine Wirtsdatei. Mit mach_inject kann man jedoch keine Dateien ändern. Denkbar ist allenfalls ein Trojaner, der das Verhalten eines unter demselben Benutzer laufenden Programms ändert. Dazu müssen beide Programme zeitgleich unter demselben Benutzer laufen. Der Spuk ist jedoch in dem Moment vorbei, wo das Opferprogramm neu gestartet wird.
Genauso falsch ist seine Behauptung, ein Angreifer könne damit Systemprogramme zur Laufzeit negativ beeinflussen. Dazu müßte der Angreifer bereits root sein, denn sonst kann er auch mit mach_inject keinen Systemprozeß ändern.
Er behauptete, man könne beliebige C
-Funktionen zur Laufzeit damit
überschreiben. Das ist vollkommen falsch. Man kann damit nur Funktionen
eigener Prozesse (nicht beliebiger Prozesse) überschreiben, die im besten Fall als
Objective C
-Funktion verfügbar sind.
Siehe oben.
Nach seinem Vortrag wurde er gefragt, ob man so auch Code in eine Shared Library einschleusen könnte. Er wußte darauf keine Antwort und sagte, man solle es halt ausprobieren. Mich hat sein Unwissen über Mac OS X dann doch erstaunt. Es ist nämlich so, daß jeder Prozeß eine private Kopie der Shared Library bekommt, sobald der Prozeß eine Speicherseite der in seinen Adreßraum eingeblendeten Bibliothek beschreiben will. Die anderen Prozesse arbeiten hingegen weiter mit der originalen schreib-geschützten Version der gemeinsamen Bibliothek im Speicher. Dieser Mechanismus nennt sich copy-on-write und ist ein bekanntes übliches Verhalten.
Woher kamen seine Fehleinschätzungen und Wissenslücken? Es mag daran liegen, daß Angelo Laub nach eigenen Angaben zu der Zeit Student der Physik, Mathematik und Astronomie war. Also fachfremd. Man sollte keine reißerischen Vorträge über Themen halten, die man nicht ausreichend beherrscht. Damit erspart man sich und anderen grundlose Panik. Allerdings muß man dann auch auf die öffentliche Aufmerksamkeit verzichten. Für mich fallen selbsternannte "Hacker", die Techniken von anderen Leuten ohne tieferes Verständnis verwenden, eher unter den Begriff "script kiddy".
In seinem Papier schrieb Angelo ferner, daß die Sicherheitsauswirkungen von mach_inject noch erforscht werden müßten. Korrekt ist, daß sie von ihm noch nicht umfassend erforscht waren zu dem Zeitpunkt, was ihm Raum gab zu haltlosen Spekulationen, die er sich und uns hätte ersparen können, wenn er das Thema genauer betrachtet und durchdacht hätte.
Es war nicht das erste Mal, daß Angelo Fehlmeldungen veröffentlichte. In einem
Vortrag (und zugehöriger Dokumentation) über die "Unsicherheiten in
Mac OS X"
behauptete er, es wäre möglich, die graphische Oberfläche eines am Rechner arbeitenden
Benutzers zu steuern, indem man sich als Normalbenutzer
per SSH
verbindet und dann ein entsprechendes Skript zur Oberflächensteuerung aufruft. Das funktioniert
definitiv nicht; man bekommt bei dem Versuch die Fehlermeldung
kCGErrorRangeCheck : Window Server communications from outside of session allowed for
root and console user only
.
INIT_Processeses(), could not establish the default connection to the WindowServer.Abort trap
Zum Zeitpunkt seiner Behauptung war 10.3.6 aktuell. Der unerlaubte Übergriff wird jedoch auch von 10.3.0 unterbunden ebenso wie von 10.4.x und späteren Versionen.
Das Skript sollte die Benutzungs-Oberfläche eines angemeldeten Administrators steuern und automatisch einen weiteren Administrator anlegen, dessen Paßwort definieren und sich dann als dieser anmelden. Das Skript funktioniert nur, wenn es der Administrator selbst ausführt. Heutzutage würde selbst das nicht mehr klappen, weil die wichtigsten Systemeinstellungen nur nach erneuter Bestätigung durch einen Administrator überhaupt zugänglich werden.
Im der gleichen Dokumentation behauptet er, Firewire-Geräte hätten per DMA Zugriff auf den gesamten virtuellen Speicher. Tatsächlich haben sie Zugriff auf den physikalischen Speicher, aber nicht auf den virtuellen. DMA-Controller kennen nämlich keine virtuellen Adressen, sondern nur physikalische. Sie lesen und schreiben direkt das RAM.
Der "Nemo" (lateinisch: niemand) von "uninformed"
machten diesbezüglich ihrem
Seiten-Namen ebenfalls alle Ehre: Er bemerkt, daß man zwar ein Programm hinsichtlich
seiner Dateizugriffe mit chroot
einschränken kann, diese
Schranke jedoch nicht für andere Prozesse des Benutzers gilt. Kommuniziert nun
das "eingeschränkte" Programm mit einem nicht-eingeschränkten, dann kann
die Schranke umfahren werden, indem das andere Programm das tut, was das
eingeschränkte nicht tuen könnte. Diese Kommunikation kann beliebig erfolgen,
auch (umständlich) über mach_inject. Es wird hierdurch
keine neue Möglichkeit eröffnet, sondern nur ein weiterer Weg dorthin.
In seinem Beispiel startet er in irgendeinem anderen Prozeß des gleichen Benutzers mit Hilfe
von mach_inject eine shell
, die auf Kommandos
per Netzwerk wartet. Die shell
ist eine Bind Shell
aus dem
Metasploit
-Framework. Er kombiniert also zwei
vorhandene Techniken.
Der mit chroot
eingeschränkte Prozeß läßt dann per Netzwerk-Kommunikation
diese shell
die gewünschten Arbeiten verrichten, die er selbst nicht
erledigen kann.
Der andere Diskussions-Punkt bei "uninformed" war, daß man mit
mach_inject natürlich auch Variablen im eigenen
Speicher ändern kann. Ihre Bedenken bestehen darin, daß root
seine System-Variablen nun nicht nur über die üblichen Funktionsaufrufe
verändern kann, sondern auch (umständlich) über mach_inject.
Sie übersehen dabei, daß der einzig wichtige Punkt darin besteht, daß eben
nur root
diese Werte ändern kann und darf. Ob er das nun mit der
einen oder anderen Funktion durchführt ist ziemlich belanglos hinsichtlich
Sicherheit. Es ist wieder nur ein weiterer Weg um legitime Dinge zu tun. Wie sie
anmerken, wäre auch eine Kernel-Extension
ein möglicher weiterer Weg.
Das entspricht ungefähr dem Folgendem: Man hat das Recht, auf eine Datei oder eine Variable zuzugreifen. Das kann jedoch auf verschiedenen Wegen geschehen. Drei Wege sind offensichtlich und gut bekannt. Ein vierter Weg ist nur echten Kennern des Systems geläufig. Sie ereifern sich nun, daß dieser vierte Weg die anderen Wege "umgehen" kann, geben jedoch zu, daß die anderen drei sich auch gegenseitig umgehen. Keiner der Wege ermöglicht jedoch Unberechtigten den Zugriff auf die Datei oder die Variable. Wer hier trotzdem Sicherheitsrisiken reindeutet, sollte als Sicherheits-Experte bitte nicht mehr hausieren gehen.
Die "Uniformierten" liessen noch einen weiteren zentralen Punkt unbeachtet liegen,
weil er ihren Phantasien im Wege wäre: Der task_for_pid()
-Befehl, der
einem die Kontrolle über einen Prozeß geben kann, tut dies nur, wenn es den
UNIX-Rechten gemäßt erlaubt wäre, denn er prüft diese (und mehr), siehe unten.
Die Jungs von "uninformed" verkürzen das jedoch unzulässiger
Weise auf "man könne damit die UNIX-Rechte umgehen". Sie lassen weg, daß man das
nur kann, wenn man die Prüfung des Befehls besteht. Und der prüft eben auch die
UNIX-Rechte. Man kann damit also die UNIX-Rechte nur "umgehen", wenn man aufgrund eben
dieser UNIX-Rechte die Erlaubnis dazu bekommt. Oder anders ausgedrückt: Man kann
die UNIX-Rechte damit eben doch nicht umgehen. Aber solche Feinheiten würde ihre
sarkastischen Bemerkungen über das System ja als puren Unfug entlarven.
Anhand der Beispiele "Angelo Laub" und "Nemo" ("uninformed") kann man leider feststellen, daß eine Unsitte auch vor selbsternannten "Hackern" nicht Halt macht: Schlagzeilen und Publicity über gründliche Recherche und die volle Wahrheit zu stellen. Die wahren Hacker sind jedoch Leute wie "Wolf", der diese hier diskutierte Technik entwickelt hat.
Der Application Enhancer von Unsanity geht praktisch den gleichen technischen Weg.
Der Application Enhancer lädt Module, die ausführbaren Code enthalten, in laufende Programme, um deren Verhalten anzupassen. Auch dieser arbeitet auf Prozeßebene: Er greift wie mach_inject auf den Speicherbereich zu, der für einen bestimmten Prozeß (ein laufendes Programm) bereitgestellt wurde.
Beide, der Application Enhancer und mach_inject, stellen eine Programmbibliothek bereit, mit der man neue Programmfunktionalitäten in bestehende laufende Programme einschleusen kann. Das, was eingeschleust wird, hat bei mach_inject keinen besonderen Namen und beim Application Enhancer wird es ein Modul genannt. In beiden Varianten kann schlechter eingeschleuster Code zum Absturz des Zielprogrammes führen.
Es gibt wie bei mach_inject sehr viele Programme und Module, die den Application Enhancer verwenden, um ihr Ziel zu erreichen. Diese Techniken, um laufende Programme zu beeinflussen, sind also alles andere als neu, sondern seit Jahren weit verbreitet im Einsatz.
Mit der Einführung der Intel-basierten Macintosh-Rechner war das Betriebssystem für diese Prozessoren anders und mit ihm war mach_inject unter Standard-Bedingungen nicht mehr möglich.
Der Grund liegt darin, daß mach_inject den Befehl task_for_pid()
verwenden muß, um den gewünschten Prozeß verändern zu können. Diesen Befehl kann nur root
oder
ein Mitglied der procmod
-Gruppe, in der standardmäßig nur root
ist, verwenden.
Verwenden bedeutet, daß anderenfalls der Aufruf dieser Funktion nicht die gewünschte task
(den
Zielprozeß) zurückgibt, sondern einen Fehler.
Vergleiche dazu folgende Quellen:
Amit Singh: Mac OS X Internals - A Systems Approach, Seite 899.
Apple Developer Connection: Mach Processes: The Task for PID Function
Für den Befehl task_for_pid()
gibt es eine Sicherheitseinstellung, deren Zustand mit der
Variable kern.tfp.policy
des Betriebssystemkerns festgelegt wird.
Man kann deren aktuellen Zustandswert ansehen mit sysctl kern.tfp.policy
und setzen mit
beispielsweise sudo sysctl -w kern.tfp.policy=2
.
Es gibt drei verschiedene Zustände für kern.tfp.policy
, die bestimmen, wer
task_for_pid()
benutzen kann:
KERN_TFP_POLICY_DENY
(0): Nur der Prozeß selbst und solche,
die unter root
laufen.
KERN_TFP_POLICY_PERMISSIVE
(1): Der Prozeß selbst und solche,
die unter root
oder
unter demselben (effektiven und realen) Benutzer laufen. Beim selben Benutzer
sind nur Prozesse, die nicht mit durch sugid
festgelegtem Benutzer
gestartet wurden, als Zielprozeß möglich.
Das entspricht dem alten Verhalten, bevor diese
Sicherheitseinschränkung in
Mac OS X
eingeführt wurde.
KERN_TFP_POLICY_RESTRICTED
(2, der Standardwert): Der Prozeß
selbst und solche, die unter
root
oder der procmod
-Gruppe laufen, in der
normalerweise nur root
ist. Im Fall der procmod
-Gruppe
darf der Zielprozeß wiederum kein sugid
-Prozeß sein und er muß
unter dem gleichen Benutzer laufen.
Vergleiche dazu folgende Quellen:
Quelltext von
Mac OS X 10.4.9 Tiger
für Intel
sysctl.h
Quelltext von
Mac OS X 10.4.9 Tiger
für Intel
vm_unix.c
Die PowerPC-Version von Mac OS X hat diese Einschränkungsmöglichkeit erst mit der Version 10.5 bekommen. Seit 10.5 gibt es keine zwei prozessorspezifischen Varianten des Betriebssystems mehr sondern eine gemeinsame.
Generell ist es nicht mehr möglich, daß der kernel
-Prozeß
diese Funktion verwendet.
Das führt umgehend zu einer Fehlerrückgabe.
Bei 10.5 wurden die möglichen Zustände für kern.tfp.policy
geändert. Den ersten gibt
es noch, die anderen beiden sind beseitigt worden. Neu hinzugekommen ist
KERN_TFP_POLICY_DEFAULT
(2), welches der Standardwert ist.
Mit 10.5 "Leopard" wurde der zusätzliche Befehl task_for_pid_posix_check
eingeführt.
Diese Funktion wird von task_for_pid
aufgerufen, um einige Extraprüfungen für
POSIX zu machen:
root
läuft, dann ist die Überprüfung bestanden.
KERN_TFP_POLICY_DENY
(0) aktiv ist und die obigen beiden Bedingungen nicht
zutrafen, dann ist die Überprüfung jetzt fehlgeschlagen, ansonsten wird weiter geprüft:
euid
des aufrufenden Prozesses die
gleiche ist wie die (reale, effektive und gesicherte) Benutzer-Identität des Zielprozesses
und die Prozeßgruppe des Zielprozesses eine Untermenge der Prozeßgruppe des
aufrufenden Prozesses ist und die Berechtigungsnachweise (credentials
) des
Zielprozesses nicht ausgetauscht wurden, dann ist die Überprüfung bestanden, ansonsten
ist sie fehlgeschlagen.
Wenn die obige Prüfung fehlgeschlagen ist, dann gibt die Funktion einen Fehler zurück. Ansonsten wird weitergeprüft:
Anscheinend wird danach noch wie in 10.4 auch auf Mach
-Ebene die Zugriffsberechtigung
auf den Zielprozeß überprüft und gegebenenfalls auch ein Fehler zurückgegeben. Dafür werden
die Funktionen check_task_access
und
mac_proc_check_get_task
aufgerufen, die diese Aufgabe nun wohl übernommen haben.
Erstere befragt den task access server
.
Die letztere befragt das Mandatory Access Control Framework
(Code Signing, Sandboxing),
das neu in Leopard hinzukam.
Vergleiche dazu folgende Quellen:
Quelltext von
Mac OS X 10.5 Leopard
sysctl.h
Quelltext von
Mac OS X 10.5 Leopard
vm_unix.c
Die Zugehörigkeit zur procmod
- oder procview
-Gruppe per
setgid
als hinreichendes Kriterium dafür, task_for_pid
verwenden zu
dürfen, hat in Versionen ab Leopard ausgedient. Wenn alle vorherigen Bedingungen erfüllt waren,
ruft der Kernel ab 10.5 also noch den taskgated
-Dämon auf,
um die Entscheidung zu fällen. Die verwendeten Sicherheitseinstellungen für den Dämon
sind konfigurierbar per Parameter und in /etc/authorization
.
Code Signing Release Notes for Mac OS X 10.5 Leopard
Zusatz: Eine lokale Kopie des Artikels, da das Original nicht mehr in ursprünglicher Form existiert.
Für nicht als root
laufende Programme ist
es dann beispielsweise eines der notwendigen Kriterien zum erfolgreichen Zugriff
auf diese Funktion, daß das Programm die Verwendung dieser Funktion deklariert und
passend signiert ist und das Betriebssystem dem Signierendem
vertraut. Dies wird von Entwicklungswerkzeugen zur Leistungs-Analyse und
Fehlersuche in Programmen verwendet. Weitere Voraussetzung ist hier ebenfalls, daß beide
Programme unter demselben Benutzer laufen.
Zwischen 10.5 und 10.7 hat sich nicht mehr viel verändert.
Ich habe den Zugriff auf die task_for_pid Funktion unter Lion getestet. Man kann die Kernel-Task-For-Pid-Policy einstellen wie man möchte und man kann sich auch zur procmod-Gruppe hinzufügen, aber es nützt alles nichts: Man kann nicht mal auf seine eigenen Prozesse damit zugreifen. Einzig root ist es generell möglich, task_for_pid zu verwenden.
Xcode kann beispielsweise keine Programme mehr debuggen, wenn man die KERN_TFP_POLICY_DENY verwendet:
23.08.11 21:15:52,200 com.apple.debugserver-141: 19 +0.011133 sec [451c/0303]: error: ::task_for_pid ( target_tport = 0x0103, pid = 17693, &task ) => err = 0x00000005 ((os/kern) failure) err = ::task_for_pid ( target_tport = 0x0103, pid = 17693, &task ) => err = 0x00000005 ((os/kern) failure) (0x00000005)
23.08.11 21:15:52,222 com.apple.debugserver-141: 21 +0.011210 sec [451c/0303]: RNBRunLoopLaunchInferior DNBProcessLaunch() returned error: 'failed to get the task for process 17693'
Verwendet man hingegen die normale Policy, dann kann sich Xcode per task_for_pid dranhängen. Die oben besprochenen Kernel-Funktionen check_task_access und mac_proc_check_get_task, die in der task_for_pid-Funktion befragt werden, entscheiden also offenbar, daß Xcode task_for_pid nutzen darf, obwohl er nicht unter root läuft. Andere Prozesse unter dem gleichen User können hingegen task_for_pid nicht nutzen. Der Kernel bestimmt also, welche Programme task_for_pid benutzen dürfen, die damit jedoch wiederum nur auf Programme unter dem gleichen Nutzer zugreifen dürfen, solange sie nicht root sind.
Die task_for_pid-Funktion ist also nur für die Task auf sich selbst erlaubt und für ausgewählte (Apple-) Applikationen unter dem gleichen User und für root. Aber nicht für allgemeine Applikationen, die unter irgendetwas anderem laufen als root.
Oder kurzgefaßt: Ein typisches Schadprogramm, das nicht unter root läuft, kann all dies nicht nutzen.
Nach bestandener POSIX-Prüfung und so weiter, siehe oben, bestimmt das Developer Tools Access (DTA) Subsystem, welche Programme Zugriff auf die DTA APIs (beispielsweise task_for_pid) haben. Dazu gehört wie oben beschrieben unter anderem Xcode. Das DTA-Subsystem prüft, ob es der vorliegenden Signatur trauen möchte.
Man kann eine weitere App auf diese API zugreifen lassen, wenn man in der Info.plist der App den Key "SecTaskAccess" mit dem Value "allowed" hinzufügt. Außerdem muß die App durch einen von Apple vergebenen Developer-Account signiert werden. Von mir wollten sie dazu unter anderem eine Kopie meines Personalausweises. So sieht das aktuelle Zertifikat für dieses Jahr aus:
Benutzt die so erstellte App dann task_for_pid, so bekommt man einmal pro Login-Session diesen Dialog zu sehen, falls man Mitglied in der "Developer Tools"-Gruppe ist:
Und wenn man nicht in der "Developer Tools"-Gruppe ist, diesen:
Der oben beschriebene taskgated
-Dämon entscheidet dann, ob Apps mit
geeigneter Signatur und ensprechendem Eintrag in ihrer Info.plist
während dieser Login-Session Zugriff bekommen:
Aug 27 20:21:19 KeyWest com.apple.SecurityServer[34]:
UID 0 authenticated as user macmark (UID 502) for right
'system.privilege.taskport'
Aug 27 20:21:19 KeyWest com.apple.SecurityServer[34]:
Succeeded authorizing right 'system.privilege.taskport'
by client '/usr/libexec/taskgated' [27022]
for authorization created by '/usr/libexec/taskgated' [27087]
Hier hat das DTA-Subsystem entschieden, daß es meiner Signatur traut, mit der dieses Programm versehen ist, das task_for_pid verwendet. Ein anderes ebenfalls von mir signiertes Programm, das das ebenfalls nutzt, erzeugte keine weitere Abfrage in dieser Login-Session. Offenbar merkt sich das System, welchen Signaturen in der aktuellen Sitzung schon vertraut wurde. Von jemand anderem signierte Programme erfordern jedoch eine separate Erlaubnis. Das kann man leicht probieren, indem man versucht, mit Xcode als Normal-User zu debuggen.
Um task_for_pid also in böser Absicht nutzen zu können, müßte man einen Trojaner erstellen, der task_for_pid verwendet und mit einem offiziellem Apple-Developer-Zertifikat signiert ist inklusive der erforderlichen Deklaration in seiner Info.plist. Das ist technisch kein Problem. Allerdings kommt man damit nicht in den App Store, weil der Einsatz untersucht wird und begründet werden muß. Und bei freier Verteilung wäre der Schädling leicht zu erkennen, dank seiner eindeutigen Entwickler-Signatur. Apple könnte diese Signatur ungültig machen und so die weitere Verwendung der Funktion durch den Schädling abstellen. Außerdem könnte eine Aktualisierung der Quarantäne-Liste sämtliche Apps des Entwicklers abfangen. Zudem ist fraglich, wer seinen Entwickler-Account, Geld oder seine Freiheit verlieren möchte, indem er seine Schädlinge signiert. Und zuletzt sind die Abfrage-Dialoge, die ein solcher Zugriff auf die DTA APIs auslöst, recht ungewöhnlich und auffällig.
Warum als Trojaner? Unix Shell-Scripte, Command-Line Tools sowie statische und dynamische shared Libraries verwenden nicht die Bundle-Struktur wie normale Mac-Apps und können daher keine Info.plist wie diese Apps nutzen, um Zugriff auf task_for_pid zu beantragen. Ein "trojanisches Pferd" kann jedoch solch eine normale App sein.
Wie wir oben gesehen haben, ist Code-Injection mit Mac OS X nur unter großem Aufwand und mit noch größeren Einschränkungen möglich.
Bei Windows sieht das jedoch ganz anders aus. Dort gibt es eine
offizielle und komfortable
API, die
das Einschleusen von Code anbietet. Mit
WriteProcessMemory
und
CreateRemoteThread
injiziert und startet man Code in einem
anderen seiner Prozesse. Und mit aktivem
SeDebugPrivilege
kann man das mit jedem beliebigen Prozeß tun.
So mancher Mac-Hacker würde für so eine API in Mac OS X seine rechte Hand geben. Unter Windows gibt es nämlich erstens diese starken Einschränkungen des vollen Zugriffs auf einen anderen Prozeß nicht und zweitens muß man nicht dermaßen viel auf niedriger Ebene programmieren, um einen Thread einzuschleusen, sondern nutzt die von Windows angebotene API.