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

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

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

Класическият начин за „убиване“ на процес

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

Точно както в UNIX-подобни системи, macOS ви позволява да прихващате всякакви сигнали към процес с изключение на два - SIGKILL и SIGSTOP. Тази статия ще се съсредоточи основно върху сигнала SIGKILL като сигнал, който причинява спиране на процес.

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

В macOS системното извикване на kill в ядрото на XNU извиква функцията psignal(SIGKILL,...). Нека се опитаме да видим какви други потребителски действия в потребителското пространство могат да бъдат извикани от функцията psignal. Нека премахнем извикванията към функцията psignal във вътрешните механизми на ядрото (въпреки че може да са нетривиални, ще ги оставим за друга статия 🙂 - проверка на подпис, грешки в паметта, обработка на изход/прекратяване, нарушения на файловата защита и т.н. .

Нека започнем прегледа с функцията и съответното системно извикване прекрати_с_полезен товар. Вижда се, че в допълнение към класическото повикване за убийство има алтернативен подход, който е специфичен за операционната система macOS и не се среща в BSD. Принципите на работа на двете системни повиквания също са сходни. Те са директни извиквания към функцията на ядрото psignal. Също така имайте предвид, че преди да убиете процес, се извършва проверка "cansignal" - дали процесът може да изпрати сигнал към друг процес; системата не позволява на нито едно приложение да убива системни процеси, например.

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

launchd

Стандартният начин за създаване на демони при стартиране на системата и контрол на живота им е launchd. Моля, обърнете внимание, че източниците са за старата версия на launchctl до macOS 10.10, примерите за кодове са предоставени с илюстративна цел. Съвременният launchctl изпраща сигнали launchd чрез 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 description);“. Можете да върнете false, когато извиквате командата „terminate“, докато приложението за потребителско пространство „умре“, тоест функцията „clientDied“ не бъде извикана.

Защита на файла

За да защитите файловете, достатъчно е да използвате Kauth API, който ви позволява да ограничите достъпа до файлове. Apple предоставя на разработчиците известия за различни събития в обхвата; за нас операциите KAUTH_VNODE_DELETE, KAUTH_VNODE_WRITE_DATA и KAUTH_VNODE_DELETE_CHILD са важни. Най-лесният начин за ограничаване на достъпа до файлове е по пътя - използваме API „vn_getpath“, за да получим пътя до файла и да сравним префикса на пътя. Обърнете внимание, че за оптимизиране на преименуването на пътищата на файловите папки, системата не разрешава достъп до всеки файл, а само до самата папка, която е преименувана. Необходимо е да се сравни родителският път и да се ограничи KAUTH_VNODE_DELETE за него.

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

Недостатъкът на този подход може да бъде ниската производителност, тъй като броят на префиксите се увеличава. За да сте сигурни, че сравнението не е равно на O(prefix*length), където prefix е броят на префиксите, length е дължината на низа, можете да използвате детерминистичен краен автомат (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

Добавяне на нов коментар