Како да ги заштитите процесите и екстензии на јадрото на macOS

Здраво, Хабр! Денес би сакал да зборувам за тоа како можете да ги заштитите процесите од напади од напаѓачи во macOS. На пример, ова е корисно за антивирус или резервен систем, особено затоа што под macOS постојат неколку начини да се „убие“ процес. Прочитајте за ова и за заштитните методи под сечењето.

Како да ги заштитите процесите и екстензии на јадрото на macOS

Класичен начин да се „убие“ процес

Добро познат начин да се „убие“ процес е да се испрати сигнал SIGKILL до процесот. Преку баш, можете да ги повикате стандардните „kill -SIGKILL PID“ или „pkill -9 NAME“ за убиство. Командата „убиј“ е позната уште од времето на UNIX и е достапна не само на macOS, туку и на други системи слични на UNIX.

Исто како и во системите слични на UNIX, macOS ви овозможува да пресретнете какви било сигнали до процес освен два - SIGKILL и SIGSTOP. Оваа статија првенствено ќе се фокусира на сигналот SIGKILL како сигнал што предизвикува уништување на процес.

специфики на macOS

На macOS, повикот на системот за убивање во кернелот XNU ја повикува функцијата psignal(SIGKILL,...). Ајде да се обидеме да видиме кои други кориснички дејства во корисничкиот простор можат да се наречат со функцијата psignal. Ајде да ги отстраниме повиците до функцијата psignal во внатрешните механизми на кернелот (иако можеби се нетривијални, ќе ги оставиме за друга статија 🙂 - проверка на потпис, грешки во меморијата, ракување со излез/крај, прекршување на заштитата на датотеки итн. .

Да го започнеме прегледот со функцијата и соодветниот системски повик прекини_со_плаќање. Се гледа дека покрај класичниот повик за убивање, постои и алтернативен пристап кој е специфичен за оперативниот систем macOS и го нема во BSD. Принципите на работа на двата системски повици се исто така слични. Тие се директни повици до псигналот на функцијата на јадрото. Исто така, забележете дека пред да се убие процес, се врши проверка на „кансигнал“ - дали процесот може да испрати сигнал до друг процес; системот не дозволува ниту една апликација да ги убие системските процеси, на пример.

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, и покрај неговото име, може да испрати не само psignal со SIGTERM, туку и SIGKILL.

Индиректно убиство - ограничување на ресурсите

Поинтересен случај може да се види во друг системски повик процес_политика. Вообичаена употреба на овој системски повик е да се ограничат ресурсите на апликацијата, како што е индексаторот да го ограничи времето на процесорот и квотите на меморијата, така што системот не е значително забавен од активностите за кеширање на датотеки. Ако апликацијата го достигнала својот лимит на ресурси, како што може да се види од функцијата proc_apply_resource_actions, сигналот SIGKILL се испраќа до процесот.

Иако овој системски повик потенцијално може да убие процес, системот не ги провери соодветно правата на процесот што го повикува системскиот повик. Всушност се проверува постоеле, но доволно е да се користи алтернативното знаменце PROC_POLICY_ACTION_SET за да се заобиколи оваа состојба.

Оттука, ако ја „ограничите“ квотата за користење на процесорот на апликацијата (на пример, дозволувајќи само 1 ns да работи), тогаш можете да го убиете секој процес во системот. Така, малициозниот софтвер може да убие кој било процес на системот, вклучувајќи го и антивирусниот процес. Интересен е и ефектот што се јавува при убивање на процес со 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 им овозможува на клиентите да овластат многу барања за други процеси. Така, можете да блокирате какви било сигнали до процесите, вклучувајќи го и сигналот SIGKILL, користејќи го гореспоменатиот 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;
}

Слично, во кернелот може да се регистрира политика на MAC, која обезбедува метод за заштита на сигналот (политика proc_check_signal), но API-то не е официјално поддржано.

Заштита за проширување на јадрото

Покрај заштитата на процесите во системот, неопходна е и заштита на самата екстензија на јадрото (kext). macOS обезбедува рамка за програмерите лесно да ги развиваат драјверите за уредите IOKit. Покрај обезбедувањето алатки за работа со уреди, IOKit обезбедува методи за натрупување на драјвери користејќи примероци од класите C++. Апликацијата во корисничкиот простор ќе може да „пронајде“ регистриран примерок од класата за да воспостави врска кернел-кориснички простор.

За да се открие бројот на примероци на класи во системот, постои алатката ioclasscount.

my_kext_ioservice = 1
my_kext_iouserclient = 1

Секоја екстензија на кернелот што сака да се регистрира со стекот на драјвери мора да декларира класа што наследува од IOService, на пример my_kext_ioservice во овој случај. Поврзувањето на корисничките апликации предизвикува создавање на нов примерок од класата што наследува од IOUserClient, во примерот my_kext_iouserclient.

Кога се обидувате да растоварате драјвер од системот (команда kextunload), се повикува виртуелната функција „bool terminate(IOOptionBits options)“. Доволно е да се врати false на повикот за да се прекине кога се обидувате да се истоварите за да се оневозможи kextunload.

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. Примероците на класите може да се растоварат со користење на функцијата кориснички простор на IOKitLib „IOCatalogueTerminate(mach_port_t, uint32_t знаменце, io_name_t опис); Можете да вратите неточно кога ја повикувате командата „terminate“ додека апликацијата за кориснички простор не „умре“, односно не се повика функцијата „clientDied“.

Заштита на датотеки

За да ги заштитите датотеките, доволно е да користите Kauth API, кој ви овозможува да го ограничите пристапот до датотеките. Apple им дава на програмерите известувања за различни настани во опсегот; за нас, операциите KAUTH_VNODE_DELETE, KAUTH_VNODE_WRITE_DATA и KAUTH_VNODE_DELETE_CHILD се важни. Најлесен начин да се ограничи пристапот до датотеките е по патека - го користиме API „vn_getpath“ за да ја добиеме патеката до датотеката и да го споредиме префиксот на патеката. Забележете дека за да се оптимизира преименувањето на патеките на папката со датотеки, системот не дозволува пристап до секоја датотека, туку само до самата папка што е преименувана. Неопходно е да се спореди матичната патека и да се ограничи 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

Сето ова може да се направи само ако вашата алатка е официјално инсталирана во кернелот. Односно, нема такви дупки за надворешен и несакан софтвер. Сепак, како што можете да видите, дури и заштитата на легитимните програми како што се антивирусни и резервни системи бара работа. Но, сега новите производи на Acronis за macOS ќе имаат дополнителна заштита од истовар од системот.

Извор: www.habr.com

Додадете коментар