macOS MACF

Reading time: 9 minutes

tip

Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Підтримайте HackTricks

Основна інформація

MACF означає Систему обов'язкового контролю доступу, яка є системою безпеки, вбудованою в операційну систему для захисту вашого комп'ютера. Вона працює, встановлюючи суворі правила щодо того, хто або що може отримати доступ до певних частин системи, таких як файли, програми та системні ресурси. Автоматично enforcing ці правила, MACF забезпечує, що лише авторизовані користувачі та процеси можуть виконувати конкретні дії, зменшуючи ризик несанкціонованого доступу або шкідливих дій.

Зверніть увагу, що MACF насправді не приймає жодних рішень, оскільки просто перехоплює дії, залишаючи рішення модулям політики (розширенням ядра), які вона викликає, таким як AppleMobileFileIntegrity.kext, Quarantine.kext, Sandbox.kext, TMSafetyNet.kext та mcxalr.kext.

Потік

  1. Процес виконує syscall/mach trap
  2. Відповідна функція викликається всередині ядра
  3. Функція викликає MACF
  4. MACF перевіряє модулі політики, які запросили підключити цю функцію у своїй політиці
  5. MACF викликає відповідні політики
  6. Політики вказують, чи дозволяють або відмовляють у дії

caution

Apple є єдиною компанією, яка може використовувати KPI MAC Framework.

Мітки

MACF використовує мітки, які потім політики перевіряють, чи повинні вони надати доступ чи ні. Код оголошення структури міток можна знайти тут, який потім використовується всередині struct ucred в тут в частині cr_label. Мітка містить прапори та кількість слотів, які можуть бути використані політиками MACF для виділення вказівників. Наприклад, Sanbox вказуватиме на профіль контейнера.

Політики MACF

Політика MACF визначає правила та умови, які застосовуються до певних операцій ядра.

Розширення ядра може налаштувати структуру mac_policy_conf, а потім зареєструвати її, викликавши mac_policy_register. З тут:

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

Легко ідентифікувати розширення ядра, які налаштовують ці політики, перевіряючи виклики до mac_policy_register. Більше того, перевіряючи дизасемблювання розширення, також можна знайти використану структуру mac_policy_conf.

Зверніть увагу, що політики MACF можуть бути зареєстровані та скасовані також динамічно.

Одним з основних полів mac_policy_conf є mpc_ops. Це поле вказує, які операції цікавлять політику. Зверніть увагу, що їх сотні, тому можливо обнулити всі з них, а потім вибрати лише ті, які цікавлять політику. З тут:

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

Практично всі хуки будуть викликані MACF, коли одна з цих операцій буде перехоплена. Однак, mpo_policy_* хуки є винятком, оскільки mpo_hook_policy_init() є зворотним викликом, який викликається під час реєстрації (тобто після mac_policy_register()), а mpo_hook_policy_initbsd() викликається під час пізньої реєстрації, коли підсистема BSD була правильно ініціалізована.

Більше того, mpo_policy_syscall хук може бути зареєстрований будь-яким kext для відкриття приватного ioctl стилю виклику інтерфейсу. Тоді клієнт користувача зможе викликати mac_syscall (#381), вказуючи в якості параметрів ім'я політики з цілим кодом та необов'язковими аргументами.
Наприклад, Sandbox.kext використовує це дуже часто.

Перевіряючи __DATA.__const* kext, можна ідентифікувати структуру mac_policy_ops, яка використовується під час реєстрації політики. Це можливо знайти, оскільки її вказівник знаходиться на зсуві всередині mpo_policy_conf, а також через кількість NULL вказівників, які будуть у цій області.

Крім того, також можливо отримати список kext, які налаштували політику, скидаючи з пам'яті структуру _mac_policy_list, яка оновлюється з кожною зареєстрованою політикою.

Ініціалізація MACF

MACF ініціалізується дуже швидко. Він налаштовується в bootstrap_thread XNU: після ipc_bootstrap викликається mac_policy_init(), який ініціалізує mac_policy_list, а через кілька моментів викликається mac_policy_initmach(). Серед іншого, ця функція отримає всі Apple kext з ключем AppleSecurityExtension у їх Info.plist, такі як ALF.kext, AppleMobileFileIntegrity.kext, Quarantine.kext, Sandbox.kext та TMSafetyNet.kext і завантажить їх.

Виклики MACF

Зазвичай можна знайти виклики до MACF, визначені в коді, такі як: #if CONFIG_MAC умовні блоки. Більше того, всередині цих блоків можна знайти виклики до mac_proc_check*, які викликають MACF для перевірки дозволів на виконання певних дій. Крім того, формат викликів MACF є: mac_<object>_<opType>_opName.

Об'єкт є одним з наступних: bpfdesc, cred, file, proc, vnode, mount, devfs, ifnet, inpcb, mbuf, ipq, pipe, sysv[msg/msq/shm/sem], posix[shm/sem], socket, kext.
opType зазвичай є check, який буде використовуватися для дозволу або заборони дії. Однак також можливо знайти notify, що дозволить kext реагувати на дану дію.

Ви можете знайти приклад у 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 */
[...]

Тоді можна знайти код mac_file_check_mmap у 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;
}

Який викликає макрос MAC_CHECK, код якого можна знайти за посиланням 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)

Який пройде через всі зареєстровані політики mac, викликаючи їх функції та зберігаючи вихідні дані в змінній error, яка може бути перевизначена лише mac_error_select кодами успіху, тому якщо будь-яка перевірка не пройде, вся перевірка зазнає невдачі, і дія не буде дозволена.

tip

Однак пам'ятайте, що не всі виклики MACF використовуються лише для відмови в діях. Наприклад, mac_priv_grant викликає макрос MAC_GRANT, який надасть запитувану привілегію, якщо будь-яка політика відповість 0:

/*
 * MAC_GRANT виконує призначену перевірку, проходячи через список
 * модулів політики та перевіряючи з кожним, як він ставиться до
 * запиту. На відміну від MAC_CHECK, він надає, якщо будь-які політики повертають '0',
 * і в іншому випадку повертає EPERM. Зверніть увагу, що він повертає своє значення через
 * 'error' в області видимості виклику.
 */
#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

Ці виклики призначені для перевірки та надання (десятків) привілеїв, визначених у bsd/sys/priv.h.
Деякий код ядра викликатиме priv_check_cred() з bsd/kern/kern_priv.c з KAuth обліковими даними процесу та одним з кодів привілеїв, який викликатиме mac_priv_check, щоб перевірити, чи будь-яка політика відмовляє у наданні привілею, а потім викликає mac_priv_grant, щоб перевірити, чи будь-яка політика надає привілей.

proc_check_syscall_unix

Цей хук дозволяє перехоплювати всі системні виклики. У bsd/dev/[i386|arm]/systemcalls.c можна побачити оголошену функцію unix_syscall, яка містить цей код:

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

Який перевірить у викликаючому процесі бітову маску, чи слід поточному системному виклику викликати mac_proc_check_syscall_unix. Це пов'язано з тим, що системні виклики викликаються так часто, що цікаво уникати виклику mac_proc_check_syscall_unix щоразу.

Зверніть увагу, що функція proc_set_syscall_filter_mask(), яка встановлює бітову маску системних викликів у процесі, викликається Sandbox для встановлення масок на пісочницях.

Відкриті системні виклики MACF

Можливо взаємодіяти з MACF через деякі системні виклики, визначені в 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*/

Посилання

tip

Вивчайте та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вивчайте та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Підтримайте HackTricks