Ինչպես ապահովել գործընթացները և միջուկի ընդլայնումները macOS-ում

Բարև, Հաբր: Այսօր ես կցանկանայի խոսել այն մասին, թե ինչպես կարող եք պաշտպանել գործընթացները macOS-ում հարձակվողների հարձակումներից: Օրինակ, սա օգտակար է հակավիրուսային կամ պահեստային համակարգի համար, մանավանդ, որ macOS-ի պայմաններում գործընթացը «սպանելու» մի քանի եղանակ կա: Կարդացեք այս մասին և պաշտպանության մեթոդները կտրվածքի տակ:

Ինչպես ապահովել գործընթացները և միջուկի ընդլայնումները macOS-ում

Գործընթացը «սպանելու» դասական միջոց

Գործընթացը «սպանելու» հայտնի միջոցը գործընթացին SIGKILL ազդանշան ուղարկելն է: Բաշի միջոցով կարող եք սպանել ստանդարտ «kill -SIGKILL PID» կամ «pkill -9 NAME»: «Kill» հրամանը հայտնի է եղել UNIX-ի ժամանակներից և հասանելի է ոչ միայն macOS-ում, այլև UNIX-ի նման այլ համակարգերում։

Ինչպես UNIX-ի նման համակարգերում, macOS-ը թույլ է տալիս գաղտնալսել գործընթացի ցանկացած ազդանշան, բացառությամբ երկուսի՝ SIGKILL-ի և SIGSTOP-ի: Այս հոդվածը հիմնականում կկենտրոնանա SIGKILL ազդանշանի վրա՝ որպես ազդանշան, որը հանգեցնում է գործընթացի ոչնչացմանը:

macOS-ի առանձնահատկությունները

MacOS-ում սպանության համակարգի կանչը XNU միջուկում կանչում է psignal(SIGKILL,...) ֆունկցիան: Փորձենք տեսնել, թե օգտվողների տարածքի ինչ այլ գործողություններ կարող են կոչվել psignal ֆունկցիայի միջոցով: Եկեք ջնջենք psignal ֆունկցիայի զանգերը միջուկի ներքին մեխանիզմներում (չնայած դրանք կարող են աննշան լինել, մենք դրանք կթողնենք մեկ այլ հոդվածի համար 🙂 - ստորագրության ստուգում, հիշողության սխալներ, ելքի/վերջացման մշակում, ֆայլերի պաշտպանության խախտումներ և այլն: .

Սկսենք վերանայումը ֆունկցիայից և համապատասխան համակարգային կանչից terminate_with_payload. Կարելի է տեսնել, որ բացի դասական սպանության զանգից, կա այլընտրանքային մոտեցում, որը հատուկ է macOS օպերացիոն համակարգին և չի գտնվել BSD-ում։ Երկու համակարգային զանգերի գործառնական սկզբունքները նույնպես նման են: Դրանք ուղղակի զանգեր են միջուկի ֆունկցիայի psignal-ին: Նկատի ունեցեք նաև, որ նախքան պրոցեսը սպանելը, կատարվում է «կանխող ազդանշանի» ստուգում՝ արդյոք գործընթացը կարող է ազդանշան ուղարկել մեկ այլ գործընթացի, համակարգը թույլ չի տալիս, օրինակ, որևէ հավելված սպանել համակարգի գործընթացները:

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-ով (գործարկում) պրոցեսը սպանելիս՝ միջուկի խուճապը 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 քաղաքականություն, որն ապահովում է ազդանշանի պաշտպանության մեթոդ (policy 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-ի համար: Դասերի օրինակները կարող են բեռնաթափվել՝ օգտագործելով «IOCatalogueTerminate» IOKitLib userspace ֆունկցիան (mach_port_t, uint32_t դրոշ, io_name_t նկարագրություն);»: «terminate» հրամանը կանչելիս կարող եք վերադարձնել false, քանի դեռ «userpace» հավելվածը «մեռնում է», այսինքն՝ «clientDied» ֆունկցիան չի կանչվում:

Ֆայլի պաշտպանություն

Ֆայլերը պաշտպանելու համար բավական է օգտագործել 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 Product Security Team-ին և շտկվել է macOS 10.14.5-ում (https://support.apple.com/en-gb/HT210119):

Ինչպես ապահովել գործընթացները և միջուկի ընդլայնումները macOS-ում

Այս ամենը կարելի է անել միայն այն դեպքում, եթե ձեր կոմունալ ծրագիրը պաշտոնապես տեղադրվել է միջուկում: Այսինքն՝ արտաքին և անցանկալի ծրագրերի համար նման բացեր չկան։ Այնուամենայնիվ, ինչպես տեսնում եք, նույնիսկ օրինական ծրագրերը, ինչպիսիք են հակավիրուսային և պահեստային համակարգերը պաշտպանելը, աշխատանք է պահանջում: Սակայն այժմ MacOS-ի համար Acronis-ի նոր արտադրանքները լրացուցիչ պաշտպանություն կունենան համակարգից բեռնաթափման դեմ:

Source: www.habr.com

Добавить комментарий