Com protegir els processos i les extensions del nucli a macOS

Hola, Habr! Avui m'agradaria parlar de com podeu protegir els processos dels atacs d'atacants a macOS. Per exemple, això és útil per a un sistema antivirus o de còpia de seguretat, sobretot perquè sota macOS hi ha diverses maneres de "matar" un procés. Llegeix sobre això i els mètodes de protecció sota el tall.

Com protegir els processos i les extensions del nucli a macOS

La forma clàssica de "matar" un procés

Una manera coneguda de "matar" un procés és enviar un senyal SIGKILL al procés. Mitjançant bash podeu cridar l'estàndard "kill -SIGKILL PID" o "pkill -9 NAME" per matar. L'ordre "kill" es coneix des dels temps d'UNIX i està disponible no només a macOS, sinó també a altres sistemes semblants a UNIX.

Igual que en sistemes semblants a UNIX, macOS us permet interceptar qualsevol senyal a un procés excepte dos: SIGKILL i SIGSTOP. Aquest article es centrarà principalment en el senyal SIGKILL com a senyal que fa que s'elimini un procés.

especificitats de macOS

A macOS, la trucada del sistema kill al nucli XNU crida a la funció psignal(SIGKILL,...). Intentem veure quines altres accions d'usuari a l'espai d'usuari es poden cridar amb la funció psignal. Eliminarem les trucades a la funció psignal en els mecanismes interns del nucli (encara que no siguin trivials, les deixarem per a un altre article 🙂 - verificació de signatura, errors de memòria, gestió de sortida/finalització, infraccions de protecció de fitxers, etc. .

Comencem la revisió amb la funció i la trucada al sistema corresponent terminar_amb_càrrega útil. Es pot veure que, a més de la clàssica trucada de matança, hi ha un enfocament alternatiu que és específic del sistema operatiu macOS i que no es troba a BSD. Els principis de funcionament d'ambdues trucades del sistema també són similars. Són trucades directes a la funció del nucli psignal. Tingueu en compte també que abans de matar un procés, es realitza una comprovació de "cansignal": si el procés pot enviar un senyal a un altre procés; el sistema no permet que cap aplicació mati els processos del sistema, per exemple.

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

llançat

Es llança la forma estàndard de crear dimonis a l'inici del sistema i controlar-ne la vida útil. Tingueu en compte que les fonts són per a la versió antiga de launchctl fins a macOS 10.10, es proporcionen exemples de codi amb finalitats il·lustratives. El launchctl modern envia senyals de llançament mitjançant XPC, la lògica de launchctl s'hi ha mogut.

Vegem com s'aturen exactament les aplicacions. Abans d'enviar el senyal SIGTERM, s'intenta aturar l'aplicació mitjançant la trucada al sistema "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));
		} 
...
<>

Sota el capó, proc_terminate, malgrat el seu nom, pot enviar no només psignal amb SIGTERM, sinó també SIGKILL.

Matança indirecta: límit de recursos

Un cas més interessant es pot veure en una altra trucada del sistema política_de_procés. Un ús comú d'aquesta crida al sistema és limitar els recursos de l'aplicació, com ara un indexador per limitar el temps de la CPU i les quotes de memòria perquè el sistema no es vegi alentit significativament per les activitats de memòria cau de fitxers. Si una aplicació ha arribat al seu límit de recursos, com es pot veure a la funció proc_apply_resource_actions, s'envia un senyal SIGKILL al procés.

Tot i que aquesta trucada al sistema podria matar un procés, el sistema no va comprovar adequadament els drets del procés que cridava la trucada al sistema. De fet, comprovant existit, però n'hi ha prou amb utilitzar el senyal alternatiu PROC_POLICY_ACTION_SET per evitar aquesta condició.

Per tant, si "limiteu" la quota d'ús de la CPU de l'aplicació (per exemple, permetent que només s'executi 1 ns), podeu matar qualsevol procés del sistema. Per tant, el programari maliciós pot matar qualsevol procés del sistema, inclòs el procés antivirus. També és interessant l'efecte que es produeix en matar un procés amb pid 1 (launchctl) - pànic del nucli quan s'intenta processar el senyal SIGKILL :)

Com protegir els processos i les extensions del nucli a macOS

Com solucionar el problema?

La manera més senzilla d'evitar que s'acabi un procés és substituir el punter de funció a la taula de trucades del sistema. Malauradament, aquest mètode no és trivial per moltes raons.

En primer lloc, el símbol que controla la ubicació de la memòria de sysent no només és privat del símbol del nucli XNU, sinó que no es pot trobar als símbols del nucli. Haureu d'utilitzar mètodes de cerca heurístics, com ara desmuntar dinàmicament la funció i cercar-hi un punter.

En segon lloc, l'estructura de les entrades de la taula depèn dels senyaladors amb què s'ha compilat el nucli. Si es declara la bandera CONFIG_REQUIRES_U32_MUNGING, la mida de l'estructura es canviarà; s'afegirà un camp addicional sy_arg_munge32. Cal fer una comprovació addicional per determinar amb quina bandera s'ha compilat el nucli o, alternativament, comprovar els punters de funció amb els coneguts.

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

Afortunadament, en les versions modernes de macOS, Apple ofereix una nova API per treballar amb processos. L'API Endpoint Security permet als clients autoritzar moltes sol·licituds a altres processos. Per tant, podeu bloquejar qualsevol senyal als processos, inclòs el senyal SIGKILL, mitjançant l'API esmentada anteriorment.

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

De la mateixa manera, es pot registrar una política MAC al nucli, que proporciona un mètode de protecció del senyal (política proc_check_signal), però l'API no és compatible oficialment.

Protecció d'extensió del nucli

A més de protegir els processos del sistema, també és necessari protegir la pròpia extensió del nucli (kext). macOS proporciona un marc perquè els desenvolupadors puguin desenvolupar fàcilment controladors de dispositiu IOKit. A més de proporcionar eines per treballar amb dispositius, IOKit ofereix mètodes per a l'apilament de controladors mitjançant instàncies de classes C++. Una aplicació a l'espai d'usuari podrà "trobar" una instància registrada de la classe per establir una relació nucli-espai d'usuari.

Per detectar el nombre d'instàncies de classe al sistema, hi ha la utilitat ioclasscount.

my_kext_ioservice = 1
my_kext_iouserclient = 1

Qualsevol extensió del nucli que vulgui registrar-se a la pila de controladors ha de declarar una classe que hereta d'IOService, per exemple my_kext_ioservice en aquest cas. La connexió d'aplicacions d'usuari provoca la creació d'una nova instància de la classe que hereta de IOUserClient, a l'exemple my_kext_iouserclient.

Quan s'intenta descarregar un controlador del sistema (ordre kextunload), es crida la funció virtual "bool terminate(IOOptionBits options)". N'hi ha prou amb tornar false a la trucada per finalitzar quan s'intenta descarregar per desactivar kextunload.

bool Kext::terminate(IOOptionBits options)
{

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

  return super::terminate(options);
}

La marca IsUnloadAllowed la pot establir l'IOUserClient quan es carrega. Quan hi ha un límit de descàrrega, l'ordre kextunload retornarà la sortida següent:

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.

S'ha de fer una protecció similar per a IOUserClient. Les instàncies de classes es poden descarregar utilitzant la funció d'espai d'usuari IOKitLib "IOCatalogueTerminate(mach_port_t, uint32_t flag, io_name_t description);". Podeu tornar false quan crideu l'ordre "terminate" fins que l'aplicació d'espai d'usuari "mor", és a dir, no es crida la funció "clientDied".

Protecció de fitxers

Per protegir els fitxers, n'hi ha prou amb utilitzar l'API Kauth, que us permet restringir l'accés als fitxers. Apple proporciona als desenvolupadors notificacions sobre diversos esdeveniments de l'abast; per a nosaltres, les operacions KAUTH_VNODE_DELETE, KAUTH_VNODE_WRITE_DATA i KAUTH_VNODE_DELETE_CHILD són importants. La manera més senzilla de restringir l'accés als fitxers és mitjançant el camí: utilitzem l'API "vn_getpath" per obtenir el camí del fitxer i comparar el prefix del camí. Tingueu en compte que per optimitzar el canvi de nom dels camins de les carpetes de fitxers, el sistema no autoritza l'accés a cada fitxer, sinó només a la pròpia carpeta que s'ha canviat de nom. Cal comparar el camí principal i restringir-hi KAUTH_VNODE_DELETE.

Com protegir els processos i les extensions del nucli a macOS

El desavantatge d'aquest enfocament pot ser el baix rendiment a mesura que augmenta el nombre de prefixos. Per assegurar-vos que la comparació no sigui igual a O (prefix*longitud), on el prefix és el nombre de prefixos, la longitud és la longitud de la cadena, podeu utilitzar un autòmat finit determinista (DFA) construït per prefixos.

Considerem un mètode per construir un DFA per a un conjunt determinat de prefixos. Inicialitzem els cursors al començament de cada prefix. Si tots els cursors apunten al mateix caràcter, augmenta cada cursor en un caràcter i recorda que la longitud de la mateixa línia és més gran en un. Si hi ha dos cursors amb símbols diferents, divideix els cursors en grups segons el símbol al qual apunten i repeteix l'algorisme per a cada grup.

En el primer cas (tots els caràcters sota els cursors són iguals), obtenim un estat DFA que només té una transició al llarg de la mateixa línia. En el segon cas, obtenim una taula de transicions de mida 256 (nombre de caràcters i nombre màxim de grups) a estats posteriors obtingudes cridant recursivament la funció.

Vegem un exemple. Per a un conjunt de prefixos (“/foo/bar/tmp/”, “/var/db/foo/”, “/foo/bar/aba/”, “foo/bar/aac/”) podeu obtenir el següent DFA. La figura només mostra les transicions que condueixen a altres estats; les altres transicions no seran definitives.

Com protegir els processos i les extensions del nucli a macOS

Quan es passa pels estats DKA, hi pot haver 3 casos.

  1. S'ha arribat a l'estat final: el camí està protegit, limitem les operacions KAUTH_VNODE_DELETE, KAUTH_VNODE_WRITE_DATA i KAUTH_VNODE_DELETE_CHILD
  2. No s'ha arribat a l'estat final, però el camí "ha acabat" (s'ha arribat al terminador nul): el camí és un pare, cal limitar KAUTH_VNODE_DELETE. Tingueu en compte que si vnode és una carpeta, haureu d'afegir un '/' al final, en cas contrari pot limitar-lo al fitxer "/foor/bar/t", que és incorrecte.
  3. No es va arribar a l'estat final, el camí no s'acabava. Cap dels prefixos coincideix amb aquest, no introduïm restriccions.

Conclusió

L'objectiu de les solucions de seguretat que s'estan desenvolupant és augmentar el nivell de seguretat de l'usuari i les seves dades. D'una banda, aquest objectiu s'aconsegueix amb el desenvolupament del producte de programari Acronis, que tanca aquelles vulnerabilitats on el propi sistema operatiu és "dèbil". D'altra banda, no hem de descuidar l'enfortiment d'aquells aspectes de seguretat que es poden millorar des del sistema operatiu, sobretot perquè tancar aquestes vulnerabilitats augmenta la nostra pròpia estabilitat com a producte. La vulnerabilitat es va informar a l'equip de seguretat del producte d'Apple i s'ha solucionat a macOS 10.14.5 (https://support.apple.com/en-gb/HT210119).

Com protegir els processos i les extensions del nucli a macOS

Tot això només es pot fer si la vostra utilitat s'ha instal·lat oficialment al nucli. És a dir, no hi ha aquestes llacunes per al programari extern i no desitjat. Tanmateix, com podeu veure, fins i tot protegir programes legítims com ara sistemes antivirus i còpies de seguretat requereix treball. Però ara els nous productes Acronis per a macOS tindran protecció addicional contra la descàrrega del sistema.

Font: www.habr.com

Afegeix comentari