macOS Library Injection
Reading time: 12 minutes
tip
Učite i vežbajte AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Učite i vežbajte GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Podržite HackTricks
- Proverite planove pretplate!
- Pridružite se 💬 Discord grupi ili telegram grupi ili pratite nas na Twitteru 🐦 @hacktricks_live.
- Podelite hakerske trikove slanjem PR-ova na HackTricks i HackTricks Cloud github repozitorijume.
caution
Kod dyld je otvorenog koda i može se pronaći na https://opensource.apple.com/source/dyld/ i može se preuzeti kao tar koristeći URL kao što je https://opensource.apple.com/tarballs/dyld/dyld-852.2.tar.gz
Dyld Proces
Pogledajte kako Dyld učitava biblioteke unutar binarnih datoteka u:
DYLD_INSERT_LIBRARIES
Ovo je kao LD_PRELOAD na Linuxu. Omogućava da se naznači proces koji će biti pokrenut da učita određenu biblioteku sa putanje (ako je env var omogućena)
Ova tehnika se takođe može koristiti kao ASEP tehnika jer svaka aplikacija koja je instalirana ima plist pod nazivom "Info.plist" koji omogućava dodeljivanje promenljivih okruženja koristeći ključ pod nazivom LSEnvironmental
.
note
Od 2012. Apple je drastično smanjio moć DYLD_INSERT_LIBRARIES
.
Idite na kod i proverite src/dyld.cpp
. U funkciji pruneEnvironmentVariables
možete videti da su DYLD_*
promenljive uklonjene.
U funkciji processRestricted
postavljena je svrha ograničenja. Proveravajući taj kod možete videti da su razlozi:
- Binarna datoteka je
setuid/setgid
- Postojanje
__RESTRICT/__restrict
sekcije u macho binarnoj datoteci. - Softver ima ovlašćenja (hardened runtime) bez
com.apple.security.cs.allow-dyld-environment-variables
ovlašćenja- Proverite ovlašćenja binarne datoteke sa:
codesign -dv --entitlements :- </path/to/bin>
- Proverite ovlašćenja binarne datoteke sa:
U novijim verzijama možete pronaći ovu logiku u drugom delu funkcije configureProcessRestrictions
. Međutim, ono što se izvršava u novijim verzijama su provere na početku funkcije (možete ukloniti if-ove vezane za iOS ili simulaciju jer se ti neće koristiti u macOS.
Validacija biblioteka
Čak i ako binarna datoteka dozvoljava korišćenje DYLD_INSERT_LIBRARIES
env promenljive, ako binarna datoteka proverava potpis biblioteke da je učita, neće učitati prilagođenu.
Da bi se učitala prilagođena biblioteka, binarna datoteka treba da ima jedno od sledećih ovlašćenja:
com.apple.security.cs.disable-library-validation
com.apple.private.security.clear-library-validation
ili binarna datoteka ne bi trebala imati hardened runtime flag ili flag za validaciju biblioteka.
Možete proveriti da li binarna datoteka ima hardened runtime sa codesign --display --verbose <bin>
proveravajući flag runtime u CodeDirectory
kao: CodeDirectory v=20500 size=767 flags=0x10000(runtime) hashes=13+7 location=embedded
Takođe možete učitati biblioteku ako je potpisana istim sertifikatom kao i binarna datoteka.
Pronađite primer kako da (zlo)upotrebite ovo i proverite ograničenja u:
macOS Dyld Hijacking & DYLD_INSERT_LIBRARIES
Dylib Hijacking
caution
Zapamtite da prethodna ograničenja validacije biblioteka takođe važe za izvođenje Dylib hijacking napada.
Kao i na Windows-u, na MacOS-u takođe možete oteti dylibs da naterate aplikacije da izvršavaju arbitrarni kod (pa, zapravo, od običnog korisnika to možda neće biti moguće jer bi vam mogla biti potrebna TCC dozvola da pišete unutar .app
paketa i otmete biblioteku).
Međutim, način na koji MacOS aplikacije učitavaju biblioteke je više ograničen nego na Windows-u. To implicira da malver programeri i dalje mogu koristiti ovu tehniku za neprimetnost, ali verovatnoća da će moći da zloupotrebe ovo za eskalaciju privilegija je mnogo manja.
Prvo, češće je pronaći da MacOS binarne datoteke ukazuju na punu putanju do biblioteka koje treba učitati. I drugo, MacOS nikada ne pretražuje u folderima $PATH za biblioteke.
Glavni deo koda vezan za ovu funkcionalnost je u ImageLoader::recursiveLoadLibraries
u ImageLoader.cpp
.
Postoje 4 različite komande zaglavlja koje macho binarna datoteka može koristiti za učitavanje biblioteka:
LC_LOAD_DYLIB
komanda je uobičajena komanda za učitavanje dylib-a.LC_LOAD_WEAK_DYLIB
komanda funkcioniše kao prethodna, ali ako dylib nije pronađen, izvršenje se nastavlja bez greške.LC_REEXPORT_DYLIB
komanda proxy-uje (ili ponovo izvozi) simbole iz različite biblioteke.LC_LOAD_UPWARD_DYLIB
komanda se koristi kada dve biblioteke zavise jedna od druge (ovo se naziva uzlazna zavisnost).
Međutim, postoje 2 tipa dylib hijacking:
- Nedostaju slabe povezane biblioteke: To znači da će aplikacija pokušati da učita biblioteku koja ne postoji konfigurisana sa LC_LOAD_WEAK_DYLIB. Tada, ako napadač postavi dylib gde se očekuje da će biti učitan.
- Činjenica da je veza "slaba" znači da će aplikacija nastaviti da radi čak i ako biblioteka nije pronađena.
- Kod vezan za ovo je u funkciji
ImageLoaderMachO::doGetDependentLibraries
uImageLoaderMachO.cpp
gde jelib->required
samofalse
kada jeLC_LOAD_WEAK_DYLIB
tačno. - Pronađite slabe povezane biblioteke u binarnim datotekama sa (kasnije imate primer kako da kreirate biblioteke za otmicu):
-
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
- **Konfigurisano sa @rpath**: Mach-O binarne datoteke mogu imati komande **`LC_RPATH`** i **`LC_LOAD_DYLIB`**. Na osnovu **vrednosti** tih komandi, **biblioteke** će biti **učitane** iz **različitih direktorijuma**.
- **`LC_RPATH`** sadrži putanje nekih foldera koji se koriste za učitavanje biblioteka od strane binarne datoteke.
- **`LC_LOAD_DYLIB`** sadrži putanju do specifičnih biblioteka koje treba učitati. Ove putanje mogu sadržati **`@rpath`**, koje će biti **zamenjene** vrednostima u **`LC_RPATH`**. Ako postoji više putanja u **`LC_RPATH`**, sve će se koristiti za pretragu biblioteke za učitavanje. Primer:
- Ako **`LC_LOAD_DYLIB`** sadrži `@rpath/library.dylib` i **`LC_RPATH`** sadrži `/application/app.app/Contents/Framework/v1/` i `/application/app.app/Contents/Framework/v2/`. Obe fascikle će se koristiti za učitavanje `library.dylib`**.** Ako biblioteka ne postoji u `[...]/v1/` i napadač bi mogao da je postavi tamo da otme učitavanje biblioteke u `[...]/v2/` jer se redosled putanja u **`LC_LOAD_DYLIB`** poštuje.
- **Pronađite rpath putanje i biblioteke** u binarnim datotekama sa: `otool -l </path/to/binary> | grep -E "LC_RPATH|LC_LOAD_DYLIB" -A 5`
> [!NOTE] > **`@executable_path`**: Je **putanja** do direktorijuma koji sadrži **glavnu izvršnu datoteku**.
>
> **`@loader_path`**: Je **putanja** do **direktorijuma** koji sadrži **Mach-O binarnu datoteku** koja sadrži komandu za učitavanje.
>
> - Kada se koristi u izvršnoj datoteci, **`@loader_path`** je zapravo **isto** kao **`@executable_path`**.
> - Kada se koristi u **dylib**, **`@loader_path`** daje **putanju** do **dylib**.
Način za **escalaciju privilegija** zloupotrebljavajući ovu funkcionalnost bio bi u retkom slučaju kada neka **aplikacija** koja se izvršava **od** **root-a** **traži** neku **biblioteku u nekom folderu gde napadač ima dozvole za pisanje.**
<div class="mdbook-alerts mdbook-alerts-tip">
<p class="mdbook-alerts-title">
<span class="mdbook-alerts-icon"></span>
tip
</p>
Lep **skener** za pronalaženje **nedostajućih biblioteka** u aplikacijama je [**Dylib Hijack Scanner**](https://objective-see.com/products/dhs.html) ili [**CLI verzija**](https://github.com/pandazheng/DylibHijack).\
Lep **izveštaj sa tehničkim detaljima** o ovoj tehnici može se pronaći [**ovde**](https://www.virusbulletin.com/virusbulletin/2015/03/dylib-hijacking-os-x).
</div>
**Primer**
<a class="content_ref" href="macos-dyld-hijacking-and-dyld_insert_libraries.md"><span class="content_ref_label">macOS Dyld Hijacking & DYLD_INSERT_LIBRARIES</span></a>
## Dlopen Hijacking
<div class="mdbook-alerts mdbook-alerts-caution">
<p class="mdbook-alerts-title">
<span class="mdbook-alerts-icon"></span>
caution
</p>
Zapamtite da **prethodna ograničenja validacije biblioteka takođe važe** za izvođenje Dlopen hijacking napada.
</div>
Iz **`man dlopen`**:
- Kada putanja **ne sadrži znak kose crte** (tj. to je samo naziv lista), **dlopen() će pretraživati**. Ako je **`$DYLD_LIBRARY_PATH`** postavljen prilikom pokretanja, dyld će prvo **gledati u tom direktorijumu**. Zatim, ako pozivajući mach-o fajl ili glavna izvršna datoteka specificiraju **`LC_RPATH`**, dyld će **gledati u tim** direktorijumima. Zatim, ako je proces **neograničen**, dyld će pretraživati u **trenutnom radnom direktorijumu**. Na kraju, za stare binarne datoteke, dyld će pokušati neke rezervne opcije. Ako je **`$DYLD_FALLBACK_LIBRARY_PATH`** postavljen prilikom pokretanja, dyld će pretraživati u **tim direktorijumima**, inače, dyld će gledati u **`/usr/local/lib/`** (ako je proces neograničen), a zatim u **`/usr/lib/`** (ove informacije su preuzete iz **`man dlopen`**).
1. `$DYLD_LIBRARY_PATH`
2. `LC_RPATH`
3. `CWD`(ako je neograničen)
4. `$DYLD_FALLBACK_LIBRARY_PATH`
5. `/usr/local/lib/` (ako je neograničen)
6. `/usr/lib/`
<div class="mdbook-alerts mdbook-alerts-caution">
<p class="mdbook-alerts-title">
<span class="mdbook-alerts-icon"></span>
caution
</p>
Ako nema kose crte u imenu, postoje 2 načina da se izvrši otmica:
- Ako je bilo koji **`LC_RPATH`** **pisan** (ali se potpis proverava, tako da za ovo takođe treba da binarna datoteka bude neograničena)
- Ako je binarna datoteka **neograničena** i tada je moguće učitati nešto iz CWD (ili zloupotrebiti jednu od pomenutih env promenljivih)
</div>
- Kada putanja **izgleda kao putanja okvira** (npr. `/stuff/foo.framework/foo`), ako je **`$DYLD_FRAMEWORK_PATH`** postavljen prilikom pokretanja, dyld će prvo potražiti u tom direktorijumu za **delimičnu putanju okvira** (npr. `foo.framework/foo`). Zatim, dyld će pokušati **datu putanju onako kako jeste** (koristeći trenutni radni direktorijum za relativne putanje). Na kraju, za stare binarne datoteke, dyld će pokušati neke rezervne opcije. Ako je **`$DYLD_FALLBACK_FRAMEWORK_PATH`** postavljen prilikom pokretanja, dyld će pretraživati te direktorijume. Inače, pretražiće **`/Library/Frameworks`** (na macOS-u ako je proces neograničen), zatim **`/System/Library/Frameworks`**.
1. `$DYLD_FRAMEWORK_PATH`
2. data putanja (koristeći trenutni radni direktorijum za relativne putanje ako je neograničen)
3. `$DYLD_FALLBACK_FRAMEWORK_PATH`
4. `/Library/Frameworks` (ako je neograničen)
5. `/System/Library/Frameworks`
<div class="mdbook-alerts mdbook-alerts-caution">
<p class="mdbook-alerts-title">
<span class="mdbook-alerts-icon"></span>
caution
</p>
Ako je putanja okvira, način da se otme bi bio:
- Ako je proces **neograničen**, zloupotrebljavajući **relativnu putanju iz CWD** pomenutih env promenljivih (čak i ako nije rečeno u dokumentaciji, ako je proces ograničen DYLD\_\* env varijable su uklonjene)
</div>
- Kada putanja **sadrži kosu crtu, ali nije putanja okvira** (tj. puna putanja ili delimična putanja do dylib-a), dlopen() prvo gleda u (ako je postavljeno) **`$DYLD_LIBRARY_PATH`** (sa delom lista iz putanje). Zatim, dyld **pokušava datu putanju** (koristeći trenutni radni direktorijum za relativne putanje (ali samo za neograničene procese)). Na kraju, za starije binarne datoteke, dyld će pokušati rezervne opcije. Ako je **`$DYLD_FALLBACK_LIBRARY_PATH`** postavljen prilikom pokretanja, dyld će pretraživati u tim direktorijumima, inače, dyld će gledati u **`/usr/local/lib/`** (ako je proces neograničen), a zatim u **`/usr/lib/`**.
1. `$DYLD_LIBRARY_PATH`
2. data putanja (koristeći trenutni radni direktorijum za relativne putanje ako je neograničen)
3. `$DYLD_FALLBACK_LIBRARY_PATH`
4. `/usr/local/lib/` (ako je neograničen)
5. `/usr/lib/`
<div class="mdbook-alerts mdbook-alerts-caution">
<p class="mdbook-alerts-title">
<span class="mdbook-alerts-icon"></span>
caution
</p>
Ako su u imenu kose crte i nije okvir, način da se otme bi bio:
- Ako je binarna datoteka **neograničena** i tada je moguće učitati nešto iz CWD ili `/usr/local/lib` (ili zloupotrebiti jednu od pomenutih env promenljivih)
</div>
<div class="mdbook-alerts mdbook-alerts-note">
<p class="mdbook-alerts-title">
<span class="mdbook-alerts-icon"></span>
note
</p>
Napomena: Ne postoje **konfiguracione datoteke** za **kontrolu pretrage dlopen**.
Napomena: Ako je glavna izvršna datoteka **set\[ug]id binarna datoteka ili potpisana sa ovlašćenjima**, tada se **sve promenljive okruženja ignorišu**, i može se koristiti samo puna putanja ([proverite ograničenja DYLD_INSERT_LIBRARIES](macos-dyld-hijacking-and-dyld_insert_libraries.md#check-dyld_insert_librery-restrictions) za detaljnije informacije)
Napomena: Apple platforme koriste "univerzalne" datoteke za kombinovanje 32-bitnih i 64-bitnih biblioteka. To znači da ne postoje **odvojene 32-bitne i 64-bitne putanje za pretragu**.
Napomena: Na Apple platformama većina OS dylibs je **kombinovana u dyld keš** i ne postoje na disku. Stoga, pozivanje **`stat()`** da se proveri da li OS dylib postoji **neće raditi**. Međutim, **`dlopen_preflight()`** koristi iste korake kao **`dlopen()`** da pronađe kompatibilnu mach-o datoteku.
</div>
**Proverite putanje**
Hajde da proverimo sve opcije sa sledećim kodom:
// 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; }
Ako ga kompajlirate i izvršite, možete videti **gde je svaka biblioteka neuspešno pretražena**. Takođe, možete **filtrirati FS logove**:
sudo fs_usage | grep "dlopentest"
## Relative Path Hijacking
Ako **privilegovana binarna aplikacija** (kao što je SUID ili neka binarna aplikacija sa moćnim ovlašćenjima) **učitava biblioteku sa relativnom putanjom** (na primer koristeći `@executable_path` ili `@loader_path`) i ima **onemogućenu validaciju biblioteka**, može biti moguće premestiti binarnu aplikaciju na lokaciju gde napadač može **modifikovati biblioteku učitanu sa relativnom putanjom**, i zloupotrebiti je za injekciju koda u proces.
## Prune `DYLD_*` and `LD_LIBRARY_PATH` env variables
U datoteci `dyld-dyld-832.7.1/src/dyld2.cpp` moguće je pronaći funkciju **`pruneEnvironmentVariables`**, koja će ukloniti svaku env varijablu koja **počinje sa `DYLD_`** i **`LD_LIBRARY_PATH=`**.
Takođe će postaviti na **null** specifično env varijable **`DYLD_FALLBACK_FRAMEWORK_PATH`** i **`DYLD_FALLBACK_LIBRARY_PATH`** za **suid** i **sgid** binarne aplikacije.
Ova funkcija se poziva iz **`_main`** funkcije iste datoteke ako se cilja na OSX na sledeći način:
#if TARGET_OS_OSX if ( !gLinkContext.allowEnvVarsPrint && !gLinkContext.allowEnvVarsPath && !gLinkContext.allowEnvVarsSharedCache ) { pruneEnvironmentVariables(envp, &apple);
i ti boolean zastavice su postavljene u istoj datoteci u kodu:
#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;
Što u suštini znači da ako je binarni fajl **suid** ili **sgid**, ili ima **RESTRICT** segment u zaglavljima ili je potpisan sa **CS_RESTRICT** oznakom, tada je **`!gLinkContext.allowEnvVarsPrint && !gLinkContext.allowEnvVarsPath && !gLinkContext.allowEnvVarsSharedCache`** tačno i env varijable su uklonjene.
Napomena: ako je CS_REQUIRE_LV tačno, tada varijable neće biti uklonjene, ali će validacija biblioteke proveriti da li koriste istu sertifikat kao originalni binarni fajl.
## Proveri Ograničenja
### 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
### Sekcija `__RESTRICT` sa segmentom `__restrict`
gcc -sectcreate __RESTRICT __restrict /dev/null hello.c -o hello-restrict DYLD_INSERT_LIBRARIES=inject.dylib ./hello-restrict
### Hardened runtime
Kreirajte novi sertifikat u Keychain-u i koristite ga za potpisivanje binarnog fajla:
Apply runtime proetction
codesign -s
Apply library validation
codesign -f -s
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
Apply CS_RESTRICT protection
codesign -f -s