Bonjour Habr ! Aujourd'hui, j'aimerais vous parler de la maniÚre de protéger vos processus contre les attaques malveillantes. macOSPar exemple, cela est utile pour les systÚmes antivirus ou de sauvegarde, notamment parce que, sous macOS Il existe plusieurs façons d'interrompre un processus. Consultez les informations ci-dessous pour en savoir plus sur les méthodes de protection.
La maniÚre classique de « tuer » un processus
Une mĂ©thode courante pour arrĂȘter un processus consiste Ă lui envoyer le signal SIGKILL. Sous Bash, vous pouvez utiliser les commandes standard « kill -SIGKILL PID » ou « pkill -9 NOM ». La commande « kill » existe depuis l'Ă©poque d'UNIX et est disponible non seulement sous⊠macOSmais aussi sur d'autres systĂšmes de type UNIX.
Tout comme dans les systĂšmes de type UNIX, macOS Permet d'intercepter tous les signaux de processus, Ă l'exception de deux : SIGKILL et SIGSTOP. Cet article se concentrera principalement sur le signal SIGKILL, qui provoque l'arrĂȘt du processus.
Spécificité macOS
Đ macOS L'appel systĂšme kill du noyau XNU invoque la fonction psignal(SIGKILL,âŠ). Voyons quelles autres actions utilisateur dans l'espace utilisateur peuvent appeler la fonction psignal. Nous exclurons les appels Ă psignal effectuĂ©s par les mĂ©canismes internes du noyau (bien qu'ils puissent ĂȘtre complexes, nous les aborderons dans un autre article :)) : vĂ©rification de signature, erreurs de mĂ©moire, gestion de la sortie/terminaison, violations de sĂ©curitĂ© des fichiers, etc.
Commençons la revue avec la fonction et l'appel systĂšme correspondant Il est clair qu'en plus de la commande d'arrĂȘt classique, il existe une approche alternative spĂ©cifique au systĂšme d'exploitation. macOS Cette fonction n'est pas prĂ©sente dans BSD. Le principe de fonctionnement des deux appels systĂšme est Ă©galement similaire : il s'agit d'appels directs Ă la fonction noyau `psignal`. Notez Ă©galement qu'avant de terminer un processus, une vĂ©rification « cansignal » est effectuĂ©e afin de dĂ©terminer si le processus est autorisĂ© Ă envoyer un signal Ă un autre processus. Le systĂšme n'autorise aucune application Ă terminer des processus systĂšme, par 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);
}
...
}
lancé
La mĂ©thode standard pour crĂ©er des dĂ©mons au dĂ©marrage du systĂšme et contrĂŽler leur durĂ©e de vie est launchd. Veuillez noter que le code source correspond Ă une version antĂ©rieure de launchctl. macOS 10.10 : des exemples de code sont fournis Ă titre dâillustration. La version moderne de launchctl envoie des signaux Ă launchd via XPC, et la logique de launchctl y a Ă©tĂ© dĂ©placĂ©e.
Voyons exactement comment les applications sont arrĂȘtĂ©es. Avant d'envoyer le signal SIGTERM, l'application est tentĂ©e d'ĂȘtre arrĂȘtĂ©e Ă l'aide de l'appel systĂšme « 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));
}
...
<>
Sous le capot, proc_terminate, malgré son nom, peut envoyer non seulement un signal p avec SIGTERM, mais aussi SIGKILL.
Tuerie indirecte â Limite de ressources
Un cas plus intĂ©ressant peut ĂȘtre vu dans un autre appel systĂšme . Une utilisation courante de cet appel systĂšme consiste Ă limiter les ressources de l'application, par exemple pour qu'un indexeur limite le temps CPU et les quotas de mĂ©moire afin que le systĂšme ne soit pas significativement ralenti par les activitĂ©s de mise en cache des fichiers. Si une application a atteint sa limite de ressources, comme le montre la fonction proc_apply_resource_actions, un signal SIGKILL est envoyĂ© au processus.
Bien que cet appel systÚme puisse potentiellement tuer un processus, le systÚme n'a pas vérifié de maniÚre adéquate les droits du processus appelant l'appel systÚme. En fait, je vérifie , mais il suffit d'utiliser l'indicateur alternatif PROC_POLICY_ACTION_SET pour contourner cette condition.
Par consĂ©quent, si vous « limitez » le quota dâutilisation du processeur de lâapplication (par exemple, nâautorisez que 1 ns Ă sâexĂ©cuter), vous pouvez alors tuer nâimporte quel processus du systĂšme. Ainsi, le malware peut tuer nâimporte quel processus du systĂšme, y compris le processus antivirus. L'effet qui se produit lors de la suppression d'un processus avec le pid 1 (launchctl) est Ă©galement intĂ©ressant - panique du noyau lors de la tentative de traitement du signal SIGKILL :)

Comment résoudre le problÚme?
Le moyen le plus simple d'empĂȘcher la suppression d'un processus consiste Ă remplacer le pointeur de fonction dans la table des appels systĂšme. Malheureusement, cette mĂ©thode nâest pas triviale pour plusieurs raisons.
PremiĂšrement, le symbole qui contrĂŽle l'emplacement mĂ©moire du systĂšme est non seulement privĂ© du symbole du noyau XNU, mais ne peut pas ĂȘtre trouvĂ© dans les symboles du noyau. Vous devrez utiliser des mĂ©thodes de recherche heuristiques, telles que le dĂ©sassemblage dynamique de la fonction et la recherche d'un pointeur dans celle-ci.
DeuxiÚmement, la structure des entrées dans le tableau dépend des options avec lesquelles le noyau a été compilé. Si le flag CONFIG_REQUIRES_U32_MUNGING est déclaré, la taille de la structure sera modifiée - un champ supplémentaire sera ajouté . Il est nécessaire d'effectuer une vérification supplémentaire pour déterminer avec quel indicateur le noyau a été compilé, ou bien de vérifier les pointeurs de fonction par rapport à ceux connus.
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
*/
};
Heureusement, dans les versions modernes, macOS Apple propose une nouvelle API pour la gestion des processus. L'API Endpoint Security permet aux clients d'autoriser de nombreuses requĂȘtes adressĂ©es Ă d'autres processus. Par exemple, tout signal destinĂ© aux processus, y compris le signal SIGKILL, peut ĂȘtre bloquĂ© grĂące Ă cette 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;
}
De mĂȘme, une stratĂ©gie MAC peut ĂȘtre enregistrĂ©e dans le noyau, qui fournit une mĂ©thode de protection du signal (politique proc_check_signal), mais l'API n'est pas officiellement prise en charge.
Protection des extensions du noyau
En plus de protĂ©ger les processus du systĂšme, il est Ă©galement nĂ©cessaire de protĂ©ger l'extension du noyau (kext) elle-mĂȘme. macOS IOKit fournit un environnement de dĂ©veloppement simplifiĂ© pour les pilotes de pĂ©riphĂ©riques IOKit. Outre les outils de gestion des pĂ©riphĂ©riques, IOKit prend en charge l'empilement de pilotes via des instances de classes C++. Une application utilisateur peut ainsi trouver une instance de classe enregistrĂ©e pour Ă©tablir la communication entre le noyau et l'espace utilisateur.
Pour détecter le nombre d'instances de classe dans le systÚme, il existe l'utilitaire ioclasscount.
my_kext_ioservice = 1
my_kext_iouserclient = 1
Toute extension du noyau qui souhaite s'enregistrer auprÚs de la pile de pilotes doit déclarer une classe qui hérite de IOService, par exemple my_kext_ioservice dans ce cas. La connexion des applications utilisateur provoque la création d'une nouvelle instance de la classe qui hérite de IOUserClient, dans l'exemple my_kext_iouserclient.
Lorsque vous essayez de décharger un pilote du systÚme (commande kextunload), la fonction virtuelle « bool terminate(IOOptionBits options) » est appelée. Il suffit de renvoyer false lors de l'appel à terminer lors de la tentative de déchargement pour désactiver kextunload.
bool Kext::terminate(IOOptionBits options)
{
if (!IsUnloadAllowed)
{
// Unload is not allowed, returning false
return false;
}
return super::terminate(options);
}
L'indicateur IsUnloadAllowed peut ĂȘtre dĂ©fini par IOUserClient lors du chargement. Lorsqu'il existe une limite de tĂ©lĂ©chargement, la commande kextunload renvoie le rĂ©sultat suivant :
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.
Une protection similaire doit ĂȘtre effectuĂ©e pour IOUserClient. Les instances de classes peuvent ĂȘtre dĂ©chargĂ©es Ă l'aide de la fonction d'espace utilisateur IOKitLib « IOCatalogueTerminate (mach_port_t, uint32_t flag, io_name_t description) ; ». Vous pouvez renvoyer false lors de l'appel de la commande « terminate » jusqu'Ă ce que l'application de l'espace utilisateur « meure », c'est-Ă -dire que la fonction « clientDied » ne soit pas appelĂ©e.
Protection des fichiers
Pour protĂ©ger les fichiers, il suffit d'utiliser l'API Kauth, qui permet de restreindre l'accĂšs aux fichiers. Apple fournit aux dĂ©veloppeurs des notifications sur divers Ă©vĂ©nements dans le cadre ; pour nous, les opĂ©rations KAUTH_VNODE_DELETE, KAUTH_VNODE_WRITE_DATA et KAUTH_VNODE_DELETE_CHILD sont importantes. Le moyen le plus simple de restreindre l'accĂšs aux fichiers est par chemin - nous utilisons l'API « vn_getpath » pour obtenir le chemin d'accĂšs au fichier et comparer le prĂ©fixe du chemin. A noter que pour optimiser le renommage des chemins des dossiers de fichiers, le systĂšme n'autorise pas l'accĂšs Ă chaque fichier, mais uniquement au dossier lui-mĂȘme qui a Ă©tĂ© renommĂ©. Il est nĂ©cessaire de comparer le chemin parent et de restreindre KAUTH_VNODE_DELETE pour celui-ci.

L'inconvĂ©nient de cette approche peut ĂȘtre une faible performance Ă mesure que le nombre de prĂ©fixes augmente. Pour vous assurer que la comparaison n'est pas Ă©gale Ă O(prefix*length), oĂč prefix est le nombre de prĂ©fixes, length est la longueur de la chaĂźne, vous pouvez utiliser un automate fini dĂ©terministe (DFA), construit par prĂ©fixes.
ConsidĂ©rons une mĂ©thode pour construire un DFA pour un ensemble donnĂ© de prĂ©fixes. On initialise les curseurs au dĂ©but de chaque prĂ©fixe. Si tous les curseurs pointent vers le mĂȘme caractĂšre, augmentez chaque curseur d'un caractĂšre et rappelez-vous que la longueur de la mĂȘme ligne est supĂ©rieure d'un caractĂšre. S'il y a deux curseurs avec des symboles diffĂ©rents, divisez les curseurs en groupes en fonction du symbole vers lequel ils pointent et rĂ©pĂ©tez l'algorithme pour chaque groupe.
Dans le premier cas (tous les caractĂšres sous les curseurs sont les mĂȘmes), on obtient un Ă©tat DFA qui n'a qu'une seule transition le long de la mĂȘme ligne. Dans le second cas, on obtient un tableau de transitions de taille 256 (nombre de caractĂšres et nombre maximum de groupes) vers les Ă©tats suivants obtenus en appelant rĂ©cursivement la fonction.
Regardons un exemple. Pour un ensemble de prĂ©fixes (« /foo/bar/tmp/ », « /var/db/foo/ », « /foo/bar/aba/ », « foo/bar/aac/ »), vous pouvez obtenir ce qui suit DFAE. La figure ne montre que les transitions menant Ă dâautres Ă©tats ; les autres transitions ne seront pas dĂ©finitives.

En passant par les états DKA, il peut y avoir 3 cas.
- L'état final est atteint - le chemin est protégé, on limite les opérations KAUTH_VNODE_DELETE, KAUTH_VNODE_WRITE_DATA et KAUTH_VNODE_DELETE_CHILD
- L'état final n'a pas été atteint, mais le chemin "s'est terminé" (le terminateur nul a été atteint) - le chemin est un parent, il faut limiter KAUTH_VNODE_DELETE. Notez que si vnode est un dossier, vous devez ajouter un '/' à la fin, sinon cela risque de le limiter au fichier « /foor/bar/t », ce qui est incorrect.
- LâĂ©tat final nâa pas Ă©tĂ© atteint, le chemin nâa pas pris fin. Aucun des prĂ©fixes ne correspond Ă celui-ci, nous n'introduisons aucune restriction.
Conclusion
L'objectif du dĂ©veloppement de solutions de sĂ©curitĂ© est d'accroĂźtre le niveau de sĂ©curitĂ© des utilisateurs et de leurs donnĂ©es. Cet objectif est atteint grĂące au dĂ©veloppement du logiciel Acronis, qui corrige les vulnĂ©rabilitĂ©s du systĂšme d'exploitation lui-mĂȘme. Par ailleurs, il ne faut pas nĂ©gliger le renforcement des aspects de sĂ©curitĂ© pouvant ĂȘtre amĂ©liorĂ©s au niveau du systĂšme d'exploitation, d'autant plus que la correction de ces vulnĂ©rabilitĂ©s renforce la rĂ©silience de notre produit. La vulnĂ©rabilitĂ© a Ă©tĂ© signalĂ©e Ă l'Ă©quipe de sĂ©curitĂ© des produits Apple et a Ă©tĂ© corrigĂ©e. macOS 10.14.5 (https://support.apple.com/en-gb/HT210119).

Tout cela n'est possible que si votre utilitaire a Ă©tĂ© officiellement installĂ© dans le noyau. Cela signifie qu'il n'existe aucune faille de sĂ©curitĂ© pour les logiciels externes et indĂ©sirables. Cependant, comme vous pouvez le constater, mĂȘme la protection des programmes lĂ©gitimes tels que les antivirus et les systĂšmes de sauvegarde exige un certain effort. Mais dĂ©sormais, de nouveaux produits Acronis pour macOS bĂ©nĂ©ficiera d'une protection supplĂ©mentaire contre le dĂ©chargement du systĂšme.
Source: habr.com
