Jinsi ya kulinda michakato na upanuzi wa kernel kwenye macOS

Habari, Habr! Leo ningependa kuzungumza juu ya jinsi unaweza kulinda michakato kutoka kwa washambuliaji kwenye macOS. Kwa mfano, hii ni muhimu kwa antivirus au mfumo wa chelezo, haswa kwani chini ya macOS kuna njia kadhaa za "kuua" mchakato. Soma kuhusu hili na mbinu za ulinzi chini ya kukata.

Jinsi ya kulinda michakato na upanuzi wa kernel kwenye macOS

Njia ya classic ya "kuua" mchakato

Njia inayojulikana ya "kuua" mchakato ni kutuma ishara ya SIGKILL kwa mchakato. Kupitia bash unaweza kupiga simu ya kawaida "kill -SIGKILL PID" au "pkill -9 NAME" ili kuua. Amri ya "kuua" imejulikana tangu siku za UNIX na haipatikani tu kwenye macOS, lakini pia kwenye mifumo mingine ya UNIX.

Kama tu katika mifumo kama UNIX, macOS hukuruhusu kukatiza ishara zozote kwa mchakato isipokuwa mbili - SIGKILL na SIGSTOP. Makala haya yataangazia mawimbi ya SIGKILL kama ishara inayosababisha mchakato kuuawa.

maelezo maalum ya macOS

Kwenye macOS, simu ya mfumo wa kuua kwenye kernel ya XNU huita psignal(SIGKILL,...) kazi. Wacha tujaribu kuona ni hatua gani zingine za mtumiaji kwenye nafasi ya mtumiaji zinaweza kuitwa na kazi ya psignal. Wacha tuondoe simu kwa utendakazi wa psignal katika mifumo ya ndani ya kernel (ingawa inaweza kuwa sio ndogo, tutaiacha kwa nakala nyingine πŸ™‚ - uthibitishaji wa saini, makosa ya kumbukumbu, utunzaji wa kutoka / kusitisha, ukiukaji wa ulinzi wa faili, n.k. .

Wacha tuanze ukaguzi na kazi na simu inayolingana ya mfumo maliza_na_malipo. Inaweza kuonekana kuwa kwa kuongeza simu ya kuua ya kawaida, kuna njia mbadala ambayo ni maalum kwa mfumo wa uendeshaji wa macOS na haipatikani katika BSD. Kanuni za uendeshaji wa simu zote mbili za mfumo pia ni sawa. Ni simu za moja kwa moja kwa kazi ya kernel psignal. Pia kumbuka kuwa kabla ya kuua mchakato, ukaguzi wa "cansignal" hufanywa - ikiwa mchakato unaweza kutuma ishara kwa mchakato mwingine; mfumo hauruhusu programu yoyote kuua michakato ya mfumo, kwa mfano.

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

ilizinduliwa

Njia ya kawaida ya kuunda daemoni wakati wa kuanzisha mfumo na kudhibiti maisha yao inazinduliwa. Tafadhali kumbuka kuwa vyanzo ni vya toleo la zamani la launchctl hadi macOS 10.10, mifano ya nambari hutolewa kwa madhumuni ya kielelezo. Launchctl ya kisasa hutuma ishara zilizozinduliwa kupitia XPC, mantiki ya launchctl imehamishwa kwake.

Wacha tuangalie jinsi maombi yanasimamishwa. Kabla ya kutuma ishara ya SIGTERM, programu inajaribiwa kusimamishwa kwa kutumia simu ya mfumo ya "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));
		} 
...
<>

Chini ya kofia, proc_terminate, licha ya jina lake, inaweza kutuma sio tu psignal na SIGTERM, lakini pia SIGKILL.

Kuua kwa njia isiyo ya moja kwa moja - Ukomo wa Rasilimali

Kesi ya kuvutia zaidi inaweza kuonekana katika simu nyingine ya mfumo mchakato_sera. Matumizi ya kawaida ya simu ya mfumo huu ni kupunguza rasilimali za programu, kama vile kiashiria kupunguza muda wa CPU na sehemu za kumbukumbu ili mfumo usipunguzwe kwa kiasi kikubwa na shughuli za kuhifadhi faili. Ikiwa programu imefikia kikomo chake cha rasilimali, kama inavyoweza kuonekana kutoka kwa proc_apply_resource_actions, ishara ya SIGKILL inatumwa kwa mchakato.

Ingawa simu hii ya mfumo inaweza kuua mchakato, mfumo haukuangalia vya kutosha haki za mchakato wa kupiga simu ya mfumo. Kwa kweli kuangalia kuwepo, lakini inatosha kutumia alamisho mbadala ya PROC_POLICY_ACTION_SET ili kukwepa hali hii.

Kwa hivyo, ikiwa "utaweka kikomo" kiwango cha matumizi ya CPU ya programu (kwa mfano, kuruhusu ns 1 tu kufanya kazi), basi unaweza kuua mchakato wowote kwenye mfumo. Kwa hivyo, programu hasidi inaweza kuua mchakato wowote kwenye mfumo, pamoja na mchakato wa antivirus. La kufurahisha pia ni athari inayotokea wakati wa kuua mchakato na pid 1 (launchctl) - hofu ya kernel wakati wa kujaribu kusindika ishara ya SIGKILL :)

Jinsi ya kulinda michakato na upanuzi wa kernel kwenye macOS

Jinsi ya kutatua tatizo?

Njia iliyonyooka zaidi ya kuzuia mchakato kuuawa ni kuchukua nafasi ya kiashiria cha kazi kwenye jedwali la simu la mfumo. Kwa bahati mbaya, njia hii haina maana kwa sababu nyingi.

Kwanza, ishara inayodhibiti eneo la kumbukumbu ya sysent sio tu ya kibinafsi kwa ishara ya XNU kernel, lakini haiwezi kupatikana katika alama za kernel. Utalazimika kutumia njia za utaftaji, kama vile kutenganisha kitendakazi kwa nguvu na kutafuta pointer ndani yake.

Pili, muundo wa maingizo kwenye jedwali inategemea bendera ambazo kernel iliundwa. Ikiwa bendera ya CONFIG_REQUIRES_U32_MUNGING itatangazwa, saizi ya muundo itabadilishwa - sehemu ya ziada itaongezwa. sy_arg_munge32. Inahitajika kufanya ukaguzi wa ziada ili kubaini ni bendera ipi ambayo punje iliundwa na, au sivyo, angalia viashiria vya utendakazi dhidi ya zinazojulikana.

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

Kwa bahati nzuri, katika matoleo ya kisasa ya macOS, Apple hutoa API mpya ya kufanya kazi na michakato. API ya Endpoint Security inaruhusu wateja kuidhinisha maombi mengi kwa michakato mingine. Kwa hivyo, unaweza kuzuia ishara zozote kwa michakato, pamoja na ishara ya SIGKILL, kwa kutumia API iliyotajwa hapo juu.

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

Vile vile, Sera ya MAC inaweza kusajiliwa katika kernel, ambayo hutoa mbinu ya ulinzi wa mawimbi (sera proc_check_signal), lakini API haitumiki rasmi.

Ulinzi wa ugani wa Kernel

Mbali na kulinda michakato katika mfumo, kulinda ugani wa kernel yenyewe (kext) pia ni muhimu. macOS hutoa mfumo kwa watengenezaji kukuza kwa urahisi madereva ya kifaa cha IOKit. Mbali na kutoa zana za kufanya kazi na vifaa, IOKit hutoa mbinu za kuweka madereva kwa kutumia mifano ya madarasa ya C++. Programu katika nafasi ya mtumiaji itaweza "kupata" mfano uliosajiliwa wa darasa ili kuanzisha uhusiano wa kernel-userspace.

Ili kugundua idadi ya matukio ya darasa kwenye mfumo, kuna matumizi ya ioclasscount.

my_kext_ioservice = 1
my_kext_iouserclient = 1

Kiendelezi chochote cha kernel kinachotaka kusajiliwa na rundo la viendeshaji lazima kitangaze darasa ambalo litarithi kutoka kwa Huduma ya IOS, kwa mfano my_kext_ioservice katika hali hii. Kuunganisha programu za mtumiaji husababisha kuundwa kwa mfano mpya wa darasa ambalo limerithi kutoka kwa IOUserClient, katika mfano my_kext_iouserclient.

Wakati wa kujaribu kupakua dereva kutoka kwa mfumo (amri ya kextunload), kazi ya virtual "bool terminate (IOOptionBits chaguzi)" inaitwa. Inatosha kurudisha uwongo kwenye simu ili kusitisha unapojaribu kupakua ili kuzima kextunload.

bool Kext::terminate(IOOptionBits options)
{

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

  return super::terminate(options);
}

Bendera ya IsUnloadAllowed inaweza kuwekwa na IOUserClient inapopakia. Wakati kuna kikomo cha upakuaji, amri ya kextunload itarudisha matokeo yafuatayo:

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.

Ulinzi sawa lazima ufanyike kwa IOUserClient. Matukio ya madarasa yanaweza kupakuliwa kwa kutumia chaguo za kukokotoa za nafasi ya mtumiaji za IOKitLib "IOCatalogueTerminate(mach_port_t, bendera ya uint32_t, maelezo ya io_name_t);". Unaweza kurudisha sivyo unapoita amri ya "sitisha" hadi programu ya "userspace" itakapokufa, yaani, chaguo la kukokotoa la "clientDied" halijaitwa.

Ulinzi wa faili

Ili kulinda faili, inatosha kutumia Kauth API, ambayo inakuwezesha kuzuia upatikanaji wa faili. Apple huwapa wasanidi programu arifa kuhusu matukio mbalimbali katika upeo; kwetu sisi, shughuli za KAUTH_VNODE_DELETE, KAUTH_VNODE_WRITE_DATA na KAUTH_VNODE_DELETE_CHILD ni muhimu. Njia rahisi zaidi ya kuzuia ufikiaji wa faili ni kwa njia - tunatumia API ya "vn_getpath" kupata njia ya faili na kulinganisha kiambishi awali cha njia. Kumbuka kwamba ili kuboresha upangaji upya wa njia za folda za faili, mfumo hauidhinishi ufikiaji wa kila faili, lakini tu kwa folda yenyewe ambayo imepewa jina. Ni muhimu kulinganisha njia ya mzazi na kuzuia KAUTH_VNODE_DELETE kwa hiyo.

Jinsi ya kulinda michakato na upanuzi wa kernel kwenye macOS

Ubaya wa mbinu hii inaweza kuwa utendaji duni kadiri idadi ya viambishi awali inavyoongezeka. Ili kuhakikisha kuwa ulinganisho hauko sawa na O(kiambishi**urefu), ambapo kiambishi awali ni idadi ya viambishi awali, urefu ni urefu wa mfuatano, unaweza kutumia bainishi bainishi otomatiki (DFA) iliyojengwa na viambishi awali.

Wacha tuchunguze njia ya kuunda DFA kwa seti fulani ya viambishi awali. Tunaanzisha viambishi mwanzoni mwa kila kiambishi awali. Ikiwa vishale vyote vinaelekeza kwa herufi moja, basi ongeza kila kielekezi kwa herufi moja na ukumbuke kuwa urefu wa mstari huo huo ni mkubwa zaidi kwa moja. Ikiwa kuna vishale viwili vilivyo na alama tofauti, gawanya vishale katika vikundi kulingana na ishara wanayoelekeza na kurudia algoriti kwa kila kikundi.

Katika kesi ya kwanza (wahusika wote chini ya cursors ni sawa), tunapata hali ya DFA ambayo ina mpito mmoja tu kwenye mstari huo. Katika kesi ya pili, tunapata jedwali la mabadiliko ya ukubwa wa 256 (idadi ya wahusika na idadi ya juu ya vikundi) kwa majimbo yaliyofuata yaliyopatikana kwa kupiga simu kwa kurudia.

Hebu tuangalie mfano. Kwa seti ya viambishi awali (β€œ/foo/bar/tmp/”, β€œ/var/db/foo/”, β€œ/foo/bar/aba/”, β€œfoo/bar/aac/”) unaweza kupata zifuatazo. DFA. Kielelezo kinaonyesha mabadiliko yanayoelekea katika majimbo mengine pekee; mabadiliko mengine hayatakuwa ya mwisho.

Jinsi ya kulinda michakato na upanuzi wa kernel kwenye macOS

Wakati wa kupitia majimbo ya DKA, kunaweza kuwa na kesi 3.

  1. Hali ya mwisho imefikiwa - njia inalindwa, tunapunguza shughuli za KAUTH_VNODE_DELETE, KAUTH_VNODE_WRITE_DATA na KAUTH_VNODE_DELETE_CHILD
  2. Hali ya mwisho haikufikiwa, lakini njia "ilimalizika" (terminator isiyofaa ilifikiwa) - njia ni ya mzazi, ni muhimu kupunguza KAUTH_VNODE_DELETE. Kumbuka kwamba ikiwa vnode ni folda, unahitaji kuongeza '/' mwishoni, vinginevyo inaweza kuiwekea kikomo kwa faili "/foor/bar/t", ambayo si sahihi.
  3. Hali ya mwisho haikufikiwa, njia haikuisha. Hakuna kiambishi awali kinacholingana na hiki, hatuongezi vizuizi.

Hitimisho

Kusudi la suluhisho za usalama zinazotengenezwa ni kuongeza kiwango cha usalama cha mtumiaji na data yake. Kwa upande mmoja, lengo hili linapatikana kwa maendeleo ya bidhaa ya programu ya Acronis, ambayo inafunga udhaifu huo ambapo mfumo wa uendeshaji yenyewe ni "dhaifu". Kwa upande mwingine, hatupaswi kupuuza kuimarisha vipengele hivyo vya usalama ambavyo vinaweza kuboreshwa kwa upande wa Mfumo wa Uendeshaji, hasa kwa vile kufunga udhaifu huo huongeza uthabiti wetu wenyewe kama bidhaa. Athari hii iliripotiwa kwa Timu ya Usalama ya Bidhaa ya Apple na imerekebishwa katika macOS 10.14.5 (https://support.apple.com/en-gb/HT210119).

Jinsi ya kulinda michakato na upanuzi wa kernel kwenye macOS

Haya yote yanaweza kufanywa tu ikiwa matumizi yako yamewekwa rasmi kwenye kernel. Hiyo ni, hakuna mianya kama hiyo kwa programu ya nje na isiyohitajika. Walakini, kama unavyoona, hata kulinda programu halali kama vile antivirus na mifumo ya chelezo inahitaji kazi. Lakini sasa bidhaa mpya za Acronis kwa macOS zitakuwa na ulinzi wa ziada dhidi ya upakuaji kutoka kwa mfumo.

Chanzo: mapenzi.com

Kuongeza maoni