MacOS жүйесінде процестер мен ядро ​​кеңейтімдерін қалай қорғауға болады

Сәлем, Хабр! Бүгін мен macOS жүйесінде процестерді шабуылдаушылардың шабуылдарынан қалай қорғауға болатыны туралы сөйлескім келеді. Мысалы, бұл антивирус немесе сақтық көшірме жүйесі үшін пайдалы, әсіресе macOS жүйесінде процесті «өлтірудің» бірнеше жолы бар. Бұл туралы және кесу астында қорғау әдістері туралы оқыңыз.

MacOS жүйесінде процестер мен ядро ​​кеңейтімдерін қалай қорғауға болады

Процесті «өлтірудің» классикалық тәсілі

Процесті «өлтірудің» белгілі тәсілі - процеске SIGKILL сигналын жіберу. Bash арқылы өлтіру үшін стандартты «kill -SIGKILL PID» немесе «pkill -9 NAME» деп атауға болады. «Kill» пәрмені UNIX күндерінен бері белгілі және тек macOS жүйесінде ғана емес, сонымен қатар UNIX-ке ұқсас басқа жүйелерде де қол жетімді.

UNIX-тәрізді жүйелердегі сияқты, macOS екеуінен басқа - SIGKILL және SIGSTOP - процеске кез келген сигналдарды ұстауға мүмкіндік береді. Бұл мақалада ең алдымен процестің жойылуына әкелетін сигнал ретінде SIGKILL сигналына назар аударылады.

macOS ерекшеліктері

MacOS жүйесінде XNU ядросындағы өлтіру жүйесінің шақыруы psignal(SIGKILL,...) функциясын шақырады. Пайдаланушы кеңістігіндегі басқа қандай пайдаланушы әрекеттерін psignal функциясы арқылы шақыруға болатынын көруге тырысайық. Ядроның ішкі механизмдеріндегі псигнал функциясына шақыруларды алып тастайық (олар тривиальды емес болуы мүмкін, біз оларды басқа мақалаға қалдырамыз 🙂 - қолтаңбаны тексеру, жад қателері, шығу/тоқтату өңдеуі, файлды қорғауды бұзу және т.б. .

Қарауды функциядан және сәйкес жүйелік шақырудан бастайық пайдалы жүктемемен_тоқтату. Классикалық өлтіру қоңырауынан басқа, macOS операциялық жүйесіне тән және BSD-де жоқ балама тәсіл бар екенін көруге болады. Екі жүйелік шақырудың да жұмыс принциптері ұқсас. Олар psignal ядро ​​функциясына тікелей шақырулар. Сондай-ақ, процесті өлтіру алдында «кансигнал» тексеру орындалатынын ескеріңіз - бұл процесс басқа процеске сигнал жібере алады ма; жүйе, мысалы, жүйе процестерін жоюға ешқандай қолданбаға рұқсат бермейді.

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

іске қосылды

Жүйені іске қосу кезінде демондарды жасаудың және олардың қызмет ету мерзімін басқарудың стандартты жолы іске қосылды. Дереккөздер launchctl қолданбасының macOS 10.10 нұсқасына дейінгі ескі нұсқасына арналғанын, код мысалдары иллюстрациялық мақсаттар үшін берілгенін ескеріңіз. Қазіргі launchctl іске қосылған сигналдарды XPC арқылы жібереді, оған launchctl логикасы ауыстырылды.

Қолданбалар қалай тоқтатылатынын қарастырайық. SIGTERM сигналын жібермес бұрын қолданбаны «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));
		} 
...
<>

Сорғыштың астында proc_terminate, оның атына қарамастан, SIGTERM көмегімен тек псигналды ғана емес, сонымен қатар SIGKILL де жібере алады.

Жанама өлтіру - Ресурс шегі

Неғұрлым қызықты жағдайды басқа жүйелік қоңыраудан көруге болады процесс_саясаты. Бұл жүйелік шақырудың жалпы қолданылуы файлды кэштеу әрекеттері арқылы жүйе айтарлықтай баяулатпауы үшін CPU уақытын және жад квотасын шектеу үшін индексатор сияқты қолданба ресурстарын шектеу болып табылады. Егер бағдарлама өзінің ресурс шегіне жеткен болса, proc_apply_resource_actions функциясынан көрініп тұрғандай, SIGKILL сигналы процеске жіберіледі.

Бұл жүйелік шақыру процесті өлтіруі мүмкін болса да, жүйе жүйелік шақыруды шақыратын процестің құқықтарын тиісті түрде тексермеді. Іс жүзінде тексеріп жатыр болды, бірақ бұл шартты айналып өту үшін PROC_POLICY_ACTION_SET балама жалауын пайдалану жеткілікті.

Демек, егер сіз қолданбаның CPU пайдалану квотасын «шектесеңіз» (мысалы, тек 1 нс жұмыс істеуге рұқсат етсеңіз), жүйедегі кез келген процесті жоюға болады. Осылайша, зиянды бағдарлама жүйедегі кез келген процесті, соның ішінде антивирустық процесті өлтіруі мүмкін. Сондай-ақ қызықты нәрсе, процесті pid 1 (launchctl) арқылы өлтіру кезінде пайда болатын әсер - SIGKILL сигналын өңдеуге тырысқан кезде ядролық дүрбелең :)

MacOS жүйесінде процестер мен ядро ​​кеңейтімдерін қалай қорғауға болады

Мәселені қалай шешуге болады?

Процестің жойылуын болдырмаудың ең қарапайым жолы жүйелік шақырулар кестесіндегі функция көрсеткішін ауыстыру болып табылады. Өкінішке орай, бұл әдіс көптеген себептерге байланысты тривиальды емес.

Біріншіден, sysent жад орнын басқаратын таңба тек XNU ядросының таңбасына ғана емес, ядро ​​таңбаларында табылмайды. Функцияны динамикалық түрде бөлшектеу және ондағы көрсеткішті іздеу сияқты эвристикалық іздеу әдістерін қолдануға тура келеді.

Екіншіден, кестедегі жазбалардың құрылымы ядро ​​құрастырылған жалаушаларға байланысты. Егер CONFIG_REQUIRES_U32_MUNGING жалаушасы жарияланса, құрылымның өлшемі өзгереді - қосымша өріс қосылады sy_arg_munge32. Ядроның қандай жалаушамен құрастырылғанын анықтау үшін қосымша тексеруді жүргізу қажет немесе баламалы түрде функция көрсеткіштерін белгіліге қарсы тексеру қажет.

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

Бақытымызға орай, macOS-тың заманауи нұсқаларында Apple процестермен жұмыс істеу үшін жаңа API ұсынады. Endpoint Security API клиенттерге басқа процестерге көптеген сұрауларды авторизациялауға мүмкіндік береді. Осылайша, жоғарыда аталған API арқылы SIGKILL сигналын қоса алғанда, процестерге кез келген сигналдарды блоктауға болады.

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

Сол сияқты, сигналды қорғау әдісін (policy proc_check_signal) қамтамасыз ететін ядрода MAC саясатын тіркеуге болады, бірақ API ресми түрде қолдау көрсетпейді.

Ядро кеңейтімін қорғау

Жүйедегі процестерді қорғаудан басқа, ядро ​​кеңейтімін (kext) қорғау да қажет. macOS әзірлеушілерге IOKit құрылғы драйверлерін оңай әзірлеу үшін негізді ұсынады. Құрылғылармен жұмыс істеуге арналған құралдарды қамтамасыз етумен қатар, IOKit C++ сыныптарының даналарын пайдаланып драйверлерді жинақтау әдістерін ұсынады. Пайдаланушы кеңістігіндегі қолданба ядро-пайдаланушы кеңістігі қатынасын орнату үшін сыныптың тіркелген данасын «табу» мүмкіндігіне ие болады.

Жүйедегі сынып даналарының санын анықтау үшін ioclasscount утилитасы бар.

my_kext_ioservice = 1
my_kext_iouserclient = 1

Драйвер стекімен тіркелгісі келетін кез келген ядро ​​​​кеңейтімі IOService жүйесінен мұраға алынатын сыныпты жариялауы керек, мысалы, бұл жағдайда my_kext_ioservice Пайдаланушы қолданбаларын қосу my_kext_iouserclient мысалында IOUserClient ішінен мұраға алатын сыныптың жаңа данасын жасауға себеп болады.

Жүйеден драйверді (kextunload пәрмені) босату әрекеті кезінде «bool terminate(IOOptionBits опциялары)» виртуалды функциясы шақырылады. Kextunload функциясын өшіру үшін жүктеуді түсіру әрекетін тоқтату үшін шақыруда false мәнін қайтару жеткілікті.

bool Kext::terminate(IOOptionBits options)
{

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

  return super::terminate(options);
}

IsUnloadAllowed жалаушасын жүктеу кезінде IOUserClient орнатуға болады. Жүктеп алу шегі болған кезде, kextunload пәрмені келесі нәтижені қайтарады:

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.

Ұқсас қорғау IOUserClient үшін жасалуы керек. Сыныптардың даналарын «IOCatalogueTerminate(mach_port_t, uint32_t жалауы, io_name_t сипаттамасы);» IOKitLib пайдаланушы кеңістігі функциясы арқылы жүктеуге болады. Пайдаланушылар кеңістігі қолданбасы «өлгенше», яғни «clientDied» функциясы шақырылмайынша, «тоқтату» пәрменін шақырған кезде false мәнін қайтара аласыз.

Файлды қорғау

Файлдарды қорғау үшін файлдарға қол жеткізуді шектеуге мүмкіндік беретін Kauth API пайдалану жеткілікті. Apple әзірлеушілерге ауқымдағы әртүрлі оқиғалар туралы хабарландырулар береді; біз үшін KAUTH_VNODE_DELETE, KAUTH_VNODE_WRITE_DATA және KAUTH_VNODE_DELETE_CHILD операциялары маңызды. Файлдарға кіруді шектеудің ең оңай жолы - жол - біз файлға жолды алу және жол префиксін салыстыру үшін «vn_getpath» API пайдаланамыз. Файл қалталарының атын өзгертуді оңтайландыру үшін жүйе әрбір файлға емес, тек атауы өзгертілген қалтаның өзіне кіруге рұқсат беретінін ескеріңіз. Ата-аналық жолды салыстыру және ол үшін KAUTH_VNODE_DELETE шектеу қажет.

MacOS жүйесінде процестер мен ядро ​​кеңейтімдерін қалай қорғауға болады

Бұл тәсілдің кемшілігі төмен өнімділік болуы мүмкін, өйткені префикстер саны артады. Салыстырудың O(префикс*ұзындық) мәніне тең еместігіне көз жеткізу үшін префикс префикстер саны, ұзындық жолдың ұзындығы болып табылады, префикстер арқылы құрастырылған детерминирленген соңғы автоматты (DFA) пайдалануға болады.

Берілген префикстер жиыны үшін DFA құру әдісін қарастырайық. Әрбір префикстің басында курсорларды инициализациялаймыз. Егер барлық курсорлар бір таңбаны көрсетсе, онда әрбір курсорды бір таңбаға көбейтіп, сол жолдың ұзындығы бір таңбаға үлкен екенін есте сақтаңыз. Таңбалары әртүрлі екі курсор болса, меңзерлерді көрсеткен таңбаға сәйкес топтарға бөліп, әр топ үшін алгоритмді қайталаңыз.

Бірінші жағдайда (курсорлардың астындағы барлық таңбалар бірдей), біз бір жол бойында бір ғана өтуі бар DFA күйін аламыз. Екінші жағдайда функцияны рекурсивті шақыру арқылы алынған кейінгі күйлерге 256 өлшемді (таңбалар саны және топтардың максималды саны) өтулер кестесін аламыз.

Мысал қарастырайық. Префикстер жинағы («/foo/bar/tmp/», «/var/db/foo/», «/foo/bar/aba/», «foo/bar/aac/») үшін келесіні алуға болады DFA. Суретте тек басқа күйлерге апаратын ауысулар көрсетілген, басқа ауысулар түпкілікті болмайды.

MacOS жүйесінде процестер мен ядро ​​кеңейтімдерін қалай қорғауға болады

DKA мемлекеттерінен өткенде 3 жағдай болуы мүмкін.

  1. Соңғы күйге жетті - жол қорғалған, біз KAUTH_VNODE_DELETE, KAUTH_VNODE_WRITE_DATA және KAUTH_VNODE_DELETE_CHILD операцияларын шектейміз.
  2. Соңғы күйге қол жеткізілмеді, бірақ жол «аяқталды» (нөлдік терминаторға жетті) - жол ата-ана, KAUTH_VNODE_DELETE шектеуі қажет. Егер vnode қалта болса, соңына '/' қосу керек екенін ескеріңіз, әйтпесе ол оны «/foor/bar/t» файлымен шектей алады, бұл дұрыс емес.
  3. Ақырғы күйге жеткен жоқ, жол біткен жоқ. Префикстердің ешқайсысы бұған сәйкес келмейді, біз шектеулер енгізбейміз.

қорытынды

Әзірленетін қауіпсіздік шешімдерінің мақсаты – пайдаланушының және оның деректерінің қауіпсіздік деңгейін арттыру. Бір жағынан, бұл мақсатқа операциялық жүйенің өзі «әлсіз» болған осалдықтарды жабатын Acronis бағдарламалық өнімін әзірлеу арқылы қол жеткізіледі. Екінші жағынан, біз ОЖ жағында жақсартуға болатын қауіпсіздік аспектілерін нығайтуды елемеуге болмайды, әсіресе мұндай осалдықтарды жабу өнім ретінде біздің тұрақтылықты арттырады. Бұл осалдық туралы Apple өнімінің қауіпсіздік тобына хабарланды және ол macOS 10.14.5 жүйесінде түзетілді (https://support.apple.com/en-gb/HT210119).

MacOS жүйесінде процестер мен ядро ​​кеңейтімдерін қалай қорғауға болады

Мұның барлығы утилитаңыз ресми түрде ядроға орнатылған жағдайда ғана жасалуы мүмкін. Яғни, сыртқы және қажетсіз бағдарламалық қамтамасыз ету үшін мұндай бос орындар жоқ. Дегенмен, көріп отырғаныңыздай, антивирус және сақтық көшірме жүйелері сияқты заңды бағдарламаларды қорғау да жұмысты қажет етеді. Бірақ енді MacOS жүйесіне арналған жаңа Acronis өнімдерінде жүйеден жүктеуден қосымша қорғаныс болады.

Ақпарат көзі: www.habr.com

пікір қалдыру