Како заштитити процесе и екстензије кернела на мацОС-у

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

Како заштитити процесе и екстензије кернела на мацОС-у

Класичан начин да се "убије" процес

Добро познати начин да се „убије“ процес је слање СИГКИЛЛ сигнала процесу. Преко басх-а можете позвати стандардни "килл -СИГКИЛЛ ПИД" или "пкилл -9 НАМЕ" да бисте убили. Команда „килл“ позната је још од времена УНИКС-а и доступна је не само на мацОС-у, већ и на другим системима сличним УНИКС-у.

Баш као у системима сличним УНИКС-у, мацОС вам омогућава да пресретнете све сигнале процесу осим два - СИГКИЛЛ и СИГСТОП. Овај чланак ће се првенствено фокусирати на СИГКИЛЛ сигнал као сигнал који узрокује да процес буде уништен.

мацОС специфичности

На мацОС-у, системски позив килл у КСНУ кернелу позива функцију псигнал(СИГКИЛЛ,...). Хајде да покушамо да видимо које друге радње корисника у корисничком простору може да позове функција псигнал. Хајде да избацимо позиве на функцију псигнал у унутрашњим механизмима кернела (иако можда нису тривијални, оставићемо их за други чланак 🙂 - верификација потписа, грешке у меморији, руковање излазом/укидањем, кршење заштите датотека итд. .

Почнимо преглед са функцијом и одговарајућим системским позивом терминате_витх_паилоад. Види се да поред класичног килл позива постоји алтернативни приступ који је специфичан за мацОС оперативни систем и који се не налази у БСД-у. Принципи рада оба системска позива су такође слични. Они су директни позиви функцији језгра псигнал. Такође имајте на уму да се пре укидања процеса врши провера „кансигнала“ – да ли процес може да пошаље сигнал другом процесу; систем не дозвољава ниједној апликацији да убије системске процесе, на пример.

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

лаунцхд

Покреће се стандардни начин за креирање демона при покретању система и контролу њиховог животног века. Имајте на уму да су извори за стару верзију лаунцхцтл до мацОС 10.10, примери кода су дати у илустративне сврхе. Модерни лаунцхцтл шаље лаунцхд сигнале преко КСПЦ-а, логика лаунцхцтл је премештена на њега.

Погледајмо како се тачно апликације заустављају. Пре слања СИГТЕРМ сигнала, апликација се покушава зауставити коришћењем системског позива „проц_терминате“.

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

Испод хаубе, проц_терминате, упркос свом имену, може послати не само псигнал са СИГТЕРМ-ом, већ и СИГКИЛЛ.

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

Занимљивији случај се може видети у другом системском позиву процесс_полици. Уобичајена употреба овог системског позива је ограничавање ресурса апликације, као што је индексер да ограничи ЦПУ време и меморијске квоте тако да систем не буде значајно успорен активностима кеширања датотека. Ако је апликација достигла ограничење ресурса, као што се може видети из функције проц_аппли_ресоурце_ацтионс, СИГКИЛЛ сигнал се шаље процесу.

Иако би овај системски позив могао потенцијално да убије процес, систем није адекватно проверио права процеса који позива системски позив. Заправо проверавам постојао, али је довољно користити алтернативну заставицу ПРОЦ_ПОЛИЦИ_АЦТИОН_СЕТ да бисте заобишли овај услов.

Дакле, ако „ограничите“ квоту коришћења ЦПУ-а апликације (на пример, дозволите да се покрене само 1 нс), онда можете убити било који процес у систему. Дакле, злонамерни софтвер може да убије било који процес у систему, укључујући и антивирусни процес. Занимљив је и ефекат који се јавља када се процес убија са пид 1 (лаунцхцтл) - паника кернела при покушају обраде сигнала СИГКИЛЛ :)

Како заштитити процесе и екстензије кернела на мацОС-у

Како решити проблем?

Најједноставнији начин да спречите да процес буде уништен је да замените показивач функције у табели системских позива. Нажалост, ова метода није тривијална из много разлога.

Прво, симбол који контролише меморијску локацију сисент-а није само приватан за КСНУ симбол кернела, већ се не може наћи у симболима кернела. Мораћете да користите методе хеуристичке претраге, као што је динамичко растављање функције и тражење показивача у њој.

Друго, структура уноса у табели зависи од заставица са којима је језгро састављено. Ако је декларисана заставица ЦОНФИГ_РЕКУИРЕС_У32_МУНГИНГ, величина структуре ће бити промењена - биће додато додатно поље си_арг_мунге32. Неопходно је извршити додатну проверу да би се утврдило са којом заставицом је језгро компајлирано, или алтернативно, проверити показиваче функција у односу на познате.

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

На срећу, у модерним верзијама мацОС-а, Аппле обезбеђује нови АПИ за рад са процесима. Ендпоинт Сецурити АПИ омогућава клијентима да овласте многе захтеве другим процесима. Дакле, можете блокирати све сигнале процесима, укључујући сигнал СИГКИЛЛ, користећи горе поменути АПИ.

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

Слично, МАЦ политика се може регистровати у кернелу, која обезбеђује метод заштите сигнала (полици проц_цхецк_сигнал), али АПИ није званично подржан.

Заштита проширења кернела

Поред заштите процеса у систему, неопходна је и заштита самог проширења кернела (кект). мацОС пружа оквир за програмере да лако развију ИОКит драјвере уређаја. Поред обезбеђивања алата за рад са уређајима, ИОКит обезбеђује методе за слагање драјвера користећи инстанце Ц++ класа. Апликација у корисничком простору ће моћи да „пронађе“ регистровану инстанцу класе да успостави однос језгро-кориснички простор.

Да бисте открили број инстанци класе у систему, постоји услужни програм иоцлассцоунт.

my_kext_ioservice = 1
my_kext_iouserclient = 1

Свака екстензија кернела која жели да се региструје са стеком драјвера мора да декларише класу која наслеђује ИОСервице, на пример ми_кект_иосервице у овом случају Повезивање корисничких апликација изазива креирање нове инстанце класе која наслеђује ИОУсерЦлиент, у примеру ми_кект_иоусерцлиент.

Када покушавате да избаците драјвер из система (команда кектунлоад), позива се виртуелна функција „боол терминате(ИООптионБитс оптионс)“. Довољно је вратити фалсе на позив да бисте прекинули када покушавате да испразните да бисте онемогућили кектунлоад.

bool Kext::terminate(IOOptionBits options)
{

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

  return super::terminate(options);
}

ИОУсерЦлиент може поставити ознаку ИсУнлоадАлловед приликом учитавања. Када постоји ограничење преузимања, команда кектунлоад ће вратити следећи излаз:

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.

Слична заштита мора да се уради за ИОУсерЦлиент. Инстанце класа се могу учитати помоћу функције корисничког простора ИОКитЛиб „ИОЦаталогуеТерминате(мацх_порт_т, уинт32_т флаг, ио_наме_т десцриптион);“. Можете вратити фалсе када позивате команду „терминате“ све док апликација корисничког простора „умре“, то јест, функција „цлиентДиед“ није позвана.

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

Да бисте заштитили датотеке, довољно је користити Каутх АПИ, који вам омогућава да ограничите приступ датотекама. Аппле обезбеђује програмерима обавештења о различитим догађајима у опсегу; за нас су важне операције КАУТХ_ВНОДЕ_ДЕЛЕТЕ, КАУТХ_ВНОДЕ_ВРИТЕ_ДАТА и КАУТХ_ВНОДЕ_ДЕЛЕТЕ_ЦХИЛД. Најлакши начин да ограничите приступ датотекама је путем путање – користимо АПИ „вн_гетпатх“ да бисмо добили путању до датотеке и упоредили префикс путање. Имајте на уму да да би оптимизовао преименовање путања фасцикли датотека, систем не овлашћује приступ свакој датотеци, већ само фасцикли која је преименована. Неопходно је упоредити родитељску путању и ограничити КАУТХ_ВНОДЕ_ДЕЛЕТЕ за њу.

Како заштитити процесе и екстензије кернела на мацОС-у

Недостатак овог приступа може бити низак учинак како се број префикса повећава. Да бисте били сигурни да поређење није једнако О(префикс*дужина), где је префикс број префикса, а дужина је дужина низа, можете користити детерминистички коначни аутомат (ДФА) изграђен од префикса.

Хајде да размотримо метод за конструисање ДФА за дати скуп префикса. Иницијализујемо курсоре на почетку сваког префикса. Ако сви курсори показују на исти знак, повећајте сваки курсор за један знак и запамтите да је дужина исте линије већа за један. Ако постоје два курсора са различитим симболима, поделите курсоре у групе према симболу на који показују и поновите алгоритам за сваку групу.

У првом случају (сви знакови испод курсора су исти), добијамо ДФА стање које има само један прелаз дуж исте линије. У другом случају добијамо табелу прелаза величине 256 (број карактера и максималан број група) у наредна стања добијена рекурзивним позивањем функције.

Погледајмо пример. За скуп префикса (“/фоо/бар/тмп/”, “/вар/дб/фоо/”, “/фоо/бар/аба/”, “фоо/бар/аац/”) можете добити следеће ДФА. На слици су приказани само прелази који воде до других стања; остали прелази неће бити коначни.

Како заштитити процесе и екстензије кернела на мацОС-у

Када пролазите кроз ДКА стања, могу бити 3 случаја.

  1. Коначно стање је достигнуто - путања је заштићена, ограничавамо операције КАУТХ_ВНОДЕ_ДЕЛЕТЕ, КАУТХ_ВНОДЕ_ВРИТЕ_ДАТА и КАУТХ_ВНОДЕ_ДЕЛЕТЕ_ЦХИЛД
  2. Завршно стање није достигнуто, али је путања „завршила“ (достигнут је нулти терминатор) - путања је родитељ, потребно је ограничити КАУТХ_ВНОДЕ_ДЕЛЕТЕ. Имајте на уму да ако је вноде фасцикла, морате да додате '/' на крају, у супротном може да га ограничи на датотеку "/фоор/бар/т", што је нетачно.
  3. Није дошло до коначног стања, пут се није завршио. Ниједан од префикса не одговара овом, не уводимо ограничења.

Закључак

Циљ безбедносних решења која се развијају је повећање нивоа безбедности корисника и његових података. С једне стране, овај циљ се постиже развојем софтверског производа Ацронис, који затвара оне рањивости у којима је сам оперативни систем „слаб“. С друге стране, не треба занемарити јачање оних безбедносних аспеката који се могу побољшати на страни ОС-а, поготово зато што затварање таквих рањивости повећава нашу сопствену стабилност као производа. Рањивост је пријављена Аппле-овом тиму за безбедност производа и исправљена је у мацОС-у 10.14.5 (хттпс://суппорт.аппле.цом/ен-гб/ХТ210119).

Како заштитити процесе и екстензије кернела на мацОС-у

Све ово се може урадити само ако је ваш услужни програм званично инсталиран у кернел. То јест, не постоје такве рупе за спољни и нежељени софтвер. Међутим, као што видите, чак и заштита легитимних програма као што су антивирусни и системи резервних копија захтева рад. Али сада ће нови Ацронис производи за мацОС имати додатну заштиту од истовара из система.

Извор: ввв.хабр.цом

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