A folyamatok és a kernelbővítmények biztonságossá tétele macOS-ben

Szia Habr! Ma arról szeretnék beszélni, hogyan védheti meg a folyamatokat a támadók támadásaival szemben a macOS rendszerben. Ez például hasznos egy vírusirtó vagy biztonsági mentési rendszer esetében, különösen azért, mert macOS alatt többféle módon is meg lehet „ölni” egy folyamatot. Olvasson erről és a védekezési módokról a vágás alatt.

A folyamatok és a kernelbővítmények biztonságossá tétele macOS-ben

A folyamat „megölésének” klasszikus módja

Egy folyamat „megölésének” jól ismert módja, ha SIGKILL jelet küldünk a folyamatnak. A bash-en keresztül hívhatja a szabványos „kill -SIGKILL PID” vagy „pkill -9 NAME” parancsot, hogy megölje. A „kill” parancs a UNIX napjai óta ismert, és nem csak macOS-en, hanem más UNIX-szerű rendszereken is elérhető.

Csakúgy, mint a UNIX-szerű rendszerekben, a macOS lehetővé teszi a folyamat bármely jelének elfogását, kivéve kettőt – a SIGKILL-t és a SIGSTOP-ot. Ez a cikk elsősorban a SIGKILL jelre fog összpontosítani, mint olyan jelre, amely egy folyamat leállítását okozza.

macOS sajátosságai

MacOS rendszeren az XNU kernelben lévő kill rendszerhívás meghívja a psignal(SIGKILL,...) függvényt. Próbáljuk meg megnézni, hogy a userspace-ben milyen egyéb felhasználói műveleteket hívhat meg a psignal függvény. Gyomláljuk ki a psignal függvény hívásait a kernel belső mechanizmusaiban (bár lehet, hogy nem triviálisak, de hagyjuk őket egy másik cikkre 🙂 - aláírás ellenőrzés, memóriahibák, kilépés/lezárás kezelés, fájlvédelmi megsértések, stb.

Kezdjük az áttekintést a függvénnyel és a megfelelő rendszerhívással terminate_with_payload. Látható, hogy a klasszikus kill call mellett létezik egy alternatív megközelítés, amely a macOS operációs rendszerre jellemző, és a BSD-ben nem található meg. A két rendszerhívás működési elve is hasonló. Ezek a kernel psignal függvény közvetlen hívásai. Vegye figyelembe azt is, hogy egy folyamat leállítása előtt egy „cansignal”-ellenőrzést hajtanak végre - hogy a folyamat képes-e jelet küldeni egy másik folyamatnak; a rendszer például nem engedélyezi egyetlen alkalmazásnak sem, hogy leállítsa 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);
	}
...
}

elindítva

Elindul a szabványos módszer a démonok létrehozására a rendszer indításakor és az élettartamuk szabályozására. Kérjük, vegye figyelembe, hogy a források a launchctl régi verziójához tartoznak a macOS 10.10-ig, a kódpéldák illusztrációs célokat szolgálnak. A modern launchctl XPC-n keresztül küldi az launchctl jeleket, az launchctl logika átkerült rá.

Nézzük meg, hogyan állnak le pontosan az alkalmazások. A SIGTERM jel küldése előtt az alkalmazást a „proc_terminate” rendszerhívás segítségével megkísérli leállítani.

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

A motorháztető alatt a proc_terminate a neve ellenére nem csak psignal-t tud küldeni SIGTERM-mel, hanem SIGKILL-lel is.

Közvetett ölés – erőforráskorlát

Érdekesebb eset egy másik rendszerhívásban látható process_policy. Ennek a rendszerhívásnak a gyakori használata az alkalmazások erőforrásainak korlátozása, például egy indexelő korlátozza a CPU-időt és a memóriakvótákat, hogy a rendszert ne lassítsák jelentősen a fájlok gyorsítótárazási tevékenységei. Ha egy alkalmazás elérte az erőforrás korlátját, amint az a proc_apply_resource_actions függvényből látható, a rendszer SIGKILL jelet küld a folyamatnak.

Bár ez a rendszerhívás potenciálisan megölhet egy folyamatot, a rendszer nem ellenőrizte megfelelően a rendszerhívást hívó folyamat jogosultságait. Valójában ellenőrzés létezett, de elegendő a PROC_POLICY_ACTION_SET alternatív jelzőt használni ennek a feltételnek a megkerüléséhez.

Ezért, ha „korlátozza” az alkalmazás CPU-használati kvótáját (például csak 1 ns-ot enged meg), akkor a rendszer bármely folyamatát leállíthatja. Így a kártevő megölheti a rendszer bármely folyamatát, beleértve a víruskereső folyamatot is. Szintén érdekes az a hatás, ami akkor jelentkezik, amikor egy folyamatot pid 1-el (launchctl) leállítanak – kernelpánik a SIGKILL jel feldolgozásakor :)

A folyamatok és a kernelbővítmények biztonságossá tétele macOS-ben

Hogyan lehet megoldani a problémát?

A folyamat leállításának legegyszerűbb módja a függvénymutató lecserélése a rendszerhívási táblában. Sajnos ez a módszer sok okból nem triviális.

Először is, a sysent memóriahelyét vezérlő szimbólum nemcsak az XNU kernelszimbólumhoz tartozik, hanem a kernelszimbólumokban sem található meg. Heurisztikus keresési módszereket kell használnia, például dinamikusan szét kell bontani a függvényt, és meg kell keresnie benne a mutatót.

Másodszor, a tábla bejegyzéseinek szerkezete attól függ, hogy a kernelt milyen zászlókkal fordították le. Ha a CONFIG_REQUIRES_U32_MUNGING jelzőt deklarálják, a szerkezet mérete megváltozik - egy további mező kerül hozzáadásra sy_arg_munge32. További ellenőrzést kell végezni annak meghatározásához, hogy a kernel melyik jelzővel lett lefordítva, vagy a függvénymutatókat össze kell hasonlítani az ismertekkel.

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 macOS modern verzióiban az Apple új API-t biztosít a folyamatokkal való munkavégzéshez. Az Endpoint Security API lehetővé teszi az ügyfelek számára, hogy számos kérést engedélyezzenek más folyamatokhoz. Így a fent említett API használatával blokkolhat bármilyen jelet a folyamatokhoz, beleértve a SIGKILL jelet is.

#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 kernelben regisztrálható egy MAC-házirend, amely jelvédelmi módszert biztosít (policy proc_check_signal), de az API hivatalosan nem támogatott.

Kernel kiterjesztésének védelme

A rendszer folyamatainak védelme mellett magának a kernelkiterjesztésnek (kext) védelme is szükséges. A macOS keretrendszert biztosít a fejlesztők számára az IOKit eszközillesztők egyszerű fejlesztéséhez. Amellett, hogy eszközöket biztosít az eszközökkel való munkavégzéshez, az IOKit módszereket biztosít az illesztőprogramok egymásra helyezéséhez a C++ osztályok példányait használva. A felhasználói térben lévő alkalmazások képesek lesznek „megtalálni” az osztály regisztrált példányát, hogy kernel-felhasználói terület kapcsolatot létesítsenek.

A rendszerben lévő osztálypéldányok számának észleléséhez létezik az ioclasscount segédprogram.

my_kext_ioservice = 1
my_kext_iouserclient = 1

Minden kernelbővítménynek, amely regisztrálni kíván az illesztőprogram-veremben, deklarálnia kell egy osztályt, amely az IOService-től örököl, például ebben az esetben a my_kext_ioservice. A felhasználói alkalmazások csatlakoztatása az osztály új példányának létrehozását eredményezi, amely az IOUserClienttől örököl, a példában my_kext_iouserclient.

Amikor megpróbál eltávolítani egy illesztőprogramot a rendszerből (kextunload parancs), a „bool terminate(IOoptionBits options)” virtuális függvény meghívódik. A kextunload letiltásához elegendő a false értéket adni a hívás befejezéséhez, amikor megpróbálja kiüríteni.

bool Kext::terminate(IOOptionBits options)
{

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

  return super::terminate(options);
}

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

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.

Hasonló védelmet kell végezni az IOUserClient esetében is. Az osztályok példányai az „IOCatalogueTerminate(mach_port_t, uint32_t flag, io_name_t description);” IOKitLib userspace függvény segítségével tölthetők ki. Hamis értéket adhat vissza a „terminate” parancs meghívásakor, amíg a userspace alkalmazás „meg nem hal”, vagyis a „clientDied” függvény nem kerül meghívásra.

Fájlvédelem

A fájlok védelméhez elegendő a Kauth API használata, amely lehetővé teszi a fájlok elérésének korlátozását. Az Apple értesítést küld a fejlesztőknek a hatókörbe tartozó különféle eseményekről, számunkra a KAUTH_VNODE_DELETE, KAUTH_VNODE_WRITE_DATA és KAUTH_VNODE_DELETE_CHILD műveletek fontosak. A fájlokhoz való hozzáférés korlátozásának legegyszerűbb módja az elérési út – a „vn_getpath” API segítségével lekérjük a fájl elérési útját, és összehasonlítjuk az elérési út előtagját. Vegye figyelembe, hogy a fájlok mappáinak elérési útjainak átnevezésének optimalizálása érdekében a rendszer nem engedélyezi a hozzáférést az egyes fájlokhoz, csak magához az átnevezett mappához. Össze kell hasonlítani a szülő elérési utat, és korlátozni kell a KAUTH_VNODE_DELETE-t.

A folyamatok és a kernelbővítmények biztonságossá tétele macOS-ben

Ennek a megközelítésnek a hátránya lehet az alacsony teljesítmény, mivel az előtagok száma növekszik. Annak biztosítására, hogy az összehasonlítás ne legyen egyenlő az O(prefix*length) értékkel, ahol az előtag az előtagok száma, a hossza pedig a karakterlánc hossza, használhat egy előtagokból felépített determinisztikus véges automatát (DFA).

Tekintsünk egy módszert DFA létrehozására egy adott előtagkészlethez. A kurzorokat az egyes előtagok elején inicializáljuk. Ha minden kurzor ugyanarra a karakterre mutat, akkor növelje meg mindegyik kurzort egy karakterrel, és ne feledje, hogy ugyanazon sor hossza eggyel nagyobb. Ha két különböző szimbólumú kurzor van, osszuk csoportokba a kurzorokat a jelnek megfelelően, amelyre mutatnak, és ismételjük meg az algoritmust mindegyik csoportnál.

Az első esetben (a kurzorok alatti összes karakter azonos) egy olyan DFA-állapotot kapunk, amelynek csak egy átmenete van ugyanazon a vonalon. A második esetben egy 256-os méretű (karakterszám és maximális csoportszám) átmenetek táblázatát kapjuk a függvény rekurzív meghívásával kapott következő állapotokba.

Nézzünk egy példát. Az előtagok készletéhez („/foo/bar/tmp/”, „/var/db/foo/”, „/foo/bar/aba/”, „foo/bar/aac/”) a következőket kaphatja DFA. Az ábra csak a más állapotokhoz vezető átmeneteket mutatja, a többi átmenet nem végleges.

A folyamatok és a kernelbővítmények biztonságossá tétele macOS-ben

A DKA állapotok áthaladásakor 3 eset lehet.

  1. Elértük a végső állapotot – az útvonal védett, korlátozzuk a KAUTH_VNODE_DELETE, KAUTH_VNODE_WRITE_DATA és KAUTH_VNODE_DELETE_CHILD műveleteket
  2. A végső állapotot nem érte el, de az elérési út „végeződött” (elérte a null lezárót) - az elérési út szülő, korlátozni kell a KAUTH_VNODE_DELETE-t. Ne feledje, hogy ha a vnode egy mappa, akkor hozzá kell adnia a „/” jelet a végéhez, különben a „/foor/bar/t” fájlra korlátozhatja, ami hibás.
  3. A végső állapotot nem érték el, az út nem ért véget. Ennek egyik előtagja sem egyezik, nem vezetünk be korlátozásokat.

Következtetés

A fejlesztés alatt álló biztonsági megoldások célja a felhasználó és adatai biztonságának növelése. Ezt a célt egyrészt az Acronis szoftvertermék fejlesztése éri el, amely bezárja azokat a sérülékenységeket, ahol maga az operációs rendszer „gyenge”. Másrészt nem szabad elhanyagolnunk azon biztonsági szempontok megerősítését sem, amelyeken OS-oldalon javítani lehet, főleg, hogy az ilyen sérülékenységek bezárása növeli saját termékünk stabilitását. A sérülékenységet jelentették az Apple termékbiztonsági csapatának, és a macOS 10.14.5 (https://support.apple.com/en-gb/HT210119) rendszerben javították.

A folyamatok és a kernelbővítmények biztonságossá tétele macOS-ben

Mindezt csak akkor lehet megtenni, ha a segédprogramot hivatalosan telepítették a kernelbe. Vagyis nincsenek ilyen kiskapuk a külső és nem kívánt szoftvereknél. Azonban amint látja, még a legális programok, például a vírusirtó és a biztonsági mentési rendszerek védelme is munkát igényel. Most azonban a macOS-hez készült új Acronis termékek további védelmet nyújtanak a rendszerből való kirakodás ellen.

Forrás: will.com

Hozzászólás