macOS MACF

Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE) Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Support HackTricks

Podstawowe informacje

MACF oznacza Mandatory Access Control Framework, który jest systemem zabezpieczeń wbudowanym w system operacyjny, aby pomóc chronić komputer. Działa poprzez ustalanie ścisłych zasad dotyczących tego, kto lub co może uzyskać dostęp do określonych części systemu, takich jak pliki, aplikacje i zasoby systemowe. Dzięki automatycznemu egzekwowaniu tych zasad, MACF zapewnia, że tylko autoryzowani użytkownicy i procesy mogą wykonywać określone działania, co zmniejsza ryzyko nieautoryzowanego dostępu lub złośliwych działań.

Należy zauważyć, że MACF nie podejmuje żadnych decyzji, ponieważ po prostu przechwytuje działania, pozostawiając decyzje modułom polityki (rozszerzenia jądra), które wywołuje, takim jak AppleMobileFileIntegrity.kext, Quarantine.kext, Sandbox.kext, TMSafetyNet.kext i mcxalr.kext.

Przepływ

  1. Proces wykonuje syscall/mach trap

  2. Odpowiednia funkcja jest wywoływana wewnątrz jądra

  3. Funkcja wywołuje MACF

  4. MACF sprawdza moduły polityki, które zażądały podpięcia tej funkcji w swojej polityce

  5. MACF wywołuje odpowiednie polityki

  6. Polityki wskazują, czy zezwalają na działanie, czy je odrzucają

Apple jest jedyną firmą, która może korzystać z KPI Framework MAC.

Etykiety

MACF używa etykiet, które następnie polityki sprawdzają, czy powinny przyznać dostęp, czy nie. Kod deklaracji struktury etykiet można znaleźć tutaj, która jest następnie używana wewnątrz struct ucred w tutaj w części cr_label. Etykieta zawiera flagi i liczbę slotów, które mogą być używane przez polityki MACF do alokacji wskaźników. Na przykład Sanbox będzie wskazywał na profil kontenera.

Polityki MACF

Polityka MACF definiuje zasady i warunki, które mają być stosowane w określonych operacjach jądra.

Rozszerzenie jądra może skonfigurować strukturę mac_policy_conf, a następnie zarejestrować ją, wywołując mac_policy_register. Z tutaj:

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

Łatwo jest zidentyfikować rozszerzenia jądra konfigurowane przez te polityki, sprawdzając wywołania do mac_policy_register. Co więcej, sprawdzając dezasemblację rozszerzenia, można również znaleźć używaną strukturę mac_policy_conf.

Zauważ, że polityki MACF mogą być rejestrowane i deregisterowane również dynamicznie.

Jednym z głównych pól mac_policy_conf jest mpc_ops. To pole określa, którymi operacjami polityka jest zainteresowana. Zauważ, że jest ich setki, więc możliwe jest wyzerowanie wszystkich z nich, a następnie wybranie tylko tych, którymi polityka jest zainteresowana. Z tutaj:

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

Almost all the hooks will be called back by MACF when one of those operations are intercepted. However, mpo_policy_* hooks are an exception because mpo_hook_policy_init() is a callback called upon registration (so after mac_policy_register()) and mpo_hook_policy_initbsd() is called during late registration once the BSD subsystem has initialised properly.

Moreover, the mpo_policy_syscall hook can be registered by any kext to expose a private ioctl style call interface. Then, a user client will be able to call mac_syscall (#381) specifying as parameters the policy name with an integer code and optional arguments. For example, the Sandbox.kext uses this a lot.

Checking the kext's __DATA.__const* is possible to identify the mac_policy_ops structure used when registering the policy. It's possible to find it because its pointer is at an offset inside mpo_policy_conf and also because the amount of NULL pointers that will be in that area.

Moreover, it's also possible to get the list of kexts that have configured a policy by dumping from memory the struct _mac_policy_list which is updated with every policy that is registered.

MACF Initialization

MACF is initialised very soon. It's set up in XNU's bootstrap_thread: after ipc_bootstrap a call to mac_policy_init() which initializes the mac_policy_list and moments later mac_policy_initmach() is called. Among other things, this function will get all the Apple kexts with the AppleSecurityExtension key in their Info.plist like ALF.kext, AppleMobileFileIntegrity.kext, Quarantine.kext, Sandbox.kext and TMSafetyNet.kext and loads them.

MACF Callouts

It's common to find callouts to MACF defined in code like: #if CONFIG_MAC conditional blocks. Moreover, inside these blocks it's possible to find calls to mac_proc_check* which calls MACF to check for permissions to perform certain actions. Moreover, the format of the MACF callouts is: mac_<object>_<opType>_opName.

The object is one of the following: bpfdesc, cred, file, proc, vnode, mount, devfs, ifnet, inpcb, mbuf, ipq, pipe, sysv[msg/msq/shm/sem], posix[shm/sem], socket, kext. The opType is usually check which will be used to allow or deny the action. However, it's also possible to find notify, which will allow the kext to react to the given action.

You can find an example in 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 */
[...]

Then, it's possible to find the code of mac_file_check_mmap in https://github.com/apple-oss-distributions/xnu/blob/94d3b452840153a99b38a3a9659680b2a006908e/security/mac_file.c#L174

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

Który wywołuje makro MAC_CHECK, którego kod można znaleźć w https://github.com/apple-oss-distributions/xnu/blob/94d3b452840153a99b38a3a9659680b2a006908e/security/mac_internal.h#L261

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

Które przejdzie przez wszystkie zarejestrowane polityki mac, wywołując ich funkcje i przechowując wynik w zmiennej error, która będzie mogła być nadpisywana tylko przez mac_error_select za pomocą kodów sukcesu, więc jeśli jakiekolwiek sprawdzenie się nie powiedzie, całe sprawdzenie się nie powiedzie, a akcja nie będzie dozwolona.

Jednak pamiętaj, że nie wszystkie wywołania MACF są używane tylko do odrzucania akcji. Na przykład, mac_priv_grant wywołuje makro MAC_GRANT, które przyzna żądane uprawnienie, jeśli jakakolwiek polityka odpowie 0:

/*
* MAC_GRANT performs the designated check by walking the policy
* module list and checking with each as to how it feels about the
* request.  Unlike MAC_CHECK, it grants if any policies return '0',
* and otherwise returns EPERM.  Note that it returns its value via
* 'error' in the scope of the caller.
*/
#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

Te wywołania mają na celu sprawdzenie i przyznanie (dziesiątek) uprawnień zdefiniowanych w bsd/sys/priv.h. Niektóre kody jądra wywołają priv_check_cred() z bsd/kern/kern_priv.c z poświadczeniami KAuth procesu oraz jednym z kodów uprawnień, które wywołają mac_priv_check, aby sprawdzić, czy jakakolwiek polityka odmawia przyznania uprawnienia, a następnie wywołuje mac_priv_grant, aby sprawdzić, czy jakakolwiek polityka przyznaje uprawnienie.

proc_check_syscall_unix

Ten hak pozwala na przechwytywanie wszystkich wywołań systemowych. W bsd/dev/[i386|arm]/systemcalls.c można zobaczyć zadeklarowaną funkcję unix_syscall, która zawiera ten kod:

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

Który sprawdzi w wywołującym procesie bitmask, czy bieżący syscall powinien wywołać mac_proc_check_syscall_unix. Dzieje się tak, ponieważ syscalls są wywoływane tak często, że warto unikać wywoływania mac_proc_check_syscall_unix za każdym razem.

Zauważ, że funkcja proc_set_syscall_filter_mask(), która ustawia bitmask syscalls w procesie, jest wywoływana przez Sandbox w celu ustawienia masek na procesach w piaskownicy.

Ekspozycja syscalli MACF

Możliwe jest interakcja z MACF za pomocą niektórych syscalli zdefiniowanych w security/mac.h:

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

References

Ucz się i ćwicz Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE) Ucz się i ćwicz Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)

Wsparcie dla HackTricks

Last updated