macOS MACF

Reading time: 10 minutes

tip

Aprende y practica AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Apoya a HackTricks

Información Básica

MACF significa Marco de Control de Acceso Obligatorio, que es un sistema de seguridad integrado en el sistema operativo para ayudar a proteger tu computadora. Funciona estableciendo reglas estrictas sobre quién o qué puede acceder a ciertas partes del sistema, como archivos, aplicaciones y recursos del sistema. Al hacer cumplir estas reglas automáticamente, MACF asegura que solo los usuarios y procesos autorizados puedan realizar acciones específicas, reduciendo el riesgo de acceso no autorizado o actividades maliciosas.

Ten en cuenta que MACF realmente no toma decisiones, ya que solo intercepta acciones, deja las decisiones a los módulos de política (extensiones del kernel) que llama como AppleMobileFileIntegrity.kext, Quarantine.kext, Sandbox.kext, TMSafetyNet.kext y mcxalr.kext.

Flujo

  1. El proceso realiza una llamada al syscall/trampa mach
  2. La función relevante se llama dentro del kernel
  3. La función llama a MACF
  4. MACF verifica los módulos de política que solicitaron enganchar esa función en su política
  5. MACF llama a las políticas relevantes
  6. Las políticas indican si permiten o deniegan la acción

caution

Apple es el único que puede usar el KPI del Marco MAC.

Etiquetas

MACF utiliza etiquetas que luego las políticas comprobarán si deben otorgar algún acceso o no. El código de la declaración de la estructura de etiquetas se puede encontrar aquí, que se utiliza dentro de la struct ucred en aquí en la parte de cr_label. La etiqueta contiene banderas y un número de slots que pueden ser utilizados por políticas MACF para asignar punteros. Por ejemplo, Sanbox apuntará al perfil del contenedor.

Políticas MACF

Una Política MACF define reglas y condiciones que se aplican en ciertas operaciones del kernel.

Una extensión del kernel podría configurar una estructura mac_policy_conf y luego registrarla llamando a mac_policy_register. Desde aquí:

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

Es fácil identificar las extensiones del kernel que configuran estas políticas al verificar las llamadas a mac_policy_register. Además, al revisar el desensamblado de la extensión, también es posible encontrar la estructura mac_policy_conf utilizada.

Tenga en cuenta que las políticas de MACF pueden registrarse y anularse también dinámicamente.

Uno de los campos principales de mac_policy_conf es mpc_ops. Este campo especifica qué operaciones le interesan a la política. Tenga en cuenta que hay cientos de ellas, por lo que es posible establecer todas en cero y luego seleccionar solo las que le interesan a la política. Desde aquí:

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;
[...]

Casi todos los hooks serán llamados por MACF cuando se intercepten una de esas operaciones. Sin embargo, los hooks mpo_policy_* son una excepción porque mpo_hook_policy_init() es un callback llamado al registrarse (después de mac_policy_register()) y mpo_hook_policy_initbsd() se llama durante el registro tardío una vez que el subsistema BSD se ha inicializado correctamente.

Además, el hook mpo_policy_syscall puede ser registrado por cualquier kext para exponer una interfaz de llamada de estilo ioctl privada. Luego, un cliente de usuario podrá llamar a mac_syscall (#381) especificando como parámetros el nombre de la política con un código entero y argumentos opcionales.
Por ejemplo, Sandbox.kext utiliza esto mucho.

Revisando el __DATA.__const* del kext es posible identificar la estructura mac_policy_ops utilizada al registrar la política. Es posible encontrarla porque su puntero está en un desplazamiento dentro de mpo_policy_conf y también debido a la cantidad de punteros NULL que habrá en esa área.

Además, también es posible obtener la lista de kexts que han configurado una política volcando de la memoria la estructura _mac_policy_list que se actualiza con cada política que se registra.

Inicialización de MACF

MACF se inicializa muy pronto. Se configura en el bootstrap_thread de XNU: después de ipc_bootstrap se llama a mac_policy_init() que inicializa la mac_policy_list y momentos después se llama a mac_policy_initmach(). Entre otras cosas, esta función obtendrá todos los kexts de Apple con la clave AppleSecurityExtension en su Info.plist como ALF.kext, AppleMobileFileIntegrity.kext, Quarantine.kext, Sandbox.kext y TMSafetyNet.kext y los carga.

Llamadas de MACF

Es común encontrar llamadas a MACF definidas en el código como: bloques condicionales #if CONFIG_MAC. Además, dentro de estos bloques es posible encontrar llamadas a mac_proc_check* que llaman a MACF para verificar permisos para realizar ciertas acciones. Además, el formato de las llamadas de MACF es: mac_<object>_<opType>_opName.

El objeto es uno de los siguientes: bpfdesc, cred, file, proc, vnode, mount, devfs, ifnet, inpcb, mbuf, ipq, pipe, sysv[msg/msq/shm/sem], posix[shm/sem], socket, kext.
El opType suele ser check que se utilizará para permitir o denegar la acción. Sin embargo, también es posible encontrar notify, que permitirá al kext reaccionar a la acción dada.

Puedes encontrar un ejemplo en 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 */
[...]

Luego, es posible encontrar el código de mac_file_check_mmap en 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;
}

El cual está llamando al macro MAC_CHECK, cuyo código se puede encontrar en 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)

Lo que revisará todas las políticas de mac registradas llamando a sus funciones y almacenando la salida dentro de la variable de error, que solo será sobreescribible por mac_error_select mediante códigos de éxito, por lo que si alguna verificación falla, la verificación completa fallará y la acción no será permitida.

tip

Sin embargo, recuerda que no todos los llamados de MACF se utilizan solo para denegar acciones. Por ejemplo, mac_priv_grant llama al macro MAC_GRANT, que otorgará el privilegio solicitado si alguna política responde con un 0:

/*
 * MAC_GRANT realiza la verificación designada al recorrer la lista de
 * módulos de políticas y consultando con cada uno sobre cómo se siente
 * respecto a la solicitud. A diferencia de MAC_CHECK, otorga si
 * alguna política devuelve '0', y de lo contrario devuelve EPERM.
 * Ten en cuenta que devuelve su valor a través de 'error' en el
 * ámbito del llamador.
 */
#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

Estas llamadas están destinadas a verificar y proporcionar (decenas de) privilegios definidos en bsd/sys/priv.h.
Algún código del kernel llamaría a priv_check_cred() desde bsd/kern/kern_priv.c con las credenciales KAuth del proceso y uno de los códigos de privilegios que llamará a mac_priv_check para ver si alguna política deniega otorgar el privilegio y luego llama a mac_priv_grant para ver si alguna política otorga el privilegio.

proc_check_syscall_unix

Este gancho permite interceptar todas las llamadas al sistema. En bsd/dev/[i386|arm]/systemcalls.c es posible ver la función declarada unix_syscall, que contiene este código:

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

Que verificará en el proceso que llama bitmask si la syscall actual debería llamar a mac_proc_check_syscall_unix. Esto se debe a que las syscalls se llaman con tanta frecuencia que es interesante evitar llamar a mac_proc_check_syscall_unix cada vez.

Tenga en cuenta que la función proc_set_syscall_filter_mask(), que establece la bitmask de las syscalls en un proceso, es llamada por Sandbox para establecer máscaras en procesos en sandbox.

Syscalls MACF expuestas

Es posible interactuar con MACF a través de algunas syscalls definidas en 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*/

Referencias

tip

Aprende y practica AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Apoya a HackTricks