macOS Thread Injection via Task port

tip

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

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

Code

1. Thread Hijacking

Спочатку викликається функція task_threads() на порту задачі для отримання списку потоків з віддаленої задачі. Вибирається потік для захоплення. Цей підхід відрізняється від звичайних методів ін'єкції коду, оскільки створення нового віддаленого потоку заборонено через міру, яка блокує thread_create_running().

Для контролю потоку викликається thread_suspend(), що зупиняє його виконання.

Єдині операції, дозволені на віддаленому потоці, включають зупинку та початок його роботи, а також отримання/модифікацію значень його регістрів. Віддалені виклики функцій ініціюються шляхом встановлення регістрів x0 до x7 на аргументи, налаштування pc на цільову функцію та відновлення потоку. Забезпечення того, щоб потік не зламався після повернення, вимагає виявлення повернення.

Одна зі стратегій полягає в реєстрації обробника виключень для віддаленого потоку за допомогою thread_set_exception_ports(), встановлюючи регістр lr на недійсну адресу перед викликом функції. Це викликає виключення після виконання функції, надсилаючи повідомлення на порт виключень, що дозволяє перевірити стан потоку для відновлення значення повернення. Альтернативно, як це було прийнято з експлойту triple_fetch Іана Біра, lr встановлюється на безкінечний цикл; регістри потоку потім постійно моніторяться, поки pc не вказує на цю інструкцію.

2. Mach ports for communication

Наступний етап полягає в створенні Mach портів для полегшення зв'язку з віддаленим потоком. Ці порти є важливими для передачі довільних прав на відправлення/отримання між задачами.

Для двостороннього зв'язку створюються два права на отримання Mach: одне в локальній, а інше в віддаленій задачі. Потім право на відправлення для кожного порту передається до відповідної задачі, що дозволяє обмінюватися повідомленнями.

Зосереджуючись на локальному порту, право на отримання утримується локальною задачею. Порт створюється за допомогою mach_port_allocate(). Виклик виклику полягає в передачі права на відправлення до цього порту в віддалену задачу.

Стратегія полягає в використанні thread_set_special_port(), щоб помістити право на відправлення до локального порту в THREAD_KERNEL_PORT віддаленого потоку. Потім віддаленому потоку вказується викликати mach_thread_self(), щоб отримати право на відправлення.

Для віддаленого порту процес в основному обернений. Віддаленому потоку вказується створити Mach порт за допомогою mach_reply_port() (оскільки mach_port_allocate() не підходить через свій механізм повернення). Після створення порту викликається mach_port_insert_right() в віддаленому потоці для встановлення права на відправлення. Це право потім зберігається в ядрі за допомогою thread_set_special_port(). Повертаючись до локальної задачі, використовується thread_get_special_port() на віддаленому потоці, щоб отримати право на відправлення до новоствореного Mach порту в віддаленій задачі.

Завершення цих кроків призводить до створення Mach портів, закладаючи основу для двостороннього зв'язку.

3. Basic Memory Read/Write Primitives

У цьому розділі увага зосереджена на використанні примітиву виконання для встановлення базових примітивів читання/запису пам'яті. Ці початкові кроки є важливими для отримання більшого контролю над віддаленим процесом, хоча примітиви на цьому етапі не будуть мати багато застосувань. Незабаром вони будуть оновлені до більш просунутих версій.

Memory reading and writing using the execute primitive

Мета полягає в тому, щоб виконати читання та запис пам'яті за допомогою специфічних функцій. Для читання пам'яті:

c
uint64_t read_func(uint64_t *address) {
return *address;
}

Для запису пам'яті:

c
void write_func(uint64_t *address, uint64_t value) {
*address = value;
}

Ці функції відповідають наступному асемблеру:

_read_func:
ldr x0, [x0]
ret
_write_func:
str x1, [x0]
ret

Визначення підходящих функцій

Сканування загальних бібліотек виявило відповідні кандидати для цих операцій:

  1. Читання пам'яті — property_getName() (libobjc):
c
const char *property_getName(objc_property_t prop) {
return prop->name;
}
  1. Запис пам'яті — _xpc_int64_set_value() (libxpc):
c
__xpc_int64_set_value:
str x1, [x0, #0x18]
ret

Щоб виконати запис 64-бітного значення за довільною адресою:

c
_xpc_int64_set_value(address - 0x18, value);

З цими примітивами встановленими, сцена готова для створення спільної пам'яті, що є значним прогресом у контролі над віддаленим процесом.

4. Налаштування спільної пам'яті

Мета полягає в тому, щоб встановити спільну пам'ять між локальними та віддаленими завданнями, спрощуючи передачу даних і полегшуючи виклик функцій з кількома аргументами. Підхід використовує libxpc та його об'єкт типу OS_xpc_shmem, який побудований на основі записів пам'яті Mach.

Огляд процесу

  1. Виділення пам'яті
  • Виділити пам'ять для спільного використання за допомогою mach_vm_allocate().
  • Використати xpc_shmem_create() для створення об'єкта OS_xpc_shmem для виділеної області.
  1. Створення спільної пам'яті в віддаленому процесі
  • Виділити пам'ять для об'єкта OS_xpc_shmem у віддаленому процесі (remote_malloc).
  • Скопіювати локальний шаблон об'єкта; виправлення вбудованого права на відправлення Mach за зміщенням 0x18 все ще необхідне.
  1. Виправлення запису пам'яті Mach
  • Вставити право на відправлення за допомогою thread_set_special_port() і перезаписати поле 0x18 ім'ям віддаленого запису.
  1. Завершення
  • Перевірити віддалений об'єкт і відобразити його за допомогою віддаленого виклику xpc_shmem_remote().

5. Досягнення повного контролю

Якщо доступне довільне виконання та канал зворотного зв'язку зі спільною пам'яттю, ви ефективно володієте цільовим процесом:

  • Довільний R/W пам'яті — використовуйте memcpy() між локальними та спільними регіонами.
  • Виклики функцій з > 8 аргументами — розмістіть додаткові аргументи на стеку відповідно до виклику arm64.
  • Передача порту Mach — передавайте права в повідомленнях Mach через встановлені порти.
  • Передача дескриптора файлу — використовуйте fileports (див. triple_fetch).

Усе це обгорнуто в бібліотеці threadexec для зручного повторного використання.


6. Особливості Apple Silicon (arm64e)

На пристроях Apple Silicon (arm64e) Коди автентифікації вказівників (PAC) захищають всі адреси повернення та багато вказівників функцій. Техніки захоплення потоків, які повторно використовують існуючий код, продовжують працювати, оскільки оригінальні значення в lr/pc вже мають дійсні підписи PAC. Проблеми виникають, коли ви намагаєтеся стрибнути до пам'яті, контрольованої зловмисником:

  1. Виділити виконувану пам'ять всередині цілі (віддалений mach_vm_allocate + mprotect(PROT_EXEC)).
  2. Скопіювати ваш вантаж.
  3. Всередині віддаленого процесу підписати вказівник:
c
uint64_t ptr = (uint64_t)payload;
ptr = ptrauth_sign_unauthenticated((void*)ptr, ptrauth_key_asia, 0);
  1. Встановіть pc = ptr у стані захопленого потоку.

Альтернативно, дотримуйтесь стандарту PAC, з'єднуючи існуючі гаджети/функції (традиційний ROP).

7. Виявлення та зміцнення з EndpointSecurity

Фреймворк EndpointSecurity (ES) відкриває події ядра, які дозволяють захисникам спостерігати або блокувати спроби ін'єкції потоків:

  • ES_EVENT_TYPE_AUTH_GET_TASK – спрацьовує, коли процес запитує порт іншого завдання (наприклад, task_for_pid()).
  • ES_EVENT_TYPE_NOTIFY_REMOTE_THREAD_CREATE – виводиться щоразу, коли створюється потік у іншому завданні.
  • ES_EVENT_TYPE_NOTIFY_THREAD_SET_STATE (додано в macOS 14 Sonoma) – вказує на маніпуляцію реєстрами існуючого потоку.

Мінімальний клієнт Swift, який виводить події віддалених потоків:

swift
import EndpointSecurity

let client = try! ESClient(subscriptions: [.notifyRemoteThreadCreate]) {
(_, msg) in
if let evt = msg.remoteThreadCreate {
print("[ALERT] remote thread in pid \(evt.target.pid) by pid \(evt.thread.pid)")
}
}
RunLoop.main.run()

Запит з osquery ≥ 5.8:

sql
SELECT target_pid, source_pid, target_path
FROM es_process_events
WHERE event_type = 'REMOTE_THREAD_CREATE';

Розгляди щодо захищеного виконання

Розповсюдження вашого додатку без права com.apple.security.get-task-allow запобігає не-root зловмисникам від отримання його task-port. Захист цілісності системи (SIP) все ще блокує доступ до багатьох бінарних файлів Apple, але стороннє програмне забезпечення повинно явно відмовитися від цього.

8. Останні публічні інструменти (2023-2025)

ІнструментРікЗауваження
task_vaccine2023Компактний PoC, що демонструє захоплення потоків з урахуванням PAC на Ventura/Sonoma
remote_thread_es2024Допоміжний засіб EndpointSecurity, що використовується кількома постачальниками EDR для відображення подій REMOTE_THREAD_CREATE

Читання вихідного коду цих проектів корисне для розуміння змін API, введених в macOS 13/14, і для підтримки сумісності між Intel ↔ Apple Silicon.

Посилання

tip

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

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