macOS Library Injection

Impara l'hacking di AWS da zero a eroe con htARTE (Esperto Red Team AWS di HackTricks)!

Altri modi per supportare HackTricks:

Il codice di dyld è open source e può essere trovato su https://opensource.apple.com/source/dyld/ e può essere scaricato come un tar utilizzando un URL come https://opensource.apple.com/tarballs/dyld/dyld-852.2.tar.gz

Processo Dyld

Dai un'occhiata a come Dyld carica le librerie all'interno dei binari in:

pagemacOS Dyld Process

DYLD_INSERT_LIBRARIES

Questo è simile al LD_PRELOAD su Linux. Consente di indicare a un processo che verrà eseguito per caricare una libreria specifica da un percorso (se la variabile di ambiente è abilitata)

Questa tecnica può essere anche usata come tecnica ASEP poiché ogni applicazione installata ha un plist chiamato "Info.plist" che consente di assegnare variabili ambientali utilizzando una chiave chiamata LSEnvironmental.

Dal 2012 Apple ha drasticamente ridotto il potere del DYLD_INSERT_LIBRARIES.

Vai al codice e controlla src/dyld.cpp. Nella funzione pruneEnvironmentVariables puoi vedere che le variabili DYLD_* vengono rimosse.

Nella funzione processRestricted viene impostato il motivo della restrizione. Controllando quel codice puoi vedere che i motivi sono:

  • Il binario è setuid/setgid

  • Esistenza della sezione __RESTRICT/__restrict nel binario macho.

  • Il software ha entitlements (runtime protetto) senza l'entitlement com.apple.security.cs.allow-dyld-environment-variables

  • Controlla gli entitlements di un binario con: codesign -dv --entitlements :- </percorso/al/bin>

Nelle versioni più aggiornate puoi trovare questa logica nella seconda parte della funzione configureProcessRestrictions. Tuttavia, ciò che viene eseguito nelle versioni più recenti sono i controlli iniziali della funzione (puoi rimuovere gli if relativi a iOS o simulazione poiché non verranno utilizzati in macOS.

Validazione delle Librerie

Anche se il binario consente di utilizzare la variabile di ambiente DYLD_INSERT_LIBRARIES, se il binario controlla la firma della libreria da caricare, non caricherà una libreria personalizzata.

Per caricare una libreria personalizzata, il binario deve avere uno dei seguenti entitlements:

o il binario non dovrebbe avere il flag hardened runtime o il flag di validazione della libreria.

Puoi verificare se un binario ha il hardened runtime con codesign --display --verbose <bin> controllando il flag runtime in CodeDirectory come: CodeDirectory v=20500 size=767 flags=0x10000(runtime) hashes=13+7 location=embedded

Puoi anche caricare una libreria se è firmata con lo stesso certificato del binario.

Trova un esempio su come (ab)usare questo e controllare le restrizioni in:

pagemacOS Dyld Hijacking & DYLD_INSERT_LIBRARIES

Dylib Hijacking

Ricorda che si applicano anche le restrizioni precedenti alla Validazione delle Librerie per eseguire attacchi di Dylib hijacking.

Come in Windows, su MacOS è possibile dirottare dylibs per far eseguire codice arbitrario dalle applicazioni (beh, in realtà da un utente normale questo potrebbe non essere possibile poiché potresti aver bisogno di un permesso TCC per scrivere all'interno di un bundle .app e dirottare una libreria). Tuttavia, il modo in cui le applicazioni MacOS caricano le librerie è più limitato rispetto a Windows. Ciò implica che gli sviluppatori di malware possono comunque utilizzare questa tecnica per furtività, ma la probabilità di poter abusare di questo per ottenere privilegi è molto più bassa.

Innanzitutto, è più comune trovare che i binari MacOS indicano il percorso completo delle librerie da caricare. E in secondo luogo, MacOS non cerca mai nelle cartelle del $PATH per le librerie.

La parte principale del codice relativa a questa funzionalità si trova in ImageLoader::recursiveLoadLibraries in ImageLoader.cpp.

Ci sono 4 diversi comandi dell'intestazione che un binario macho può utilizzare per caricare librerie:

  • Il comando LC_LOAD_DYLIB è il comando comune per caricare una dylib.

  • Il comando LC_LOAD_WEAK_DYLIB funziona come il precedente, ma se la dylib non viene trovata, l'esecuzione continua senza errori.

  • Il comando LC_REEXPORT_DYLIB procura (o ri-esporta) i simboli da una libreria diversa.

  • Il comando LC_LOAD_UPWARD_DYLIB viene utilizzato quando due librerie dipendono l'una dall'altra (questo è chiamato una dipendenza ascendente).

Tuttavia, ci sono 2 tipi di dirottamento dylib:

  • Librerie mancanti con collegamento debole: Questo significa che l'applicazione cercherà di caricare una libreria che non esiste configurata con LC_LOAD_WEAK_DYLIB. Quindi, se un attaccante inserisce una dylib dove ci si aspetta che venga caricata, verrà caricata.

  • Il fatto che il collegamento sia "debole" significa che l'applicazione continuerà a funzionare anche se la libreria non viene trovata.

  • Il codice relativo a questo si trova nella funzione ImageLoaderMachO::doGetDependentLibraries di ImageLoaderMachO.cpp dove lib->required è vero solo quando LC_LOAD_WEAK_DYLIB è vero.

  • Trova librerie con collegamento debole nei binari con (successivamente hai un esempio su come creare librerie di dirottamento):

otool -l </percorso/al/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

* **Configurato con @rpath**: I binari Mach-O possono avere i comandi **`LC_RPATH`** e **`LC_LOAD_DYLIB`**. In base ai **valori** di quei comandi, le **librerie** verranno **caricate** da **directory diverse**.
* **`LC_RPATH`** contiene i percorsi di alcune cartelle utilizzate per caricare le librerie dal binario.
* **`LC_LOAD_DYLIB`** contiene il percorso delle librerie specifiche da caricare. Questi percorsi possono contenere **`@rpath`**, che verrà **sostituito** dai valori in **`LC_RPATH`**. Se ci sono diversi percorsi in **`LC_RPATH`**, ognuno verrà utilizzato per cercare la libreria da caricare. Esempio:
* Se **`LC_LOAD_DYLIB`** contiene `@rpath/library.dylib` e **`LC_RPATH`** contiene `/application/app.app/Contents/Framework/v1/` e `/application/app.app/Contents/Framework/v2/`. Entrambe le cartelle verranno utilizzate per caricare `library.dylib`. Se la libreria non esiste in `[...]/v1/` e l'attaccante potrebbe collocarla lì per dirottare il caricamento della libreria in `[...]/v2/` poiché viene seguito l'ordine dei percorsi in **`LC_LOAD_DYLIB`**.
* **Trova i percorsi rpath e le librerie** nei binari con: `otool -l </percorso/al/binario> | grep -E "LC_RPATH|LC_LOAD_DYLIB" -A 5`

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

**`@executable_path`**: È il **percorso** della directory contenente il **file eseguibile principale**.

**`@loader_path`**: È il **percorso** della **directory** contenente il **binario Mach-O** che contiene il comando di caricamento.

* Quando usato in un eseguibile, **`@loader_path`** è effettivamente lo **stesso** di **`@executable_path`**.
* Quando usato in una **dylib**, **`@loader_path`** fornisce il **percorso** della **dylib**.

</div>

Il modo per **escalare i privilegi** abusando di questa funzionalità sarebbe nel raro caso in cui un'applicazione in esecuzione **da** **root** sta **cercando** una **libreria in una cartella in cui l'attaccante ha le autorizzazioni di scrittura.**

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

Un ottimo **scanner** per trovare **librerie mancanti** nelle applicazioni è [**Dylib Hijack Scanner**](https://objective-see.com/products/dhs.html) o una [**versione CLI**](https://github.com/pandazheng/DylibHijack).\
Un bel **report con dettagli tecnici** su questa tecnica può essere trovato [**qui**](https://www.virusbulletin.com/virusbulletin/2015/03/dylib-hijacking-os-x).

</div>

**Esempio**

<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'>

Ricorda che si applicano anche le **restrizioni precedenti alla convalida delle librerie** per eseguire attacchi di Dlopen hijacking.

</div>

Da **`man dlopen`**:

* Quando il percorso **non contiene un carattere slash** (cioè è solo un nome di foglia), **dlopen() effettuerà una ricerca**. Se **`$DYLD_LIBRARY_PATH`** era impostato all'avvio, dyld cercherà prima in quella directory. Successivamente, se il file mach-o chiamante o l'eseguibile principale specificano un **`LC_RPATH`**, allora dyld cercherà in quelle directory. Successivamente, se il processo è **non limitato**, dyld cercherà nella **directory di lavoro corrente**. Infine, per i binari più vecchi, dyld proverà alcuni fallback. Se **`$DYLD_FALLBACK_LIBRARY_PATH`** era impostato all'avvio, dyld cercherà in quelle directory, altrimenti dyld cercherà in **`/usr/local/lib/`** (se il processo è non limitato), e poi in **`/usr/lib/`** (queste informazioni sono tratte da **`man dlopen`**).
1. `$DYLD_LIBRARY_PATH`
2. `LC_RPATH`
3. `CWD`(se non limitato)
4. `$DYLD_FALLBACK_LIBRARY_PATH`
5. `/usr/local/lib/` (se non limitato)
6. `/usr/lib/`

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

Se non ci sono slash nel nome, ci sarebbero 2 modi per fare un dirottamento:

* Se qualsiasi **`LC_RPATH`** è **scrivibile** (ma la firma viene verificata, quindi per questo è necessario che il binario sia non limitato)
* Se il binario è **non limitato** e quindi è possibile caricare qualcosa dalla CWD (o abusando una delle variabili di ambiente menzionate)

</div>

* Quando il percorso **sembra un percorso di framework** (ad es. `/stuff/foo.framework/foo`), se **`$DYLD_FRAMEWORK_PATH`** era impostato all'avvio, dyld cercherà prima in quella directory per il **percorso parziale del framework** (ad es. `foo.framework/foo`). Successivamente, dyld proverà il **percorso fornito così com'è** (usando la directory di lavoro corrente per i percorsi relativi). Infine, per i binari più vecchi, dyld proverà alcuni fallback. Se **`$DYLD_FALLBACK_FRAMEWORK_PATH`** era impostato all'avvio, dyld cercherà in quelle directory. Altrimenti, cercherà in **`/Library/Frameworks`** (su macOS se il processo è non limitato), quindi in **`/System/Library/Frameworks`**.
1. `$DYLD_FRAMEWORK_PATH`
2. percorso fornito (usando la directory di lavoro corrente per i percorsi relativi se non limitato)
3. `$DYLD_FALLBACK_FRAMEWORK_PATH`
4. `/Library/Frameworks` (se non limitato)
5. `/System/Library/Frameworks`

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

Se si tratta di un percorso di framework, il modo per dirottarlo sarebbe:

* Se il processo è **non limitato**, abusando del **percorso relativo dalla CWD** delle variabili di ambiente menzionate (anche se non è detto nei documenti se il processo è limitato le variabili di ambiente DYLD\_\* vengono rimosse)

</div>

* Quando il percorso **contiene uno slash ma non è un percorso di framework** (cioè un percorso completo o un percorso parziale a una dylib), dlopen() cerca prima (se impostato) in **`$DYLD_LIBRARY_PATH`** (con parte foglia dal percorso). Successivamente, dyld **prova il percorso fornito** (usando la directory di lavoro corrente per i percorsi relativi (ma solo per i processi non limitati)). Infine, per i binari più vecchi, dyld proverà i fallback. Se **`$DYLD_FALLBACK_LIBRARY_PATH`** era impostato all'avvio, dyld cercherà in quelle directory, altrimenti cercherà in **`/usr/local/lib/`** (se il processo è non limitato), e poi in **`/usr/lib/`**.
1. `$DYLD_LIBRARY_PATH`
2. percorso fornito (usando la directory di lavoro corrente per i percorsi relativi se non limitato)
3. `$DYLD_FALLBACK_LIBRARY_PATH`
4. `/usr/local/lib/` (se non limitato)
5. `/usr/lib/`

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

Se ci sono slash nel nome e non è un framework, il modo per dirottarlo sarebbe:

* Se il binario è **non limitato** e quindi è possibile caricare qualcosa dalla CWD o da `/usr/local/lib` (o abusando una delle variabili di ambiente menzionate)

</div>

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

Nota: Non ci sono **file di configurazione** per **controllare la ricerca di dlopen**.

Nota: Se l'eseguibile principale è un binario **set\[ug]id o firmato con entitlements**, allora **tutte le variabili di ambiente vengono ignorate**, e può essere utilizzato solo un percorso completo ([controlla le restrizioni di DYLD\_INSERT\_LIBRARIES](macos-dyld-hijacking-and-dyld\_insert\_libraries.md#check-dyld\_insert\_librery-restrictions) per informazioni più dettagliate)

Nota: Le piattaforme Apple utilizzano file "universal" per combinare librerie a 32 e 64 bit. Ciò significa che non ci sono **percorsi di ricerca separati per 32 e 64 bit**.

Nota: Sulle piattaforme Apple la maggior parte delle dylib di sistema è **combinata nella cache dyld** e non esiste su disco. Pertanto, chiamare **`stat()`** per verificare preventivamente se una dylib di sistema esiste **non funzionerà**. Tuttavia, **`dlopen_preflight()`** utilizza gli stessi passaggi di **`dlopen()`** per trovare un file mach-o compatibile.

</div>

**Controlla i percorsi**

Verifichiamo tutte le opzioni con il seguente codice:
```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;
}

Se lo compili ed esegui, puoi vedere dove è stata cercata senza successo ciascuna libreria. Inoltre, potresti filtrare i log del FS:

sudo fs_usage | grep "dlopentest"

Dirottamento del percorso relativo

Se un binario/app privilegiato (come un SUID o qualche binario con potenti entitlement) sta caricando una libreria con percorso relativo (ad esempio utilizzando @executable_path o @loader_path) e ha la Validazione della Libreria disabilitata, potrebbe essere possibile spostare il binario in una posizione in cui l'attaccante potrebbe modificare la libreria caricata con percorso relativo, e sfruttarla per iniettare codice nel processo.

Potatura delle variabili d'ambiente DYLD_* e LD_LIBRARY_PATH

Nel file dyld-dyld-832.7.1/src/dyld2.cpp è possibile trovare la funzione pruneEnvironmentVariables, che rimuoverà qualsiasi variabile d'ambiente che inizia con DYLD_ e LD_LIBRARY_PATH=.

Imposterà inoltre a null specificamente le variabili d'ambiente DYLD_FALLBACK_FRAMEWORK_PATH e DYLD_FALLBACK_LIBRARY_PATH per i binari suid e sgid.

Questa funzione viene chiamata dalla funzione _main dello stesso file se si sta mirando a OSX in questo modo:

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

e quei flag booleani sono impostati nello stesso file nel codice:

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

Quindi, se il binario è suid o sgid, o ha un segmento RESTRICT negli header o è stato firmato con il flag CS_RESTRICT, allora !gLinkContext.allowEnvVarsPrint && !gLinkContext.allowEnvVarsPath && !gLinkContext.allowEnvVarsSharedCache è vero e le variabili di ambiente vengono eliminate.

Si noti che se CS_REQUIRE_LV è vero, le variabili non verranno eliminate, ma la convalida della libreria verificherà che stiano utilizzando lo stesso certificato del binario originale.

Controlla le Restrizioni

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

Sezione __RESTRICT con il segmento __restrict

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

Runtime rafforzato

Crea un nuovo certificato nell'accessorio Portachiavi e usalo per firmare il binario:

# 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

Si noti che anche se ci sono binari firmati con i flag **`0x0(none)`**, possono ottenere dinamicamente il flag **`CS_RESTRICT`** quando vengono eseguiti e quindi questa tecnica non funzionerà su di essi.

È possibile verificare se un proc ha questo flag con (ottieni csops qui):

csops -status <pid>

Riferimenti

Impara l'hacking di AWS da zero a eroe con htARTE (Esperto Red Team AWS di HackTricks)!

Altri modi per supportare HackTricks:

Last updated