Salam, Habr! Bu gün mən macOS-da prosesləri təcavüzkarların hücumlarından necə qoruya biləcəyiniz haqqında danışmaq istərdim. Məsələn, bu, antivirus və ya ehtiyat nüsxə sistemi üçün faydalıdır, xüsusən macOS-da prosesi “öldürməyin” bir neçə yolu var. Bu və kəsik altında qorunma üsulları haqqında oxuyun.
Bir prosesi “öldürməyin” klassik yolu
Prosesi “öldürməyin” məşhur yolu prosesə SIGKILL siqnalı göndərməkdir. Bash vasitəsilə siz öldürmək üçün standart “kill -SIGKILL PID” və ya “pkill -9 NAME” çağıra bilərsiniz. “Öldür” əmri UNIX günlərindən məlumdur və təkcə macOS-da deyil, digər UNIX-ə bənzər sistemlərdə də mövcuddur.
UNIX-ə bənzər sistemlərdə olduğu kimi, macOS sizə iki prosesdən başqa - SIGKILL və SIGSTOP-dan başqa istənilən siqnalı tutmağa imkan verir. Bu məqalə, ilk növbədə, bir prosesin öldürülməsinə səbəb olan bir siqnal kimi SIGKILL siqnalına diqqət yetirəcəkdir.
macOS xüsusiyyətləri
MacOS-da XNU nüvəsindəki öldürmə sistemi çağırışı psignal(SIGKILL,...) funksiyasını çağırır. İstifadəçi məkanında başqa hansı istifadəçi hərəkətlərinin psignal funksiyası ilə adlandırıla biləcəyini görməyə çalışaq. Nüvənin daxili mexanizmlərindəki psiqnal funksiyasına edilən zəngləri aradan qaldıraq (onlar qeyri-ciddi olsalar da, onları başqa məqaləyə buraxacağıq 🙂 - imzanın yoxlanılması, yaddaş səhvləri, çıxış/xitamla işləmə, faylın qorunması pozuntuları və s. .
Baxışa funksiya və müvafiq sistem çağırışı ilə başlayaq
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);
}
...
}
işə salındı
Sistemin işə salınması zamanı demonlar yaratmaq və onların ömrünü idarə etmək üçün standart üsul işə salınıb. Nəzərə alın ki, mənbələr launchctl-in macOS 10.10-a qədər köhnə versiyası üçündür, kod nümunələri illüstrativ məqsədlər üçün verilmişdir. Müasir launchctl işə salınmış siqnalları XPC vasitəsilə göndərir, launchctl məntiqi ona köçürülüb.
Tətbiqlərin tam olaraq necə dayandırıldığına baxaq. SIGTERM siqnalını göndərməzdən əvvəl “proc_terminate” sistem çağırışından istifadə edərək proqram dayandırılmağa cəhd edilir.
<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));
}
...
<>
Başlıq altında proc_terminate, adına baxmayaraq, yalnız SIGTERM ilə deyil, həm də SIGKILL ilə siqnal göndərə bilər.
Dolayı Öldürmə - Resurs Limiti
Daha maraqlı bir hadisəni başqa bir sistem çağırışında görmək olar
Bu sistem çağırışı potensial olaraq prosesi öldürə bilsə də, sistem sistem çağırışını çağıran prosesin hüquqlarını adekvat şəkildə yoxlamadı. Əslində yoxlayır
Beləliklə, tətbiqin CPU istifadə kvotasını "məhdudlaşdırarsanız" (məsələn, yalnız 1 ns işləməyə icazə verirsinizsə), sistemdəki istənilən prosesi öldürə bilərsiniz. Beləliklə, zərərli proqram sistemdəki istənilən prosesi, o cümlədən antivirus prosesini öldürə bilər. SIGKILL siqnalını emal etməyə çalışarkən pid 1 (launchctl) ilə prosesi öldürərkən baş verən təsir də maraqlıdır - nüvə panikası :)
Problemi necə həll etmək olar?
Prosesin öldürülməsinin qarşısını almağın ən sadə yolu sistem çağırış cədvəlində funksiya göstəricisini əvəz etməkdir. Təəssüf ki, bu üsul bir çox səbəblərə görə əhəmiyyətsizdir.
Birincisi, sysent-in yaddaş yerini idarə edən simvol yalnız XNU nüvə simvolu üçün özəl deyil, nüvə simvollarında tapıla bilməz. Funksiyanı dinamik şəkildə sökmək və orada göstərici axtarmaq kimi evristik axtarış metodlarından istifadə etməli olacaqsınız.
İkincisi, cədvəldəki girişlərin strukturu nüvənin tərtib edildiyi bayraqlardan asılıdır. CONFIG_REQUIRES_U32_MUNGING bayrağı elan edilərsə, strukturun ölçüsü dəyişdiriləcək - əlavə sahə əlavə olunacaq
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
*/
};
Xoşbəxtlikdən, macOS-un müasir versiyalarında Apple proseslərlə işləmək üçün yeni API təqdim edir. Endpoint Security API müştərilərə digər proseslərə çoxlu sorğulara icazə verməyə imkan verir. Beləliklə, yuxarıda qeyd olunan API-dən istifadə edərək SIGKILL siqnalı da daxil olmaqla proseslərə istənilən siqnalları blok edə bilərsiniz.
#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;
}
Eynilə, MAC Siyasəti kerneldə qeydə alına bilər ki, bu da siqnaldan qorunma metodunu (policy proc_check_signal) təmin edir, lakin API rəsmi olaraq dəstəklənmir.
Kernel uzadılması qorunması
Sistemdəki prosesləri qorumaqla yanaşı, nüvə uzantısının özünü (kext) qorumaq da lazımdır. macOS tərtibatçılar üçün IOKit cihaz sürücülərini asanlıqla inkişaf etdirmək üçün çərçivə təqdim edir. Qurğularla işləmək üçün alətlər təqdim etməklə yanaşı, IOKit C++ siniflərinin nümunələrindən istifadə edərək sürücülərin yığılması üsullarını təqdim edir. İstifadəçi məkanındakı proqram nüvə-istifadəçi məkanı əlaqəsi yaratmaq üçün sinfin qeydə alınmış nümunəsini “tapa” biləcək.
Sistemdəki sinif nümunələrinin sayını aşkar etmək üçün ioclasscount yardım proqramı var.
my_kext_ioservice = 1
my_kext_iouserclient = 1
Sürücü yığını ilə qeydiyyatdan keçmək istəyən hər hansı nüvə genişləndirilməsi IOService-dən miras qalan sinfi elan etməlidir, məsələn, bu halda my_kext_ioservice. İstifadəçi proqramlarının qoşulması my_kext_iouserclient misalında IOUserClient-dən miras qalan sinfin yeni nümunəsinin yaradılmasına səbəb olur.
Sürücünü sistemdən boşaltmağa çalışarkən (kextunload əmri), “bool terminate(IOOptionBits options)” virtual funksiyası çağırılır. Kextunload-u söndürmək üçün yükləməni boşaltmağa çalışarkən dayandırmaq üçün çağırışda false qaytarmaq kifayətdir.
bool Kext::terminate(IOOptionBits options)
{
if (!IsUnloadAllowed)
{
// Unload is not allowed, returning false
return false;
}
return super::terminate(options);
}
IsUnloadAllowed bayrağı yükləmə zamanı IOUserClient tərəfindən təyin edilə bilər. Yükləmə limiti olduqda, kextunload əmri aşağıdakı çıxışı qaytaracaq:
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.
Oxşar qorunma IOUserClient üçün də edilməlidir. Sinif nümunələri “IOCatalogueTerminate(mach_port_t, uint32_t flag, io_name_t description);” IOKitLib istifadəçi məkanı funksiyasından istifadə etməklə yüklənə bilər. İstifadəçilər məkanı proqramı “ölənə qədər”, yəni “clientDied” funksiyası çağırılmayana qədər “sonlandırmaq” əmrini çağırarkən false qaytara bilərsiniz.
Fayl qorunması
Faylları qorumaq üçün fayllara girişi məhdudlaşdırmağa imkan verən Kauth API-dən istifadə etmək kifayətdir. Apple tərtibatçıları əhatə dairəsində müxtəlif hadisələr haqqında bildirişlər təqdim edir; bizim üçün KAUTH_VNODE_DELETE, KAUTH_VNODE_WRITE_DATA və KAUTH_VNODE_DELETE_CHILD əməliyyatları vacibdir. Fayllara girişi məhdudlaşdırmağın ən asan yolu yoldur - faylın yolunu əldə etmək və yol prefiksini müqayisə etmək üçün "vn_getpath" API-dən istifadə edirik. Qeyd edək ki, fayl qovluğu yollarının adının dəyişdirilməsini optimallaşdırmaq üçün sistem hər bir fayla deyil, yalnız adı dəyişdirilmiş qovluğun özünə giriş icazəsi verir. Ana yolu müqayisə etmək və bunun üçün KAUTH_VNODE_DELETE-i məhdudlaşdırmaq lazımdır.
Bu yanaşmanın dezavantajı prefikslərin sayı artdıqca aşağı performans ola bilər. Müqayisə O (prefiks* uzunluq) ilə bərabər olmadığından əmin olmaq üçün, burada prefiks prefikslərin sayıdır, uzunluq sətir uzunluğudur, prefikslər tərəfindən qurulmuş deterministik sonlu avtomatdan (DFA) istifadə edə bilərsiniz.
Verilmiş prefikslər dəsti üçün DFA-nın qurulması metodunu nəzərdən keçirək. Hər bir prefiksin əvvəlində kursorları işə salırıq. Bütün kursorlar eyni simvolu göstərirsə, onda hər kursoru bir simvol artırın və eyni xəttin uzunluğunun bir simvol daha böyük olduğunu unutmayın. Fərqli simvollu iki kursor varsa, kursorları göstərdikləri simvola görə qruplara bölün və hər qrup üçün alqoritmi təkrarlayın.
Birinci halda (kursorların altındakı bütün simvollar eynidır), biz eyni xətt boyunca yalnız bir keçidi olan DFA vəziyyəti alırıq. İkinci halda, funksiyanı rekursiv çağırmaqla əldə edilən sonrakı vəziyyətlərə 256 ölçülü (simvolların sayı və qrupların maksimum sayı) keçid cədvəlini alırıq.
Bir nümunəyə baxaq. Prefikslər dəsti üçün (“/foo/bar/tmp/”, “/var/db/foo/”, “/foo/bar/aba/”, “foo/bar/aac/”) aşağıdakıları əldə edə bilərsiniz. DFA. Şəkil yalnız digər dövlətlərə aparan keçidləri göstərir, digər keçidlər yekun olmayacaq.
DKA dövlətlərindən keçərkən 3 hal ola bilər.
- Son vəziyyətə çatıldı - yol qorunur, biz KAUTH_VNODE_DELETE, KAUTH_VNODE_WRITE_DATA və KAUTH_VNODE_DELETE_CHILD əməliyyatlarını məhdudlaşdırırıq
- Son vəziyyətə çatmadı, lakin yol "bitdi" (null terminatora çatıldı) - yol valideyndir, KAUTH_VNODE_DELETE-i məhdudlaşdırmaq lazımdır. Nəzərə alın ki, vnode qovluqdursa, sonuna '/' əlavə etməlisiniz, əks halda onu “/foor/bar/t” faylı ilə məhdudlaşdıra bilər, bu yanlışdır.
- Son vəziyyətə çatmadı, yol bitmədi. Prefikslərin heç biri buna uyğun gəlmir, biz məhdudiyyətlər tətbiq etmirik.
Nəticə
Hazırlanan təhlükəsizlik həllərinin məqsədi istifadəçinin və onun məlumatlarının təhlükəsizlik səviyyəsini artırmaqdır. Bir tərəfdən, bu məqsədə əməliyyat sisteminin özünün “zəif” olduğu zəiflikləri bağlayan Acronis proqram məhsulunun inkişafı ilə nail olunur. Digər tərəfdən, biz OS tərəfində təkmilləşdirilə bilən təhlükəsizlik aspektlərinin gücləndirilməsini laqeyd etməməliyik, xüsusən belə zəifliklərin bağlanması məhsul olaraq öz sabitliyimizi artırır. Boşluq Apple Məhsul Təhlükəsizliyi Qrupuna bildirildi və macOS 10.14.5-də (https://support.apple.com/en-gb/HT210119) aradan qaldırıldı.
Bütün bunlar yalnız yardım proqramınız nüvəyə rəsmi olaraq quraşdırıldığı təqdirdə edilə bilər. Yəni xarici və arzuolunmaz proqram təminatı üçün belə boşluqlar yoxdur. Bununla belə, gördüyünüz kimi, hətta antivirus və ehtiyat sistemlər kimi qanuni proqramların qorunması iş tələb edir. Lakin indi macOS üçün yeni Acronis məhsulları sistemdən yüklənməyə qarşı əlavə qorunma əldə edəcək.
Mənbə: www.habr.com