Hvordan man beskytter processer og udvidelser i macOS

Hej, Habr! I dag vil jeg gerne tale om, hvordan man kan beskytte processer mod indtrængen fra kriminelle i macOS. For eksempel er dette nyttigt for antivirusprogrammer eller backupsystemer, især i lyset af at der i macOS findes flere metoder til at "dræbe" en proces. Læs mere om dette og metoderne til beskyttelse nedenfor.

Hvordan man beskytter processer og udvidelser i macOS

Den klassiske metode til at "dræbe" en proces

En velkendt måde at "dræbe" en proces på er at sende et SIGKILL-signal til processen. Gennem bash kan man kalde standard "kill -SIGKILL PID" eller "pkill -9 NAME" for at dræbe. Kommandoen "kill" har været kendt siden UNIX-tiden og er tilgængelig ikke kun i macOS, men også på andre UNIX-lignende systemer.

Ligesom i UNIX-lignende systemer tillader macOS at opfange alle signaler til processen bortset fra to - SIGKILL og SIGSTOP. Denne artikel vil primært fokusere på SIGKILL-signalet, som er det signal, der medfører drab på processen.

Særligheder ved macOS

I macOS kalder systemopkaldet kill i XNU-kernen funktionen psignal(SIGKILL,…). Lad os se, hvilke andre handlinger fra brugerrummet der kan kalde funktionen psignal. Vi vil udelukke opkald til psignal-funktionen i de interne mekanismer i kernen (selvom de kan være ikke-trivielle, vil vi lade dem være til en anden artikel 🙂 - signaturverificering, hukommelsesfejl, håndtering af exit/terminate, overtrædelse af filbeskyttelse osv.).

Lad os begynde gennemgangen med funktionen og det relevante systemopkald terminate_with_payload. Det ses, at der udover det klassiske kill-opkald også findes en alternativ tilgang, som er specifik for macOS og ikke findes i BSD. Principperne for begge systemopkald er også nært beslægtede. De udgør direkte kald til kernens psignal-funktion. Vi bemærker også, at der foretages en kontrol før drab på processen - "cansignal" - om processen kan sende et signal til en anden proces, systemet tillader ikke, at nogen applikationer dræber systemprocesser, for eksempel.

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() vil pendere en SIGKILL til den angivne tråd eller
		 * returnere, hvis tråden og/eller opgaven allerede er under afslutning. Uanset hvad, vil den
		 * aktuelle tråd ikke vende tilbage til brugerrummet.
		 */
		psignal_thread_with_reason(target_proc, current_thread(), SIGKILL, signal_reason);
	} else {
		psignal_with_reason(target_proc, SIGKILL, signal_reason);
	}
...
}

launchd

Den standardmetode til at skabe daemons ved systemstart og kontrollere deres levetid er launchd. Jeg vil bemærke, at kildeteksterne er for den gamle version af launchctl før macOS 10.10, og kodeeksemplerne er givet som illustration. Den moderne launchctl sender signaler til launchd via XPC, og logikken for launchctl er blevet flyttet ind i dette.

Lad os se på, hvordan applikationer stoppes. Inden der sendes et SIGTERM-signal, forsøges applikationen stoppet ved hjælp af systemopkaldet “proc_terminate”.

...
	error = proc_terminate(j->p, &sig);
	if (error) {
		job_log(j, LOG_ERR | LOG_CONSOLE, "Kunne ikke afslutte job: %d: %s", error, strerror(error));
		job_log(j, LOG_NOTICE | LOG_CONSOLE, "Bruger fallback mulighed for at afslutte job...");
		error = kill2(j->p, SIGTERM);
		if (error) {
			job_log(j, LOG_ERR, "Kunne ikke sende signal til job: %d: %s", error, strerror(error));
		} 
...

Under motorhjelmen kan proc_terminate, på trods af sit navn, ikke kun sende psignal med SIGTERM, men også SIGKILL.

Indirekte drab — ressourcebegrænsning

Et mere interessant tilfælde kan ses i et andet systemopkald process_policy. Standardbrug af dette systemopkald er begrænsninger på applikationsressourcer, f.eks. for en indeksator begrænses det til en kvote for CPU-tid og hukommelse, så systemet ikke væsentligt bremses af filcaching. Hvis applikationen når begrænsningen af ressourcer, som man kan se fra funktionen proc_apply_resource_actions, sendes der et SIGKILL-signal til processen.

Selvom dette systemopkald potentielt kan dræbe en proces, har systemet ikke korrekt kontrolleret rettighederne for den proces, der kalder systemopkaldet. Faktisk var kontrollen til stede, men det er tilstrækkeligt at bruge det alternative flag PROC_POLICY_ACTION_SET for at omgå denne betingelse.

Herfra, hvis man "begrænser" CPU-brugsgrænsen til applikationen (for eksempel kun tillader 1 ns at køre), kan man dræbe enhver proces i systemet. På den måde kan skadelig software dræbe enhver proces på systemet, inklusive antivirusprocessen. Effekten ved at dræbe processen med pid 1 (launchctl) er også interessant — kernel panic opstår ved forsøget på at behandle SIGKILL-signal 🙂

Hvordan man beskytter processer og udvidelser i macOS

Hvordan løser man problemet?

Den mest ligefremme måde at forhindre drab på processer er at ændre pegeren til funktionen i systemkaldtabellen. Desværre er denne metode ikke-trivial af mange årsager

For det første er symbolet, der repræsenterer placeringen af sysent i hukommelsen, ikke kun et privat symbol i XNU-kernen, men kan heller ikke findes i kerne-symbolerne. Man må bruge heuristiske metoder til at finde det, for eksempel dynamisk disassembling af funktionen og lede efter pegeren i den.

For det andet afhænger strukturen af posterne i tabellen af de flag, som kernen er bygget med. Hvis flaget CONFIG_REQUIRES_U32_MUNGING er erklæret, ændres størrelsen af strukturen — et ekstra felt tilføjes sy_arg_munge32. Der skal udføres en ekstra kontrol af, hvilket flag kernen er kompileret med; alternativt kan pegerne til funktioner sammenlignes med kendte.

struct sysent {         
        /* system call table */
        sy_call_t       *sy_call;       /* implementerende funktion */
#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;        /* antal args */
        uint16_t        sy_arg_bytes;   /* Total størrelse af argumenter i bytes for
                                         * 32-bit system calls
                                         */
};

Heldigvis leverer Apple i moderne versioner af macOS et nyt API til håndtering af processer. Endpoint Security API gør det muligt for klienter at godkende mange anmodninger til andre processer. På den måde kan man blokere for eventuelle signaler til processer, herunder SIGKILL-signalet ved hjælp af det nævnte API.

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

Tilsvarende kan man registrere en MAC Policy i kernen, som giver en metode til beskyttelse mod signaler (policy proc_check_signal), men API'et understøttes ikke officielt.

Kerneudvidelsesbeskyttelse

Udover at beskytte processerne i systemet er det også nødvendigt at beskytte selve kernetilføjelsen (kext). macOS tilbyder udviklerne et framework til nem udvikling af enhedsdrivere kaldet IOKit. Udover at give muligheder for at arbejde med enheder, tilbyder IOKit metoder til driverstabling (driver stacking) via instanser af C++ klasser. Applikationen i userspace vil kunne "finde" den registrerede klasseinstans for at etablere forbindelse mellem kernel og userspace.

Til at registrere antallet af klasseinstanser i systemet findes værktøjet ioclasscount.

my_kext_ioservice = 1
my_kext_iouserclient = 1

Enhver kernetilføjelse, der ønsker at registrere sig i driverstakken, skal erklære en klasse, som er arvet fra IOService; for eksempel my_kext_ioservice i dette tilfælde. Tilslutningen af brugerapplikationer medfører oprettelse af en ny klasseinstans, der er arvet fra IOUserClient, i eksemplet my_kext_iouserclient.

Når man forsøger at afbryde driveren fra systemet (kommandoen kextunload), kaldes den virtuelle funktion “bool terminate(IOOptionBits options)”. Det er tilstrækkeligt at returnere false ved kaldet af funktionen terminate, når man forsøger at afbryde, for at forbyde kextunload.

bool Kext::terminate(IOOptionBits options)
{

  if (!IsUnloadAllowed)
  {
    // Afladning er ikke tilladt, returnerer false
    return false;
  }

  return super::terminate(options);
}

Flaget IsUnloadAllowed kan sættes af IOUserClient ved indlæsning. Hvis afladning begrænses, vil kommandoen kextunload returnere følgende output:

admin@admins-Mac drivermanager % sudo kextunload ./test.kext
Adgangskode:
(kernel) Kan ikke fjerne kext my.kext.test; tjenester kunne ikke afsluttes - 0xe00002c7.
Fejl efter afladning af my.kext.test - (iokit/common) unsupported function.

En lignende beskyttelse skal også implementeres for IOUserClient. Klasseinstanser kan aflades ved hjælp af userspace funktionen IOKitLib “IOCatalogueTerminate(mach_port_t, uint32_t flag, io_name_t description);”. Man kan returnere false ved kaldet af kommandoen “terminate”, så længe userspace-applikationen ikke “dør”, det vil sige ikke kalder funktionen “clientDied”.

Beskyttelse af filer

For file protection, it is sufficient to use the Kauth API, which allows restricting access to files. Apple provides developers with notifications about various events in the scope, and for us, operations KAUTH_VNODE_DELETE, KAUTH_VNODE_WRITE_DATA, and KAUTH_VNODE_DELETE_CHILD are important. The easiest way to restrict access to files is by path — we use the API 'vn_getpath' to obtain the path to the file and compare the prefix of the path. Note that to optimize the renaming of folder paths containing files, the system does not authorize access to each file but only to the folder that has been renamed. It is necessary to compare the parent path and restrict KAUTH_VNODE_DELETE for it.

Hvordan man beskytter processer og udvidelser i macOS

A drawback of this approach may be low performance as the number of prefixes increases. To avoid the comparison being O(prefix*length), where prefix is the number of prefixes and length is the string length, a deterministic finite automaton (DFA) constructed from the prefixes can be used.

Let us consider the method of constructing a DFA for this set of prefixes. We initialize cursors at the beginning of each prefix. If all cursors point to the same character, we increase each cursor by one character and remember that the length of the matching string increases by one. If there are two cursors with different characters, we will divide the cursors into groups based on the character they point to and repeat the algorithm for each group.

In the first case (when all characters under the cursors are the same), we obtain a DFA state that has only one transition based on the matching string. In the second case, we get a transition table of size 256 (the number of characters and the maximum number of groups) for the subsequent states obtained through a recursive function call.

Let us consider an example. For the set of prefixes ('\/foo\/bar\/tmp\/', '\/var\/db\/foo\/', '\/foo\/bar\/aba\/', 'foo\/bar\/aac\/'), we can obtain the following DFA. The illustration only shows transitions leading to other states; other transitions will not be terminal.

Hvordan man beskytter processer og udvidelser i macOS

When traversing through the states of the DFA, there can be 3 cases.

  1. The final state has been reached — the path is protected, restricting operations KAUTH_VNODE_DELETE, KAUTH_VNODE_WRITE_DATA, and KAUTH_VNODE_DELETE_CHILD.
  2. Den endelige tilstand blev ikke nået, men stien "sluttede" (nul-terminator nået) — stien er parent, KAUTH_VNODE_DELETE skal begrænses. Bemærk, at hvis vnode er en mappe, skal der tilføjes ‘/’ til slutningen; ellers kan man begrænse til filen “/foor/bar/t”, hvilket er forkert.
  3. Den endelige tilstand blev ikke nået, stien sluttede ikke. Ingen af præfikserne matcher her, vi indfører ingen begrænsninger.

Konklusion

Målet med de udviklede sikkerhedsløsninger er at øge sikkerhedsniveauet for brugeren og deres data. På den ene side opnås dette mål gennem udviklingen af Acronis-produktet, som lukker de sårbarheder, hvor selve styresystemet er "svagt". På den anden side må man ikke forsømme at styrke de sikkerhedsaspekter, der kan forbedres på OS-siden, især da lukkede sårbarheder øger vores egen modstandsdygtighed som produkt. Sårbarheden blev rapporteret af Apple Product Security Team og blev rettet i macOS 10.14.5 (https://support.apple.com/en-gb/HT210119).

Hvordan man beskytter processer og udvidelser i macOS

Alt dette kan kun gøres, hvis dit værktøj blev officielt installeret i kernen. Det vil sige, at der ikke er nogen omveje for eksterne og uønskede programmer. Men som du kan se, kræver det endda lidt arbejde at beskytte legitime programmer såsom antivirus og backup-systemer. Men til gengæld vil de nye Acronis-produkter til macOS nu have ekstra beskyttelse mod at blive udskudt fra systemet.

Kilde: habr.com

Køb pålidelig webhosting med DDoS-beskyttelse, VPS VDS-servere 🔥 Køb pålidelig webhosting med DDoS-beskyttelse, VPS VDS-servere | ProHoster