Kā aizsargāt procesus un kodola paplašinājumus operētājsistēmā MacOS

Sveiks, Habr! Šodien es vēlētos runāt par to, kā MacOS var aizsargāt procesus no uzbrucēju uzbrukumiem. Piemēram, tas ir noderīgi pretvīrusu vai rezerves sistēmai, jo īpaši tāpēc, ka operētājsistēmā macOS ir vairāki veidi, kā “nogalināt” procesu. Par to un aizsardzības metodēm lasiet zem griezuma.

Kā aizsargāt procesus un kodola paplašinājumus operētājsistēmā MacOS

Klasisks veids, kā “nogalināt” procesu

Labi zināms veids, kā “nogalināt” procesu, ir procesam nosūtīt SIGKILL signālu. Izmantojot bash, varat izsaukt standarta “kill -SIGKILL PID” vai “pkill -9 NAME”, lai nogalinātu. Komanda “kill” ir zināma kopš UNIX laikiem un ir pieejama ne tikai macOS, bet arī citās UNIX līdzīgās sistēmās.

Tāpat kā UNIX līdzīgās sistēmās, macOS ļauj pārtvert visus procesa signālus, izņemot divus — SIGKILL un SIGSTOP. Šajā rakstā galvenā uzmanība tiks pievērsta SIGKILL signālam kā signālam, kas izraisa procesa nogalināšanu.

macOS specifika

Operētājsistēmā MacOS nogalināšanas sistēmas izsaukums XNU kodolā izsauc funkciju psignal (SIGKILL,...). Mēģināsim redzēt, kādas citas lietotāja darbības userspace var izsaukt ar funkciju psignal. Izslēgsim psignal funkcijas izsaukumus kodola iekšējos mehānismos (lai gan tie var būt nenozīmīgi, atstāsim tos citam rakstam 🙂 - paraksta pārbaude, atmiņas kļūdas, izejas/izbeigšanas apstrāde, failu aizsardzības pārkāpumi utt. .

Sāksim apskatu ar funkciju un atbilstošo sistēmas izsaukumu terminate_with_payload. Redzams, ka papildus klasiskajam kill call ir alternatīva pieeja, kas raksturīga macOS operētājsistēmai un nav atrodama BSD. Arī abu sistēmas izsaukumu darbības principi ir līdzīgi. Tie ir tiešie izsaukumi uz kodola funkcijas psignal. Tāpat ņemiet vērā, ka pirms procesa iznīcināšanas tiek veikta “kansignāla” pārbaude - vai process var nosūtīt signālu citam procesam; sistēma neļauj nevienai lietojumprogrammai iznīcināt sistēmas procesus, piemēram.

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

palaists

Tiek palaists standarta veids, kā izveidot dēmonus sistēmas startēšanas laikā un kontrolēt to kalpošanas laiku. Lūdzu, ņemiet vērā, ka avoti ir paredzēti vecajai launchctl versijai līdz pat macOS 10.10, kodu piemēri ir sniegti ilustratīviem nolūkiem. Modern launchctl sūta launchd signālus caur XPC, uz to ir pārvietota launchctl loģika.

Apskatīsim, kā tieši lietojumprogrammas tiek apturētas. Pirms SIGTERM signāla nosūtīšanas lietojumprogrammu mēģina apturēt, izmantojot sistēmas izsaukumu “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));
		} 
...
<>

Zem pārsega proc_terminate, neskatoties uz nosaukumu, var nosūtīt ne tikai psignālu ar SIGTERM, bet arī SIGKILL.

Netiešā nogalināšana — resursu ierobežojums

Interesantāku gadījumu var redzēt citā sistēmas izsaukumā procesa_politika. Parasti šo sistēmas izsaukumu izmanto lietojumprogrammu resursu ierobežošanai, piemēram, indeksētājam, lai ierobežotu CPU laiku un atmiņas kvotas, lai failu kešatmiņas darbības būtiski nepalēninātu sistēmu. Ja lietojumprogramma ir sasniegusi savu resursu ierobežojumu, kā redzams no proc_apply_resource_actions funkcijas, procesam tiek nosūtīts SIGKILL signāls.

Lai gan šis sistēmas izsaukums potenciāli varētu iznīcināt procesu, sistēma pienācīgi nepārbaudīja procesa tiesības, kas izsauc sistēmas zvanu. Patiesībā pārbauda pastāvēja, taču pietiek ar alternatīvo karogu PROC_POLICY_ACTION_SET, lai apietu šo nosacījumu.

Tādējādi, ja jūs “ierobežojat” lietojumprogrammas CPU izmantošanas kvotu (piemēram, ļaujot darboties tikai 1 ns), varat iznīcināt jebkuru procesu sistēmā. Tādējādi ļaunprogrammatūra var nogalināt jebkuru procesu sistēmā, tostarp pretvīrusu procesu. Interesants ir arī efekts, kas rodas, nogalinot procesu ar pid 1 (launchctl) - kodola panika mēģinot apstrādāt SIGKILL signālu :)

Kā aizsargāt procesus un kodola paplašinājumus operētājsistēmā MacOS

Kā atrisināt problēmu?

Vienkāršākais veids, kā novērst procesa iznīcināšanu, ir aizstāt funkcijas rādītāju sistēmas izsaukuma tabulā. Diemžēl šī metode nav triviāla daudzu iemeslu dēļ.

Pirmkārt, simbols, kas kontrolē sysent atmiņas vietu, ir ne tikai privāts XNU kodola simbolam, bet arī to nevar atrast kodola simbolos. Jums būs jāizmanto heiristiskās meklēšanas metodes, piemēram, dinamiski jāizjauc funkcija un jāmeklē tajā rādītājs.

Otrkārt, tabulas ierakstu struktūra ir atkarīga no karodziņiem, ar kuriem kodols tika kompilēts. Ja tiek deklarēts karodziņš CONFIG_REQUIRES_U32_MUNGING, struktūras lielums tiks mainīts - tiks pievienots papildu lauks sy_arg_munge32. Ir jāveic papildu pārbaude, lai noteiktu, ar kuru karogu kodols tika kompilēts, vai arī jāpārbauda funkciju norādes pret zināmajiem.

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

Par laimi, modernajās macOS versijās Apple nodrošina jaunu API darbam ar procesiem. Endpoint Security API ļauj klientiem autorizēt daudzus pieprasījumus citiem procesiem. Tādējādi jūs varat bloķēt jebkādus signālus procesiem, tostarp SIGKILL signālu, izmantojot iepriekš minēto 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;
}

Tāpat kodolā var reģistrēt MAC politiku, kas nodrošina signāla aizsardzības metodi (policy proc_check_signal), taču API netiek oficiāli atbalstīts.

Kodola paplašinājuma aizsardzība

Papildus procesu aizsardzībai sistēmā ir nepieciešama arī paša kodola paplašinājuma (kext) aizsardzība. MacOS nodrošina ietvaru izstrādātājiem, lai viegli izstrādātu IOKit ierīču draiverus. Papildus rīku nodrošināšanai darbam ar ierīcēm, IOKit nodrošina metodes draiveru sakraušanai, izmantojot C++ klašu gadījumus. Lietojumprogramma lietotāja telpā varēs “atrast” reģistrētu klases gadījumu, lai izveidotu kodola un lietotāja telpas attiecības.

Lai noteiktu klases gadījumu skaitu sistēmā, ir ioclasscount utilīta.

my_kext_ioservice = 1
my_kext_iouserclient = 1

Jebkurš kodola paplašinājums, kas vēlas reģistrēties draiveru stekā, ir jādeklarē klase, kas tiek mantota no IOService, piemēram, šajā gadījumā my_kext_ioservice. Lietotāja lietojumprogrammu pievienošana izraisa jaunas klases instances izveidi, kas tiek mantota no IOUserClient, piemērā my_kext_iouserclient.

Mēģinot izlādēt draiveri no sistēmas (kextunload komanda), tiek izsaukta virtuālā funkcija “bool terminate(IOoptionBits options)”. Pietiek atgriezt false zvanā, lai pārtrauktu, mēģinot izlādēt, lai atspējotu kextunload.

bool Kext::terminate(IOOptionBits options)
{

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

  return super::terminate(options);
}

IOUserClient var iestatīt karogu IsUnloadAllowed ielādes laikā. Ja ir lejupielādes ierobežojums, komanda kextunload atgriezīs šādu izvadi:

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.

Līdzīga aizsardzība jāveic IOUserClient. Klašu gadījumus var izlādēt, izmantojot IOKitLib lietotāja telpas funkciju “IOCatalogueTerminate(mach_port_t, uint32_t flag, io_name_t description);”. Izsaucot komandu “terminate”, varat atgriezt false, līdz lietotāja telpas lietojumprogramma “nomirst”, tas ir, funkcija “clientDied” netiek izsaukta.

Failu aizsardzība

Lai aizsargātu failus, pietiek ar Kauth API izmantošanu, kas ļauj ierobežot piekļuvi failiem. Apple nodrošina izstrādātājiem paziņojumus par dažādiem darbības jomas notikumiem; mums svarīgas ir darbības KAUTH_VNODE_DELETE, KAUTH_VNODE_WRITE_DATA un KAUTH_VNODE_DELETE_CHILD. Vienkāršākais veids, kā ierobežot piekļuvi failiem, ir ceļš — mēs izmantojam “vn_getpath” API, lai iegūtu ceļu uz failu un salīdzinātu ceļa prefiksu. Ņemiet vērā, ka, lai optimizētu failu mapju ceļu pārdēvēšanu, sistēma neļauj piekļūt katram failam, bet tikai pašai mapei, kas ir pārdēvēta. Ir jāsalīdzina vecāku ceļš un jāierobežo KAUTH_VNODE_DELETE.

Kā aizsargāt procesus un kodola paplašinājumus operētājsistēmā MacOS

Šīs pieejas trūkums var būt zema veiktspēja, jo palielinās prefiksu skaits. Lai nodrošinātu, ka salīdzinājums nav vienāds ar O (prefikss*garums), kur prefikss ir prefiksu skaits, garums ir virknes garums, varat izmantot deterministisku galīgo automātu (DFA), ko veido prefiksi.

Apskatīsim metodi DFA konstruēšanai noteiktai prefiksu kopai. Mēs inicializējam kursorus katra prefiksa sākumā. Ja visi kursori norāda uz vienu un to pašu rakstzīmi, tad palieliniet katru kursoru par vienu rakstzīmi un atcerieties, ka vienas rindas garums ir par vienu vairāk. Ja ir divi kursori ar dažādiem simboliem, sadaliet kursorus grupās atbilstoši simbolam, uz kuru tie norāda, un atkārtojiet algoritmu katrai grupai.

Pirmajā gadījumā (visas rakstzīmes zem kursoriem ir vienādas), mēs iegūstam DFA stāvokli, kurā ir tikai viena pāreja pa to pašu līniju. Otrajā gadījumā mēs iegūstam 256 lieluma pāreju tabulu (rakstzīmju skaits un maksimālais grupu skaits) uz nākamajiem stāvokļiem, kas iegūti, rekursīvi izsaucot funkciju.

Apskatīsim piemēru. Prefiksu kopai (“/foo/bar/tmp/”, “/var/db/foo/”, “/foo/bar/aba/”, “foo/bar/aac/”) varat iegūt šādu informāciju DFA. Attēlā parādītas tikai pārejas, kas ved uz citiem stāvokļiem; citas pārejas nebūs galīgas.

Kā aizsargāt procesus un kodola paplašinājumus operētājsistēmā MacOS

Izejot cauri DKA stāvokļiem, var būt 3 gadījumi.

  1. Ir sasniegts gala stāvoklis - ceļš ir aizsargāts, ierobežojam darbības KAUTH_VNODE_DELETE, KAUTH_VNODE_WRITE_DATA un KAUTH_VNODE_DELETE_CHILD
  2. Galīgais stāvoklis netika sasniegts, bet ceļš “beidzās” (tika sasniegts nulles terminators) - ceļš ir vecāks, jāierobežo KAUTH_VNODE_DELETE. Ņemiet vērā: ja vnode ir mape, beigās ir jāpievieno '/', pretējā gadījumā tas var ierobežot to līdz failam “/foor/bar/t”, kas ir nepareizs.
  3. Galīgais stāvoklis netika sasniegts, ceļš nebeidzās. Neviens no prefiksiem neatbilst šim, mēs neieviešam ierobežojumus.

Secinājums

Izstrādājamo drošības risinājumu mērķis ir paaugstināt lietotāja un viņa datu drošības līmeni. No vienas puses, šis mērķis tiek sasniegts, izstrādājot Acronis programmatūras produktu, kas novērš tās ievainojamības, kurās pati operētājsistēma ir “vāja”. No otras puses, nevajadzētu atstāt novārtā to drošības aspektu stiprināšanu, kurus var uzlabot OS pusē, jo īpaši tāpēc, ka šādu ievainojamību aizvēršana palielina mūsu pašu kā produkta stabilitāti. Par ievainojamību tika ziņots Apple produktu drošības komandai, un tā ir novērsta operētājsistēmā macOS 10.14.5 (https://support.apple.com/en-gb/HT210119).

Kā aizsargāt procesus un kodola paplašinājumus operētājsistēmā MacOS

To visu var izdarīt tikai tad, ja jūsu utilīta ir oficiāli instalēta kodolā. Tas nozīmē, ka ārējai un nevēlamai programmatūrai šādu nepilnību nav. Tomēr, kā redzat, pat likumīgu programmu, piemēram, pretvīrusu un dublēšanas sistēmu aizsardzībai ir jāstrādā. Bet tagad jaunajiem Acronis produktiem, kas paredzēti macOS, būs papildu aizsardzība pret izkraušanu no sistēmas.

Avots: www.habr.com

Pievieno komentāru