Hogyan védjük a folyamatokat és a kernel kiterjesztéseit macOS-en

Helló, Habr! Ma arról szeretnék beszélni, hogy miként lehet megvédeni a folyamatokat a rosszindulatú támadásoktól macOS-en. Például ez hasznos lehet egy vírusirtó vagy biztonsági mentési rendszer számára, különösen figyelembe véve, hogy a macOS-en több módszer is létezik a folyamat "megölésére". Erről és a védelmi módszerekről olvashattok a tovább mögött.

Hogyan védjük a folyamatokat és a kernel kiterjesztéseit macOS-en

A klasszikus módszer "megölni" a folyamatot

Mindenki számára ismert módja a folyamat "megölésének" az, hogy SIGKILL jelet küldünk a folyamatnak. Bash alatt a szabványos "kill -SIGKILL PID" vagy "pkill -9 NAME" parancsok használhatók a megöléshez. A "kill" parancs már az UNIX korában ismert volt, és nemcsak a macOS-en, hanem más UNIX-szerű rendszereken is elérhető.

Ahogy a UNIX-szerű rendszerekben, a macOS lehetővé teszi, hogy bármilyen jelet elfogj a folyamat számára, kivéve kettőt: SIGKILL és SIGSTOP. Ebben a cikkben elsősorban a SIGKILL jel fog szerepelni, mint a folyamat megölését kiváltó jel.

A macOS sajátosságai

A macOS-en a kill rendszerhívás az XNU kernelben a psignal(SIGKILL,…) funkciót hívja meg. Nézzük meg, hogy a felhasználói térben milyen egyéb felhasználói műveletek hívhatják meg a psignal funkciót. Kiszelektáljuk a psignal függvény hívásait a kernel belső mechanizmusaiból (bár azok sem triviálisak, de ezeket egy másik cikkre hagyjuk 🙂 – aláírás ellenőrzés, memóriahibák, exit/terminate kezelése, fájlvédelmi megsértés stb.).

Nézzük meg a funkciót és a megfelelő rendszerhívást terminate_with_payload. Látható, hogy a klasszikus kill hívás mellett létezik egy alternatív megközelítés, amely kifejezetten a macOS operációs rendszerre jellemző, és nem fordul elő a BSD-ben. Mindkét rendszerhívás működési elve is hasonló. Ezek a psignal kernelfunkció közvetlen hívásait jelentik. Érdemes megjegyezni, hogy a folyamat megölése előtt történik egy "cansignal" ellenőrzés – a rendszer nem engedi, hogy bármely alkalmazás megölje a rendszerfolyamatokat.

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

A rendszerindításkor indított démonok létrehozásának és életciklusuk fennmaradásának standard módja a launchd. Felhívom a figyelmet arra, hogy a források a régi launchctl verzióhoz készültek macOS 10.10 előtt, a kódpéldák illusztrációként szolgálnak. A modern launchctl az XPC-n keresztül küldi el a jeleket a launchd-nek, a launchctl logikáját pedig átvitték hozzá.

Nézzük meg, hogyan történik az alkalmazások leállítása. A SIGTERM jelzés küldése előtt az alkalmazást megpróbálják leállítani a "proc_terminate" rendszerhívás segítségével.

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

A proc_terminate a neve ellenére a SIGTERM mellet psignal küldésére is képes, sőt SIGKILL-ra is.

Közvetett leölés — erőforrás-korlátozás

Egy érdekesebb esetet láthatunk egy másik rendszerhívásnál process_policy. A rendszerhívás standard használata az alkalmazások erőforrásainak korlátozása, például egy indexelő esetében a processzoridő és a memória kvótájának korlátozása, hogy a rendszer ne lassuljon le a fájlok gyorsítótárazása miatt. Ha az alkalmazás elérte az erőforrás-korlátozást, mint ahogy azt a proc_apply_resource_actions függvényből láthatjuk, akkor a folyamatnak SIGKILL jelet küldenek.

Bár ez a rendszerhívás potenciálisan képes a folyamat megölésére, a rendszer nem ellenőrizte megfelelően a hívó folyamat jogait. Valójában a vizsgálat létezett, de elegendő az alternatív PROC_POLICY_ACTION_SET jelzőt használni ennek a feltételnek a megkerüléséhez.

Ha a CPU használati kvótát „korlátozzuk” egy alkalmazás számára (például csak 1 ns végrehajtást engedélyezve), akkor bármely folyamatot megölhetünk a rendszerben. Így a kártékony program bármely folyamatot megölhet a rendszeren, beleértve a víruskereső folyamatot is. Érdekes hatás figyelhető meg a 1 pid-ű folyamat (launchctl) megölésekor - kernel pánik lép fel a SIGKILL jelzés feldolgozása során 🙂

Hogyan védjük a folyamatokat és a kernel kiterjesztéseit macOS-en

Hogyan oldható meg a probléma?

A legkézenfekvőbb módja, hogy megakadályozzuk a folyamat megölését, az, hogy felülírjuk a függvény mutatóját a rendszerhívások táblázatában. Sajnos ez a módszer sok okból nem triviális.

Először is, a memóriában a sysent helyét jelző szimbólum nemcsak a XNU magához tartozó privát szimbólum, hanem nem található meg a mag szimbólumai között sem. Heurisztikus keresési módszereket kell alkalmazni, például a függvény dinamikus disassemblálását és a mutató keresését benne.

Másodsorban, a táblázat rekordstruktúrája a mag összeállításához használt zászlóktól függ. Ha a CONFIG_REQUIRES_U32_MUNGING zászló be van jelentve, a struktúra mérete megváltozik - egy további mező kerül hozzáadásra. sy_arg_munge32. További ellenőrzést kell végezni arra vonatkozóan, hogy mely zászlóval lett a mag összeállítva, például összevetve a funkciók mutatóit a közismert mutatókkal.

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

Szerencsére a modern macOS verziókban az Apple új API-t kínál a folyamatokkal való munkára. Az Endpoint Security API lehetővé teszi az ügyfelek számára, hogy sok kérdést engedélyezzenek más folyamatokhoz. Így bármilyen jelzés blokkolható a folyamatok számára, beleértve a SIGKILL jelzést is a fent említett API segítségével.

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

Hasonlóképpen, a magban regisztrálhatunk MAC Policy-t, amely védelmi módszert biztosít a jelzések ellen (policy proc_check_signal), azonban az API hivatalosan nem támogatott.

A kernel kiterjesztés védelme

A rendszerfolyamatok védelme mellett alapvető fontosságú a kernel kiterjesztés (kext) védelme is. A macOS a fejlesztők számára egy keretet biztosít az IOKit eszközmeghajtók kényelmes fejlesztéséhez. Az IOKit nemcsak az eszközökkel való munkát segíti, hanem a meghajtók egymásra építésének módszereit is biztosítja C++ osztálypéldányok segítségével. A felhasználói térben lévő alkalmazás

Az osztálypéldányok számának felfedezésére létezik az ioclasscount segédprogram.

my_kext_ioservice = 1
my_kext_iouserclient = 1

Bárminek, ami a meghajtók halmazában szeretne regisztrálni, be kell jelenteni egy IOService-től örökölt osztályt, például a my_kext_ioservice-t ebben az esetben. A felhasználói alkalmazások csatlakoztatása új osztálypéldány létrehozását okozza, amely az IOUserClient-től öröklődik, a példában my_kext_iouserclient.

A meghajtó eltávolítása a rendszerből (kextunload parancs) során a virtuális funkció "bool terminate(IOOptionBits options)" hívódik meg. Elég, ha a terminate funkció hívásakor false-t adunk vissza az eltávolítási kísérlet során, hogy megakadályozzuk a kextunload-ot.

bool Kext::terminate(IOOptionBits options)
{

  if (!IsUnloadAllowed)
  {
    // Az eltávolítás nem engedélyezett, false-t adunk vissza
    return false;
  }

  return super::terminate(options);
}

Az IsUnloadAllowed jelzőt az IOUserClient állíthatja be a betöltéskor. Ha a betöltés korlátozva van, a kextunload parancs a következő kimenetet adja:

admin@admins-Mac drivermanager % sudo kextunload ./test.kext
Jelszó:
(kernel) Nem lehet eltávolítani a kext my.kext.test; a szolgáltatások nem tudtak leállni - 0xe00002c7.
Sikertelen a my.kext.test eltávolítása - (iokit/common) nem támogatott funkció.

Hasonló védelmet kell megvalósítani az IOUserClient esetében is. Az osztálypéldányokat az IOKitLib felhasználói funkciójával, az “IOCatalogueTerminate(mach_port_t, uint32_t flag, io_name_t description);” lehet eltávolítani. A "terminate" parancs hívása során false-t lehet visszaadni, amíg a felhasználói tér alkalmazás nem "hal meg", vagyis nem hívódik meg a "clientDied" funkció.

Fájlok védelme

A fájlok védelméhez elegendő a Kauth API használata, amely lehetővé teszi a fájlokhoz való hozzáférés korlátozását. Az Apple értesítéseket biztosít a fejlesztők számára a scope különböző eseményeiről, számunkra fontosak a KAUTH_VNODE_DELETE, KAUTH_VNODE_WRITE_DATA és KAUTH_VNODE_DELETE_CHILD műveletek. A fájlokhoz való hozzáférést legkönnyebben az útvonal alapján korlátozhatjuk — az API 'vn_getpath' használatával nyerjük ki a fájl útvonalát, és összehasonlítjuk az útvonal prefixét. Megjegyezzük, hogy a fájlok mappaátnevezésének optimalizálása érdekében a rendszer nem engedélyezi minden fájl hozzáférését, csak a mappáét, amelyet átneveztek. Összehasonlítást kell végezni a szülői úttal, és korlátozni kell a KAUTH_VNODE_DELETE-t ennek megfelelően.

Hogyan védjük a folyamatokat és a kernel kiterjesztéseit macOS-en

Ennek a megközelítésnek a hátránya lehet a teljesítmény csökkenése a prefixek számának növekedésével. Annak érdekében, hogy az összehasonlítás ne legyen O(prefix*length) , ahol a prefix a prefixek száma, a length pedig a karakterlánc hossza, determinisztikus véges automatát (DFA) használhatunk, amelyet a prefixek alapján építünk.

Nézzük meg a DFA felépítésének módját a megadott prefixek halmaza számára. Inicializáljuk a kurzorokat minden prefix elején. Ha minden kurzor ugyanarra a karakterre mutat, akkor minden kurzort egy karakterrel növelünk, és emlékezünk arra, hogy az egyforma karakterlánc hossza eggyel nőtt. Ha két kurzor létezik, amelyek alatt különböző karakterek állnak, akkor a kurzorokat a rámutató karakterek alapján csoportosítjuk, és megismételjük az algoritmust minden csoportra.

Az első esetben (minden kurzor alatt ugyanazok a karakterek) egy olyan DFA állapotot kapunk, amelynek csak egy átmenete van az azonos karakterláncon. A második esetben egy 256 méretű átmeneti táblázatot kapunk (a karakterek száma és a maximális csoportok száma) a következő állapotokhoz, amelyeket a függvény rekurzív hívásával nyerünk.

Nézzük meg a példát. A prefixek halmaza (“/foo/bar/tmp/”, “/var/db/foo/”, “/foo/bar/aba/”, “foo/bar/aac/”) alapján a következő DFA érhető el. A diagramon csak a más állapotokba vezető átmenetek szerepelnek, más átmenetek nem lesznek véglegesek.

Hogyan védjük a folyamatokat és a kernel kiterjesztéseit macOS-en

A DFA állapotainak áthaladásakor három eset fordulhat elő.

  1. Végső állapotot értünk el — az útvonal védett, korlátozzuk a KAUTH_VNODE_DELETE, KAUTH_VNODE_WRITE_DATA és KAUTH_VNODE_DELETE_CHILD műveleteket.
  2. Nem érkezett el a végső állapot, de az út 'végén' (elértük a null-terminálót) — az út egy szülő, a KAUTH_VNODE_DELETE-t korlátozni kell. Meg kell jegyezni, hogy ha a vnode egy mappa, akkor ’/’ -ot kell hozzáadni a végéhez, különben a ' /foor/bar/t' fájlra korlátozást lehet alkalmazni, ami hibás.
  3. Nem érkezett el a végső állapot, az út nem zárult le. Egyik előtag sem felel meg ennek, nem alkalmazunk korlátozásokat.

Zárás

A kidolgozott biztonsági megoldások célja a felhasználók és az adataik biztonsági szintjének növelése. Egyrészt ezt a célt az Acronis szoftvertermékének fejlesztésével érjük el, amely megszünteti azokat a sebezhetőségeket, ahol az operációs rendszer 'gyenge'. Másrészt nem szabad elhanyagolni az operációs rendszer biztonsági szempontjainak megerősítését sem, mivel az ilyen sebezhetőségek megszüntetése fokozza a termékünk saját ellenállóságát. A sebezhetőséget az Apple Product Security Team jelentette be és a macOS 10.14.5-ös verzióban javították (https://support.apple.com/en-gb/HT210119).

Hogyan védjük a folyamatokat és a kernel kiterjesztéseit macOS-en

Ez mind csak akkor lehetséges, ha a segédprogram hivatalosan telepítve volt a rendszer kernelébe. Tehát külső és nem kívánt szoftvereknek nincsenek ilyen kiskapui. Azonban, ahogy látható, még a legitim programok, mint például az antivírus és a biztonsági mentési rendszer védelme érdekében is keményen kell dolgozni. De most az új Acronis termékek a macOS számára extra védelmet kapnak a rendszerből való eltávolítással szemben.

Forrás: habr.com

Megbízható hosting vásárlása weboldalak számára, DDoS védelemmel, VPS VDS szerverek. 🔥 Megbízható webtárhely vásárlása DDoS védelemmel, VPS VDS szerverek | ProHoster