macOS MACF

Reading time: 11 minutes

tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE)

Soutenir HackTricks

Informations de base

MACF signifie Mandatory Access Control Framework, qui est un système de sécurité intégré au système d'exploitation pour aider à protéger votre ordinateur. Il fonctionne en établissant des règles strictes sur qui ou quoi peut accéder à certaines parties du système, telles que des fichiers, des applications et des ressources système. En appliquant automatiquement ces règles, MACF garantit que seuls les utilisateurs et processus autorisés peuvent effectuer des actions spécifiques, réduisant ainsi le risque d'accès non autorisé ou d'activités malveillantes.

Notez que MACF ne prend pas vraiment de décisions, car il intercepte simplement les actions, laissant les décisions aux modules de politique (extensions du noyau) qu'il appelle comme AppleMobileFileIntegrity.kext, Quarantine.kext, Sandbox.kext, TMSafetyNet.kext et mcxalr.kext.

Flux

  1. Le processus effectue un appel système/trap mach
  2. La fonction pertinente est appelée à l'intérieur du noyau
  3. La fonction appelle MACF
  4. MACF vérifie les modules de politique qui ont demandé à accrocher cette fonction dans leur politique
  5. MACF appelle les politiques pertinentes
  6. Les politiques indiquent si elles autorisent ou refusent l'action

caution

Apple est le seul à pouvoir utiliser le KPI du cadre MAC.

Étiquettes

MACF utilise des étiquettes que les politiques vérifieront ensuite pour déterminer si elles doivent accorder un accès ou non. Le code de la déclaration de structure des étiquettes peut être trouvé ici, qui est ensuite utilisé à l'intérieur de la struct ucred dans ici dans la partie cr_label. L'étiquette contient des indicateurs et un nombre de slots qui peuvent être utilisés par les politiques MACF pour allouer des pointeurs. Par exemple, Sanbox pointera vers le profil du conteneur.

Politiques MACF

Une politique MACF définit des règles et des conditions à appliquer dans certaines opérations du noyau.

Une extension de noyau pourrait configurer une structure mac_policy_conf puis l'enregistrer en appelant mac_policy_register. À partir de ici:

c
#define mpc_t	struct mac_policy_conf *

/**
@brief Mac policy configuration

This structure specifies the configuration information for a
MAC policy module.  A policy module developer must supply
a short unique policy name, a more descriptive full name, a list of label
namespaces and count, a pointer to the registered enty point operations,
any load time flags, and optionally, a pointer to a label slot identifier.

The Framework will update the runtime flags (mpc_runtime_flags) to
indicate that the module has been registered.

If the label slot identifier (mpc_field_off) is NULL, the Framework
will not provide label storage for the policy.  Otherwise, the
Framework will store the label location (slot) in this field.

The mpc_list field is used by the Framework and should not be
modified by policies.
*/
/* XXX - reorder these for better aligment on 64bit platforms */
struct mac_policy_conf {
const char		*mpc_name;		/** policy name */
const char		*mpc_fullname;		/** full name */
const char		**mpc_labelnames;	/** managed label namespaces */
unsigned int		 mpc_labelname_count;	/** number of managed label namespaces */
struct mac_policy_ops	*mpc_ops;		/** operation vector */
int			 mpc_loadtime_flags;	/** load time flags */
int			*mpc_field_off;		/** label slot */
int			 mpc_runtime_flags;	/** run time flags */
mpc_t			 mpc_list;		/** List reference */
void			*mpc_data;		/** module data */
};

Il est facile d'identifier les extensions du noyau configurant ces politiques en vérifiant les appels à mac_policy_register. De plus, en vérifiant le désassemblage de l'extension, il est également possible de trouver la structure mac_policy_conf utilisée.

Notez que les politiques MACF peuvent également être enregistrées et désenregistrées dynamiquement.

L'un des principaux champs de la mac_policy_conf est le mpc_ops. Ce champ spécifie les opérations qui intéressent la politique. Notez qu'il y en a des centaines, il est donc possible de les mettre toutes à zéro et ensuite de sélectionner uniquement celles qui intéressent la politique. D'après ici:

c
struct mac_policy_ops {
mpo_audit_check_postselect_t		*mpo_audit_check_postselect;
mpo_audit_check_preselect_t		*mpo_audit_check_preselect;
mpo_bpfdesc_label_associate_t		*mpo_bpfdesc_label_associate;
mpo_bpfdesc_label_destroy_t		*mpo_bpfdesc_label_destroy;
mpo_bpfdesc_label_init_t		*mpo_bpfdesc_label_init;
mpo_bpfdesc_check_receive_t		*mpo_bpfdesc_check_receive;
mpo_cred_check_label_update_execve_t	*mpo_cred_check_label_update_execve;
mpo_cred_check_label_update_t		*mpo_cred_check_label_update;
[...]

Presque tous les hooks seront rappelés par MACF lorsque l'une de ces opérations est interceptée. Cependant, les hooks mpo_policy_* sont une exception car mpo_hook_policy_init() est un rappel appelé lors de l'enregistrement (donc après mac_policy_register()) et mpo_hook_policy_initbsd() est appelé lors de l'enregistrement tardif une fois que le sous-système BSD a été correctement initialisé.

De plus, le hook mpo_policy_syscall peut être enregistré par n'importe quel kext pour exposer une interface d'appel de style ioctl privée. Ensuite, un client utilisateur pourra appeler mac_syscall (#381) en spécifiant comme paramètres le nom de la politique avec un code entier et des arguments optionnels.
Par exemple, le Sandbox.kext utilise cela beaucoup.

Vérifier le __DATA.__const* du kext permet d'identifier la structure mac_policy_ops utilisée lors de l'enregistrement de la politique. Il est possible de la trouver car son pointeur se trouve à un décalage à l'intérieur de mpo_policy_conf et aussi à cause du nombre de pointeurs NULL qui seront dans cette zone.

De plus, il est également possible d'obtenir la liste des kexts qui ont configuré une politique en vidant de la mémoire la structure _mac_policy_list qui est mise à jour avec chaque politique qui est enregistrée.

Initialisation de MACF

MACF est initialisé très tôt. Il est configuré dans le bootstrap_thread de XNU : après ipc_bootstrap, un appel à mac_policy_init() qui initialise la mac_policy_list et quelques instants plus tard mac_policy_initmach() est appelé. Parmi d'autres choses, cette fonction obtiendra tous les kexts Apple avec la clé AppleSecurityExtension dans leur Info.plist comme ALF.kext, AppleMobileFileIntegrity.kext, Quarantine.kext, Sandbox.kext et TMSafetyNet.kext et les charge.

Appels MACF

Il est courant de trouver des appels à MACF définis dans le code comme : #if CONFIG_MAC blocs conditionnels. De plus, à l'intérieur de ces blocs, il est possible de trouver des appels à mac_proc_check* qui appelle MACF pour vérifier les permissions pour effectuer certaines actions. De plus, le format des appels MACF est : mac_<object>_<opType>_opName.

L'objet est l'un des suivants : bpfdesc, cred, file, proc, vnode, mount, devfs, ifnet, inpcb, mbuf, ipq, pipe, sysv[msg/msq/shm/sem], posix[shm/sem], socket, kext.
Le opType est généralement check qui sera utilisé pour autoriser ou refuser l'action. Cependant, il est également possible de trouver notify, ce qui permettra au kext de réagir à l'action donnée.

Vous pouvez trouver un exemple dans https://github.com/apple-oss-distributions/xnu/blob/94d3b452840153a99b38a3a9659680b2a006908e/bsd/kern/kern_mman.c#L621:

int
mmap(proc_t p, struct mmap_args *uap, user_addr_t *retval)
{
[...]
#if CONFIG_MACF
			error = mac_file_check_mmap(vfs_context_ucred(ctx),
			    fp->fp_glob, prot, flags, file_pos + pageoff,
&maxprot);
if (error) {
(void)vnode_put(vp);
goto bad;
}
#endif /* MAC */
[...]

Ensuite, il est possible de trouver le code de mac_file_check_mmap dans https://github.com/apple-oss-distributions/xnu/blob/94d3b452840153a99b38a3a9659680b2a006908e/security/mac_file.c#L174

c
mac_file_check_mmap(struct ucred *cred, struct fileglob *fg, int prot,
int flags, uint64_t offset, int *maxprot)
{
int error;
int maxp;

maxp = *maxprot;
MAC_CHECK(file_check_mmap, cred, fg, NULL, prot, flags, offset, &maxp);
if ((maxp | *maxprot) != *maxprot) {
panic("file_check_mmap increased max protections");
}
*maxprot = maxp;
return error;
}

Qui appelle la macro MAC_CHECK, dont le code peut être trouvé dans https://github.com/apple-oss-distributions/xnu/blob/94d3b452840153a99b38a3a9659680b2a006908e/security/mac_internal.h#L261

c
/*
* MAC_CHECK performs the designated check by walking the policy
* module list and checking with each as to how it feels about the
* request.  Note that it returns its value via 'error' in the scope
* of the caller.
*/
#define MAC_CHECK(check, args...) do {                              \
error = 0;                                                      \
MAC_POLICY_ITERATE({                                            \
if (mpc->mpc_ops->mpo_ ## check != NULL) {              \
DTRACE_MACF3(mac__call__ ## check, void *, mpc, int, error, int, MAC_ITERATE_CHECK); \
int __step_err = mpc->mpc_ops->mpo_ ## check (args); \
DTRACE_MACF2(mac__rslt__ ## check, void *, mpc, int, __step_err); \
error = mac_error_select(__step_err, error);         \
}                                                           \
});                                                             \
} while (0)

Qui passera en revue toutes les politiques mac enregistrées en appelant leurs fonctions et en stockant la sortie dans la variable d'erreur, qui ne pourra être remplacée que par mac_error_select par des codes de succès, donc si un contrôle échoue, le contrôle complet échouera et l'action ne sera pas autorisée.

tip

Cependant, rappelez-vous que tous les appels MACF ne sont pas utilisés uniquement pour refuser des actions. Par exemple, mac_priv_grant appelle le macro MAC_GRANT, qui accordera le privilège demandé si une politique répond avec un 0 :

/*
 * MAC_GRANT effectue la vérification désignée en parcourant la liste des modules de politique
 * et en vérifiant avec chacun d'eux ce qu'il en pense de la
 * demande. Contrairement à MAC_CHECK, il accorde si des politiques retournent '0',
 * et retourne sinon EPERM. Notez qu'il retourne sa valeur via
 * 'error' dans le contexte de l'appelant.
 */
#define MAC_GRANT(check, args...) do {                              \
    error = EPERM;                                                  \
    MAC_POLICY_ITERATE({                                            \
	if (mpc->mpc_ops->mpo_ ## check != NULL) {                  \
	        DTRACE_MACF3(mac__call__ ## check, void *, mpc, int, error, int, MAC_ITERATE_GRANT); \
	        int __step_res = mpc->mpc_ops->mpo_ ## check (args); \
	        if (__step_res == 0) {                              \
	                error = 0;                                  \
	        }                                                   \
	        DTRACE_MACF2(mac__rslt__ ## check, void *, mpc, int, __step_res); \
	    }                                                           \
    });                                                             \
} while (0)

priv_check & priv_grant

Ces appels sont destinés à vérifier et à fournir (des dizaines de) privilèges définis dans bsd/sys/priv.h.
Certaines parties du code du noyau appelleraient priv_check_cred() depuis bsd/kern/kern_priv.c avec les informations d'identification KAuth du processus et l'un des codes de privilège qui appellera mac_priv_check pour voir si une politique refuse d'accorder le privilège, puis elle appelle mac_priv_grant pour voir si une politique accorde le privilège.

proc_check_syscall_unix

Ce hook permet d'intercepter tous les appels système. Dans bsd/dev/[i386|arm]/systemcalls.c, il est possible de voir la fonction déclarée unix_syscall, qui contient ce code :

c
#if CONFIG_MACF
if (__improbable(proc_syscall_filter_mask(proc) != NULL && !bitstr_test(proc_syscall_filter_mask(proc), syscode))) {
error = mac_proc_check_syscall_unix(proc, syscode);
if (error) {
goto skip_syscall;
}
}
#endif /* CONFIG_MACF */

Qui vérifiera dans le processus appelant bitmask si l'appel système actuel doit appeler mac_proc_check_syscall_unix. Cela est dû au fait que les appels système sont effectués si fréquemment qu'il est intéressant d'éviter d'appeler mac_proc_check_syscall_unix à chaque fois.

Notez que la fonction proc_set_syscall_filter_mask(), qui définit le bitmask des appels système dans un processus, est appelée par Sandbox pour définir des masques sur les processus isolés.

Appels système MACF exposés

Il est possible d'interagir avec MACF via certains appels système définis dans security/mac.h:

c
/*
* Extended non-POSIX.1e interfaces that offer additional services
* available from the userland and kernel MAC frameworks.
*/
#ifdef __APPLE_API_PRIVATE
__BEGIN_DECLS
int      __mac_execve(char *fname, char **argv, char **envv, mac_t _label);
int      __mac_get_fd(int _fd, mac_t _label);
int      __mac_get_file(const char *_path, mac_t _label);
int      __mac_get_link(const char *_path, mac_t _label);
int      __mac_get_pid(pid_t _pid, mac_t _label);
int      __mac_get_proc(mac_t _label);
int      __mac_set_fd(int _fildes, const mac_t _label);
int      __mac_set_file(const char *_path, mac_t _label);
int      __mac_set_link(const char *_path, mac_t _label);
int      __mac_mount(const char *type, const char *path, int flags, void *data,
struct mac *label);
int      __mac_get_mount(const char *path, struct mac *label);
int      __mac_set_proc(const mac_t _label);
int      __mac_syscall(const char *_policyname, int _call, void *_arg);
__END_DECLS
#endif /*__APPLE_API_PRIVATE*/

Références

tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE)

Soutenir HackTricks