Cum să protejați procesele și extensiile de kernel pe macOS

Bună, Habr! Astăzi aș dori să vorbesc despre cum puteți proteja procesele de atacurile atacatorilor în macOS. De exemplu, acest lucru este util pentru un sistem antivirus sau de rezervă, mai ales că sub macOS există mai multe modalități de a „ucide” un proces. Citiți despre acest lucru și despre metodele de protecție sub tăietură.

Cum să protejați procesele și extensiile de kernel pe macOS

Modul clasic de a „ucide” un proces

O modalitate binecunoscută de a „ucide” un proces este de a trimite un semnal SIGKILL către proces. Prin bash puteți apela standardul „kill -SIGKILL PID” sau „pkill -9 NAME” pentru a ucide. Comanda „kill” este cunoscută încă din zilele UNIX și este disponibilă nu numai pe macOS, ci și pe alte sisteme asemănătoare UNIX.

La fel ca în sistemele de tip UNIX, macOS vă permite să interceptați orice semnal către un proces, cu excepția a două - SIGKILL și SIGSTOP. Acest articol se va concentra în primul rând pe semnalul SIGKILL ca semnal care determină omorârea unui proces.

specificul macOS

Pe macOS, apelul de sistem kill din nucleul XNU apelează funcția psignal(SIGKILL,...). Să încercăm să vedem ce alte acțiuni ale utilizatorului din spațiul utilizatorului pot fi apelate de funcția psignal. Să eliminăm apelurile la funcția psignal din mecanismele interne ale nucleului (deși pot fi non-triviale, le lăsăm pentru alt articol 🙂 - verificarea semnăturii, erori de memorie, manipulare de ieșire/terminare, încălcări ale protecției fișierelor etc. .

Să începem revizuirea cu funcția și apelul de sistem corespunzător terminate_with_payload. Se poate observa că, pe lângă apelul kill clasic, există o abordare alternativă care este specifică sistemului de operare macOS și nu se găsește în BSD. Principiile de funcționare ale ambelor apeluri de sistem sunt, de asemenea, similare. Sunt apeluri directe către funcția kernel psignal. De asemenea, rețineți că, înainte de a ucide un proces, se efectuează o verificare „cansignal” - dacă procesul poate trimite un semnal către alt proces; sistemul nu permite niciunei aplicații să ucidă procesele sistemului, de exemplu.

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() will pend a SIGKILL on the specified thread or
		 * return if the thread and/or task are already terminating. Either way, the
		 * current thread won't return to userspace.
		 */
		psignal_thread_with_reason(target_proc, current_thread(), SIGKILL, signal_reason);
	} else {
		psignal_with_reason(target_proc, SIGKILL, signal_reason);
	}
...
}

lansatd

Este lansată modalitatea standard de a crea demoni la pornirea sistemului și de a le controla durata de viață. Vă rugăm să rețineți că sursele sunt pentru versiunea veche de launchctl până la macOS 10.10, exemple de cod sunt furnizate în scopuri ilustrative. Launchctl modern trimite semnale de lansare prin XPC, logica launchctl a fost mutată în el.

Să vedem cum exact aplicațiile sunt oprite. Înainte de a trimite semnalul SIGTERM, se încearcă oprirea aplicației folosind apelul de sistem „proc_terminate”.

<launchctl src/core.c>
...
	error = proc_terminate(j->p, &sig);
	if (error) {
		job_log(j, LOG_ERR | LOG_CONSOLE, "Could not terminate job: %d: %s", error, strerror(error));
		job_log(j, LOG_NOTICE | LOG_CONSOLE, "Using fallback option to terminate job...");
		error = kill2(j->p, SIGTERM);
		if (error) {
			job_log(j, LOG_ERR, "Could not signal job: %d: %s", error, strerror(error));
		} 
...
<>

Sub capotă, proc_terminate, în ciuda numelui său, poate trimite nu numai psignal cu SIGTERM, ci și SIGKILL.

Ucidere indirectă - Limită de resurse

Un caz mai interesant poate fi văzut într-un alt apel de sistem proces_politică. O utilizare comună a acestui apel de sistem este limitarea resurselor aplicației, cum ar fi ca un indexator să limiteze timpul CPU și cotele de memorie, astfel încât sistemul să nu fie încetinit semnificativ de activitățile de stocare în cache a fișierelor. Dacă o aplicație și-a atins limita de resurse, așa cum se poate vedea din funcția proc_apply_resource_actions, un semnal SIGKILL este trimis procesului.

Deși acest apel de sistem ar putea ucide un proces, sistemul nu a verificat în mod adecvat drepturile procesului care a apelat apelul de sistem. De fapt, verific a existat, dar este suficient să folosiți indicatorul alternativ PROC_POLICY_ACTION_SET pentru a ocoli această condiție.

Prin urmare, dacă „limitați” cota de utilizare a CPU a aplicației (de exemplu, permițând doar 1 ns să ruleze), atunci puteți opri orice proces din sistem. Astfel, malware-ul poate ucide orice proces din sistem, inclusiv procesul antivirus. De asemenea, interesant este efectul care apare la uciderea unui proces cu pid 1 (launchctl) - panica nucleului atunci când încercați să procesați semnalul SIGKILL :)

Cum să protejați procesele și extensiile de kernel pe macOS

Cum se rezolvă problema?

Cea mai simplă modalitate de a preveni oprirea unui proces este înlocuirea indicatorului de funcție din tabelul de apeluri de sistem. Din păcate, această metodă nu este banală din multe motive.

În primul rând, simbolul care controlează locația de memorie a sysent nu este doar privat pentru simbolul nucleului XNU, dar nu poate fi găsit în simbolurile nucleului. Va trebui să utilizați metode de căutare euristică, cum ar fi dezasamblarea dinamică a funcției și căutarea unui pointer în ea.

În al doilea rând, structura intrărilor din tabel depinde de steaguri cu care a fost compilat nucleul. Dacă se declară steagul CONFIG_REQUIRES_U32_MUNGING, dimensiunea structurii va fi modificată - va fi adăugat un câmp suplimentar sy_arg_munge32. Este necesar să se efectueze o verificare suplimentară pentru a determina cu ce flag a fost compilat nucleul sau, alternativ, să se verifice indicatorii de funcție față de cei cunoscuți.

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
                                         */
};

Din fericire, în versiunile moderne de macOS, Apple oferă un nou API pentru lucrul cu procesele. API-ul Endpoint Security permite clienților să autorizeze multe cereri către alte procese. Astfel, puteți bloca orice semnal către procese, inclusiv semnalul SIGKILL, folosind API-ul menționat mai sus.

#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;
}

În mod similar, o politică MAC poate fi înregistrată în nucleu, care oferă o metodă de protecție a semnalului (policy proc_check_signal), dar API-ul nu este acceptat oficial.

Protecția extensiei kernelului

Pe lângă protejarea proceselor din sistem, este necesară și protejarea extensiei kernelului în sine (kext). macOS oferă un cadru pentru dezvoltatori pentru a dezvolta cu ușurință drivere de dispozitiv IOKit. Pe lângă furnizarea de instrumente pentru lucrul cu dispozitive, IOKit oferă metode pentru stivuirea driverelor folosind instanțe ale claselor C++. O aplicație din spațiul utilizatorului va putea „găsi” o instanță înregistrată a clasei pentru a stabili o relație kernel-spațiu utilizator.

Pentru a detecta numărul de instanțe de clasă din sistem, există utilitarul ioclasscount.

my_kext_ioservice = 1
my_kext_iouserclient = 1

Orice extensie de kernel care dorește să se înregistreze cu stiva de drivere trebuie să declare o clasă care moștenește de la IOService, de exemplu my_kext_ioservice în acest caz. Conectarea aplicațiilor utilizator determină crearea unei noi instanțe a clasei care moștenește de la IOUserClient, în exemplu my_kext_iouserclient.

Când încercați să descărcați un driver din sistem (comanda kextunload), este apelată funcția virtuală „bool terminate(IOOptionBits options)”. Este suficient să returnați false la apel pentru a termina atunci când încercați să descărcați pentru a dezactiva kextunload.

bool Kext::terminate(IOOptionBits options)
{

  if (!IsUnloadAllowed)
  {
    // Unload is not allowed, returning false
    return false;
  }

  return super::terminate(options);
}

Indicatorul IsUnloadAllowed poate fi setat de IOUserClient la încărcare. Când există o limită de descărcare, comanda kextunload va returna următoarea ieșire:

admin@admins-Mac drivermanager % sudo kextunload ./test.kext
Password:
(kernel) Can't remove kext my.kext.test; services failed to terminate - 0xe00002c7.
Failed to unload my.kext.test - (iokit/common) unsupported function.

Protecție similară trebuie făcută pentru IOUserClient. Instanțele claselor pot fi descărcate folosind funcția de spațiu de utilizator IOKitLib „IOCatalogueTerminate(mach_port_t, uint32_t flag, io_name_t description);”. Puteți returna false atunci când apelați comanda „terminate” până când aplicația userspace „moare”, adică funcția „clientDied” nu este apelată.

Protecția fișierelor

Pentru a proteja fișierele, este suficient să utilizați API-ul Kauth, care vă permite să restricționați accesul la fișiere. Apple oferă dezvoltatorilor notificări despre diverse evenimente din domeniu; pentru noi, operațiunile KAUTH_VNODE_DELETE, KAUTH_VNODE_WRITE_DATA și KAUTH_VNODE_DELETE_CHILD sunt importante. Cel mai simplu mod de a restricționa accesul la fișiere este calea - folosim API-ul „vn_getpath” pentru a obține calea către fișier și pentru a compara prefixul căii. Rețineți că pentru a optimiza redenumirea căilor de foldere de fișiere, sistemul nu autorizează accesul la fiecare fișier, ci doar la folderul în sine care a fost redenumit. Este necesar să comparați calea părinte și să restricționați KAUTH_VNODE_DELETE pentru aceasta.

Cum să protejați procesele și extensiile de kernel pe macOS

Dezavantajul acestei abordări poate fi performanța scăzută pe măsură ce numărul de prefixe crește. Pentru a vă asigura că comparația nu este egală cu O (prefix*lungime), unde prefixul este numărul de prefixe, lungimea este lungimea șirului, puteți utiliza un automat finit determinist (DFA) construit prin prefixe.

Să luăm în considerare o metodă de construire a unui DFA pentru un anumit set de prefixe. Inițializam cursoarele la începutul fiecărui prefix. Dacă toate cursoarele indică același caracter, atunci creșteți fiecare cursor cu un caracter și amintiți-vă că lungimea aceleiași linii este mai mare cu unu. Dacă există două cursoare cu simboluri diferite, împărțiți cursoarele în grupuri în funcție de simbolul către care indică și repetați algoritmul pentru fiecare grup.

În primul caz (toate caracterele de sub cursoare sunt aceleași), obținem o stare DFA care are o singură tranziție de-a lungul aceleiași linii. În al doilea caz, obținem un tabel de tranziții de dimensiunea 256 (număr de caractere și număr maxim de grupuri) la stările ulterioare obținute prin apelarea recursiv a funcției.

Să ne uităm la un exemplu. Pentru un set de prefixe („/foo/bar/tmp/”, „/var/db/foo/”, „/foo/bar/aba/”, „foo/bar/aac/”) puteți obține următoarele DFA. Figura arată doar tranzițiile care conduc la alte state; alte tranziții nu vor fi definitive.

Cum să protejați procesele și extensiile de kernel pe macOS

Când treceți prin statele DKA, pot exista 3 cazuri.

  1. Starea finală a fost atinsă - calea este protejată, limităm operațiunile KAUTH_VNODE_DELETE, KAUTH_VNODE_WRITE_DATA și KAUTH_VNODE_DELETE_CHILD
  2. Starea finală nu a fost atinsă, dar calea „s-a încheiat” (terminatorul nul a fost atins) - calea este părinte, este necesar să se limiteze KAUTH_VNODE_DELETE. Rețineți că, dacă vnode este un folder, trebuie să adăugați un „/” la sfârșit, altfel îl poate limita la fișierul „/foor/bar/t”, care este incorect.
  3. Starea finală nu a fost atinsă, drumul nu s-a încheiat. Niciunul dintre prefixe nu se potrivește cu acesta, nu introducem restricții.

Concluzie

Scopul soluțiilor de securitate în curs de dezvoltare este de a crește nivelul de securitate al utilizatorului și al datelor acestuia. Pe de o parte, acest obiectiv este atins prin dezvoltarea produsului software Acronis, care închide acele vulnerabilități în care sistemul de operare în sine este „slab”. Pe de altă parte, nu trebuie să neglijăm întărirea acelor aspecte de securitate care pot fi îmbunătățite din partea sistemului de operare, mai ales că închiderea unor astfel de vulnerabilități crește stabilitatea proprie ca produs. Vulnerabilitatea a fost raportată echipei de securitate a produselor Apple și a fost remediată în macOS 10.14.5 (https://support.apple.com/en-gb/HT210119).

Cum să protejați procesele și extensiile de kernel pe macOS

Toate acestea pot fi făcute numai dacă utilitarul dumneavoastră a fost instalat oficial în nucleu. Adică, nu există astfel de lacune pentru software-ul extern și nedorit. Cu toate acestea, după cum puteți vedea, chiar și protejarea programelor legitime, cum ar fi sistemele antivirus și de rezervă, necesită muncă. Dar acum noile produse Acronis pentru macOS vor avea protecție suplimentară împotriva descărcarii din sistem.

Sursa: www.habr.com

Adauga un comentariu