MacOS дээрх процессууд болон цөмийн өргөтгөлүүдийг хэрхэн хамгаалах вэ

Сайн уу, Хабр! Өнөөдөр би macOS дээрх халдагчдын халдлагаас процессыг хэрхэн хамгаалах талаар ярихыг хүсч байна. Жишээлбэл, энэ нь вирусны эсрэг эсвэл нөөцлөх системд хэрэгтэй, ялангуяа macOS үйлдлийн систем дээр процессыг "алах" хэд хэдэн арга байдаг. Энэ болон хамгаалалтын аргуудын талаар уншина уу.

MacOS дээрх процессууд болон цөмийн өргөтгөлүүдийг хэрхэн хамгаалах вэ

Үйл явцыг "алах" сонгодог арга

Процессыг "алах" сайн мэддэг арга бол процесс руу SIGKILL дохио илгээх явдал юм. Bash-ээр дамжуулан "kill -SIGKILL PID" эсвэл "pkill -9 NAME" гэсэн стандартыг дуудаж алах боломжтой. “kill” команд нь UNIX-ийн үеэс мэдэгдэж байсан бөгөөд зөвхөн macOS дээр төдийгүй UNIX-тэй төстэй бусад системүүдэд байдаг.

UNIX-тэй төстэй системүүдийн нэгэн адил macOS нь SIGKILL болон SIGSTOP хоёроос бусад процесст ямар ч дохиог таслах боломжийг олгодог. Энэ нийтлэл нь үйл явцыг устгахад хүргэдэг дохио болох SIGKILL дохиог голчлон анхаарах болно.

macOS-ийн онцлог

MacOS дээр XNU цөм дэх kill системийн дуудлага нь psignal(SIGKILL,...) функцийг дууддаг. Хэрэглэгчийн талбар дахь өөр ямар хэрэглэгчийн үйлдлийг psignal функцээр дуудаж болохыг олж мэдье. Цөмийн дотоод механизм дахь psignal функцийн дуудлагыг устгацгаая (хэдийгээр тэдгээр нь өчүүхэн биш байж болох ч бид тэдгээрийг өөр нийтлэлд үлдээх болно 🙂 - гарын үсгийн баталгаажуулалт, санах ойн алдаа, гаралт / дуусгавар ажиллах, файлын хамгаалалтын зөрчил гэх мэт). .

Функц болон холбогдох системийн дуудлагаар тоймыг эхлүүлье Ачаалалтай_тасах. Сонгодог kill дуудлагаас гадна 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);
	}
...
}

эхлүүлсэн

Системийг эхлүүлэх үед демонуудыг бий болгож, тэдгээрийн ашиглалтын хугацааг хянах стандарт аргыг эхлүүлсэн. Эх сурвалжууд нь 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 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 нь үйлчлүүлэгчдэд бусад процессуудад олон хүсэлтийг зөвшөөрөх боломжийг олгодог. Тиймээс та дээр дурдсан 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;
}

Үүний нэгэн адил MAC бодлогыг цөмд бүртгэж болох бөгөөд энэ нь дохионы хамгаалалтын аргыг (proc_check_signal бодлого) хангадаг боловч 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 options)" виртуал функц дуудагдана. 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 flag, io_name_t description);" IOKitLib хэрэглэгчийн орон зай функцийг ашиглан ангиудын тохиолдлуудыг буулгаж болно. Хэрэглэгчийн орон зайн программыг "үхэх" хүртэл, өөрөөр хэлбэл "clientDied" функц дуудагдахгүй болтол "kesmanate" командыг дуудахдаа 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

сэтгэгдэл нэмэх