كيفية حماية العمليات وامتدادات kernel على نظام التشغيل macOS

مرحبًا حبر! أود اليوم أن أتحدث عن كيفية حماية العمليات من هجمات المهاجمين في نظام التشغيل macOS. على سبيل المثال، يعد هذا مفيدًا لنظام مكافحة الفيروسات أو نظام النسخ الاحتياطي، خاصة أنه يوجد في نظام التشغيل macOS عدة طرق "لقتل" العملية. اقرأ عن هذا وعن طرق الحماية تحت القطع.

كيفية حماية العمليات وامتدادات kernel على نظام التشغيل macOS

الطريقة الكلاسيكية "لقتل" العملية

إحدى الطرق المعروفة "لقتل" عملية ما هي إرسال إشارة SIGKILL إلى العملية. من خلال bash، يمكنك استدعاء المعيار "kill -SIGKILL PID" أو "pkill -9 NAME" للقتل. كان الأمر "kill" معروفًا منذ أيام UNIX وهو متاح ليس فقط على نظام التشغيل macOS، ولكن أيضًا على الأنظمة الأخرى المشابهة لـ UNIX.

تمامًا كما هو الحال في الأنظمة المشابهة لـ UNIX، يتيح لك نظام التشغيل macOS اعتراض أي إشارات لعملية ما باستثناء اثنتين - SIGKILL وSIGSTOP. ستركز هذه المقالة بشكل أساسي على إشارة SIGKILL كإشارة تؤدي إلى إنهاء العملية.

مواصفات نظام التشغيل MacOS

في نظام التشغيل macOS، يستدعي استدعاء نظام التوقف في XNU kernel الدالة psignal(SIGKILL,...). دعونا نحاول معرفة ما هي إجراءات المستخدم الأخرى في مساحة المستخدم التي يمكن استدعاؤها بواسطة وظيفة psignal. دعونا نتخلص من استدعاءات وظيفة psignal في الآليات الداخلية للنواة (على الرغم من أنها قد تكون غير تافهة، إلا أننا سنتركها لمقال آخر 🙂 - التحقق من التوقيع، وأخطاء الذاكرة، ومعالجة الخروج/الإنهاء، وانتهاكات حماية الملفات، وما إلى ذلك) .

لنبدأ المراجعة بالوظيفة واستدعاء النظام المقابل termin_with_payload. يمكن ملاحظة أنه بالإضافة إلى استدعاء الإيقاف الكلاسيكي، هناك نهج بديل خاص بنظام التشغيل macOS وغير موجود في BSD. مبادئ التشغيل لكلا مكالمات النظام متشابهة أيضًا. إنها مكالمات مباشرة إلى وظيفة kernel 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 الحديث إشارات 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.

القتل غير المباشر - الحد من الموارد

يمكن رؤية حالة أكثر إثارة للاهتمام في استدعاء نظام آخر Process_policy. الاستخدام الشائع لاستدعاء النظام هذا هو الحد من موارد التطبيق، مثل قيام المفهرس بتحديد وقت وحدة المعالجة المركزية وحصص الذاكرة بحيث لا يتباطأ النظام بشكل ملحوظ بسبب أنشطة التخزين المؤقت للملفات. إذا وصل أحد التطبيقات إلى حد الموارد الخاص به، كما يمكن رؤيته من الدالة proc_apply_resource_actions، فسيتم إرسال إشارة SIGKILL إلى العملية.

على الرغم من أن استدعاء النظام هذا قد يؤدي إلى إنهاء العملية، إلا أن النظام لم يتحقق بشكل كافٍ من حقوق العملية التي تستدعي استدعاء النظام. التحقق في الواقع موجودةولكن يكفي استخدام العلامة البديلة PROC_POLICY_ACTION_SET لتجاوز هذا الشرط.

ومن ثم، إذا قمت "بالحد" من حصة استخدام وحدة المعالجة المركزية للتطبيق (على سبيل المثال، السماح بتشغيل 1 نانوثانية فقط)، فيمكنك إنهاء أي عملية في النظام. وبالتالي، يمكن للبرامج الضارة قتل أي عملية على النظام، بما في ذلك عملية مكافحة الفيروسات. ومن المثير للاهتمام أيضًا التأثير الذي يحدث عند قتل عملية باستخدام pid 1 (launchctl) - ذعر النواة عند محاولة معالجة إشارة SIGKILL :)

كيفية حماية العمليات وامتدادات kernel على نظام التشغيل macOS

كيفية حل هذه المشكلة؟

الطريقة الأكثر مباشرة لمنع إيقاف العملية هي استبدال مؤشر الوظيفة في جدول استدعاء النظام. لسوء الحظ، هذه الطريقة ليست تافهة لأسباب عديدة.

أولاً، الرمز الذي يتحكم في موقع ذاكرة sysent ليس خاصًا برمز kernel XNU فقط، ولكن لا يمكن العثور عليه في رموز kernel. سيتعين عليك استخدام أساليب البحث الإرشادية، مثل تفكيك الوظيفة ديناميكيًا والبحث عن مؤشر فيها.

ثانيا، يعتمد هيكل الإدخالات في الجدول على العلامات التي تم تجميع النواة بها. إذا تم الإعلان عن علامة 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 واجهة برمجة تطبيقات جديدة للعمل مع العمليات. تسمح واجهة برمجة تطبيقات Endpoint Security للعملاء بتفويض العديد من الطلبات لعمليات أخرى. وبالتالي، يمكنك حظر أي إشارات للعمليات، بما في ذلك إشارة 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) غير مدعومة رسميًا.

حماية امتداد النواة

بالإضافة إلى حماية العمليات في النظام، من الضروري أيضًا حماية امتداد kernel نفسه (kext). يوفر macOS إطار عمل للمطورين لتطوير برامج تشغيل أجهزة IOKit بسهولة. بالإضافة إلى توفير أدوات للعمل مع الأجهزة، يوفر IOKit طرقًا لتكديس برامج التشغيل باستخدام مثيلات فئات C++. سيتمكن التطبيق الموجود في مساحة المستخدم من "العثور" على مثيل مسجل للفئة لإنشاء علاقة بين kernel ومساحة المستخدم.

لاكتشاف عدد مثيلات الفئة في النظام، توجد الأداة المساعدة ioclasscount.

my_kext_ioservice = 1
my_kext_iouserclient = 1

أي امتداد نواة يرغب في التسجيل في مكدس برنامج التشغيل يجب أن يعلن عن فئة ترث من IOService، على سبيل المثال my_kext_ioservice في هذه الحالة، يؤدي توصيل تطبيقات المستخدم إلى إنشاء مثيل جديد للفئة التي ترث من IOUserClient، في المثال my_kext_iouserclient.

عند محاولة إلغاء تحميل برنامج التشغيل من النظام (أمر kextunload)، يتم استدعاء الوظيفة الافتراضية "bool termin(IOOptionBits options)". يكفي إرجاع خطأ عند استدعاء الإنهاء عند محاولة التفريغ لتعطيل 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 flag, io_name_t description);". يمكنك إرجاع خطأ عند استدعاء أمر "إنهاء" حتى "يموت" تطبيق مساحة المستخدم، أي لا يتم استدعاء وظيفة "clientDied".

حماية الملفات

لحماية الملفات، يكفي استخدام Kauth API، الذي يسمح لك بتقييد الوصول إلى الملفات. توفر Apple للمطورين إشعارات حول الأحداث المختلفة في النطاق؛ بالنسبة لنا، تعد العمليات KAUTH_VNODE_DELETE وKAUTH_VNODE_WRITE_DATA وKAUTH_VNODE_DELETE_CHILD مهمة. أسهل طريقة لتقييد الوصول إلى الملفات هي عن طريق المسار - نستخدم واجهة برمجة التطبيقات "vn_getpath" للحصول على المسار إلى الملف ومقارنة بادئة المسار. لاحظ أنه لتحسين إعادة تسمية مسارات مجلدات الملفات، لا يسمح النظام بالوصول إلى كل ملف، ولكن فقط إلى المجلد نفسه الذي تمت إعادة تسميته. من الضروري مقارنة المسار الأصلي وتقييد KAUTH_VNODE_DELETE له.

كيفية حماية العمليات وامتدادات kernel على نظام التشغيل macOS

قد يكون عيوب هذا الأسلوب انخفاض الأداء مع زيادة عدد البادئات. للتأكد من أن المقارنة لا تساوي O(بادئة*طول)، حيث البادئة هي عدد البادئات، والطول هو طول السلسلة، يمكنك استخدام آلة حتمية محدودة (DFA) تم إنشاؤها بواسطة البادئات.

دعونا نفكر في طريقة لإنشاء DFA لمجموعة معينة من البادئات. نقوم بتهيئة المؤشرات في بداية كل بادئة. إذا كانت جميع المؤشرات تشير إلى نفس الحرف، فقم بزيادة كل مؤشر بحرف واحد وتذكر أن طول السطر نفسه أكبر بحرف واحد. إذا كان هناك مؤشران برموز مختلفة، قم بتقسيم المؤشرات إلى مجموعات وفقًا للرمز الذي يشيرون إليه وكرر الخوارزمية لكل مجموعة.

في الحالة الأولى (جميع الأحرف الموجودة تحت المؤشرات هي نفسها)، نحصل على حالة DFA لها انتقال واحد فقط على نفس السطر. في الحالة الثانية، نحصل على جدول التحولات بالحجم 256 (عدد الأحرف والحد الأقصى لعدد المجموعات) إلى الحالات اللاحقة التي تم الحصول عليها عن طريق استدعاء الدالة بشكل متكرر.

لنلقي نظرة على مثال. بالنسبة لمجموعة البادئات ("/foo/bar/tmp/"، "/var/db/foo/"، "/foo/bar/aba/"، "foo/bar/aac/") يمكنك الحصول على ما يلي DFA. يوضح الشكل التحولات المؤدية إلى دول أخرى فقط، ولن تكون التحولات الأخرى نهائية.

كيفية حماية العمليات وامتدادات kernel على نظام التشغيل 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).

كيفية حماية العمليات وامتدادات kernel على نظام التشغيل macOS

كل هذا لا يمكن القيام به إلا إذا تم تثبيت الأداة المساعدة الخاصة بك رسميًا في النواة. أي أنه لا توجد مثل هذه الثغرات للبرامج الخارجية وغير المرغوب فيها. ومع ذلك، كما ترون، حتى حماية البرامج الشرعية مثل أنظمة مكافحة الفيروسات والنسخ الاحتياطي تتطلب العمل. ولكن الآن ستتمتع منتجات Acronis الجديدة لنظام التشغيل macOS بحماية إضافية ضد التفريغ من النظام.

المصدر: www.habr.com

إضافة تعليق