Si të mbroni proceset dhe shtesat e kernelit në macOS

Përshëndetje, Habr! Sot do të doja të flisja se si mund t'i mbroni proceset nga sulmet nga sulmuesit në macOS. Për shembull, kjo është e dobishme për një sistem antivirus ose rezervë, veçanërisht pasi nën macOS ka disa mënyra për të "vrarë" një proces. Lexoni për këtë dhe metodat e mbrojtjes nën prerje.

Si të mbroni proceset dhe shtesat e kernelit 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 një sinjali SIGKILL në proces. Nëpërmjet bash ju mund të thërrisni standardin "kill -SIGKILL PID" ose "pkill -9 NAME" për të vrarë. Komanda "vras" është e njohur që nga ditët e UNIX dhe është e disponueshme jo vetëm në macOS, por edhe në sisteme të tjera të ngjashme me UNIX.

Ashtu si në sistemet e ngjashme me UNIX, macOS ju lejon të përgjoni çdo sinjal në një proces përveç dy - SIGKILL dhe SIGSTOP. Ky artikull do të fokusohet kryesisht në sinjalin SIGKILL si një sinjal që shkakton vdekjen e një procesi.

specifikat e macOS

Në macOS, thirrja e sistemit kill në kernelin XNU thërret funksionin psignal(SIGKILL,...). Le të përpiqemi të shohim se cilat veprime të tjera të përdoruesit në hapësirën e përdoruesit mund të quhen nga funksioni psignal. Le të heqim thirrjet për funksionin psignal në mekanizmat e brendshëm të kernelit (edhe pse mund të jenë jo të parëndësishëm, do t'i lëmë për një artikull tjetër 🙂 - verifikimi i nënshkrimit, gabimet e kujtesës, trajtimi i daljes/përfundimit, shkeljet e mbrojtjes së skedarëve, etj. .

Le të fillojmë rishikimin me funksionin dhe thirrjen përkatëse të sistemit terminate_me_payload. Mund të shihet se përveç thirrjes klasike të vrasjes, ekziston një qasje alternative që është specifike për sistemin operativ macOS dhe nuk gjendet në BSD. Parimet e funksionimit të të dy thirrjeve të sistemit janë gjithashtu të ngjashme. Ato janë thirrje direkte në psignalin e funksionit të kernelit. Vini re gjithashtu se përpara mbylljes së një procesi, kryhet një kontroll "kansignal" - nëse procesi mund të dërgojë një sinjal në një proces tjetër; sistemi nuk lejon asnjë aplikacion të vrasë proceset e sistemit, 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);
	}
...
}

nisur

Hapet mënyra standarde për të krijuar demonë në fillimin e sistemit dhe për të kontrolluar jetëgjatësinë e tyre. Ju lutemi vini re se burimet janë për versionin e vjetër të launchctl deri në macOS 10.10, shembuj kodesh janë dhënë për qëllime ilustruese. Launchctl moderne dërgon sinjale të lëshuara përmes XPC, logjika launchctl është zhvendosur në të.

Le të shohim se si janë ndalur saktësisht aplikacionet. Përpara dërgimit të sinjalit SIGTERM, aplikacioni tentohet të ndalet duke përdorur thirrjen e sistemit “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));
		} 
...
<>

Nën kapuç, proc_terminate, pavarësisht nga emri i tij, mund të dërgojë jo vetëm psignal me SIGTERM, por edhe SIGKILL.

Vrasja indirekte - Kufiri i burimeve

Një rast më interesant mund të shihet në një thirrje tjetër sistemi proces_politika. Një përdorim i zakonshëm i kësaj thirrjeje sistemi është të kufizojë burimet e aplikacionit, si p.sh. për një indeksues për të kufizuar kohën e CPU dhe kuotat e memories në mënyrë që sistemi të mos ngadalësohet ndjeshëm nga aktivitetet e ruajtjes së skedarëve. Nëse një aplikacion ka arritur kufirin e burimit të tij, siç mund të shihet nga funksioni proc_apply_resource_actions, një sinjal SIGKILL i dërgohet procesit.

Megjithëse kjo thirrje sistemi mund të vrasë potencialisht një proces, sistemi nuk ka kontrolluar në mënyrë adekuate të drejtat e procesit që thërret thirrjen e sistemit. Në fakt duke kontrolluar ekzistonte, por mjafton të përdorni flamurin alternativ PROC_POLICY_ACTION_SET për të anashkaluar këtë kusht.

Prandaj, nëse "kufizoni" kuotën e përdorimit të CPU-së të aplikacionit (për shembull, duke lejuar vetëm 1 ns të ekzekutohet), atëherë mund të mbytni çdo proces në sistem. Kështu, malware mund të vrasë çdo proces në sistem, duke përfshirë procesin e antivirusit. Gjithashtu interesant është efekti që ndodh kur mbytni një proces me pid 1 (launchctl) - paniku i kernelit kur përpiqeni të përpunoni sinjalin SIGKILL :)

Si të mbroni proceset dhe shtesat e kernelit në macOS

Si ta zgjidhim problemin?

Mënyra më e drejtpërdrejtë për të parandaluar vdekjen e një procesi është zëvendësimi i treguesit të funksionit në tabelën e thirrjeve të sistemit. Fatkeqësisht, kjo metodë nuk është e parëndësishme për shumë arsye.

Së pari, simboli që kontrollon vendndodhjen e kujtesës së sysent nuk është vetëm privat për simbolin e kernelit XNU, por nuk mund të gjendet në simbolet e kernelit. Ju do të duhet të përdorni metoda heuristike kërkimi, të tilla si çmontimi dinamik i funksionit dhe kërkimi i një treguesi në të.

Së dyti, struktura e hyrjeve në tabelë varet nga flamujt me të cilët është përpiluar kerneli. Nëse deklarohet flamuri CONFIG_REQUIRES_U32_MUNGING, madhësia e strukturës do të ndryshohet - do të shtohet një fushë shtesë sy_arg_munge32. Është e nevojshme të kryhet një kontroll shtesë për të përcaktuar se me cilin flamur është përpiluar kerneli, ose, në mënyrë alternative, të kontrollohen treguesit e funksionit kundrejt atyre të njohur.

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

Për fat të mirë, në versionet moderne të macOS, Apple ofron një API të re për të punuar me proceset. Endpoint Security API i lejon klientët të autorizojnë shumë kërkesa për procese të tjera. Kështu, ju mund të bllokoni çdo sinjal për proceset, duke përfshirë sinjalin SIGKILL, duke përdorur API-në e lartpërmendur.

#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ë mënyrë të ngjashme, një politikë MAC mund të regjistrohet në kernel, e cila ofron një metodë të mbrojtjes së sinjalit (policy proc_check_signal), por API nuk mbështetet zyrtarisht.

Mbrojtja e zgjerimit të kernelit

Përveç mbrojtjes së proceseve në sistem, është gjithashtu e nevojshme mbrojtja e vetë zgjerimit të kernelit (kext). macOS ofron një kornizë për zhvilluesit që të zhvillojnë me lehtësi drejtuesit e pajisjes IOKit. Përveç sigurimit të mjeteve për të punuar me pajisje, IOKit ofron metoda për grumbullimin e drejtuesve duke përdorur shembuj të klasave C++. Një aplikacion në hapësirën e përdoruesve do të jetë në gjendje të "gjeni" një shembull të regjistruar të klasës për të krijuar një marrëdhënie kernel-hapësirë ​​përdoruesi.

Për të zbuluar numrin e rasteve të klasës në sistem, ekziston mjeti i ioclasscount.

my_kext_ioservice = 1
my_kext_iouserclient = 1

Çdo shtesë e kernelit që dëshiron të regjistrohet me pirgun e drejtuesve duhet të deklarojë një klasë që trashëgon nga IOService, për shembull my_kext_ioservice në këtë rast. Lidhja e aplikacioneve të përdoruesit shkakton krijimin e një shembulli të ri të klasës që trashëgon nga IOUserClient, në shembullin my_kext_iouserclient.

Kur përpiqeni të shkarkoni një drejtues nga sistemi (komandë kextunload), thirret funksioni virtual "bool terminate (opsionet e IOOptionBits)". Mjafton të ktheni false në thirrjen për të përfunduar kur përpiqeni të shkarkoni për të çaktivizuar kextunload.

bool Kext::terminate(IOOptionBits options)
{

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

  return super::terminate(options);
}

Flamuri IsUnloadAllowed mund të vendoset nga IOUserClient kur ngarkohet. Kur ka një kufi shkarkimi, komanda kextunload do të kthejë daljen e mëposhtme:

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.

Mbrojtje e ngjashme duhet të bëhet për IOUserClient. Instancat e klasave mund të shkarkohen duke përdorur funksionin e hapësirës së përdoruesve të IOKitLib "IOCatalogueTerminate(mach_port_t, flamuri uint32_t, përshkrimi io_name_t);". Ju mund të ktheni false kur thërrisni komandën "terminate" derisa aplikacioni i hapësirës së përdoruesit "të vdesë", domethënë funksioni "clientDied" nuk thirret.

Mbrojtja e skedarit

Për të mbrojtur skedarët, mjafton të përdorni Kauth API, i cili ju lejon të kufizoni aksesin në skedarë. Apple u ofron zhvilluesve njoftime për ngjarje të ndryshme në fushëveprimin; për ne, operacionet KAUTH_VNODE_DELETE, KAUTH_VNODE_WRITE_DATA dhe KAUTH_VNODE_DELETE_CHILD janë të rëndësishme. Mënyra më e lehtë për të kufizuar aksesin në skedarë është me shteg - ne përdorim API-në "vn_getpath" për të marrë shtegun drejt skedarit dhe për të krahasuar prefiksin e shtegut. Vini re se për të optimizuar riemërtimin e shtigjeve të dosjeve të skedarëve, sistemi nuk autorizon hyrjen në çdo skedar, por vetëm në vetë dosjen që është riemërtuar. Është e nevojshme të krahasohet shtegu prind dhe të kufizohet KAUTH_VNODE_DELETE për të.

Si të mbroni proceset dhe shtesat e kernelit në macOS

Disavantazhi i kësaj qasjeje mund të jetë performanca e ulët me rritjen e numrit të parashtesave. Për të siguruar që krahasimi të mos jetë i barabartë me O (prefiks*gjatësia), ku prefiksi është numri i parashtesave, gjatësia është gjatësia e vargut, mund të përdorni një automat të fundëm përcaktues (DFA) të ndërtuar nga parashtesa.

Le të shqyrtojmë një metodë për ndërtimin e një DFA për një grup të caktuar parashtesash. Ne inicializojmë kursorët në fillim të çdo parashtese. Nëse të gjithë kursorët tregojnë të njëjtin karakter, atëherë rriteni çdo kursor me një karakter dhe mbani mend se gjatësia e së njëjtës rresht është më e madhe për një. Nëse ka dy kursorë me simbole të ndryshme, ndajini kursorët në grupe sipas simbolit ku ata tregojnë dhe përsëritni algoritmin për secilin grup.

Në rastin e parë (të gjithë personazhet nën kursorët janë të njëjtë), marrim një gjendje DFA që ka vetëm një tranzicion përgjatë së njëjtës linjë. Në rastin e dytë, marrim një tabelë të tranzicionit me madhësi 256 (numri i karaktereve dhe numri maksimal i grupeve) në gjendjet pasuese të marra duke thirrur në mënyrë rekursive funksionin.

Le të shohim një shembull. Për një grup parashtesash (“/foo/bar/tmp/”, “/var/db/foo/”, “/foo/bar/aba/”, “foo/bar/aac/”) mund të merrni sa vijon DFA. Figura tregon vetëm tranzicionet që çojnë në gjendje të tjera; tranzicionet e tjera nuk do të jenë përfundimtare.

Si të mbroni proceset dhe shtesat e kernelit në macOS

Kur kaloni nëpër shtetet e DKA, mund të ketë 3 raste.

  1. Gjendja përfundimtare është arritur - shtegu është i mbrojtur, ne kufizojmë operacionet KAUTH_VNODE_DELETE, KAUTH_VNODE_WRITE_DATA dhe KAUTH_VNODE_DELETE_CHILD
  2. Gjendja përfundimtare nuk u arrit, por shtegu "mbaroi" (u arrit terminatori null) - shtegu është prind, është e nevojshme të kufizohet KAUTH_VNODE_DELETE. Vini re se nëse vnode është një dosje, duhet të shtoni një '/' në fund, përndryshe mund ta kufizojë atë në skedarin "/foor/bar/t", i cili është i pasaktë.
  3. Gjendja përfundimtare nuk u arrit, rruga nuk mbaroi. Asnjë prej parashtesave nuk përputhet me këtë, ne nuk vendosim kufizime.

Përfundim

Qëllimi i zgjidhjeve të sigurisë që po zhvillohen është rritja e nivelit të sigurisë së përdoruesit dhe të dhënave të tij. Nga njëra anë, ky qëllim arrihet me zhvillimin e produktit softuer Acronis, i cili mbyll ato dobësi ku vetë sistemi operativ është "i dobët". Nga ana tjetër, nuk duhet të neglizhojmë forcimin e atyre aspekteve të sigurisë që mund të përmirësohen në anën e OS, veçanërisht pasi mbyllja e dobësive të tilla rrit stabilitetin tonë si produkt. Dobësia iu raportua Ekipit të Sigurisë së Produkteve të Apple dhe është rregulluar në macOS 10.14.5 (https://support.apple.com/en-gb/HT210119).

Si të mbroni proceset dhe shtesat e kernelit në macOS

E gjithë kjo mund të bëhet vetëm nëse programi juaj është instaluar zyrtarisht në kernel. Kjo do të thotë, nuk ka zbrazëtira të tilla për softuerin e jashtëm dhe të padëshiruar. Sidoqoftë, siç mund ta shihni, edhe mbrojtja e programeve legjitime si antiviruset dhe sistemet rezervë kërkon punë. Por tani produktet e reja Acronis për macOS do të kenë mbrojtje shtesë kundër shkarkimit nga sistemi.

Burimi: www.habr.com

Shto një koment