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 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
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ó
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
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 :)
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
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.
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 DKA állapotok áthaladásakor 3 eset lehet.
- 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
- 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.
- 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.
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