Hvernig á að vernda ferla og kjarnaviðbætur á macOS

Halló, Habr! Í dag langar mig að tala um hvernig þú getur verndað ferli gegn árásum árásarmanna í macOS. Til dæmis er þetta gagnlegt fyrir vírusvarnar- eða öryggisafritunarkerfi, sérstaklega þar sem undir macOS eru nokkrar leiðir til að „drepa“ ferli. Lestu um þetta og verndaraðferðir undir skurðinum.

Hvernig á að vernda ferla og kjarnaviðbætur á macOS

Klassíska leiðin til að „drepa“ ferli

Vel þekkt leið til að „drepa“ ferli er að senda SIGKILL merki til ferlisins. Í gegnum bash geturðu kallað venjulegt „kill -SIGKILL PID“ eða „pkill -9 NAME“ til að drepa. „kill“ skipunin hefur verið þekkt frá dögum UNIX og er ekki aðeins fáanleg á macOS, heldur einnig á öðrum UNIX-líkum kerfum.

Rétt eins og í UNIX-líkum kerfum, gerir macOS þér kleift að stöðva öll merki í ferli nema tvö - SIGKILL og SIGSTOP. Þessi grein mun fyrst og fremst einblína á SIGKILL merkið sem merki sem veldur því að ferli er drepið.

macOS sérkenni

Á macOS kallar drepakerfiskallið í XNU kjarnanum psignal(SIGKILL,...) aðgerðina. Við skulum reyna að sjá hvaða aðrar aðgerðir notenda í notendarými er hægt að kalla með psignal aðgerðinni. Við skulum eyða símtölum í psignal aðgerðina í innra kerfi kjarnans (þótt þau séu kannski ekki léttvæg, þá látum við þau eftir í aðra grein 🙂 - staðfesting á undirskrift, minnisvillur, meðhöndlun útganga/loka, brot á skráarvörn o.s.frv. .

Byrjum yfirferðina með aðgerðinni og samsvarandi kerfiskalli slíta_með_payload. Það má sjá að til viðbótar við klassíska drápskallið er önnur nálgun sem er sérstök fyrir macOS stýrikerfið og er ekki að finna í BSD. Starfsreglur beggja kerfiskallanna eru líka svipaðar. Þetta eru bein símtöl í kjarnaaðgerðina psignal. Athugaðu einnig að áður en ferli er drepið er „cansignal“ athugun framkvæmd - hvort ferlið geti sent merki til annars ferlis; kerfið leyfir ekki neinu forriti að drepa kerfisferla, til dæmis.

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

hleypt af stokkunum

Hefðbundin leið til að búa til púka við ræsingu kerfisins og stjórna líftíma þeirra er opnuð. Vinsamlegast athugaðu að heimildirnar eru fyrir gömlu útgáfuna af launchctl upp að macOS 10.10, kóðadæmi eru veitt til lýsingar. Nútíma launchctl sendir ræst merki í gegnum XPC, launchctl rökfræði hefur verið færð til þess.

Við skulum skoða hvernig nákvæmlega umsóknir eru stöðvaðar. Áður en SIGTERM merkið er sent er reynt að stöðva forritið með því að nota „proc_terminate“ kerfiskallið.

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

Undir hettunni getur proc_terminate, þrátt fyrir nafnið, sent ekki aðeins psignal með SIGTERM, heldur einnig SIGKILL.

Óbeint dráp - auðlindatakmörk

Áhugaverðara mál má sjá í öðru kerfiskalli ferli_stefna. Algeng notkun þessa kerfiskalls er að takmarka forritaauðlindir, svo sem fyrir skráarbúnað til að takmarka örgjörvatíma og minniskvóta þannig að kerfið hægist ekki verulega á skráarskyndiminni. Ef forrit hefur náð auðlindamörkum sínum, eins og sést á aðgerðinni proc_apply_resource_actions, er SIGKILL merki sent í ferlið.

Þó að þetta kerfiskall gæti hugsanlega drepið ferli, athugaði kerfið ekki nægilega réttindi ferlisins sem hringdi í kerfiskallið. Reyndar að athuga verið til, en það er nóg að nota valfánann PROC_POLICY_ACTION_SET til að komast framhjá þessu skilyrði.

Þess vegna, ef þú „takmarkar“ örgjörvanotkunarkvóta forritsins (til dæmis leyfir aðeins 1 ns að keyra), þá geturðu drepið hvaða ferli sem er í kerfinu. Þannig getur spilliforritið drepið hvaða ferli sem er á kerfinu, þar með talið vírusvarnarferlið. Einnig áhugavert eru áhrifin sem verða þegar ferli er drepið með pid 1 (launchctl) - kjarna læti þegar reynt er að vinna úr SIGKILL merkinu :)

Hvernig á að vernda ferla og kjarnaviðbætur á macOS

Hvernig á að leysa vandamálið?

Einfaldasta leiðin til að koma í veg fyrir að ferli sé drepið er að skipta um aðgerðabendil í kerfiskallatöflunni. Því miður er þessi aðferð ekki léttvæg af mörgum ástæðum.

Í fyrsta lagi er táknið sem stjórnar minnisstaðsetningu sysent ekki aðeins einkamál XNU kjarnatáknisins heldur er ekki hægt að finna það í kjarnatáknum. Þú verður að nota heuristic leitaraðferðir, svo sem að taka aðgerðina í sundur á virkan hátt og leita að bendili í henni.

Í öðru lagi fer uppbygging færslna í töflunni eftir fánum sem kjarninn var settur saman með. Ef CONFIG_REQUIRES_U32_MUNGING fáninn er lýst yfir, verður stærð skipulagsins breytt - viðbótarreitur verður bætt við sy_arg_munge32. Nauðsynlegt er að framkvæma viðbótarathugun til að ákvarða hvaða fána kjarninn var settur saman með, eða að öðrum kosti, athuga virknibendingar gegn þekktum.

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

Sem betur fer, í nútíma útgáfum af macOS, býður Apple upp á nýtt API til að vinna með ferla. Endpoint Security API gerir viðskiptavinum kleift að heimila margar beiðnir til annarra ferla. Þannig geturðu lokað á öll merki til ferla, þar á meðal SIGKILL merki, með því að nota ofangreinda 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;
}

Á sama hátt er hægt að skrá MAC stefnu í kjarnanum, sem veitir merkjaverndaraðferð (policy proc_check_signal), en API er ekki opinberlega stutt.

Kjarnalengingarvörn

Auk þess að vernda ferla í kerfinu er einnig nauðsynlegt að vernda kjarnaviðbótina sjálfa (kext). macOS býður upp á ramma fyrir forritara til að þróa IOKit tækjarekla auðveldlega. Auk þess að útvega verkfæri til að vinna með tæki, býður IOKit upp á aðferðir til að stafla bílstjóra með því að nota tilvik af C++ flokkum. Forrit í notendarýminu mun geta „finnið“ skráð tilvik af bekknum til að koma á sambandi kjarna og notendarýmis.

Til að greina fjölda flokkatilvika í kerfinu er ioclasscount tólið.

my_kext_ioservice = 1
my_kext_iouserclient = 1

Sérhver kjarnaviðbót sem vill skrá sig í ökumannsstaflann verður að lýsa yfir flokki sem erfir frá IOService, til dæmis my_kext_ioservice í þessu tilfelli. Tenging notendaforrita veldur því að nýtt tilvik af bekknum sem erfir frá IOUserClient, í dæminu my_kext_iouserclient, verður til.

Þegar reynt er að losa ökumann úr kerfinu (kextunload skipun), er sýndaraðgerðin „bool terminate(IOOptionBits options)“ kölluð. Það er nóg að skila false á símtalinu til að hætta þegar reynt er að afferma til að slökkva á kextunload.

bool Kext::terminate(IOOptionBits options)
{

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

  return super::terminate(options);
}

IsUnloadAllowed fánann getur verið stilltur af IOUserClient við hleðslu. Þegar það er niðurhalstakmörk mun kextunload skipunin skila eftirfarandi úttak:

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.

Svipaða vernd verður að gera fyrir IOUserClient. Hægt er að afferma tilvik af flokkum með því að nota IOKitLib notendarýmisaðgerðina „IOCatalogueTerminate(mach_port_t, uint32_t flag, io_name_t description);“. Þú getur skilað ósatt þegar þú hringir í „terminate“ skipunina þar til notendarýmisforritið „deyr“, það er „clientDied“ aðgerðin er ekki kölluð.

Skráavernd

Til að vernda skrár er nóg að nota Kauth API, sem gerir þér kleift að takmarka aðgang að skrám. Apple veitir forriturum tilkynningar um ýmsa atburði í umfanginu; fyrir okkur eru aðgerðirnar KAUTH_VNODE_DELETE, KAUTH_VNODE_WRITE_DATA og KAUTH_VNODE_DELETE_CHILD mikilvægar. Auðveldasta leiðin til að takmarka aðgang að skrám er með slóð - við notum „vn_getpath“ API til að fá slóðina að skránni og bera saman slóðaforskeytið. Athugaðu að til að fínstilla endurnefna slóða skráarmöppu leyfir kerfið ekki aðgang að hverri skrá, heldur aðeins að möppunni sjálfri sem hefur verið endurnefna. Nauðsynlegt er að bera saman foreldraslóðina og takmarka KAUTH_VNODE_DELETE fyrir hana.

Hvernig á að vernda ferla og kjarnaviðbætur á macOS

Ókosturinn við þessa aðferð getur verið lítil frammistaða þar sem forskeytum fjölgar. Til að tryggja að samanburðurinn sé ekki jafn O(forskeyti*lengd), þar sem forskeyti er fjöldi forskeyta, lengd er lengd strengsins, geturðu notað ákveðinn endanlegt sjálfvirkan (DFA) byggðan af forskeytum.

Við skulum íhuga aðferð til að smíða DFA fyrir tiltekið sett af forskeytum. Við frumstillum bendilinn í upphafi hvers forskeyti. Ef allir bendillarnir benda á sama staf, aukið þá hvern bendil um einn staf og munið að lengdin á sömu línu er einum meiri. Ef það eru tveir bendillar með mismunandi táknum skaltu skipta bendilunum í hópa í samræmi við táknið sem þeir benda á og endurtaka reikniritið fyrir hvern hóp.

Í fyrra tilvikinu (allir stafir undir bendilinn eru eins) fáum við DFA ástand sem hefur aðeins eina umskipti eftir sömu línu. Í öðru tilvikinu fáum við töflu yfir umbreytingar af stærð 256 (fjöldi stafa og hámarksfjöldi hópa) yfir í síðari ástand sem fæst með því að kalla fallið afturkvæmt.

Við skulum skoða dæmi. Fyrir sett af forskeytum ("/foo/bar/tmp/", "/var/db/foo/", "/foo/bar/aba/", "foo/bar/aac/") geturðu fengið eftirfarandi DFA. Myndin sýnir aðeins umskipti sem leiða til annarra ríkja; önnur umskipti verða ekki endanleg.

Hvernig á að vernda ferla og kjarnaviðbætur á macOS

Þegar farið er í gegnum DKA-ríkin geta verið 3 tilvik.

  1. Lokaástandinu er náð - leiðin er vernduð, við takmörkum aðgerðirnar KAUTH_VNODE_DELETE, KAUTH_VNODE_WRITE_DATA og KAUTH_VNODE_DELETE_CHILD
  2. Endanlegu ástandi var ekki náð, en slóðin „lokaði“ (nulllokunarstöðinni var náð) - slóðin er foreldri, það er nauðsynlegt að takmarka KAUTH_VNODE_DELETE. Athugaðu að ef vnode er mappa þarftu að bæta við '/' í lokin, annars gæti það takmarkað það við skrána “/foor/bar/t”, sem er rangt.
  3. Endanlegu ástandi var ekki náð, leiðin endaði ekki. Ekkert af forskeytum passar við þetta, við tökum ekki upp takmarkanir.

Ályktun

Markmiðið með þeim öryggislausnum sem verið er að þróa er að auka öryggisstig notandans og gagna hans. Annars vegar er þessu markmiði náð með þróun Acronis hugbúnaðarvörunnar, sem lokar þeim veikleikum þar sem stýrikerfið sjálft er „veikt“. Á hinn bóginn ættum við ekki að vanrækja að styrkja þá öryggisþætti sem hægt er að bæta á stýrikerfishliðinni, sérstaklega þar sem að loka slíkum veikleikum eykur okkar eigin stöðugleika sem vöru. Varnarleysið var tilkynnt til Apple vöruöryggisteymis og hefur verið lagað í macOS 10.14.5 (https://support.apple.com/en-gb/HT210119).

Hvernig á að vernda ferla og kjarnaviðbætur á macOS

Allt þetta er aðeins hægt að gera ef tólið þitt hefur verið opinberlega sett upp í kjarnanum. Það er, það eru engar slíkar glufur fyrir utanaðkomandi og óæskilegan hugbúnað. Hins vegar, eins og þú sérð, þarf jafnvel að vernda lögmæt forrit eins og vírusvarnarkerfi og öryggisafritunarkerfi vinnu. En nú munu nýjar Acronis vörur fyrir macOS hafa viðbótarvörn gegn affermingu úr kerfinu.

Heimild: www.habr.com

Bæta við athugasemd