Ahoj Habr! Dnes by som chcel hovoriť o tom, ako môžete chrániť procesy pred útokmi útočníkov v systéme MacOS. Je to užitočné napríklad pre antivírusový alebo záložný systém, najmä preto, že v systéme MacOS existuje niekoľko spôsobov, ako „zabiť“ proces. Prečítajte si o tom a spôsoboch ochrany pod rezom.
Klasický spôsob, ako „zabiť“ proces
Dobre známy spôsob, ako „zabiť“ proces, je poslať do procesu signál SIGKILL. Prostredníctvom bash môžete zabiť štandardne „kill -SIGKILL PID“ alebo „pkill -9 NAME“. Príkaz „kill“ je známy už od čias UNIX a je dostupný nielen na macOS, ale aj na iných systémoch podobných UNIX.
Rovnako ako v systémoch podobných UNIX, macOS vám umožňuje zachytiť akékoľvek signály procesu okrem dvoch - SIGKILL a SIGSTOP. Tento článok sa primárne zameria na signál SIGKILL ako signál, ktorý spôsobí zabitie procesu.
špecifiká macOS
V systéme macOS systémové volanie kill v jadre XNU volá funkciu psignal(SIGKILL,...). Skúsme sa pozrieť, aké ďalšie akcie používateľa v používateľskom priestore možno volať pomocou funkcie psignal. Vylúčme volania funkcie psignal vo vnútorných mechanizmoch jadra (hoci môžu byť netriviálne, necháme si ich na iný článok 🙂 - overenie podpisu, chyby pamäte, spracovanie ukončenia/ukončenia, porušenie ochrany súborov atď. .
Začnime prehľad funkciou a príslušným systémovým volaním
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);
}
...
}
spustený
Spustí sa štandardný spôsob vytvárania démonov pri štarte systému a riadenia ich životnosti. Upozorňujeme, že zdroje sú pre starú verziu launchctl až po macOS 10.10, príklady kódu sú uvedené na ilustračné účely. Moderný launchctl posiela spúšťané signály cez XPC, logika launchctl bola presunutá do neho.
Pozrime sa, ako presne sa zastavujú aplikácie. Pred odoslaním signálu SIGTERM sa aplikácia pokúsi zastaviť pomocou systémového volania „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));
}
...
<>
Pod kapotou proc_terminate, napriek svojmu názvu, dokáže vysielať nielen psignál so SIGTERM, ale aj SIGKILL.
Nepriame zabitie – limit zdrojov
Zaujímavejší prípad možno vidieť v inom systémovom volaní
Hoci toto systémové volanie môže potenciálne zabiť proces, systém dostatočne neskontroloval práva procesu, ktorý volá systémové volanie. Vlastne kontrola
Ak teda „obmedzíte“ kvótu využitia procesora aplikácie (napríklad povolíte spustenie iba 1 ns), môžete zabiť akýkoľvek proces v systéme. Malvér teda môže zabiť akýkoľvek proces v systéme vrátane antivírusového procesu. Zaujímavý je aj efekt, ktorý nastáva pri zabíjaní procesu pomocou pid 1 (launchctl) - panika jadra pri pokuse o spracovanie signálu SIGKILL :)
Ako vyriešiť problém?
Najjednoduchší spôsob, ako zabrániť zabitiu procesu, je nahradiť ukazovateľ funkcie v tabuľke systémových volaní. Bohužiaľ, táto metóda nie je triviálna z mnohých dôvodov.
Po prvé, symbol, ktorý riadi umiestnenie pamäte sysent, nie je len súkromný pre symbol jadra XNU, ale nemožno ho nájsť v symboloch jadra. Budete musieť použiť heuristické metódy vyhľadávania, ako je dynamická demontáž funkcie a hľadanie ukazovateľa v nej.
Po druhé, štruktúra záznamov v tabuľke závisí od príznakov, s ktorými bolo jadro kompilované. Ak je deklarovaný príznak CONFIG_REQUIRES_U32_MUNGING, zmení sa veľkosť štruktúry - pribudne ďalšie pole
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
*/
};
Našťastie v moderných verziách macOS Apple poskytuje nové API pre prácu s procesmi. Endpoint Security API umožňuje klientom autorizovať mnohé požiadavky na iné procesy. Pomocou vyššie uvedeného API tak môžete blokovať akékoľvek signály do procesov, vrátane signálu SIGKILL.
#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;
}
Podobne je možné v jadre zaregistrovať MAC politiku, ktorá poskytuje metódu ochrany signálu (policy proc_check_signal), ale API nie je oficiálne podporované.
Ochrana rozšírenia jadra
Okrem ochrany procesov v systéme je potrebná aj ochrana samotného rozšírenia jadra (kext). macOS poskytuje vývojárom rámec na jednoduchý vývoj ovládačov zariadení IOKit. Okrem poskytovania nástrojov na prácu so zariadeniami poskytuje IOKit metódy pre stohovanie ovládačov pomocou inštancií tried C++. Aplikácia v užívateľskom priestore bude schopná „nájsť“ zaregistrovanú inštanciu triedy na vytvorenie vzťahu jadro-užívateľský priestor.
Na zistenie počtu inštancií tried v systéme existuje utilita ioclasscount.
my_kext_ioservice = 1
my_kext_iouserclient = 1
Každé rozšírenie jadra, ktoré sa chce zaregistrovať do zásobníka ovládačov, musí deklarovať triedu, ktorá dedí od IOService, v tomto prípade napríklad my_kext_ioservice. Pripojenie používateľských aplikácií spôsobí vytvorenie novej inštancie triedy, ktorá sa dedí od IOUserClient, v príklade my_kext_iouserclient.
Pri pokuse o uvoľnenie ovládača zo systému (príkaz kextunload) sa zavolá virtuálna funkcia „bool terminate(IOOptionBits options)“. Na deaktiváciu kextunload stačí vrátiť false na ukončenie hovoru pri pokuse o uvoľnenie.
bool Kext::terminate(IOOptionBits options)
{
if (!IsUnloadAllowed)
{
// Unload is not allowed, returning false
return false;
}
return super::terminate(options);
}
Príznak IsUnloadAllowed môže byť nastavený pomocou IOUserClient pri načítaní. Keď existuje limit sťahovania, príkaz kextunload vráti nasledujúci výstup:
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.
Podobná ochrana musí byť vykonaná pre IOUserClient. Inštancie tried je možné uvoľniť pomocou funkcie užívateľského priestoru IOKitLib „IOCatalogueTerminate(mach_port_t, príznak uint32_t, popis io_name_t);“. Pri volaní príkazu „terminate“ môžete vrátiť hodnotu false, kým aplikácia používateľského priestoru „nezomrie“, to znamená, že funkcia „clientDied“ nebude zavolaná.
Ochrana súborov
Na ochranu súborov stačí použiť Kauth API, ktoré umožňuje obmedziť prístup k súborom. Apple poskytuje vývojárom upozornenia na rôzne udalosti v rozsahu, pre nás sú dôležité operácie KAUTH_VNODE_DELETE, KAUTH_VNODE_WRITE_DATA a KAUTH_VNODE_DELETE_CHILD. Najjednoduchší spôsob, ako obmedziť prístup k súborom, je cestou – na získanie cesty k súboru a porovnanie predpony cesty používame API „vn_getpath“. Upozorňujeme, že na optimalizáciu premenovania ciest priečinkov súborov systém neautorizuje prístup ku každému súboru, ale iba k samotnému priečinku, ktorý bol premenovaný. Je potrebné porovnať nadradenú cestu a obmedziť pre ňu KAUTH_VNODE_DELETE.
Nevýhodou tohto prístupu môže byť nízky výkon so zvyšujúcim sa počtom prefixov. Aby ste sa uistili, že porovnanie sa nerovná O (predpona*dĺžka), kde predpona je počet predpôn, dĺžka je dĺžka reťazca, môžete použiť deterministický konečný automat (DFA) zostavený z predpon.
Uvažujme o metóde konštrukcie DFA pre danú množinu predpôn. Kurzory inicializujeme na začiatku každého prefixu. Ak všetky kurzory ukazujú na rovnaký znak, zväčšite každý kurzor o jeden znak a nezabudnite, že dĺžka toho istého riadku je o jeden väčšia. Ak existujú dva kurzory s rôznymi symbolmi, rozdeľte kurzory do skupín podľa symbolu, na ktorý ukazujú, a zopakujte algoritmus pre každú skupinu.
V prvom prípade (všetky znaky pod kurzormi sú rovnaké) dostaneme stav DFA, ktorý má len jeden prechod pozdĺž tej istej čiary. V druhom prípade dostaneme tabuľku prechodov veľkosti 256 (počet znakov a maximálny počet skupín) do následných stavov získanú rekurzívnym volaním funkcie.
Pozrime sa na príklad. Pre sadu predpôn („/foo/bar/tmp/“, „/var/db/foo/“, „/foo/bar/aba/“, „foo/bar/aac/“) môžete získať nasledovné DFA. Na obrázku sú znázornené len prechody vedúce do iných stavov, ostatné prechody nebudú konečné.
Pri prechádzaní štátmi DKA môžu nastať 3 prípady.
- Dosiahnutý konečný stav - cesta je chránená, obmedzujeme operácie KAUTH_VNODE_DELETE, KAUTH_VNODE_WRITE_DATA a KAUTH_VNODE_DELETE_CHILD
- Konečný stav nebol dosiahnutý, ale cesta „skončila“ (bol dosiahnutý nulový terminátor) - cesta je nadradená, je potrebné obmedziť KAUTH_VNODE_DELETE. Všimnite si, že ak je vnode priečinok, musíte na koniec pridať '/', inak to môže obmedziť na súbor „/foor/bar/t“, čo je nesprávne.
- Konečný stav sa nedosiahol, cesta neskončila. Žiadna z predpôn sa nezhoduje s touto, nezavádzame obmedzenia.
Záver
Cieľom vyvíjaných bezpečnostných riešení je zvýšiť úroveň bezpečnosti užívateľa a jeho dát. Na jednej strane je tento cieľ dosiahnutý vývojom softvérového produktu Acronis, ktorý uzatvára tie zraniteľnosti, pri ktorých je samotný operačný systém „slabý“. Na druhej strane by sme nemali zanedbávať posilnenie tých bezpečnostných aspektov, ktoré je možné zlepšiť na strane OS, najmä preto, že uzavretie takýchto zraniteľností zvyšuje našu vlastnú stabilitu ako produktu. Táto chyba zabezpečenia bola nahlásená tímu Apple Product Security a bola opravená v systéme macOS 10.14.5 (https://support.apple.com/en-gb/HT210119).
Toto všetko je možné vykonať iba vtedy, ak bol váš nástroj oficiálne nainštalovaný do jadra. To znamená, že neexistujú žiadne takéto medzery pre externý a nechcený softvér. Ako však vidíte, aj ochrana legitímnych programov, ako sú antivírusové a zálohovacie systémy, si vyžaduje prácu. Teraz však budú mať nové produkty Acronis pre macOS dodatočnú ochranu proti uvoľneniu zo systému.
Zdroj: hab.com