Si sihet mbrojtja e proceseve dhe shtrirjeve të bërthamës në macOS

Përshëndetje, Habr! Sot do të flas për mënyrat si mund të mbrohen proceset nga sulmet e hakerëve në macOS. Kjo është e dobishme sidomos për antivirusin ose sistemin e kopjimit, veçanërisht për shkak se në macOS ekzistojnë disa mënyra për të "vrarë" një proces. Më këtë dhe metodat e mbrojtjes lexoni më poshtë.

Si sihet mbrojtja e proceseve dhe shtrirjeve të bërthamës në macOS

Mënyra klasike për të "vrarë" një proces

Një mënyrë e njohur për të "vrarë" një proces është dërgimi i sinjalit SIGKILL procesit. Në bash, mund të thërrasësh metodat standarde "kill -SIGKILL PID" ose "pkill -9 NAME" për t'i bërë atë. Komanda "kill" është e njohur që nga koha e UNIX-it dhe është e disponueshme jo vetëm në macOS, por edhe në sisteme të tjera UNIX-like.

Po ashtu si në sistemet UNIX-like, macOS lejon kapjen e çdo sinjali për procesin përveç dy - SIGKILL dhe SIGSTOP. Ky artikull do të shqyrtojë kryesisht sinjalin SIGKILL, si sinjal që shkakton vrasjen e procesit.

Specifika e macOS

NĂ« macOS, thirrja sistemike kill nĂ« bĂ«rthamĂ«n XNU thĂ«rret funksionin psignal(SIGKILL,
). Le tĂ« shohim se cilat veprime tĂ« tjera tĂ« pĂ«rdoruesit nĂ« userspace mund tĂ« thĂ«rrasin funksionin psignal. Do tĂ« pĂ«rjashtojmĂ« thirrjet e funksionit psignal nĂ« mekanizmat e brendshĂ«m tĂ« bĂ«rthamĂ«s (ndonĂ«se ato mund tĂ« jenĂ« jo triviale, por do t’i lĂ«mĂ« ato pĂ«r njĂ« artikull tjetĂ«r 🙂 — verifikimi i nĂ«nshkrimit, gabimet e memories, trajtimi exit/terminate, shkelja e mbrojtjes sĂ« skedarĂ«ve, etj.).

Le tĂ« fillojmĂ« shqyrtimin me funksionin dhe thirrjen pĂ«rkatĂ«se sistemike. terminate_with_payload. ËshtĂ« e dukshme se pĂ«rveç thirrjes klasike kill, ekziston njĂ« qasje alternative, e cila Ă«shtĂ« specifike pĂ«r sistemin operativ macOS dhe nuk gjendet nĂ« BSD. Parimet e funksionimit tĂ« tĂ« dy thirrjeve sistemike janĂ« gjithashtu tĂ« ngjashme. ato pĂ«rbĂ«jnĂ« thirrje direkte tĂ« funksionit tĂ« bĂ«rthamĂ«s psignal. Po ashtu, le tĂ« vĂ«mĂ« re se para se tĂ« vrasĂ« procesin bĂ«het njĂ« kontroll “cansignal” – nĂ«se procesi mund tĂ« dĂ«rgojĂ« sinjal tjetĂ«r procesi, sistemi nuk lejon asnjĂ« aplikacion tĂ« vrasĂ« proceset sistemike, pĂ«r shembull.

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);
	}
...
}

launchd

Mënyra standarde për të krijuar demonë në nisjen e sistemit dhe për të kontrolluar kohëzgjatjen e tyre është launchd. Dua të theksoj se burimet janë për një version të vjetër të launchctl para macOS 10.10, shembujt e kodit janë dhënë për ilustim. Launchctl modern dërgon sinjale në launchd përmes XPC, logjika e launchctl është transferuar tek ai.

Le tĂ« shqyrtojmĂ« se si ndalohet saktĂ«sisht aplikacionet. Para dĂ«rgimit tĂ« sinjalit SIGTERM, aplikacionet pĂ«rpiqen tĂ« ndalen pĂ«rmes thirrjes sistemore “proc_terminate”.

...
	error = proc_terminate(j->p, &sig);
	if (error) {
		job_log(j, LOG_ERR | LOG_CONSOLE, "Nuk e mundi të përfundojë punën: %d: %s", error, strerror(error));
		job_log(j, LOG_NOTICE | LOG_CONSOLE, "Duke përdorur opsionin rezervë për të përfunduar punën...");
		error = kill2(j->p, SIGTERM);
		if (error) {
			job_log(j, LOG_ERR, "Nuk e mundi të dërgojë sinjalin punës: %d: %s", error, strerror(error));
		} 
...

Pavarësisht emrit të tij, proc_terminate mund të dërgojë jo vetëm psignal me SIGTERM, por edhe SIGKILL.

Vrasja indirekte — kufizim nĂ« burime

NjĂ« rast mĂ« interesant mund tĂ« shihet nĂ« njĂ« thirrje tjetĂ«r tĂ« sistemit process_policy. PĂ«rdorimi standard i kĂ«saj thirrjeje tĂ« sistemit — kufizime nĂ« burimet e aplikacioneve, p.sh. pĂ«r indeksuesin, njĂ« kufizim nĂ« kuotĂ«n e kohĂ«s sĂ« procesorit dhe memories, nĂ« mĂ«nyrĂ« qĂ« sistemi tĂ« mos ngadalĂ«sohet ndjeshĂ«m pĂ«r shkak tĂ« veprimeve tĂ« ruajtjes nĂ« skedar. NĂ«se aplikacioni arrin kufizimin e burimeve, siç mund tĂ« shihet nga funksioni proc_apply_resource_actions, atij i dĂ«rgohet sinjali SIGKILL.

Megjithëse kjo thirrje e sistemit mund të prodhojë potencialisht vrasjen e procesit, sistemi nuk e ka kontrolluar adekuat të drejtat e procesit që thërret thirrjen e sistemit. Në të vërtetë, kontrolli existon, porashtu mjafton të përdorni një flamur alternativ PROC_POLICY_ACTION_SET për të anashkaluar këtë kusht.

Nga kĂ«tu, nĂ«se "kufizoni" kuotĂ«n e pĂ«rdorimit tĂ« CPU pĂ«r aplikacionin (pĂ«r shembull, lejoni tĂ« ekzekutohet vetĂ«m 1 ns), mund tĂ« vrisni çdo proces nĂ« sistem. NĂ« kĂ«tĂ« mĂ«nyrĂ«, njĂ« malware mund tĂ« eliminojĂ« çdo proces nĂ« sistem, duke pĂ«rfshirĂ« edhe procesin e antivirusit. Po ashtu, efekti qĂ« ndodh kur vritet procesi me pid 1 (launchctl) Ă«shtĂ« mjaft interesant — panic i kernel-it kur pĂ«rpiqet tĂ« pĂ«rpunojĂ« sinjalin SIGKILL 🙂

Si sihet mbrojtja e proceseve dhe shtrirjeve të bërthamës në macOS

Si të zgjidhni problemin?

Mënyra më e drejtpërdrejtë për të ndaluar vrasjen e procesit është të zëvendësosh treguesin e funksionit në tabelën e thirrjeve sistemike. Fatkeqësisht, ky metodë është jo triviale për shumë arsye.

Së pari, simboli që ka të bëjë me vendndodhjen e sysent në memorie është një simbol privat i bërthamës XNU dhe nuk mund të gjendet në simbolet e bërthamës. Do të duhet të përdorim metoda heuristike për kërkimin, siç është disassamble dinamike e funksionit dhe kërkimi i treguesit në të.

SĂ« pari, struktura e regjistrimeve nĂ« tabelĂ« varet nga flamujt me tĂ« cilĂ«t Ă«shtĂ« ndĂ«rtuar bĂ«rthama. NĂ«se Ă«shtĂ« shpallur flamuri CONFIG_REQUIRES_U32_MUNGING, atĂ«herĂ« madhĂ«sia e strukturĂ«s do tĂ« ndryshojĂ« - do tĂ« shtohet njĂ« fushĂ« shtesĂ«. sy_arg_munge32. ËshtĂ« e nevojshme tĂ« kryhet njĂ« kontroll shtesĂ« pĂ«r atĂ« me cilin flamur Ă«shtĂ« kompiluar bĂ«rthama, si njĂ« mundĂ«si tĂ« krahasohen treguesit nĂ« funksione me ato tĂ« njohura.

struct sysent {         /* tabela e thirrjeve sistemike */
        sy_call_t       *sy_call;       /* funksioni i implementimit */
#if CONFIG_REQUIRES_U32_MUNGING || (__arm__ && (__BIGGEST_ALIGNMENT__ > 4))
        sy_munge_t      *sy_arg_munge32; /* muesi i argumenteve të thirrjeve sistemike për procesin 32-bit */
#endif
        int32_t         sy_return_type; /* tipet e kthimeve të thirrjeve sistemike */
        int16_t         sy_narg;        /* numri i argumenteve */
        uint16_t        sy_arg_bytes;   /* Madhësia totale e argumenteve në bytes për
                                         * thirrjet sistemike 32-bit
                                         */
};

Për fat të mirë, në versionet moderne të macOS, Apple ofron një API të ri për të punuar me proceset. Endpoint Security API lejon klientët të autorizojnë shumë kërkesa për procese të tjera. Kështu, është e mundur të bllokohet çdo sinjal për proceset, duke përfshirë sinjalin SIGKILL me anë të API-së të përmendur më lart.

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

Njëlloj si në kernel, mund të regjistrohet një Politika MAC, e cila ofron një metodë mbrojtjeje nga sinjalet (policy proc_check_signal), megjithëse API nuk mbështetet zyrtarisht.

Mbrojtja e zgjerimit të kernelit

Përveç mbrojtjes së proceseve në sistem, është e nevojshme edhe mbrojtja e vetë zgjerimit të kernelit (kext). macOS ofron një kornizë për zhvilluesit për zhvillimin e lehtë të drejtuesve të pajisjeve IOKit. Përveç ofrimit të mjeteve për punë me pajisjet, IOKit siguron metoda për grumbullimin e drejtuesve (driver stacking) përmes instancave të klasave C++. Një aplikacion në userspace mund të "gjejë" një instancë të regjistruar të klasës për të vendosur një lidhje midis kernelit dhe userspace.

Për të zbuluar numrin e instancave të klasave në sistem, ekziston një utilitar i quajtur ioclasscount.

my_kext_ioservice = 1
my_kext_iouserclient = 1

Çdo zgjerim kernelit qĂ« dĂ«shiron tĂ« regjistrohet nĂ« grumbullin e drejtuesve Ă«shtĂ« i detyruar tĂ« shpallĂ« njĂ« klasĂ« tĂ« trashĂ«guar nga IOService, pĂ«r shembull, my_kext_ioservice nĂ« kĂ«tĂ« rast. Lidha e aplikacioneve tĂ« pĂ«rdoruesit shkakton krijimin e njĂ« instancĂ« tĂ« re tĂ« klasĂ«s qĂ« trashĂ«gohet nga IOUserClient, nĂ« shembullin my_kext_iouserclient.

Kur gjatĂ« pĂ«rpjekjes pĂ«r tĂ« shkarkuar drejtuesin nga sistemi (komanda kextunload), thirret funksioni virtual “bool terminate(IOOptionBits options)”. Mjafton tĂ« kthehet false nĂ« thirrjen e funksionit terminate gjatĂ« pĂ«rpjekjes pĂ«r shkarkim, pĂ«r tĂ« ndaluar kextunload.

bool Kext::terminate(IOOptionBits options)
{

  if (!IsUnloadAllowed)
  {
    // Shkarkimi nuk lejohet, po kthehet false
    return false;
  }

  return super::terminate(options);
}

Flamuri IsUnloadAllowed mund të vendoset nga IOUserClient gjatë ngarkimit. Kur ndalohet ngarkimi, komanda kextunload do të kthejë daljen e mëposhtme:

admin@admins-Mac drivermanager % sudo kextunload ./test.kext
Fjalëkalimi:
(kernel) Nuk mund të hiqet kext my.kext.test; shërbimet dështuan për t'u ndaluar - 0xe00002c7.
Dështoi të shkarkojë my.kext.test - (iokit/common) funksion i pavlefshëm.

NjĂ« mbrojtje tĂ« ngjashme duhet tĂ« bĂ«het edhe pĂ«r IOUserClient. Instancat e klasave mund tĂ« shkaktohen pĂ«rmes funksionit tĂ« userspace IOKitLib “IOCatalogueTerminate(mach_port_t, uint32_t flag, io_name_t description);”. Mund tĂ« kthehen false nĂ« thirrjen e komandĂ«s “terminate” derisa aplikacioni i userspace tĂ« “vdesĂ«â€, domethĂ«nĂ«, derisa tĂ« ndodhi thirrja e funksionit “clientDied”.

Mbrojtja e skedarëve

PĂ«r mbrojtjen e skedareve, mjafton tĂ« pĂ«rdorni Kauth API, i cili lejon kufizimin e aksesit nĂ« skedare. Apple u siguron zhvilluesve njoftime pĂ«r ngjarje tĂ« ndryshme nĂ« scope, ku pĂ«r ne janĂ« tĂ« rĂ«ndĂ«sishme operacionet KAUTH_VNODE_DELETE, KAUTH_VNODE_WRITE_DATA dhe KAUTH_VNODE_DELETE_CHILD. Kufizimi i aksesit nĂ« skedare Ă«shtĂ« mĂ« i thjeshtĂ« nĂ«pĂ«rmjet rrugĂ«s — pĂ«rdorim API-nĂ« “vn_getpath” pĂ«r tĂ« marrĂ« rrugĂ«n drejt skedarit dhe bĂ«jmĂ« krahasimin e prefix-it tĂ« rrugĂ«s. Vlen tĂ« theksohet se pĂ«r optimizimin e rinovimit tĂ« rrugĂ«ve tĂ« dosjeve, sistemi nuk autorizon aksesin nĂ« çdo skedar, por vetĂ«m nĂ« dosjen qĂ« Ă«shtĂ« rinovuar. ËshtĂ« e nevojshme tĂ« bĂ«het krahasimi i rrugĂ«s prind dhe tĂ« kufizohet KAUTH_VNODE_DELETE pĂ«r tĂ«.

Si sihet mbrojtja e proceseve dhe shtrirjeve të bërthamës në macOS

Një mangësi e këtij qasjeje mund të jetë performanca e ulët në rritjen e numrit të prefix-eve. Për të siguruar që krahasimi të mos jetë O(prefix*length), ku prefix është numri i prefix-eve dhe length është gjatësia e vargut, mund të përdorim një automatë të fundëm deterministik (DFA), të ndërtuar sipas prefix-eve.

Le të shqyrtojmë metodën e ndërtimit të DKA për këtë grup prefixesh. Inicializojmë kursoret në fillim të çdo prefixi. Nëse të gjithë kursoret tregojnë për të njëjtin simbol, rrisim secilin kursor me një simbol dhe mbajmë mend se gjatësia e vargjeve të njëjta është rritur me një. Nëse ka dy kursore, simbolet nën të cilat ndodhen janë të ndryshme, ndajmë kursoret në grupe sipas simbolit që ato tregojnë dhe përsërisim algoritmin për secilën grup.

Në rastin e parë (kur të gjitha simbolet nën kursore janë të njëjta) përftojmë gjendjen DKA, e cila ka vetëm një kalim sipas vargut të njëjtë. Në rastin e dytë, marrim një tabelë kalimi me madhësi 256 (numri i simboleve dhe numri maksimal i grupeve) për gjendjet e ardhshme, të marra gjatë thirrjes rekurzive të funksionit.

Le tĂ« shqyrtojmĂ« njĂ« shembull. PĂ«r grupin e prefixeve (“/foo/bar/tmp/”, “/var/db/foo/”, “/foo/bar/aba/”, “foo/bar/aac/”) mund tĂ« pĂ«rftojmĂ« kĂ«tĂ« DKA. NĂ« ilustrim janĂ« treguar vetĂ«m kalimet qĂ« çojnĂ« nĂ« gjendje tĂ« tjera, kalimet e tjera nuk do tĂ« jenĂ« pĂ«rfundimtare.

Si sihet mbrojtja e proceseve dhe shtrirjeve të bërthamës në macOS

Gjatë kalimit përmes gjendjeve të DKA mund të ndodhin 3 raste.

  1. Arrithet një gjendje përfundimtare - rruga është e sigurt, kufizoni operacionet KAUTH_VNODE_DELETE, KAUTH_VNODE_WRITE_DATA dhe KAUTH_VNODE_DELETE_CHILD
  2. Nuk Ă«shtĂ« arritur njĂ« gjendje pĂ«rfundimtare, por rruga "mbaroi" (u arrit zero-terminatori) - rruga Ă«shtĂ« prind, Ă«shtĂ« e nevojshme tĂ« kufizohet KAUTH_VNODE_DELETE. VĂ«reni se nĂ«se vnode Ă«shtĂ« njĂ« dosje, duhet tĂ« shtoni nĂ« fund ‘/’, ndryshe mund tĂ« ndodhi kufizimi nĂ« skedarin “/foor/bar/t”, qĂ« Ă«shtĂ« e gabuar.
  3. Nuk është arritur një gjendje përfundimtare, rruga nuk mbaroi. Asnjë nga prefiksët nuk përputhet me këtë, nuk vendosim kufizime.

Përfundimi

Qëllimi i zgjidhjeve të sigurisë që po zhvillohen është rritja e nivelit të sigurisë për përdoruesin dhe të dhënat e tij. Nga njëra anë, ky qëllim sigurohet përmes zhvillimit të produktit software Acronis, i cili mbulon ato dobie ku "është e dobët" vetë sistemi operativ. Nga ana tjetër, nuk duhet të injorojmë forcimin e aspekteve të sigurisë që mund të përmirësohen nga ana e OS, veçanërisht sepse mbyllja e tillë e dobësive rrit stabilitetin tonë si produkt. Dobësia u raportua nga Apple Product Security Team dhe u rregullua në macOS 10.14.5 (https://support.apple.com/en-gb/HT210119).

Si sihet mbrojtja e proceseve dhe shtrirjeve të bërthamës në macOS

Të gjitha këto mund të bëhen vetëm nëse utilitari juaj është instaluar zyrtarisht në bllok. Kështu, për software që është i jashtëm dhe i padëshiruar nuk ka skema të tilla. Megjithatë, siç e shihni, edhe për mbrojtjen e programeve legjitime, si antivirus dhe sistemi i kopjimit, kërkohet përpjekje. Por tani produktet e reja Acronis për macOS do të kenë mbrojtje shtesë nga shkarkimi nga sistemi.

Burimi: habr.com

Bli njĂ« hosting tĂ« besueshĂ«m pĂ«r faqet me mbrojtje DDoS, VPS VDS serverĂ« đŸ”„ Bli njĂ« hosting tĂ« besueshĂ«m pĂ«r faqet me mbrojtje DDoS, VPS VDS serverĂ« | ProHoster