macOS Library Injection

Support HackTricks

Код dyld є відкритим і його можна знайти за адресою https://opensource.apple.com/source/dyld/ і завантажити tar, використовуючи URL, такий як https://opensource.apple.com/tarballs/dyld/dyld-852.2.tar.gz

Dyld Process

Подивіться, як Dyld завантажує бібліотеки всередині бінарних файлів у:

macOS Dyld Process

DYLD_INSERT_LIBRARIES

Це схоже на LD_PRELOAD на Linux. Це дозволяє вказати процес, який буде запущено, щоб завантажити конкретну бібліотеку з шляху (якщо змінна середовища увімкнена)

Цю техніку також можна використовувати як техніку ASEP, оскільки кожен встановлений додаток має plist під назвою "Info.plist", який дозволяє призначати змінні середовища за допомогою ключа LSEnvironmental.

З 2012 року Apple різко зменшила можливості DYLD_INSERT_LIBRARIES.

Перейдіть до коду та перевірте src/dyld.cpp. У функції pruneEnvironmentVariables ви можете побачити, що DYLD_* змінні видаляються.

У функції processRestricted встановлюється причина обмеження. Перевіряючи цей код, ви можете побачити, що причини такі:

  • Бінарний файл є setuid/setgid

  • Наявність секції __RESTRICT/__restrict у бінарному файлі macho.

  • Програмне забезпечення має права (посилена середа виконання) без прав com.apple.security.cs.allow-dyld-environment-variables

  • Перевірте права бінарного файлу за допомогою: codesign -dv --entitlements :- </path/to/bin>

У більш нових версіях ви можете знайти цю логіку в другій частині функції configureProcessRestrictions. Однак те, що виконується в новіших версіях, є початковими перевірками функції (ви можете видалити if, пов'язані з iOS або емуляцією, оскільки вони не будуть використовуватися в macOS.

Library Validation

Навіть якщо бінарний файл дозволяє використовувати змінну середовища DYLD_INSERT_LIBRARIES, якщо бінарний файл перевіряє підпис бібліотеки для завантаження, він не завантажить кастомну бібліотеку.

Щоб завантажити кастомну бібліотеку, бінарний файл повинен мати одне з наступних прав:

або бінарний файл не повинен мати прапор посиленої середовища виконання або прапор перевірки бібліотек.

Ви можете перевірити, чи має бінарний файл посилену середу виконання за допомогою codesign --display --verbose <bin>, перевіряючи прапор runtime у CodeDirectory так: CodeDirectory v=20500 size=767 flags=0x10000(runtime) hashes=13+7 location=embedded

Ви також можете завантажити бібліотеку, якщо вона підписана тим же сертифікатом, що й бінарний файл.

Знайдіть приклад, як (зловживати) цим і перевірте обмеження в:

macOS Dyld Hijacking & DYLD_INSERT_LIBRARIES

Dylib Hijacking

Пам'ятайте, що попередні обмеження перевірки бібліотек також застосовуються для виконання атак на викрадення Dylib.

Як і в Windows, в MacOS ви також можете викрадати dylibs, щоб змусити додатки виконувати произвольний код (насправді, з боку звичайного користувача це може бути неможливо, оскільки вам може знадобитися дозвіл TCC, щоб записати в пакет .app і викрасти бібліотеку). Однак спосіб, яким додатки MacOS завантажують бібліотеки, є більш обмеженим, ніж у Windows. Це означає, що розробники шкідливого ПЗ все ще можуть використовувати цю техніку для прихованості, але ймовірність того, що вони зможуть зловживати цим для підвищення привілеїв, значно нижча.

По-перше, більш поширено знаходити, що бінарні файли MacOS вказують повний шлях до бібліотек для завантаження. По-друге, MacOS ніколи не шукає в папках $PATH для бібліотек.

Основна частина коду, пов'язана з цією функціональністю, знаходиться в ImageLoader::recursiveLoadLibraries у ImageLoader.cpp.

Існує 4 різні команди заголовка, які бінарний файл macho може використовувати для завантаження бібліотек:

  • LC_LOAD_DYLIB команда є звичайною командою для завантаження dylib.

  • LC_LOAD_WEAK_DYLIB команда працює як попередня, але якщо dylib не знайдено, виконання продовжується без жодної помилки.

  • LC_REEXPORT_DYLIB команда проксі (або повторно експортує) символи з іншої бібліотеки.

  • LC_LOAD_UPWARD_DYLIB команда використовується, коли дві бібліотеки залежать одна від одної (це називається вгору залежністю).

Однак існує 2 типи викрадення dylib:

  • Відсутні слабко пов'язані бібліотеки: Це означає, що додаток спробує завантажити бібліотеку, яка не існує, налаштовану з LC_LOAD_WEAK_DYLIB. Тоді, якщо зловмисник помістить dylib туди, де її очікують, вона буде завантажена.

  • Той факт, що зв'язок "слабкий", означає, що додаток продовжить працювати, навіть якщо бібліотека не знайдена.

  • Код, пов'язаний з цим, знаходиться у функції ImageLoaderMachO::doGetDependentLibraries у ImageLoaderMachO.cpp, де lib->required є лише false, коли LC_LOAD_WEAK_DYLIB є true.

  • Знайдіть слабко пов'язані бібліотеки у бінарних файлах за допомогою (у вас пізніше буде приклад, як створити бібліотеки для викрадення):

otool -l </path/to/bin> | grep LC_LOAD_WEAK_DYLIB -A 5 cmd LC_LOAD_WEAK_DYLIB cmdsize 56 name /var/tmp/lib/libUtl.1.dylib (offset 24) time stamp 2 Wed Jun 21 12:23:31 1969 current version 1.0.0 compatibility version 1.0.0

* **Налаштовано з @rpath**: Бінарні файли Mach-O можуть мати команди **`LC_RPATH`** та **`LC_LOAD_DYLIB`**. Виходячи з **значень** цих команд, **бібліотеки** будуть **завантажені** з **різних директорій**.
* **`LC_RPATH`** містить шляхи до деяких папок, які використовуються для завантаження бібліотек бінарним файлом.
* **`LC_LOAD_DYLIB`** містить шлях до конкретних бібліотек для завантаження. Ці шляхи можуть містити **`@rpath`**, який буде **замінений** значеннями в **`LC_RPATH`**. Якщо в **`LC_RPATH`** є кілька шляхів, всі вони будуть використані для пошуку бібліотеки для завантаження. Приклад:
* Якщо **`LC_LOAD_DYLIB`** містить `@rpath/library.dylib`, а **`LC_RPATH`** містить `/application/app.app/Contents/Framework/v1/` та `/application/app.app/Contents/Framework/v2/`. Обидві папки будуть використані для завантаження `library.dylib`**.** Якщо бібліотека не існує в `[...]/v1/`, зловмисник може помістити її туди, щоб викрасти завантаження бібліотеки в `[...]/v2/`, оскільки порядок шляхів у **`LC_LOAD_DYLIB`** дотримується.
* **Знайдіть шляхи rpath та бібліотеки** у бінарних файлах за допомогою: `otool -l </path/to/binary> | grep -E "LC_RPATH|LC_LOAD_DYLIB" -A 5`

<div data-gb-custom-block data-tag="hint" data-style='info'>

**`@executable_path`**: Це **шлях** до директорії, що містить **основний виконуваний файл**.

**`@loader_path`**: Це **шлях** до **директорії**, що містить **Mach-O бінарний файл**, який містить команду завантаження.

* Коли використовується в виконуваному файлі, **`@loader_path`** фактично є **тим же**, що й **`@executable_path`**.
* Коли використовується в **dylib**, **`@loader_path`** дає **шлях** до **dylib**.

</div>

Спосіб **підвищення привілеїв**, зловживаючи цією функціональністю, буде в рідкісному випадку, коли **додаток**, що виконується **root**, **шукає** якусь **бібліотеку в якійсь папці, де зловмисник має права на запис.**

<div data-gb-custom-block data-tag="hint" data-style='success'>

Чудовий **сканер** для знаходження **відсутніх бібліотек** у додатках - це [**Dylib Hijack Scanner**](https://objective-see.com/products/dhs.html) або [**CLI версія**](https://github.com/pandazheng/DylibHijack).\
Чудовий **звіт з технічними деталями** про цю техніку можна знайти [**тут**](https://www.virusbulletin.com/virusbulletin/2015/03/dylib-hijacking-os-x).

</div>

**Example**

<div data-gb-custom-block data-tag="content-ref" data-url='macos-dyld-hijacking-and-dyld_insert_libraries.md'>

[macos-dyld-hijacking-and-dyld\_insert\_libraries.md](macos-dyld-hijacking-and-dyld\_insert\_libraries.md)

</div>

## Dlopen Hijacking

<div data-gb-custom-block data-tag="hint" data-style='danger'>

Пам'ятайте, що **попередні обмеження перевірки бібліотек також застосовуються** для виконання атак на викрадення Dlopen.

</div>

З **`man dlopen`**:

* Коли шлях **не містить символа слешу** (тобто це лише ім'я), **dlopen() буде шукати**. Якщо **`$DYLD_LIBRARY_PATH`** було встановлено під час запуску, dyld спочатку **шукатиме в цій директорії**. Далі, якщо викликаючий mach-o файл або основний виконуваний файл вказують **`LC_RPATH`**, тоді dyld **шукатиме в цих** директоріях. Далі, якщо процес є **необмеженим**, dyld буде шукати в **поточній робочій директорії**. Нарешті, для старих бінарних файлів dyld спробує деякі резервні варіанти. Якщо **`$DYLD_FALLBACK_LIBRARY_PATH`** було встановлено під час запуску, dyld буде шукати в **цих директоріях**, інакше dyld буде шукати в **`/usr/local/lib/`** (якщо процес є необмеженим), а потім у **`/usr/lib/`** (ця інформація була взята з **`man dlopen`**).
1. `$DYLD_LIBRARY_PATH`
2. `LC_RPATH`
3. `CWD`(якщо необмежений)
4. `$DYLD_FALLBACK_LIBRARY_PATH`
5. `/usr/local/lib/` (якщо необмежений)
6. `/usr/lib/`

<div data-gb-custom-block data-tag="hint" data-style='danger'>

Якщо немає слешів в імені, буде 2 способи зробити викрадення:

* Якщо будь-який **`LC_RPATH`** є **записуваним** (але підпис перевіряється, тому для цього вам також потрібно, щоб бінарний файл був необмеженим)
* Якщо бінарний файл є **необмеженим**, тоді можливо завантажити щось з CWD (або зловживаючи однією з згаданих змінних середовища)

</div>

* Коли шлях **схожий на шлях фреймворка** (наприклад, `/stuff/foo.framework/foo`), якщо **`$DYLD_FRAMEWORK_PATH`** було встановлено під час запуску, dyld спочатку шукатиме в цій директорії для **часткового шляху фреймворка** (наприклад, `foo.framework/foo`). Далі dyld спробує **вказаний шлях як є** (використовуючи поточну робочу директорію для відносних шляхів). Нарешті, для старих бінарних файлів dyld спробує деякі резервні варіанти. Якщо **`$DYLD_FALLBACK_FRAMEWORK_PATH`** було встановлено під час запуску, dyld буде шукати в цих директоріях. Інакше, він буде шукати в **`/Library/Frameworks`** (на macOS, якщо процес є необмеженим), потім **`/System/Library/Frameworks`**.
1. `$DYLD_FRAMEWORK_PATH`
2. вказаний шлях (використовуючи поточну робочу директорію для відносних шляхів, якщо необмежений)
3. `$DYLD_FALLBACK_FRAMEWORK_PATH`
4. `/Library/Frameworks` (якщо необмежений)
5. `/System/Library/Frameworks`

<div data-gb-custom-block data-tag="hint" data-style='danger'>

Якщо шлях фреймворка, спосіб викрадення полягатиме в:

* Якщо процес є **необмеженим**, зловживаючи **відносним шляхом з CWD** та згаданими змінними середовища (навіть якщо в документації не сказано, що якщо процес обмежений, змінні середовища DYLD\_\* видаляються)

</div>

* Коли шлях **містить слеш, але не є шляхом фреймворка** (тобто повний шлях або частковий шлях до dylib), dlopen() спочатку шукає (якщо встановлено) у **`$DYLD_LIBRARY_PATH`** (з частиною шляху). Далі dyld **пробує вказаний шлях** (використовуючи поточну робочу директорію для відносних шляхів (але лише для необмежених процесів)). Нарешті, для старіших бінарних файлів dyld спробує резервні варіанти. Якщо **`$DYLD_FALLBACK_LIBRARY_PATH`** було встановлено під час запуску, dyld буде шукати в цих директоріях, інакше dyld буде шукати в **`/usr/local/lib/`** (якщо процес є необмеженим), а потім у **`/usr/lib/`**.
1. `$DYLD_LIBRARY_PATH`
2. вказаний шлях (використовуючи поточну робочу директорію для відносних шляхів, якщо необмежений)
3. `$DYLD_FALLBACK_LIBRARY_PATH`
4. `/usr/local/lib/` (якщо необмежений)
5. `/usr/lib/`

<div data-gb-custom-block data-tag="hint" data-style='danger'>

Якщо в імені є слеші і це не фреймворк, спосіб викрадення полягатиме в:

* Якщо бінарний файл є **необмеженим**, тоді можливо завантажити щось з CWD або `/usr/local/lib` (або зловживаючи однією з згаданих змінних середовища)

</div>

<div data-gb-custom-block data-tag="hint" data-style='info'>

Примітка: Немає **конфігураційних файлів для контролю пошуку dlopen**.

Примітка: Якщо основний виконуваний файл є **set\[ug]id бінарним файлом або підписаний з правами**, тоді **всі змінні середовища ігноруються**, і можна використовувати лише повний шлях ([перевірте обмеження DYLD\_INSERT\_LIBRARIES](macos-dyld-hijacking-and-dyld\_insert\_libraries.md#check-dyld\_insert\_librery-restrictions) для більш детальної інформації)

Примітка: Платформи Apple використовують "універсальні" файли для поєднання 32-бітних і 64-бітних бібліотек. Це означає, що **немає окремих 32-бітних і 64-бітних шляхів пошуку**.

Примітка: На платформах Apple більшість OS dylibs **об'єднані в кеш dyld** і не існують на диску. Тому виклик **`stat()`** для попередньої перевірки, чи існує OS dylib, **не спрацює**. Однак **`dlopen_preflight()`** використовує ті ж кроки, що й **`dlopen()`**, щоб знайти сумісний mach-o файл.

</div>

**Check paths**

Давайте перевіримо всі варіанти за допомогою наступного коду:
```c
// gcc dlopentest.c -o dlopentest -Wl,-rpath,/tmp/test
#include <dlfcn.h>
#include <stdio.h>

int main(void)
{
void* handle;

fprintf("--- No slash ---\n");
handle = dlopen("just_name_dlopentest.dylib",1);
if (!handle) {
fprintf(stderr, "Error loading: %s\n\n\n", dlerror());
}

fprintf("--- Relative framework ---\n");
handle = dlopen("a/framework/rel_framework_dlopentest.dylib",1);
if (!handle) {
fprintf(stderr, "Error loading: %s\n\n\n", dlerror());
}

fprintf("--- Abs framework ---\n");
handle = dlopen("/a/abs/framework/abs_framework_dlopentest.dylib",1);
if (!handle) {
fprintf(stderr, "Error loading: %s\n\n\n", dlerror());
}

fprintf("--- Relative Path ---\n");
handle = dlopen("a/folder/rel_folder_dlopentest.dylib",1);
if (!handle) {
fprintf(stderr, "Error loading: %s\n\n\n", dlerror());
}

fprintf("--- Abs Path ---\n");
handle = dlopen("/a/abs/folder/abs_folder_dlopentest.dylib",1);
if (!handle) {
fprintf(stderr, "Error loading: %s\n\n\n", dlerror());
}

return 0;
}

Якщо ви скомпілюєте та виконаєте це, ви зможете побачити де кожну бібліотеку шукали безуспішно. Також ви можете фільтрувати журнали FS:

sudo fs_usage | grep "dlopentest"

Relative Path Hijacking

Якщо привілейований бінар/додаток (наприклад, SUID або якийсь бінар з потужними правами) завантажує бібліотеку з відносним шляхом (наприклад, використовуючи @executable_path або @loader_path) і має відключену валідацію бібліотек, може бути можливим перемістити бінар у місце, де зловмисник може модифікувати бібліотеку з відносним шляхом, і зловживати цим для ін'єкції коду в процес.

Prune DYLD_* and LD_LIBRARY_PATH env variables

У файлі dyld-dyld-832.7.1/src/dyld2.cpp можна знайти функцію pruneEnvironmentVariables, яка видалить будь-яку змінну середовища, що починається з DYLD_ та LD_LIBRARY_PATH=.

Вона також встановить в null конкретно змінні середовища DYLD_FALLBACK_FRAMEWORK_PATH та DYLD_FALLBACK_LIBRARY_PATH для suid та sgid бінарів.

Ця функція викликається з функції _main того ж файлу, якщо націлена на OSX таким чином:

#if TARGET_OS_OSX
if ( !gLinkContext.allowEnvVarsPrint && !gLinkContext.allowEnvVarsPath && !gLinkContext.allowEnvVarsSharedCache ) {
pruneEnvironmentVariables(envp, &apple);

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

#if TARGET_OS_OSX
// support chrooting from old kernel
bool isRestricted = false;
bool libraryValidation = false;
// any processes with setuid or setgid bit set or with __RESTRICT segment is restricted
if ( issetugid() || hasRestrictedSegment(mainExecutableMH) ) {
isRestricted = true;
}
bool usingSIP = (csr_check(CSR_ALLOW_TASK_FOR_PID) != 0);
uint32_t flags;
if ( csops(0, CS_OPS_STATUS, &flags, sizeof(flags)) != -1 ) {
// On OS X CS_RESTRICT means the program was signed with entitlements
if ( ((flags & CS_RESTRICT) == CS_RESTRICT) && usingSIP ) {
isRestricted = true;
}
// Library Validation loosens searching but requires everything to be code signed
if ( flags & CS_REQUIRE_LV ) {
isRestricted = false;
libraryValidation = true;
}
}
gLinkContext.allowAtPaths                = !isRestricted;
gLinkContext.allowEnvVarsPrint           = !isRestricted;
gLinkContext.allowEnvVarsPath            = !isRestricted;
gLinkContext.allowEnvVarsSharedCache     = !libraryValidation || !usingSIP;
gLinkContext.allowClassicFallbackPaths   = !isRestricted;
gLinkContext.allowInsertFailures         = false;
gLinkContext.allowInterposing         	 = true;

Яке в основному означає, що якщо бінарний файл є suid або sgid, або має сегмент RESTRICT у заголовках, або був підписаний з прапором CS_RESTRICT, тоді !gLinkContext.allowEnvVarsPrint && !gLinkContext.allowEnvVarsPath && !gLinkContext.allowEnvVarsSharedCache є істинним, і змінні середовища обрізаються.

Зверніть увагу, що якщо CS_REQUIRE_LV є істинним, тоді змінні не будуть обрізані, але валідація бібліотеки перевірить, чи використовують вони той же сертифікат, що й оригінальний бінарний файл.

Перевірка обмежень

SUID & SGID

# Make it owned by root and suid
sudo chown root hello
sudo chmod +s hello
# Insert the library
DYLD_INSERT_LIBRARIES=inject.dylib ./hello

# Remove suid
sudo chmod -s hello

Розділ __RESTRICT з сегментом __restrict

gcc -sectcreate __RESTRICT __restrict /dev/null hello.c -o hello-restrict
DYLD_INSERT_LIBRARIES=inject.dylib ./hello-restrict

Ускладнений виконуваний код

Створіть новий сертифікат у Ключниці та використайте його для підписання бінарного файлу:

# Apply runtime proetction
codesign -s <cert-name> --option=runtime ./hello
DYLD_INSERT_LIBRARIES=inject.dylib ./hello #Library won't be injected

# Apply library validation
codesign -f -s <cert-name> --option=library ./hello
DYLD_INSERT_LIBRARIES=inject.dylib ./hello-signed #Will throw an error because signature of binary and library aren't signed by same cert (signs must be from a valid Apple-signed developer certificate)

# Sign it
## If the signature is from an unverified developer the injection will still work
## If it's from a verified developer, it won't
codesign -f -s <cert-name> inject.dylib
DYLD_INSERT_LIBRARIES=inject.dylib ./hello-signed

# Apply CS_RESTRICT protection
codesign -f -s <cert-name> --option=restrict hello-signed
DYLD_INSERT_LIBRARIES=inject.dylib ./hello-signed # Won't work

Зверніть увагу, що навіть якщо є бінарні файли, підписані з прапорами 0x0(none), вони можуть отримати прапор CS_RESTRICT динамічно під час виконання, і тому ця техніка не спрацює в них.

Ви можете перевірити, чи має процес цей прапор за допомогою (отримати csops тут):

csops -status <pid>

і потім перевірте, чи увімкнено прапор 0x800.

Посилання

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

Last updated