Hallo, Habr! Heute möchte ich darĂŒber sprechen, wie man Prozesse in macOS vor Angriffen von Hackern schĂŒtzen kann. Dies ist beispielsweise nĂŒtzlich fĂŒr Antivirensoftware oder Backup-Systeme, vor allem angesichts der Tatsache, dass es unter macOS mehrere Möglichkeiten gibt, einen Prozess "zu beenden". Lesen Sie mehr dazu und zu den Schutzmethoden im Folgenden.
Klassischer Weg, um einen Prozess zu beenden
Der allgemein bekannte Weg, um einen Prozess zu beenden, ist das Senden eines SIGKILL-Signals an den Prozess. Dies kann ĂŒber bash mit den Befehlen "kill -SIGKILL PID" oder "pkill -9 NAME" zum Beenden des Prozesses aufgerufen werden. Der Befehl "kill" ist seit den AnfĂ€ngen von UNIX bekannt und in macOS sowie anderen UNIX-Ă€hnlichen Systemen verfĂŒgbar.
Wie auch in UNIX-Ă€hnlichen Systemen ermöglicht es macOS, alle Signale an einen Prozess abzufangen, mit zwei Ausnahmen â SIGKILL und SIGSTOP. In diesem Artikel liegt der Fokus hauptsĂ€chlich auf dem Signal SIGKILL, da es das Signal ist, das den Prozess beendet.
Spezifika von macOS
In macOS löst der Systemaufruf kill im XNU-Kernel die Funktion psignal(SIGKILL, âŠ) aus. Lassen Sie uns untersuchen, welche weiteren Benutzeraktionen im Userspace die Funktion psignal auslösen können. Wir werden Aufrufe der Funktion psignal in den internen Mechanismen des Kernels ausscheiden (obwohl auch diese nicht trivial sein könnten, lassen wir sie fĂŒr einen anderen Artikel đ â ĂberprĂŒfung der Signatur, Speicherfehler, Verarbeitung von Exit/Terminate, Verletzungen des Dateischutzes usw.).
Beginnen wir mit einem Ăberblick ĂŒber die Funktion und den entsprechenden Systemaufruf. . Es ist ersichtlich, dass neben dem klassischen Aufruf kill ein alternativer Ansatz existiert, der spezifisch fĂŒr das Betriebssystem macOS ist und in BSD nicht vorkommt. Die Prinzipien der beiden Systemaufrufe sind ebenfalls Ă€hnlich. Sie stellen direkte Aufrufe der Kernfunktion psignal dar. Zudem sollten wir beachten, dass vor dem Beenden des Prozesses die âcansignalâ-ĂberprĂŒfung erfolgt â ob der Prozess einem anderen Prozess ein Signal senden kann. Das System erlaubt es keinem Anwendung, Systemprozesse abzutöten.
static int
terminate_with_payload_internal(struct proc *cur_proc, int target_pid, uint32_t reason_namespace,
uint64_t reason_code, user_addr_t payload, uint32_t payload_size,
user_addr_t reason_string, uint64_t reason_flags)
{
...
target_proc = proc_find(target_pid);
...
if (!cansignal(cur_proc, cur_cred, target_proc, SIGKILL)) {
proc_rele(target_proc);
return EPERM;
}
...
if (target_pid == cur_proc->p_pid) {
/*
* psignal_thread_with_reason() setzt ein SIGKILL auf den angegebenen Thread oder
* gibt zurĂŒck, wenn der Thread und/oder die Aufgabe bereits beendet werden. In jedem Fall wird der
* aktuelle Thread nicht zu Benutzerraum zurĂŒckkehren.
*/
psignal_thread_with_reason(target_proc, current_thread(), SIGKILL, signal_reason);
} else {
psignal_with_reason(target_proc, SIGKILL, signal_reason);
}
...
}
launchd
Der Standardweg zum Erstellen von Daemons beim Systemstart und zur Kontrolle ihrer Lebensdauer ist launchd. Ich möchte darauf hinweisen, dass die Quellcodes fĂŒr eine Ă€ltere Version von launchctl vor macOS 10.10 angegeben sind; die Codebeispiele dienen lediglich zur Veranschaulichung. Das moderne launchctl sendet Signale an launchd ĂŒber XPC, die Logik von launchctl wurde in dieses System ĂŒbertragen.
Betrachten wir, wie die Beendigung von Anwendungen tatsĂ€chlich erfolgt. Bevor das SIGTERM-Signal gesendet wird, wird versucht, die Anwendung mit dem Systemaufruf âproc_terminateâ zu stoppen.
...
error = proc_terminate(j->p, &sig);
if (error) {
job_log(j, LOG_ERR | LOG_CONSOLE, "Konnte den Job nicht beenden: %d: %s", error, strerror(error));
job_log(j, LOG_NOTICE | LOG_CONSOLE, "Fallback-Option zur Beendigung des Jobs wird verwendet...");
error = kill2(j->p, SIGTERM);
if (error) {
job_log(j, LOG_ERR, "Konnte dem Job kein Signal senden: %d: %s", error, strerror(error));
}
...
Trotz seines Namens kann proc_terminate unter der Haube nicht nur psignal mit SIGTERM senden, sondern auch SIGKILL.
Indirekte Tötung â RessourcenbeschrĂ€nkung
Ein interessanterer Fall ist in einem anderen Systemaufruf zu sehen . Die Standardnutzung dieses Systemaufrufs sind EinschrĂ€nkungen der Ressourcen von Anwendungen, zum Beispiel fĂŒr den Indexer eine Begrenzung der Quoten fĂŒr CPU-Zeit und Speicher, um zu verhindern, dass das System erheblich durch Datei-Caching verlangsamt wird. Wenn eine Anwendung die RessourcenbeschrĂ€nkung erreicht hat, wie aus der Funktion proc_apply_resource_actions ersichtlich ist, wird dem Prozess das Signal SIGKILL gesendet.
Obwohl dieser Systemaufruf potenziell die Tötung eines Prozesses durchfĂŒhren kann, hat das System nicht angemessen die Rechte des Prozesses ĂŒberprĂŒft, der den Systemaufruf tĂ€tigt. TatsĂ€chlich gab es eine ĂberprĂŒfung , aber es reicht aus, das alternative Flag PROC_POLICY_ACTION_SET zu verwenden, um diese Bedingung zu umgehen.
Damit kann, wenn die CPU-Nutzung des Prozesses (zum Beispiel die AusfĂŒhrung von nur 1 ns) âeingeschrĂ€nktâ wird, jeder Prozess im System beendet werden. Ein Angreifer könnte somit jeden Prozess im System, einschlieĂlich des Antivirenprogramms, beenden. Auch der Effekt, der beim Töten des Prozesses mit pid 1 (launchctl) auftritt, ist interessant â ein Kernel-Panik bei dem Versuch, das SIGKILL-Signal zu verarbeiten đ.

Wie löst man das Problem?
Der direkteste Weg, das Töten eines Prozesses zu verhindern, besteht darin, den Funktionszeiger in der Systemaufruftabelle zu ersetzen. Leider ist dieser Ansatz aus vielen GrĂŒnden nicht trivial.
Erstens ist das Symbol, das fĂŒr die Position von sysent im Speicher verantwortlich ist, nicht nur ein privates Symbol des XNU-Kernels, sondern kann auch in den Kernel-Symbolen nicht gefunden werden. Es mĂŒssen heuristische Suchmethoden verwendet werden, wie beispielsweise das dynamische Disassemblieren der Funktion und das Suchen des Zeigers darin.
Zweitens hĂ€ngt die Struktur der EintrĂ€ge in der Tabelle von den Flags ab, mit denen der Kernel kompiliert wurde. Wenn das FLAG CONFIG_REQUIRES_U32_MUNGING deklariert ist, wird die GröĂe der Struktur geĂ€ndert â ein zusĂ€tzliches Feld wird hinzugefĂŒgt. . Es ist erforderlich, eine zusĂ€tzliche ĂberprĂŒfung durchzufĂŒhren, mit welchem Flag der Kernel kompiliert wurde, zum Beispiel durch den Abgleich der Funktionszeiger mit bekannten.
struct sysent { /* system call table */
sy_call_t *sy_call; /* implementing function */
#if CONFIG_REQUIRES_U32_MUNGING || (__arm__ && (__BIGGEST_ALIGNMENT__ > 4))
sy_munge_t *sy_arg_munge32; /* system call arguments munger for 32-bit process */
#endif
int32_t sy_return_type; /* system call return types */
int16_t sy_narg; /* number of args */
uint16_t sy_arg_bytes; /* Total size of arguments in bytes for
* 32-bit system calls
*/
};
GlĂŒcklicherweise bietet Apple in den modernen Versionen von macOS eine neue API zur Arbeit mit Prozessen an. Die Endpoint Security API ermöglicht es Clients, viele Anfragen an andere Prozesse zu autorisieren. So können alle Signale an Prozesse, einschlieĂlich des Signals SIGKILL, mit der oben genannten API blockiert werden.
#include <bsm/libbsm.h>
#include <EndpointSecurity/EndpointSecurity.h>
#include <unistd.h>
int main(int argc, const char * argv[]) {
es_client_t* cli = nullptr;
{
auto res = es_new_client(&cli, ^(es_client_t * client, const es_message_t * message) {
switch (message->event_type) {
case ES_EVENT_TYPE_AUTH_SIGNAL:
{
auto& msg = message->event.signal;
auto target = msg.target;
auto& token = target->audit_token;
auto pid = audit_token_to_pid(token);
printf("signal '%d' sent to pid '%d'n", msg.sig, pid);
es_respond_auth_result(client, message, pid == getpid() ? ES_AUTH_RESULT_DENY : ES_AUTH_RESULT_ALLOW, false);
}
break;
default:
break;
}
});
}
{
es_event_type_t evs[] = { ES_EVENT_TYPE_AUTH_SIGNAL };
es_subscribe(cli, evs, sizeof(evs) / sizeof(*evs));
}
printf("%dn", getpid());
sleep(60); // could be replaced with other waiting primitive
es_unsubscribe_all(cli);
es_delete_client(cli);
return 0;
}
Ăhnlich kann im Kernel eine MAC-Policy registriert werden, die eine Schutzmethode gegen Signale (Policy proc_check_signal) bereitstellt, allerdings wird die API nicht offiziell unterstĂŒtzt.
Schutz des Kernel-Extensions
Neben dem Prozessschutz im System ist auch der Schutz der Kernel-Erweiterung (kext) zwingend erforderlich. macOS bietet Entwicklern ein Framework fĂŒr eine bequeme Entwicklung von IOKit-GerĂ€tetreibern. ZusĂ€tzlich zu den Werkzeugen fĂŒr die GerĂ€teverwaltung ermöglicht IOKit Methoden zum Stapeln von Treibern (Driver Stacking) mittels Instanzen von C++-Klassen. Eine Anwendung im Userspace kann eine registrierte Klasseninstanz âfindenâ, um eine Verbindung zwischen Kernel und Userspace herzustellen.
Zur Ermittlung der Anzahl der Klasseninstanzen im System gibt es das Tool ioclasscount.
my_kext_ioservice = 1
my_kext_iouserclient = 1
Jede Kernel-Erweiterung, die sich im Treiberstapel registrieren möchte, muss eine Klasse deklarieren, die von IOService abgeleitet ist, zum Beispiel my_kext_ioservice in diesem Fall. Die Verbindung von Benutzeranwendungen fĂŒhrt zur Erstellung einer neuen Klasseninstanz, die von IOUserClient abgeleitet ist, im Beispiel my_kext_iouserclient.
Beim Versuch, den Treiber aus dem System zu entladen (Befehl kextunload) wird die virtuelle Funktion âbool terminate(IOOptionBits options)â aufgerufen. Es reicht aus, false beim Aufruf der Funktion terminate zurĂŒckzugeben, um kextunload zu verhindern.
bool Kext::terminate(IOOptionBits options)
{
if (!IsUnloadAllowed)
{
// Entladen ist nicht erlaubt, false zurĂŒckgeben
return false;
}
return super::terminate(options);
}
Das Flag IsUnloadAllowed kann bei der Ladezeit von IOUserClient gesetzt werden. Bei einer EntladebeschrĂ€nkung gibt der Befehl kextunload die folgende Ausgabe zurĂŒck:
admin@admins-Mac drivermanager % sudo kextunload ./test.kext
Passwort:
(kernel) Kann kext my.kext.test nicht entfernen; Dienste konnten nicht beendet werden - 0xe00002c7.
Entladen von my.kext.test fehlgeschlagen - (iokit/common) nicht unterstĂŒtzte Funktion.
Ein Ă€hnlicher Schutz muss auch fĂŒr IOUserClient durchgefĂŒhrt werden. Instanzen von Klassen können mit der Userspace-Funktion IOKitLib âIOCatalogueTerminate(mach_port_t, uint32_t flag, io_name_t description);â entladen werden. Es kann false beim Aufruf des Befehls âterminateâ zurĂŒckgegeben werden, solange die Userspace-Anwendung nicht âstirbtâ, d.h. solange die Funktion âclientDiedâ nicht aufgerufen wird.
Dateischutz
Zum Schutz von Dateien genĂŒgt die Verwendung der Kauth API, die den Zugriff auf Dateien einschrĂ€nkt. Apple bietet Entwicklern Benachrichtigungen ĂŒber verschiedene Ereignisse im Scope an; fĂŒr uns sind die Operationen KAUTH_VNODE_DELETE, KAUTH_VNODE_WRITE_DATA und KAUTH_VNODE_DELETE_CHILD von Bedeutung. Der Zugriff auf Dateien kann am einfachsten ĂŒber den Pfad eingeschrĂ€nkt werden â wir verwenden die API âvn_getpathâ, um den Pfad zur Datei zu erhalten und vergleichen das PrĂ€fix des Pfades. Es sei bemerkt, dass zur Optimierung der Umbenennung von Ordnerpfaden mit Dateien das System nicht den Zugriff auf jede Datei autorisiert, sondern nur auf den Ordner, der umbenannt wurde. Es ist erforderlich, den ĂŒbergeordneten Pfad zu vergleichen und KAUTH_VNODE_DELETE fĂŒr ihn zu beschrĂ€nken.

Ein Nachteil dieses Ansatzes kann die geringe Leistung bei zunehmender Zahl von PrÀfixen sein. Um zu verhindern, dass der Vergleich O(prefix*length) wird, wobei prefix die Anzahl der PrÀfixe und length die LÀnge des Strings ist, kann ein deterministischer endlicher Automat (DEA) verwendet werden, der aus den PrÀfixen aufgebaut ist.
Betrachten wir, wie ein DFA fĂŒr diese Menge von PrĂ€fixen aufgebaut wird. Wir initialisieren die Zeiger am Anfang jedes PrĂ€fixes. Wenn alle Zeiger auf dasselbe Zeichen zeigen, erhöhen wir jeden Zeiger um ein Zeichen und merken uns, dass die LĂ€nge der identischen Zeichenkette um eins gröĂer ist. Wenn es zwei Zeiger gibt, deren Zeichen unterschiedlich sind, teilen wir die Zeiger in Gruppen nach dem Zeichen, auf das sie zeigen, und wiederholen den Algorithmus fĂŒr jede Gruppe.
Im ersten Fall (wenn alle Zeichen unter den Zeigern identisch sind) erhalten wir einen Zustand des DFA, der nur einen Ăbergang ĂŒber die identische Zeichenkette hat. Im zweiten Fall erhalten wir eine Ăbergangstabelle von der GröĂe 256 (Anzahl der Zeichen und maximale Anzahl der Gruppen) in die folgenden ZustĂ€nde, die durch den rekursiven Aufruf der Funktion erzielt werden.
Betrachten wir ein Beispiel. FĂŒr die Menge der PrĂ€fixe ("/foo/bar/tmp/", "/var/db/foo/", "/foo/bar/aba/", "foo/bar/aac/") kann der folgende DFA erstellt werden. Im Bild sind nur die ĂbergĂ€nge zu anderen ZustĂ€nden angegeben; andere ĂbergĂ€nge werden nicht als endgĂŒltig betrachtet.

Beim Durchlaufen der ZustÀnde des DFA kann es 3 FÀlle geben.
- Der endgĂŒltige Zustand wurde erreicht â der Pfad ist gesichert, Operationen wie KAUTH_VNODE_DELETE, KAUTH_VNODE_WRITE_DATA und KAUTH_VNODE_DELETE_CHILD sind eingeschrĂ€nkt.
- Der endgĂŒltige Zustand wurde nicht erreicht, aber der Pfad "endete" (Null-Terminator erreicht) â der Pfad ist der ĂŒbergeordnete, es mĂŒssen EinschrĂ€nkungen fĂŒr KAUTH_VNODE_DELETE gelten. Beachten Sie, dass wenn vnode ein Ordner ist, am Ende â/â hinzugefĂŒgt werden muss, andernfalls könnte eine EinschrĂ€nkung auf die Datei â/foor/bar/tâ angewendet werden, was nicht korrekt ist.
- Der endgĂŒltige Zustand wurde nicht erreicht, der Pfad ist nicht abgeschlossen. Keiner der PrĂ€fixe stimmt ĂŒberein, daher werden keine EinschrĂ€nkungen eingefĂŒhrt.
Fazit
Ziel der entwickelten Sicherheitslösungen ist es, die Sicherheit der Benutzer und ihrer Daten zu erhöhen. Einerseits wird dieses Ziel durch die Entwicklung des Softwareprodukts Acronis erreicht, das die Schwachstellen schlieĂt, wo die Betriebssysteme anfĂ€llig sind. Andererseits sollten auch die Sicherheitsaspekte nicht vernachlĂ€ssigt werden, die auf der OS-Seite verbessert werden können, besonders da das SchlieĂen solcher Schwachstellen unsere eigene WiderstandsfĂ€higkeit als Produkt erhöht. Eine Schwachstelle wurde dem Apple Product Security Team gemeldet und wurde in macOS 10.14.5 behoben (https://support.apple.com/en-gb/HT210119).

All dies kann nur erfolgen, wenn Ihr Tool offiziell im Kernel installiert wurde. Das bedeutet, dass es fĂŒr externe und unerwĂŒnschte Software keine solchen Schlupflöcher gibt. Wie Sie sehen können, erfordert es jedoch auch MĂŒhe, legitime Programme wie Antiviren- und Backup-Systeme zu schĂŒtzen. Aber jetzt werden die neuen Produkte von Acronis fĂŒr macOS ĂŒber zusĂ€tzlichen Schutz gegen das Herunterladen aus dem System verfĂŒgen.
Quelle: habr.com
