Hoe om prosesse en kernuitbreidings op macOS te beskerm

Hallo, Habr! Vandag wil ek praat oor hoe u prosesse teen aanvalle deur aanvallers in macOS kan beskerm. Dit is byvoorbeeld nuttig vir 'n antivirus- of rugsteunstelsel, veral aangesien daar onder macOS verskeie maniere is om 'n proses te "doodmaak". Lees hieroor en beskermingsmetodes onder die snit.

Hoe om prosesse en kernuitbreidings op macOS te beskerm

Die klassieke manier om 'n proses te "doodmaak".

'n Bekende manier om 'n proses te "doodmaak", is om 'n SIGKILL-sein na die proses te stuur. Deur bash kan jy die standaard "kill -SIGKILL PID" of "pkill -9 NAME" noem om dood te maak. Die "kill"-opdrag is sedert die dae van UNIX bekend en is nie net op macOS beskikbaar nie, maar ook op ander UNIX-agtige stelsels.

Net soos in UNIX-agtige stelsels, laat macOS jou toe om enige seine na 'n proses te onderskep behalwe twee - SIGKILL en SIGSTOP. Hierdie artikel sal hoofsaaklik fokus op die SIGKILL-sein as 'n sein wat veroorsaak dat 'n proses doodgemaak word.

macOS besonderhede

Op macOS roep die doodstelseloproep in die XNU-kern die psignal(SIGKILL,...)-funksie. Kom ons probeer om te sien watter ander gebruikeraksies in gebruikersruimte deur die psignal-funksie genoem kan word. Kom ons verwyder oproepe na die psignal-funksie in die interne meganismes van die kern (alhoewel hulle dalk nie-triviaal is, sal ons dit vir 'n ander artikel laat 🙂 - handtekeningverifikasie, geheuefoute, uitgang/beëindig hantering, lêerbeskermingskendings, ens. .

Kom ons begin die hersiening met die funksie en die ooreenstemmende stelseloproep beëindig_met_loonvrag. Dit kan gesien word dat daar benewens die klassieke doodoproep 'n alternatiewe benadering is wat spesifiek vir die macOS-bedryfstelsel is en nie in BSD gevind word nie. Die bedryfsbeginsels van beide stelseloproepe is ook soortgelyk. Dit is direkte oproepe na die kernfunksie psignal. Let ook daarop dat voordat 'n proses doodgemaak word, 'n "cansignal"-kontrole uitgevoer word - of die proses 'n sein na 'n ander proses kan stuur; die stelsel laat byvoorbeeld geen toepassing toe om stelselprosesse dood te maak nie.

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

van stapel gestuur

Die standaard manier om daemone te skep tydens die opstart van die stelsel en hul leeftyd te beheer, word bekendgestel. Neem asseblief kennis dat die bronne vir die ou weergawe van launchctl tot macOS 10.10 is, kodevoorbeelde word vir illustratiewe doeleindes verskaf. Moderne launchctl stuur lanseerde seine via XPC, launchctl-logika is daarna geskuif.

Kom ons kyk hoe presies toepassings gestop word. Voordat die SIGTERM-sein gestuur word, word gepoog om die toepassing gestop te word deur die "proc_terminate"-stelseloproep te gebruik.

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

Onder die enjinkap kan proc_terminate, ten spyte van sy naam, nie net psignal met SIGTERM stuur nie, maar ook SIGKILL.

Indirekte doodmaak - Hulpbronbeperking

'n Meer interessante geval kan in 'n ander stelseloproep gesien word proses_beleid. 'n Algemene gebruik van hierdie stelseloproep is om toepassingshulpbronne te beperk, soos vir 'n indekseerder om SVE-tyd en geheuekwotas te beperk sodat die stelsel nie aansienlik vertraag word deur lêerkasaktiwiteite nie. As 'n toepassing sy hulpbronlimiet bereik het, soos gesien kan word uit die proc_apply_resource_actions-funksie, word 'n SIGKILL-sein na die proses gestuur.

Alhoewel hierdie stelseloproep moontlik 'n proses kan doodmaak, het die stelsel nie die regte van die proses wat die stelseloproep oproep, voldoende nagegaan nie. Eintlik nagaan bestaan ​​het, maar dit is genoeg om die alternatiewe vlag PROC_POLICY_ACTION_SET te gebruik om hierdie toestand te omseil.

Dus, as jy die toepassing se SVE-gebruikkwota "beperk" (byvoorbeeld om slegs 1 ns toe te laat om te loop), dan kan jy enige proses in die stelsel doodmaak. Die wanware kan dus enige proses op die stelsel doodmaak, insluitend die antivirusproses. Ook interessant is die effek wat plaasvind wanneer 'n proses met pid 1 (launchctl) doodgemaak word - kernpaniek wanneer u die SIGKILL-sein probeer verwerk :)

Hoe om prosesse en kernuitbreidings op macOS te beskerm

Hoe kan u die probleem oplos?

Die eenvoudigste manier om te verhoed dat 'n proses doodgemaak word, is om die funksiewyser in die stelseloproeptabel te vervang. Ongelukkig is hierdie metode om baie redes nie-triviaal.

Eerstens, die simbool wat sysent se geheue ligging beheer, is nie net privaat vir die XNU kernsimbool nie, maar kan nie in kernsimbole gevind word nie. Jy sal heuristiese soekmetodes moet gebruik, soos om die funksie dinamies uitmekaar te haal en vir 'n wyser daarin te soek.

Tweedens hang die struktuur van inskrywings in die tabel af van die vlae waarmee die kern saamgestel is. As die CONFIG_REQUIRES_U32_MUNGING vlag verklaar word, sal die grootte van die struktuur verander word - 'n bykomende veld sal bygevoeg word sy_arg_munge32. Dit is nodig om 'n bykomende kontrole uit te voer om te bepaal met watter vlag die kern saamgestel is, of alternatiewelik, kontroleer funksiewysers teenoor bekendes.

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

Gelukkig bied Apple in moderne weergawes van macOS 'n nuwe API om met prosesse te werk. Die Endpoint Security API laat kliënte toe om baie versoeke na ander prosesse te magtig. U kan dus enige seine na prosesse blokkeer, insluitend die SIGKILL-sein, deur die bogenoemde API te gebruik.

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

Net so kan 'n MAC-beleid in die kern geregistreer word, wat 'n seinbeskermingsmetode (beleid proc_check_signal) bied, maar die API word nie amptelik ondersteun nie.

Kernel uitbreiding beskerming

Benewens die beskerming van prosesse in die stelsel, is die beskerming van die kernuitbreiding self (kext) ook nodig. macOS bied 'n raamwerk vir ontwikkelaars om maklik IOKit-toestelbestuurders te ontwikkel. Benewens die verskaffing van gereedskap om met toestelle te werk, bied IOKit metodes vir bestuurderstapeling deur gebruik te maak van gevalle van C++-klasse. 'n Toepassing in die gebruikersruimte sal 'n geregistreerde instansie van die klas kan "vind" om 'n kern-gebruikersruimte-verhouding te vestig.

Om die aantal klasgevalle in die stelsel op te spoor, is daar die ioclasscount-nutsding.

my_kext_ioservice = 1
my_kext_iouserclient = 1

Enige kernuitbreiding wat by die drywerstapel wil registreer, moet 'n klas verklaar wat van IOService erf, byvoorbeeld my_kext_ioservice in hierdie geval.Koppeling van gebruikertoepassings veroorsaak die skepping van 'n nuwe instansie van die klas wat van IOUserClient erf, in die voorbeeld my_kext_iouserclient.

As u probeer om 'n bestuurder van die stelsel af te laai (kextunload-opdrag), word die virtuele funksie "bool terminate (IOOptionBits-opsies)" genoem. Dit is genoeg om vals terug te gee op die oproep om te beëindig wanneer u probeer aflaai om kextunload te deaktiveer.

bool Kext::terminate(IOOptionBits options)
{

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

  return super::terminate(options);
}

Die IsUnloadAllowed-vlag kan deur die IOUserClient gestel word wanneer dit gelaai word. Wanneer daar 'n aflaailimiet is, sal die kextunload-opdrag die volgende uitvoer terugstuur:

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.

Soortgelyke beskerming moet vir IOUserClient gedoen word. Gevalle van klasse kan afgelaai word met behulp van die IOKitLib-gebruikersruimtefunksie "IOCatalogueTerminate(mach_port_t, uint32_t flag, io_name_t description);". U kan vals terugstuur wanneer u die "terminate"-opdrag oproep totdat die gebruikersruimtetoepassing "sterf", dit wil sê, die "clientDied"-funksie word nie opgeroep nie.

Lêerbeskerming

Om lêers te beskerm, is dit genoeg om die Kauth API te gebruik, wat jou toelaat om toegang tot lêers te beperk. Apple voorsien ontwikkelaars van kennisgewings oor verskeie gebeure in die omvang; vir ons is die bedrywighede KAUTH_VNODE_DELETE, KAUTH_VNODE_WRITE_DATA en KAUTH_VNODE_DELETE_CHILD belangrik. Die maklikste manier om toegang tot lêers te beperk, is deur pad - ons gebruik die "vn_getpath" API om die pad na die lêer te kry en die padvoorvoegsel te vergelyk. Let daarop dat om die hernoeming van lêergidspaaie te optimaliseer, magtig die stelsel nie toegang tot elke lêer nie, maar slegs tot die lêergids self wat hernoem is. Dit is nodig om die ouerpad te vergelyk en KAUTH_VNODE_DELETE daarvoor te beperk.

Hoe om prosesse en kernuitbreidings op macOS te beskerm

Die nadeel van hierdie benadering kan lae werkverrigting wees namate die aantal voorvoegsels toeneem. Om te verseker dat die vergelyking nie gelyk is aan O(voorvoegsel*lengte), waar voorvoegsel die aantal voorvoegsels is, lengte die lengte van die string is, kan jy 'n deterministiese eindige outomaat (DFA) gebruik wat deur voorvoegsels gebou is.

Kom ons kyk na 'n metode om 'n DFA vir 'n gegewe stel voorvoegsels te konstrueer. Ons inisialiseer die wysers aan die begin van elke voorvoegsel. As alle wysers na dieselfde karakter wys, verhoog dan elke wyser met een karakter en onthou dat die lengte van dieselfde lyn met een groter is. As daar twee wysers met verskillende simbole is, verdeel die wysers in groepe volgens die simbool waarna hulle wys en herhaal die algoritme vir elke groep.

In die eerste geval (al die karakters onder die wysers is dieselfde), kry ons 'n DFA-toestand wat net een oorgang langs dieselfde lyn het. In die tweede geval kry ons 'n tabel van oorgange van grootte 256 (aantal karakters en maksimum aantal groepe) na daaropvolgende toestande wat verkry word deur die funksie rekursief te roep.

Kom ons kyk na 'n voorbeeld. Vir 'n stel voorvoegsels ("/foo/bar/tmp/", "/var/db/foo/", "/foo/bar/aba/", "foo/bar/aac/") kan jy die volgende kry DFA. Die figuur toon slegs oorgange wat na ander state lei; ander oorgange sal nie finaal wees nie.

Hoe om prosesse en kernuitbreidings op macOS te beskerm

As u deur die DKA-state gaan, kan daar 3 gevalle wees.

  1. Die finale toestand is bereik - die pad is beskerm, ons beperk die bewerkings KAUTH_VNODE_DELETE, KAUTH_VNODE_WRITE_DATA en KAUTH_VNODE_DELETE_CHILD
  2. Die finale toestand is nie bereik nie, maar die pad het “geëindig” (die nulbeëindiger is bereik) - die pad is 'n ouer, dit is nodig om KAUTH_VNODE_DELETE te beperk. Let daarop dat as vnode 'n vouer is, jy 'n '/' aan die einde moet byvoeg, anders kan dit dit beperk tot die lêer "/foor/bar/t", wat verkeerd is.
  3. Die finale toestand is nie bereik nie, die pad het nie geëindig nie. Geen van die voorvoegsels pas by hierdie een nie, ons stel nie beperkings in nie.

Gevolgtrekking

Die doel van die sekuriteitsoplossings wat ontwikkel word, is om die vlak van sekuriteit van die gebruiker en sy data te verhoog. Aan die een kant word hierdie doelwit bereik deur die ontwikkeling van die Acronis-sagtewareproduk, wat daardie kwesbaarhede toemaak waar die bedryfstelsel self “swak” is. Aan die ander kant moet ons nie nalaat om daardie sekuriteitsaspekte te versterk wat aan die OS-kant verbeter kan word nie, veral aangesien die sluiting van sulke kwesbaarhede ons eie stabiliteit as 'n produk verhoog. Die kwesbaarheid is by die Apple-produksekuriteitspan aangemeld en is reggestel in macOS 10.14.5 (https://support.apple.com/en-gb/HT210119).

Hoe om prosesse en kernuitbreidings op macOS te beskerm

Dit alles kan slegs gedoen word as u hulpprogram amptelik in die kern geïnstalleer is. Dit wil sê, daar is nie sulke skuiwergate vir eksterne en ongewenste sagteware nie. Soos u egter kan sien, verg selfs die beskerming van wettige programme soos antivirus- en rugsteunstelsels werk. Maar nou sal nuwe Acronis-produkte vir macOS addisionele beskerming hê teen aflaai van die stelsel.

Bron: will.com

Voeg 'n opmerking