MacOS-da jarayonlar va yadro kengaytmalarini qanday himoya qilish kerak

Salom, Xabr! Bugun men macOS-da jarayonlarni tajovuzkorlarning hujumlaridan qanday himoya qilish haqida gaplashmoqchiman. Misol uchun, bu antivirus yoki zaxira tizimi uchun foydalidir, ayniqsa macOS-da jarayonni "o'ldirish" uchun bir necha usullar mavjud. Bu va kesish ostida himoya usullari haqida o'qing.

MacOS-da jarayonlar va yadro kengaytmalarini qanday himoya qilish kerak

Jarayonni "o'ldirish" ning klassik usuli

Jarayonni "o'ldirish" ning taniqli usuli bu jarayonga SIGKILL signalini yuborishdir. Bash orqali siz o'ldirish uchun standart "kill -SIGKILL PID" yoki "pkill -9 NAME" ni chaqirishingiz mumkin. "O'ldirish" buyrug'i UNIX davridan beri ma'lum va u nafaqat macOS-da, balki boshqa UNIX-ga o'xshash tizimlarda ham mavjud.

Xuddi UNIX-ga o'xshash tizimlarda bo'lgani kabi, macOS ikkita jarayondan tashqari har qanday signalni ushlab turishga imkon beradi - SIGKILL va SIGSTOP. Ushbu maqola birinchi navbatda SIGKILL signaliga, jarayonni o'ldirishga olib keladigan signalga qaratiladi.

macOS xususiyatlari

MacOS’da XNU yadrosidagi o‘ldirish tizimi chaqiruvi psignal(SIGKILL,...) funksiyasini chaqiradi. Keling, foydalanuvchilar maydonidagi boshqa qanday foydalanuvchi harakatlarini psignal funksiyasi deb atash mumkinligini ko'rib chiqaylik. Yadroning ichki mexanizmlaridagi psignal funktsiyasiga qo'ng'iroqlarni o'chirib tashlaylik (ular ahamiyatsiz bo'lishi mumkin bo'lsa-da, biz ularni boshqa maqolaga qoldiramiz 🙂 - imzoni tekshirish, xotira xatolari, chiqish/to'xtatish, fayllarni himoya qilish buzilishi va hk. .

Ko'rib chiqishni funktsiya va tegishli tizim chaqiruvi bilan boshlaylik to'lash_yuk bilan_tugatish. Ko'rinib turibdiki, klassik o'ldirish chaqiruvidan tashqari, macOS operatsion tizimiga xos bo'lgan va BSD-da topilmaydigan muqobil yondashuv mavjud. Ikkala tizim chaqiruvining ishlash tamoyillari ham o'xshash. Ular psignal yadro funktsiyasiga to'g'ridan-to'g'ri qo'ng'iroqlardir. Shuni ham yodda tutingki, jarayonni o'ldirishdan oldin, "kansignal" tekshiruvi amalga oshiriladi - jarayon boshqa jarayonga signal yuborishi mumkinmi; tizim, masalan, tizim jarayonlarini o'chirishga hech qanday dasturga ruxsat bermaydi.

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);
	}
...
}

ishga tushirildi

Tizim ishga tushganda demonlarni yaratish va ularning ishlash muddatini nazorat qilishning standart usuli ishga tushirildi. Esda tutingki, manbalar launchctl ning macOS 10.10gacha bo‘lgan eski versiyasiga mo‘ljallangan, kod misollari illyustrativ maqsadlarda keltirilgan. Zamonaviy launchctl ishga tushirilgan signallarni XPC orqali yuboradi, launchctl mantig'i unga ko'chirildi.

Keling, ilovalar qanday aniq to'xtatilganini ko'rib chiqaylik. SIGTERM signalini yuborishdan oldin, dasturni "proc_terminate" tizim chaqiruvi yordamida to'xtatishga harakat qilinadi.

<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));
		} 
...
<>

Kaput ostida proc_terminate, nomiga qaramay, SIGTERM bilan nafaqat psignal, balki SIGKILL ham yuborishi mumkin.

Bilvosita o'ldirish - Resurs chegarasi

Yana bir qiziq holatni boshqa tizim chaqiruvida ko'rish mumkin jarayon_siyosat. Ushbu tizim chaqiruvidan keng tarqalgan foydalanish dastur resurslarini cheklashdir, masalan, indeksator protsessor vaqtini va xotira kvotalarini cheklashi uchun tizim fayllarni keshlash faoliyati tufayli sezilarli darajada sekinlashmaydi. Agar dastur o'zining resurs chegarasiga yetgan bo'lsa, proc_apply_resource_actions funksiyasidan ko'rinib turibdiki, jarayonga SIGKILL signali yuboriladi.

Garchi ushbu tizim chaqiruvi jarayonni o'ldirishi mumkin bo'lsa-da, tizim tizim chaqiruvini chaqiruvchi jarayonning huquqlarini etarli darajada tekshirmagan. Haqiqatan ham tekshirish mavjud edi, lekin bu shartni chetlab o'tish uchun PROC_POLICY_ACTION_SET muqobil bayroqdan foydalanish kifoya.

Shunday qilib, agar siz dasturning protsessordan foydalanish kvotasini "cheklab qo'ysangiz" (masalan, faqat 1 ns ishlashiga ruxsat bersangiz), tizimdagi har qanday jarayonni o'ldirishingiz mumkin. Shunday qilib, zararli dastur tizimdagi har qanday jarayonni, shu jumladan antivirus jarayonini o'ldirishi mumkin. Bundan tashqari, pid 1 (launchctl) bilan jarayonni o'ldirishda yuzaga keladigan ta'sir ham qiziq - SIGKILL signalini qayta ishlashga urinayotganda yadro vahima :)

MacOS-da jarayonlar va yadro kengaytmalarini qanday himoya qilish kerak

Muammoni qanday hal qilish mumkin?

Jarayonning nobud bo'lishining oldini olishning eng oddiy usuli bu tizim chaqiruvlari jadvalidagi funktsiya ko'rsatkichini almashtirishdir. Afsuski, bu usul ko'p sabablarga ko'ra ahamiyatsiz emas.

Birinchidan, sysent xotirasining joylashuvini boshqaruvchi belgi nafaqat XNU yadro belgisiga tegishli, balki yadro belgilarida topilmaydi. Siz funktsiyani dinamik ravishda qismlarga ajratish va undagi ko'rsatgichni qidirish kabi evristik qidiruv usullaridan foydalanishingiz kerak bo'ladi.

Ikkinchidan, jadvaldagi yozuvlarning tuzilishi yadro tuzilgan bayroqlarga bog'liq. Agar CONFIG_REQUIRES_U32_MUNGING bayrog'i e'lon qilinsa, strukturaning o'lchami o'zgaradi - qo'shimcha maydon qo'shiladi sy_arg_munge32. Yadro qaysi bayroq bilan tuzilganligini aniqlash uchun qo'shimcha tekshiruv o'tkazish yoki muqobil ravishda funktsiya ko'rsatkichlarini ma'lum bo'lganlarga nisbatan tekshirish kerak.

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
                                         */
};

Yaxshiyamki, macOS ning zamonaviy versiyalarida Apple jarayonlar bilan ishlash uchun yangi API taqdim etadi. Endpoint Security API mijozlarga boshqa jarayonlarga ko'plab so'rovlarni avtorizatsiya qilish imkonini beradi. Shunday qilib, yuqorida aytib o'tilgan API yordamida jarayonlarga har qanday signallarni, shu jumladan SIGKILL signalini bloklashingiz mumkin.

#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;
}

Xuddi shunday, MAC siyosati yadroda ro'yxatdan o'tkazilishi mumkin, bu signalni himoya qilish usulini ta'minlaydi (policy proc_check_signal), lekin API rasman qo'llab-quvvatlanmaydi.

Yadro kengaytmasini himoya qilish

Tizimdagi jarayonlarni himoya qilishdan tashqari, yadro kengaytmasining o'zini (kext) himoya qilish ham zarur. macOS dasturchilarga IOKit qurilma drayverlarini osongina ishlab chiqish uchun asos yaratadi. Qurilmalar bilan ishlash vositalarini taqdim etishdan tashqari, IOKit C++ sinflari misollaridan foydalangan holda drayverlarni stacking usullarini taqdim etadi. Foydalanuvchilar maydonidagi dastur yadro-foydalanuvchi aloqasini o'rnatish uchun sinfning ro'yxatdan o'tgan nusxasini "topishi" mumkin bo'ladi.

Tizimdagi sinf namunalari sonini aniqlash uchun ioclasscount yordam dasturi mavjud.

my_kext_ioservice = 1
my_kext_iouserclient = 1

Drayvlar stekida ro'yxatdan o'tmoqchi bo'lgan har qanday yadro kengaytmasi IOService'dan meros bo'ladigan sinfni e'lon qilishi kerak, masalan, my_kext_ioservice bu holda.Foydalanuvchi ilovalarini ulash my_kext_iouserclient misolida IOUserClient'dan meros bo'ladigan sinfning yangi nusxasini yaratishga olib keladi.

Tizimdan drayverni tushirishga urinayotganda (kextunload buyrug'i), "bool terminate(IOOptionBits options)" virtual funksiyasi chaqiriladi. Kextunloadni o'chirish uchun tushirishga urinayotganda tugatish uchun qo'ng'iroqda yolg'onni qaytarish kifoya.

bool Kext::terminate(IOOptionBits options)
{

  if (!IsUnloadAllowed)
  {
    // Unload is not allowed, returning false
    return false;
  }

  return super::terminate(options);
}

IsUnloadAllowed bayrog'i yuklashda IOUserClient tomonidan o'rnatilishi mumkin. Yuklab olish chegarasi mavjud bo'lganda, kextunload buyrug'i quyidagi natijani qaytaradi:

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.

Xuddi shunday himoya IOUserClient uchun ham amalga oshirilishi kerak. Sinflar misollarini “IOCatalogueTerminate(mach_port_t, uint32_t flag, io_name_t description);” IOKitLib foydalanuvchi maydoni funksiyasi yordamida olib tashlash mumkin. Siz “tugatish” buyrug'ini chaqirganda, “foydalanuvchilar maydoni” ilovasi “o'lgunicha”, ya'ni “clientDied” funksiyasi chaqirilmaguncha “false”ni qaytarishingiz mumkin.

Fayl himoyasi

Fayllarni himoya qilish uchun fayllarga kirishni cheklash imkonini beruvchi Kauth API dan foydalanish kifoya. Apple ishlab chiquvchilarga turli xil hodisalar haqida bildirishnomalar beradi; biz uchun KAUTH_VNODE_DELETE, KAUTH_VNODE_WRITE_DATA va KAUTH_VNODE_DELETE_CHILD operatsiyalari muhim. Fayllarga kirishni cheklashning eng oson yo'li bu yo'l - biz faylga yo'lni olish va yo'l prefiksini solishtirish uchun "vn_getpath" API-dan foydalanamiz. Fayl papkalari yo'llarining nomini o'zgartirishni optimallashtirish uchun tizim har bir faylga emas, balki faqat nomi o'zgartirilgan jildning o'ziga kirishga ruxsat berishini unutmang. Ota-ona yo'lini solishtirish va buning uchun KAUTH_VNODE_DELETE ni cheklash kerak.

MacOS-da jarayonlar va yadro kengaytmalarini qanday himoya qilish kerak

Ushbu yondashuvning nochorligi past ishlash bo'lishi mumkin, chunki prefikslar soni ortib boradi. Taqqoslash O (prefiks* uzunlik) ga teng bo'lmasligiga ishonch hosil qilish uchun, bu erda prefiks - prefikslar soni, uzunlik - satr uzunligi, siz prefikslar tomonidan qurilgan deterministik chekli avtomatdan (DFA) foydalanishingiz mumkin.

Keling, berilgan prefikslar to'plami uchun DFA qurish usulini ko'rib chiqaylik. Har bir prefiksning boshida kursorlarni ishga tushiramiz. Agar barcha kursorlar bir xil belgiga ishora qilsa, unda har bir kursorni bitta belgiga oshiring va bir xil chiziq uzunligi bittaga katta ekanligini unutmang. Har xil belgilarga ega ikkita kursor mavjud bo'lsa, kursorlarni ular ko'rsatgan belgiga ko'ra guruhlarga bo'ling va har bir guruh uchun algoritmni takrorlang.

Birinchi holda (kursorlar ostidagi barcha belgilar bir xil), biz bir xil chiziq bo'ylab faqat bitta o'tishga ega bo'lgan DFA holatini olamiz. Ikkinchi holda, funktsiyani rekursiv chaqirish orqali olingan keyingi holatlarga 256 o'lchamdagi (belgilar soni va maksimal guruhlar soni) o'tish jadvalini olamiz.

Keling, bir misolni ko'rib chiqaylik. Prefikslar to'plami uchun (“/foo/bar/tmp/”, “/var/db/foo/”, “/foo/bar/aba/”, “foo/bar/aac/”) quyidagilarni olishingiz mumkin. DFA. Rasmda faqat boshqa holatlarga olib boradigan o'tishlar ko'rsatilgan; boshqa o'tishlar yakuniy bo'lmaydi.

MacOS-da jarayonlar va yadro kengaytmalarini qanday himoya qilish kerak

DKA davlatlari orqali o'tayotganda 3 ta holat bo'lishi mumkin.

  1. Yakuniy holatga erishildi - yo'l himoyalangan, biz KAUTH_VNODE_DELETE, KAUTH_VNODE_WRITE_DATA va KAUTH_VNODE_DELETE_CHILD operatsiyalarini cheklaymiz
  2. Yakuniy holatga erishilmadi, lekin yo'l "tugadi" (null terminatorga erishildi) - yo'l ota-ona, KAUTH_VNODE_DELETEni cheklash kerak. E'tibor bering, agar vnode papka bo'lsa, oxiriga "/" qo'shishingiz kerak, aks holda u "/foor/bar/t" fayli bilan cheklanishi mumkin, bu noto'g'ri.
  3. Yakuniy holatga erishilmadi, yo'l tugamadi. Prefikslarning hech biri bunga mos kelmaydi, biz cheklovlar kiritmaymiz.

xulosa

Ishlab chiqilayotgan xavfsizlik yechimlarining maqsadi foydalanuvchi va uning ma'lumotlari xavfsizligi darajasini oshirishdan iborat. Bir tomondan, bu maqsadga Acronis dasturiy mahsulotini ishlab chiqish orqali erishiladi, bu esa operatsion tizimning o'zi "zaif" bo'lgan zaifliklarni yopadi. Boshqa tomondan, biz OS tomonida yaxshilanishi mumkin bo'lgan xavfsizlik jihatlarini kuchaytirishni e'tiborsiz qoldirmasligimiz kerak, ayniqsa, bunday zaifliklarni yopish mahsulot sifatida o'z barqarorligimizni oshiradi. Zaiflik haqida Apple mahsulot xavfsizligi guruhiga xabar berildi va macOS 10.14.5 (https://support.apple.com/en-gb/HT210119) da tuzatildi.

MacOS-da jarayonlar va yadro kengaytmalarini qanday himoya qilish kerak

Bularning barchasi faqat sizning yordamchi dasturingiz yadroga rasman o'rnatilgan bo'lsa amalga oshirilishi mumkin. Ya'ni, tashqi va kiruvchi dasturlar uchun bunday bo'shliqlar yo'q. Biroq, ko'rib turganingizdek, hatto antivirus va zaxira tizimlari kabi qonuniy dasturlarni himoya qilish ham mehnat talab qiladi. Ammo endi MacOS uchun yangi Acronis mahsulotlari tizimdan tushirishdan qo'shimcha himoyaga ega bo'ladi.

Manba: www.habr.com

a Izoh qo'shish