Pozdrav, Habr! Danas bih želio razgovarati o tome kako možete zaštititi procese od napada napadača u macOS-u. Na primjer, ovo je korisno za antivirusni ili backup sustav, pogotovo jer pod macOS-om postoji nekoliko načina da se "ubije" proces. Pročitajte o ovome i metodama zaštite ispod reza.
Klasičan način "ubijanja" procesa
Dobro poznati način "ubijanja" procesa je slanje SIGKILL signala procesu. Kroz bash možete pozvati standardni "kill -SIGKILL PID" ili "pkill -9 NAME" za ubijanje. Naredba “kill” poznata je još od dana UNIX-a i dostupna je ne samo na macOS-u, već i na drugim sustavima sličnim UNIX-u.
Baš kao u sustavima sličnim UNIX-u, macOS vam omogućuje presretanje bilo kojeg signala procesu osim dva - SIGKILL i SIGSTOP. Ovaj članak će se prvenstveno fokusirati na SIGKILL signal kao signal koji uzrokuje gašenje procesa.
specifičnosti macOS-a
Na macOS-u, sistemski poziv kill u jezgri XNU poziva funkciju psignal(SIGKILL,...). Pokušajmo vidjeti koje druge korisničke radnje u korisničkom prostoru može pozvati funkcija psignal. Uklonimo pozive funkcije psignal u internim mehanizmima kernela (iako možda nisu trivijalni, ostavit ćemo ih za drugi članak 🙂 - provjera potpisa, pogreške u memoriji, rukovanje izlazom/završetkom, kršenja zaštite datoteka itd. .
Započnimo pregled s funkcijom i odgovarajućim pozivom sustava
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);
}
...
}
lansirand
Standardni način za stvaranje demona pri pokretanju sustava i kontrolu njihovog trajanja je launchd. Imajte na umu da su izvori za staru verziju launchctl-a do macOS 10.10, primjeri koda navedeni su u ilustrativne svrhe. Moderni launchctl šalje launchd signale preko XPC-a, launchctl logika je premještena na njega.
Pogledajmo kako se točno zaustavljaju aplikacije. Prije slanja signala SIGTERM, aplikacija se pokušava zaustaviti pomoću sistemskog poziva “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));
}
...
<>
Ispod haube, proc_terminate, unatoč svom nazivu, može poslati ne samo psignal sa SIGTERM, već i SIGKILL.
Indirektno ubijanje - ograničenje resursa
Zanimljiviji slučaj može se vidjeti u drugom pozivu sustava
Iako bi ovaj sistemski poziv potencijalno mogao ubiti proces, sustav nije adekvatno provjerio prava procesa koji poziva sistemski poziv. Zapravo provjeravam
Stoga, ako "ograničite" kvotu upotrebe procesora aplikacije (na primjer, dopustite samo 1 ns da se pokrene), tada možete ubiti bilo koji proces u sustavu. Stoga zlonamjerni softver može ubiti bilo koji proces u sustavu, uključujući antivirusni proces. Zanimljiv je i efekt koji se javlja kada se ubije proces s pid 1 (launchctl) - kernel panika kada pokušava obraditi SIGKILL signal :)
Kako riješiti problem?
Najjednostavniji način za sprječavanje prekida procesa je zamjena pokazivača funkcije u tablici sistemskih poziva. Nažalost, ova metoda nije trivijalna iz mnogo razloga.
Prvo, simbol koji kontrolira memorijsku lokaciju sysenta nije samo privatan za simbol XNU kernela, već se ne može pronaći u simbolima kernela. Morat ćete koristiti heurističke metode pretraživanja, kao što je dinamičko rastavljanje funkcije i traženje pokazivača u njoj.
Drugo, struktura unosa u tablici ovisi o oznakama s kojima je kernel kompajliran. Ako je zastavica CONFIG_REQUIRES_U32_MUNGING deklarirana, veličina strukture će se promijeniti - dodatno polje će biti dodano
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
*/
};
Srećom, u modernim verzijama macOS-a Apple nudi novi API za rad s procesima. Endpoint Security API omogućuje klijentima da autoriziraju mnoge zahtjeve drugim procesima. Stoga možete blokirati sve signale procesima, uključujući signal SIGKILL, koristeći gore spomenuti API.
#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;
}
Slično tome, MAC politika se može registrirati u kernelu, koja pruža metodu zaštite signala (pravilo proc_check_signal), ali API nije službeno podržan.
Zaštita proširenja kernela
Osim zaštite procesa u sustavu, neophodna je i zaštita same kernel ekstenzije (kext). macOS pruža okvir za programere za jednostavan razvoj IOKit upravljačkih programa uređaja. Uz pružanje alata za rad s uređajima, IOKit pruža metode za slaganje upravljačkih programa pomoću instanci C++ klasa. Aplikacija u korisničkom prostoru moći će "pronaći" registriranu instancu klase kako bi uspostavila odnos kernel-korisnički prostor.
Za otkrivanje broja instanci klase u sustavu postoji uslužni program ioclasscount.
my_kext_ioservice = 1
my_kext_iouserclient = 1
Svako proširenje kernela koje se želi registrirati u skupu upravljačkih programa mora deklarirati klasu koja nasljeđuje od IOService, na primjer my_kext_ioservice u ovom slučaju. Povezivanje korisničkih aplikacija uzrokuje stvaranje nove instance klase koja nasljeđuje od IOUserClient, u primjeru my_kext_iouserclient.
Prilikom pokušaja uklanjanja upravljačkog programa iz sustava (kextunload naredba), poziva se virtualna funkcija “bool terminate(IOOptionBits options)”. Dovoljno je vratiti false na poziv za prekid prilikom pokušaja pražnjenja da biste onemogućili kextunload.
bool Kext::terminate(IOOptionBits options)
{
if (!IsUnloadAllowed)
{
// Unload is not allowed, returning false
return false;
}
return super::terminate(options);
}
Oznaku IsUnloadAllowed može postaviti IOUserClient prilikom učitavanja. Kada postoji ograničenje preuzimanja, naredba kextunload vratit će sljedeći izlaz:
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.
Slična zaštita mora se napraviti za IOUserClient. Instance klasa mogu se iskrcati pomoću funkcije korisničkog prostora IOKitLib “IOCatalogueTerminate(mach_port_t, uint32_t flag, io_name_t description);”. Možete vratiti false prilikom pozivanja naredbe “terminate” dok aplikacija korisničkog prostora ne “umre”, odnosno dok se ne pozove funkcija “clientDied”.
Zaštita datoteka
Za zaštitu datoteka dovoljno je koristiti Kauth API koji omogućuje ograničavanje pristupa datotekama. Apple pruža programerima obavijesti o raznim događajima u opsegu; za nas su važne operacije KAUTH_VNODE_DELETE, KAUTH_VNODE_WRITE_DATA i KAUTH_VNODE_DELETE_CHILD. Najlakši način za ograničavanje pristupa datotekama je put - koristimo API “vn_getpath” da dobijemo put do datoteke i usporedimo prefiks puta. Imajte na umu da radi optimizacije preimenovanja putova mapa datoteka, sustav ne autorizira pristup svakoj datoteci, već samo samoj mapi koja je preimenovana. Potrebno je usporediti nadređenu stazu i ograničiti KAUTH_VNODE_DELETE za nju.
Nedostatak ovog pristupa može biti niska izvedba kako se broj prefiksa povećava. Kako biste osigurali da usporedba nije jednaka O(prefiks*duljina), gdje je prefiks broj prefiksa, duljina je duljina niza, možete koristiti deterministički konačni automat (DFA) izgrađen od prefiksa.
Razmotrimo metodu za konstruiranje DFA za dati skup prefiksa. Inicijaliziramo kursore na početku svakog prefiksa. Ako svi kursori pokazuju na isti znak, povećajte svaki kursor za jedan znak i zapamtite da je duljina istog retka veća za jedan. Ako postoje dva kursora s različitim simbolima, podijelite kursore u skupine prema simbolu na koji pokazuju i ponovite algoritam za svaku grupu.
U prvom slučaju (svi znakovi ispod kursora su isti) dobivamo DFA stanje koje ima samo jedan prijelaz duž iste linije. U drugom slučaju dobivamo tablicu prijelaza veličine 256 (broj znakova i maksimalan broj grupa) u naknadna stanja dobivena rekurzivnim pozivom funkcije.
Pogledajmo primjer. Za skup prefiksa (“/foo/bar/tmp/”, “/var/db/foo/”, “/foo/bar/aba/”, “foo/bar/aac/”) možete dobiti sljedeće DFA. Slika prikazuje samo prijelaze koji vode u druga stanja; drugi prijelazi neće biti konačni.
Kada prolazite kroz DKA države, mogu postojati 3 slučaja.
- Završno stanje je postignuto - put je zaštićen, ograničavamo operacije KAUTH_VNODE_DELETE, KAUTH_VNODE_WRITE_DATA i KAUTH_VNODE_DELETE_CHILD
- Nije postignuto konačno stanje, ali je put “završio” (dostignut je nulti terminator) - put je roditelj, potrebno je ograničiti KAUTH_VNODE_DELETE. Imajte na umu da ako je vnode mapa, morate dodati '/' na kraju, inače bi se mogla ograničiti na datoteku “/foor/bar/t”, što je netočno.
- Konačno stanje nije postignuto, put nije završio. Nijedan od prefiksa ne odgovara ovom, ne uvodimo ograničenja.
Zaključak
Cilj sigurnosnih rješenja koja se razvijaju je povećati razinu sigurnosti korisnika i njegovih podataka. S jedne strane, ovaj cilj je postignut razvojem softverskog proizvoda Acronis, koji zatvara one ranjivosti gdje je sam operativni sustav “slab”. S druge strane, ne bismo smjeli zanemariti jačanje onih sigurnosnih aspekata koji se mogu poboljšati na strani OS-a, pogotovo jer zatvaranje takvih ranjivosti povećava vlastitu stabilnost kao proizvoda. Ranjivost je prijavljena Appleovom timu za sigurnost proizvoda i popravljena je u macOS 10.14.5 (https://support.apple.com/en-gb/HT210119).
Sve ovo možete učiniti samo ako je vaš uslužni program službeno instaliran u kernel. Odnosno, nema takvih rupa za vanjski i neželjeni softver. Međutim, kao što vidite, čak i zaštita legitimnih programa kao što su antivirusni i sigurnosni sustavi zahtijevaju rad. Ali sada će novi Acronis proizvodi za macOS imati dodatnu zaštitu od pražnjenja iz sustava.
Izvor: www.habr.com