Përshëndetje, Habr! Sot do të doja të flisja se si mund t'i mbroni proceset nga sulmet nga sulmuesit në macOS. Për shembull, kjo është e dobishme për një sistem antivirus ose rezervë, veçanërisht pasi nën macOS ka disa mënyra për të "vrarë" një proces. Lexoni për këtë dhe metodat e mbrojtjes nën prerje.
Mënyra klasike për të "vrarë" një proces
Një mënyrë e njohur për të "vrarë" një proces është dërgimi i një sinjali SIGKILL në proces. Nëpërmjet bash ju mund të thërrisni standardin "kill -SIGKILL PID" ose "pkill -9 NAME" për të vrarë. Komanda "vras" është e njohur që nga ditët e UNIX dhe është e disponueshme jo vetëm në macOS, por edhe në sisteme të tjera të ngjashme me UNIX.
Ashtu si në sistemet e ngjashme me UNIX, macOS ju lejon të përgjoni çdo sinjal në një proces përveç dy - SIGKILL dhe SIGSTOP. Ky artikull do të fokusohet kryesisht në sinjalin SIGKILL si një sinjal që shkakton vdekjen e një procesi.
specifikat e macOS
Në macOS, thirrja e sistemit kill në kernelin XNU thërret funksionin psignal(SIGKILL,...). Le të përpiqemi të shohim se cilat veprime të tjera të përdoruesit në hapësirën e përdoruesit mund të quhen nga funksioni psignal. Le të heqim thirrjet për funksionin psignal në mekanizmat e brendshëm të kernelit (edhe pse mund të jenë jo të parëndësishëm, do t'i lëmë për një artikull tjetër 🙂 - verifikimi i nënshkrimit, gabimet e kujtesës, trajtimi i daljes/përfundimit, shkeljet e mbrojtjes së skedarëve, etj. .
Le të fillojmë rishikimin me funksionin dhe thirrjen përkatëse të sistemit
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);
}
...
}
nisur
Hapet mënyra standarde për të krijuar demonë në fillimin e sistemit dhe për të kontrolluar jetëgjatësinë e tyre. Ju lutemi vini re se burimet janë për versionin e vjetër të launchctl deri në macOS 10.10, shembuj kodesh janë dhënë për qëllime ilustruese. Launchctl moderne dërgon sinjale të lëshuara përmes XPC, logjika launchctl është zhvendosur në të.
Le të shohim se si janë ndalur saktësisht aplikacionet. Përpara dërgimit të sinjalit SIGTERM, aplikacioni tentohet të ndalet duke përdorur thirrjen e sistemit “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));
}
...
<>
Nën kapuç, proc_terminate, pavarësisht nga emri i tij, mund të dërgojë jo vetëm psignal me SIGTERM, por edhe SIGKILL.
Vrasja indirekte - Kufiri i burimeve
Një rast më interesant mund të shihet në një thirrje tjetër sistemi
Megjithëse kjo thirrje sistemi mund të vrasë potencialisht një proces, sistemi nuk ka kontrolluar në mënyrë adekuate të drejtat e procesit që thërret thirrjen e sistemit. Në fakt duke kontrolluar
Prandaj, nëse "kufizoni" kuotën e përdorimit të CPU-së të aplikacionit (për shembull, duke lejuar vetëm 1 ns të ekzekutohet), atëherë mund të mbytni çdo proces në sistem. Kështu, malware mund të vrasë çdo proces në sistem, duke përfshirë procesin e antivirusit. Gjithashtu interesant është efekti që ndodh kur mbytni një proces me pid 1 (launchctl) - paniku i kernelit kur përpiqeni të përpunoni sinjalin SIGKILL :)
Si ta zgjidhim problemin?
Mënyra më e drejtpërdrejtë për të parandaluar vdekjen e një procesi është zëvendësimi i treguesit të funksionit në tabelën e thirrjeve të sistemit. Fatkeqësisht, kjo metodë nuk është e parëndësishme për shumë arsye.
Së pari, simboli që kontrollon vendndodhjen e kujtesës së sysent nuk është vetëm privat për simbolin e kernelit XNU, por nuk mund të gjendet në simbolet e kernelit. Ju do të duhet të përdorni metoda heuristike kërkimi, të tilla si çmontimi dinamik i funksionit dhe kërkimi i një treguesi në të.
Së dyti, struktura e hyrjeve në tabelë varet nga flamujt me të cilët është përpiluar kerneli. Nëse deklarohet flamuri CONFIG_REQUIRES_U32_MUNGING, madhësia e strukturës do të ndryshohet - do të shtohet një fushë shtesë
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
*/
};
Për fat të mirë, në versionet moderne të macOS, Apple ofron një API të re për të punuar me proceset. Endpoint Security API i lejon klientët të autorizojnë shumë kërkesa për procese të tjera. Kështu, ju mund të bllokoni çdo sinjal për proceset, duke përfshirë sinjalin SIGKILL, duke përdorur API-në e lartpërmendur.
#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;
}
Në mënyrë të ngjashme, një politikë MAC mund të regjistrohet në kernel, e cila ofron një metodë të mbrojtjes së sinjalit (policy proc_check_signal), por API nuk mbështetet zyrtarisht.
Mbrojtja e zgjerimit të kernelit
Përveç mbrojtjes së proceseve në sistem, është gjithashtu e nevojshme mbrojtja e vetë zgjerimit të kernelit (kext). macOS ofron një kornizë për zhvilluesit që të zhvillojnë me lehtësi drejtuesit e pajisjes IOKit. Përveç sigurimit të mjeteve për të punuar me pajisje, IOKit ofron metoda për grumbullimin e drejtuesve duke përdorur shembuj të klasave C++. Një aplikacion në hapësirën e përdoruesve do të jetë në gjendje të "gjeni" një shembull të regjistruar të klasës për të krijuar një marrëdhënie kernel-hapësirë përdoruesi.
Për të zbuluar numrin e rasteve të klasës në sistem, ekziston mjeti i ioclasscount.
my_kext_ioservice = 1
my_kext_iouserclient = 1
Çdo shtesë e kernelit që dëshiron të regjistrohet me pirgun e drejtuesve duhet të deklarojë një klasë që trashëgon nga IOService, për shembull my_kext_ioservice në këtë rast. Lidhja e aplikacioneve të përdoruesit shkakton krijimin e një shembulli të ri të klasës që trashëgon nga IOUserClient, në shembullin my_kext_iouserclient.
Kur përpiqeni të shkarkoni një drejtues nga sistemi (komandë kextunload), thirret funksioni virtual "bool terminate (opsionet e IOOptionBits)". Mjafton të ktheni false në thirrjen për të përfunduar kur përpiqeni të shkarkoni për të çaktivizuar kextunload.
bool Kext::terminate(IOOptionBits options)
{
if (!IsUnloadAllowed)
{
// Unload is not allowed, returning false
return false;
}
return super::terminate(options);
}
Flamuri IsUnloadAllowed mund të vendoset nga IOUserClient kur ngarkohet. Kur ka një kufi shkarkimi, komanda kextunload do të kthejë daljen e mëposhtme:
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.
Mbrojtje e ngjashme duhet të bëhet për IOUserClient. Instancat e klasave mund të shkarkohen duke përdorur funksionin e hapësirës së përdoruesve të IOKitLib "IOCatalogueTerminate(mach_port_t, flamuri uint32_t, përshkrimi io_name_t);". Ju mund të ktheni false kur thërrisni komandën "terminate" derisa aplikacioni i hapësirës së përdoruesit "të vdesë", domethënë funksioni "clientDied" nuk thirret.
Mbrojtja e skedarit
Për të mbrojtur skedarët, mjafton të përdorni Kauth API, i cili ju lejon të kufizoni aksesin në skedarë. Apple u ofron zhvilluesve njoftime për ngjarje të ndryshme në fushëveprimin; për ne, operacionet KAUTH_VNODE_DELETE, KAUTH_VNODE_WRITE_DATA dhe KAUTH_VNODE_DELETE_CHILD janë të rëndësishme. Mënyra më e lehtë për të kufizuar aksesin në skedarë është me shteg - ne përdorim API-në "vn_getpath" për të marrë shtegun drejt skedarit dhe për të krahasuar prefiksin e shtegut. Vini re se për të optimizuar riemërtimin e shtigjeve të dosjeve të skedarëve, sistemi nuk autorizon hyrjen në çdo skedar, por vetëm në vetë dosjen që është riemërtuar. Është e nevojshme të krahasohet shtegu prind dhe të kufizohet KAUTH_VNODE_DELETE për të.
Disavantazhi i kësaj qasjeje mund të jetë performanca e ulët me rritjen e numrit të parashtesave. Për të siguruar që krahasimi të mos jetë i barabartë me O (prefiks*gjatësia), ku prefiksi është numri i parashtesave, gjatësia është gjatësia e vargut, mund të përdorni një automat të fundëm përcaktues (DFA) të ndërtuar nga parashtesa.
Le të shqyrtojmë një metodë për ndërtimin e një DFA për një grup të caktuar parashtesash. Ne inicializojmë kursorët në fillim të çdo parashtese. Nëse të gjithë kursorët tregojnë të njëjtin karakter, atëherë rriteni çdo kursor me një karakter dhe mbani mend se gjatësia e së njëjtës rresht është më e madhe për një. Nëse ka dy kursorë me simbole të ndryshme, ndajini kursorët në grupe sipas simbolit ku ata tregojnë dhe përsëritni algoritmin për secilin grup.
Në rastin e parë (të gjithë personazhet nën kursorët janë të njëjtë), marrim një gjendje DFA që ka vetëm një tranzicion përgjatë së njëjtës linjë. Në rastin e dytë, marrim një tabelë të tranzicionit me madhësi 256 (numri i karaktereve dhe numri maksimal i grupeve) në gjendjet pasuese të marra duke thirrur në mënyrë rekursive funksionin.
Le të shohim një shembull. Për një grup parashtesash (“/foo/bar/tmp/”, “/var/db/foo/”, “/foo/bar/aba/”, “foo/bar/aac/”) mund të merrni sa vijon DFA. Figura tregon vetëm tranzicionet që çojnë në gjendje të tjera; tranzicionet e tjera nuk do të jenë përfundimtare.
Kur kaloni nëpër shtetet e DKA, mund të ketë 3 raste.
- Gjendja përfundimtare është arritur - shtegu është i mbrojtur, ne kufizojmë operacionet KAUTH_VNODE_DELETE, KAUTH_VNODE_WRITE_DATA dhe KAUTH_VNODE_DELETE_CHILD
- Gjendja përfundimtare nuk u arrit, por shtegu "mbaroi" (u arrit terminatori null) - shtegu është prind, është e nevojshme të kufizohet KAUTH_VNODE_DELETE. Vini re se nëse vnode është një dosje, duhet të shtoni një '/' në fund, përndryshe mund ta kufizojë atë në skedarin "/foor/bar/t", i cili është i pasaktë.
- Gjendja përfundimtare nuk u arrit, rruga nuk mbaroi. Asnjë prej parashtesave nuk përputhet me këtë, ne nuk vendosim kufizime.
Përfundim
Qëllimi i zgjidhjeve të sigurisë që po zhvillohen është rritja e nivelit të sigurisë së përdoruesit dhe të dhënave të tij. Nga njëra anë, ky qëllim arrihet me zhvillimin e produktit softuer Acronis, i cili mbyll ato dobësi ku vetë sistemi operativ është "i dobët". Nga ana tjetër, nuk duhet të neglizhojmë forcimin e atyre aspekteve të sigurisë që mund të përmirësohen në anën e OS, veçanërisht pasi mbyllja e dobësive të tilla rrit stabilitetin tonë si produkt. Dobësia iu raportua Ekipit të Sigurisë së Produkteve të Apple dhe është rregulluar në macOS 10.14.5 (https://support.apple.com/en-gb/HT210119).
E gjithë kjo mund të bëhet vetëm nëse programi juaj është instaluar zyrtarisht në kernel. Kjo do të thotë, nuk ka zbrazëtira të tilla për softuerin e jashtëm dhe të padëshiruar. Sidoqoftë, siç mund ta shihni, edhe mbrojtja e programeve legjitime si antiviruset dhe sistemet rezervë kërkon punë. Por tani produktet e reja Acronis për macOS do të kenë mbrojtje shtesë kundër shkarkimit nga sistemi.
Burimi: www.habr.com