Ahoj, Habr! Dnes bych rád mluvil o tom, jak můžete chránit své procesy před škodlivými útoky. macOSTo je například užitečné pro antivirové nebo zálohovací systémy, zejména s ohledem na skutečnost, že pod macOS Existuje několik způsobů, jak proces „ukončit“. Níže naleznete další informace a metody ochrany.
Klasický způsob, jak „zabít“ proces
Známým způsobem, jak ukončit proces, je odeslání signálu SIGKILL. V bash můžete k jeho ukončení použít standardní příkazy „kill -SIGKILL PID“ nebo „pkill -9 NAME“. Příkaz „kill“ existuje již od dob UNIXu a je k dispozici nejen v macOS, ale také na jiných systémech podobných UNIXu.
Stejně jako v systémech podobných UNIXu, macOS Umožňuje zachytit jakýkoli procesní signál kromě dvou – SIGKILL a SIGSTOP. Tento článek se zaměří především na signál SIGKILL, který způsobuje ukončení procesu.
Specifičnost macOS
В macOS Systémové volání kill v jádře XNU volá funkci psignal(SIGKILL,…). Zkusme se podívat, jaké další uživatelské akce v uživatelském prostoru mohou funkci psignal volat. Vyfiltrujeme volání funkce psignal v interních mechanismech jádra (i když mohou být netriviální, necháme si je na jiný článek :)) – ověřování podpisu, chyby paměti, zpracování ukončení/exit, narušení zabezpečení souborů atd.
Začněme recenzi funkcí a odpovídajícím systémovým voláním Je zřejmé, že kromě klasického volání kill existuje alternativní přístup, který je specifický pro daný operační systém. macOS a nenachází se v BSD. Principy fungování obou systémových volání jsou také podobné. Jsou to přímá volání funkce jádra psignal. Všimněte si také, že před ukončením procesu se provede kontrola „cansignal“ – zda proces může poslat signál jinému procesu. Systém například neumožňuje žádné aplikaci ukončit systémové procesy.
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
Standardní způsob, jak vytvářet démony při spuštění systému a řídit jejich životnost, je launchd. Upozorňujeme, že zdrojový kód je pro starší verzi launchctl, před macOS V verzi 10.10 jsou uvedeny příklady kódu pro ilustrační účely. Moderní launchctl odesílá signály do launchd přes XPC a logika launchctl byla přesunuta do něj.
Podívejme se, jak přesně se aplikace zastavují. Před odesláním signálu SIGTERM se aplikace pokusí zastavit pomocí systémového volání „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 může proc_terminate, navzdory svému názvu, vysílat nejen psignál pomocí SIGTERM, ale také SIGKILL.
Nepřímé zabíjení – limit zdrojů
Zajímavější případ lze vidět v jiném systémovém volání . Běžným použitím tohoto systémového volání je omezení aplikačních prostředků, například indexátor k omezení času procesoru a kvót paměti, aby systém nebyl výrazně zpomalován aktivitami ukládání souborů do mezipaměti. Pokud aplikace dosáhla limitu prostředků, jak je vidět z funkce proc_apply_resource_actions, je do procesu odeslán signál SIGKILL.
Ačkoli toto systémové volání mohlo potenciálně zabít proces, systém dostatečně nezkontroloval práva procesu volajícího systémové volání. Vlastně kontrola , ale k obejití této podmínky stačí použít alternativní příznak PROC_POLICY_ACTION_SET.
Pokud tedy „omezíte“ kvótu využití procesoru aplikace (například povolíte spuštění pouze 1 ns), můžete zabít jakýkoli proces v systému. Malware tedy může zabít jakýkoli proces v systému, včetně antivirového procesu. Zajímavý je také efekt, který nastává při zabíjení procesu pomocí pid 1 (launchctl) - panika jádra při pokusu o zpracování signálu SIGKILL :)

Jak problém vyřešit?
Nejpřímější způsob, jak zabránit zabití procesu, je nahradit ukazatel funkce v tabulce systémových volání. Bohužel je tato metoda z mnoha důvodů netriviální.
Za prvé, symbol, který řídí umístění paměti sysent, není soukromý pouze pro symbol jádra XNU, ale nelze jej nalézt v symbolech jádra. Budete muset použít heuristické metody vyhledávání, jako je dynamické rozebrání funkce a hledání ukazatele v ní.
Za druhé, struktura položek v tabulce závisí na příznacích, se kterými bylo jádro zkompilováno. Pokud je deklarován příznak CONFIG_REQUIRES_U32_MUNGING, velikost struktury se změní - přidá se další pole . Je nutné provést dodatečnou kontrolu, abychom zjistili, se kterým příznakem bylo jádro zkompilováno, nebo alternativně zkontrolovat ukazatele funkcí proti známým.
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štěstí v moderních verzích macOS Apple poskytuje nové API pro práci s procesy. Endpoint Security API umožňuje klientům autorizovat mnoho požadavků na jiné procesy. Například jakékoli signály pro procesy, včetně signálu SIGKILL, lze pomocí výše zmíněného API blokovat.
#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;
}
Podobně lze v jádře zaregistrovat MAC Policy, která poskytuje metodu ochrany signálu (policy proc_check_signal), ale API není oficiálně podporováno.
Ochrana rozšíření jádra
Kromě ochrany procesů v systému je nutné chránit i samotné rozšíření jádra (kext). macOS IOKit poskytuje vývojářům framework pro snadný vývoj ovladačů zařízení IOKit. Kromě nástrojů pro práci se zařízeními IOKit podporuje metody stohování ovladačů pomocí instancí tříd C++. Uživatelská aplikace může „najít“ registrovanou instanci třídy a navázat komunikaci mezi jádrem a uživatelským prostorem.
Pro zjištění počtu instancí tříd v systému existuje obslužný program ioclasscount.
my_kext_ioservice = 1
my_kext_iouserclient = 1
Jakékoli rozšíření jádra, které se chce zaregistrovat do zásobníku ovladačů, musí deklarovat třídu, která dědí od IOService, v tomto případě například my_kext_ioservice. Připojení uživatelských aplikací způsobí vytvoření nové instance třídy, která dědí od IOUserClient, v příkladu my_kext_iouserclient.
Při pokusu o uvolnění ovladače ze systému (příkaz kextunload) se zavolá virtuální funkce „bool terminate(IOOptionBits options)“. K deaktivaci kextunload stačí vrátit false při ukončení volání při pokusu o uvolnění.
bool Kext::terminate(IOOptionBits options)
{
if (!IsUnloadAllowed)
{
// Unload is not allowed, returning false
return false;
}
return super::terminate(options);
}
Příznak IsUnloadAllowed může být nastaven pomocí IOUserClient při načítání. Pokud existuje limit stahování, příkaz kextunload vrátí následující 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í být provedena pro IOUserClient. Instance tříd lze uvolnit pomocí funkce uživatelského prostoru IOKitLib „IOCatalogueTerminate(mach_port_t, příznak uint32_t, popis io_name_t);“. Při volání příkazu „terminate“ můžete vracet false, dokud uživatelská aplikace „neumře“, to znamená, že funkce „clientDied“ není volána.
Ochrana souborů
K ochraně souborů stačí použít Kauth API, které umožňuje omezit přístup k souborům. Apple poskytuje vývojářům upozornění na různé události v rozsahu, pro nás jsou důležité operace KAUTH_VNODE_DELETE, KAUTH_VNODE_WRITE_DATA a KAUTH_VNODE_DELETE_CHILD. Nejjednodušší způsob, jak omezit přístup k souborům, je pomocí cesty – k získání cesty k souboru a porovnání předpony cesty používáme API „vn_getpath“. Všimněte si, že pro optimalizaci přejmenování cest složek souborů systém neautorizuje přístup ke každému souboru, ale pouze k samotné složce, která byla přejmenována. Je nutné porovnat nadřazenou cestu a omezit pro ni KAUTH_VNODE_DELETE.

Nevýhodou tohoto přístupu může být nízký výkon s rostoucím počtem prefixů. Chcete-li zajistit, aby se porovnání nerovnalo O(prefix*délka), kde prefix je počet prefixů, délka je délka řetězce, můžete použít deterministický konečný automat (DFA) sestavený z prefixů.
Zvažme metodu pro konstrukci DFA pro danou sadu prefixů. Kurzory inicializujeme na začátku každého prefixu. Pokud všechny kurzory ukazují na stejný znak, zvyšte každý kurzor o jeden znak a nezapomeňte, že délka stejného řádku je o jeden větší. Pokud existují dva kurzory s různými symboly, rozdělte kurzory do skupin podle symbolu, na který ukazují, a opakujte algoritmus pro každou skupinu.
V prvním případě (všechny znaky pod kurzory jsou stejné) dostaneme stav DFA, který má pouze jeden přechod podél stejné čáry. V druhém případě dostaneme tabulku přechodů o velikosti 256 (počet znaků a maximální počet skupin) do následných stavů získanou rekurzivním voláním funkce.
Podívejme se na příklad. Pro sadu předpon („/foo/bar/tmp/“, „/var/db/foo/“, „/foo/bar/aba/“, „foo/bar/aac/“) můžete získat následující DFA. Obrázek ukazuje pouze přechody vedoucí do jiných stavů, ostatní přechody nebudou konečné.

Při průchodu státy DKA mohou nastat 3 případy.
- Bylo dosaženo konečného stavu - cesta je chráněna, omezujeme operace KAUTH_VNODE_DELETE, KAUTH_VNODE_WRITE_DATA a KAUTH_VNODE_DELETE_CHILD
- Nebylo dosaženo konečného stavu, ale cesta „skončila“ (byl dosažen nulový terminátor) - cesta je rodič, je nutné omezit KAUTH_VNODE_DELETE. Všimněte si, že pokud je vnode složka, musíte na konec přidat '/', jinak to může omezit na soubor „/foor/bar/t“, což je nesprávné.
- Nebylo dosaženo konečného stavu, cesta nekončila. Žádná z předpon neodpovídá této, nezavádíme omezení.
Závěr
Cílem vývoje bezpečnostních řešení je zvýšit úroveň zabezpečení uživatelů a jejich dat. Tohoto cíle je dosaženo vývojem softwaru Acronis, který řeší zranitelnosti v samotném operačním systému. Na druhou stranu bychom neměli zanedbávat posilování těch aspektů zabezpečení, které lze na straně operačního systému vylepšit, zejména proto, že uzavření takových zranitelností zvyšuje naši vlastní odolnost jako produktu. Zranitelnost byla nahlášena týmu Apple Product Security a byla opravena v roce macOS 10.14.5 (https://support.apple.com/en-gb/HT210119).

To vše lze provést pouze tehdy, pokud byl váš nástroj oficiálně nainstalován v jádře. To znamená, že neexistují žádné takové mezery pro externí a nežádoucí software. Jak však vidíte, i ochrana legitimních programů, jako jsou antivirové programy a zálohovací systémy, vyžaduje určité úsilí. Nyní však nové produkty Acronis pro macOS bude mít dodatečnou ochranu proti vyložení ze systému.
Zdroj: www.habr.com
