Esto es similar al LD_PRELOAD en Linux. Permite indicar a un proceso que se va a ejecutar para cargar una biblioteca específica desde una ruta (si la variable de entorno está habilitada).
Esta técnica también puede ser utilizada como técnica ASEP ya que cada aplicación instalada tiene un archivo plist llamado "Info.plist" que permite la asignación de variables de entorno utilizando una clave llamada LSEnvironmental.
Desde 2012 Apple ha reducido drásticamente el poder de DYLD_INSERT_LIBRARIES.
Ve al código y verifica src/dyld.cpp. En la función pruneEnvironmentVariables puedes ver que las variables DYLD_* son eliminadas.
En la función processRestricted se establece la razón de la restricción. Revisando ese código puedes ver que las razones son:
El binario es setuid/setgid
Existencia de la sección __RESTRICT/__restrict en el binario macho.
Verifica los entitlements de un binario con: codesign -dv --entitlements :- </ruta/al/bin>
En versiones más actualizadas puedes encontrar esta lógica en la segunda parte de la función configureProcessRestrictions. Sin embargo, lo que se ejecuta en las versiones más nuevas son las verificaciones iniciales de la función (puedes eliminar los ifs relacionados con iOS o simulación ya que no se usarán en macOS.
Validación de Bibliotecas
Incluso si el binario permite el uso de la variable de entorno DYLD_INSERT_LIBRARIES, si el binario verifica la firma de la biblioteca para cargarla, no cargará una personalizada.
Para cargar una biblioteca personalizada, el binario necesita tener uno de los siguientes entitlements:
o el binario no debe tener la bandera de tiempo de ejecución endurecido o la bandera de validación de biblioteca.
Puedes verificar si un binario tiene tiempo de ejecución endurecido con codesign --display --verbose <bin> verificando la bandera de tiempo de ejecución en CodeDirectory como: CodeDirectory v=20500 size=767 flags=0x10000(runtime) hashes=13+7 location=embedded
También puedes cargar una biblioteca si está firmada con el mismo certificado que el binario.
Encuentra un ejemplo de cómo (ab)usar esto y verificar las restricciones en:
Recuerda que las restricciones previas de Validación de Bibliotecas también se aplican para realizar ataques de secuestro de Dylib.
Al igual que en Windows, en MacOS también puedes secuestrar dylibs para hacer que las aplicacionesejecutencódigo arbitrario (bueno, en realidad desde un usuario regular esto podría no ser posible ya que es posible que necesites un permiso TCC para escribir dentro de un paquete .app y secuestrar una biblioteca).
Sin embargo, la forma en que las aplicaciones de MacOS cargan bibliotecas es más restringida que en Windows. Esto implica que los desarrolladores de malware aún pueden usar esta técnica para sigilo, pero la probabilidad de poder abusar de esto para escalar privilegios es mucho menor.
En primer lugar, es más común encontrar que los binarios de MacOS indican la ruta completa a las bibliotecas a cargar. Y en segundo lugar, MacOS nunca busca en las carpetas de $PATH las bibliotecas.
La parte principal del código relacionado con esta funcionalidad está en ImageLoader::recursiveLoadLibraries en ImageLoader.cpp.
Hay 4 comandos de encabezado diferentes que un binario macho puede usar para cargar bibliotecas:
El comando LC_LOAD_DYLIB es el comando común para cargar un dylib.
El comando LC_LOAD_WEAK_DYLIB funciona como el anterior, pero si no se encuentra el dylib, la ejecución continúa sin ningún error.
El comando LC_REEXPORT_DYLIB lo que hace es hacer de intermediario (o reexportar) los símbolos de una biblioteca diferente.
El comando LC_LOAD_UPWARD_DYLIB se utiliza cuando dos bibliotecas dependen entre sí (esto se llama una dependencia ascendente).
Sin embargo, hay 2 tipos de secuestro de dylib:
Bibliotecas vinculadas débilmente faltantes: Esto significa que la aplicación intentará cargar una biblioteca que no existe configurada con LC_LOAD_WEAK_DYLIB. Entonces, si un atacante coloca un dylib donde se espera, será cargado.
El hecho de que el enlace sea "débil" significa que la aplicación seguirá ejecutándose incluso si la biblioteca no se encuentra.
El código relacionado con esto está en la función ImageLoaderMachO::doGetDependentLibraries de ImageLoaderMachO.cpp donde lib->requiredesfalsesolo cuandoLC_LOAD_WEAK_DYLIB` es verdadero.
Encuentra bibliotecas vinculadas débilmente en binarios con (más adelante tienes un ejemplo de cómo crear bibliotecas de secuestro):
otool -l </ruta/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
* **Configurado con @rpath**: Los binarios Mach-O pueden tener los comandos **`LC_RPATH`** y **`LC_LOAD_DYLIB`**. Basado en los **valores** de esos comandos, las **bibliotecas** se van a **cargar** desde **diferentes directorios**.
* **`LC_RPATH`** contiene las rutas de algunas carpetas utilizadas para cargar bibliotecas por el binario.
* **`LC_LOAD_DYLIB`** contiene la ruta a bibliotecas específicas para cargar. Estas rutas pueden contener **`@rpath`**, que será **reemplazado** por los valores en **`LC_RPATH`**. Si hay varias rutas en **`LC_RPATH`**, todas se utilizarán para buscar la biblioteca a cargar. Ejemplo:
* Si **`LC_LOAD_DYLIB`** contiene `@rpath/library.dylib` y **`LC_RPATH`** contiene `/application/app.app/Contents/Framework/v1/` y `/application/app.app/Contents/Framework/v2/`. Ambas carpetas se utilizarán para cargar `library.dylib`. Si la biblioteca no existe en `[...]/v1/` y el atacante podría colocarla allí para secuestrar la carga de la biblioteca en `[...]/v2/` ya que se sigue el orden de las rutas en **`LC_LOAD_DYLIB`**.
* **Encontrar rutas rpath y bibliotecas** en binarios con: `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`**: Es la **ruta** al directorio que contiene el **archivo ejecutable principal**.
**`@loader_path`**: Es la **ruta** al **directorio** que contiene el **binario Mach-O** que contiene el comando de carga.
* Cuando se usa en un ejecutable, **`@loader_path`** es efectivamente lo **mismo** que **`@executable_path`**.
* Cuando se usa en un **dylib**, **`@loader_path`** proporciona la **ruta** a la **dylib**.
</div>
La forma de **escalar privilegios** abusando de esta funcionalidad sería en el raro caso de que una **aplicación** que se ejecuta **como** **root** esté **buscando** alguna **biblioteca en alguna carpeta donde el atacante tenga permisos de escritura.**
<div data-gb-custom-block data-tag="hint" data-style='success'>
Un buen **escáner** para encontrar **bibliotecas faltantes** en aplicaciones es [**Dylib Hijack Scanner**](https://objective-see.com/products/dhs.html) o una [**versión CLI**](https://github.com/pandazheng/DylibHijack).\
Un buen **informe con detalles técnicos** sobre esta técnica se puede encontrar [**aquí**](https://www.virusbulletin.com/virusbulletin/2015/03/dylib-hijacking-os-x).
</div>
**Ejemplo**
<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>
## Secuestro de Dlopen
<div data-gb-custom-block data-tag="hint" data-style='danger'>
Recuerda que **también se aplican restricciones previas de Validación de Bibliotecas** para realizar ataques de secuestro de Dlopen.
</div>
Desde **`man dlopen`**:
* Cuando la ruta **no contiene un carácter de barra inclinada** (es decir, es solo un nombre de hoja), **dlopen() buscará**. Si **`$DYLD_LIBRARY_PATH`** estaba configurado al inicio, dyld primero buscará en ese directorio. Luego, si el archivo mach-o que llama o el ejecutable principal especifican un **`LC_RPATH`**, entonces dyld buscará en esos directorios. Luego, si el proceso está **sin restricciones**, dyld buscará en el **directorio de trabajo actual**. Por último, para binarios antiguos, dyld intentará algunos fallbacks. Si **`$DYLD_FALLBACK_LIBRARY_PATH`** estaba configurado al inicio, dyld buscará en esos directorios, de lo contrario, dyld buscará en **`/usr/local/lib/`** (si el proceso está sin restricciones), y luego en **`/usr/lib/`** (esta información fue tomada de **`man dlopen`**).
1. `$DYLD_LIBRARY_PATH`
2. `LC_RPATH`
3. `CWD`(si está sin restricciones)
4. `$DYLD_FALLBACK_LIBRARY_PATH`
5. `/usr/local/lib/` (si está sin restricciones)
6. `/usr/lib/`
<div data-gb-custom-block data-tag="hint" data-style='danger'>
Si no hay barras inclinadas en el nombre, habría 2 formas de hacer un secuestro:
* Si algún **`LC_RPATH`** es **editable** (pero la firma se verifica, por lo que también necesitas que el binario esté sin restricciones)
* Si el binario está **sin restricciones** y luego es posible cargar algo desde el CWD (o abusando de una de las variables de entorno mencionadas)
</div>
* Cuando la ruta **parece ser una ruta de framework** (por ejemplo, `/stuff/foo.framework/foo`), si **`$DYLD_FRAMEWORK_PATH`** estaba configurado al inicio, dyld primero buscará en ese directorio la **ruta parcial del framework** (por ejemplo, `foo.framework/foo`). Luego, dyld intentará la **ruta proporcionada tal cual** (usando el directorio de trabajo actual para rutas relativas). Por último, para binarios antiguos, dyld intentará algunos fallbacks. Si **`$DYLD_FALLBACK_FRAMEWORK_PATH`** estaba configurado al inicio, dyld buscará en esos directorios. De lo contrario, buscará en **`/Library/Frameworks`** (en macOS si el proceso está sin restricciones), luego en **`/System/Library/Frameworks`**.
1. `$DYLD_FRAMEWORK_PATH`
2. ruta proporcionada (usando el directorio de trabajo actual para rutas relativas si está sin restricciones)
3. `$DYLD_FALLBACK_FRAMEWORK_PATH`
4. `/Library/Frameworks` (si está sin restricciones)
5. `/System/Library/Frameworks`
<div data-gb-custom-block data-tag="hint" data-style='danger'>
Si es una ruta de framework, la forma de secuestrarla sería:
* Si el proceso está **sin restricciones**, abusando de la **ruta relativa desde CWD** de las variables de entorno mencionadas (aunque no se menciona en la documentación si el proceso está restringido, las variables de entorno DYLD\_\* se eliminan)
</div>
* Cuando la ruta **contiene una barra inclinada pero no es una ruta de framework** (es decir, una ruta completa o parcial a una dylib), dlopen() primero buscará (si está configurado) en **`$DYLD_LIBRARY_PATH`** (con la parte de hoja de la ruta). Luego, dyld **intentará la ruta proporcionada** (usando el directorio de trabajo actual para rutas relativas (pero solo para procesos sin restricciones)). Por último, para binarios antiguos, dyld intentará fallbacks. Si **`$DYLD_FALLBACK_LIBRARY_PATH`** estaba configurado al inicio, dyld buscará en esos directorios, de lo contrario, dyld buscará en **`/usr/local/lib/`** (si el proceso está sin restricciones), y luego en **`/usr/lib/`**.
1. `$DYLD_LIBRARY_PATH`
2. ruta proporcionada (usando el directorio de trabajo actual para rutas relativas si está sin restricciones)
3. `$DYLD_FALLBACK_LIBRARY_PATH`
4. `/usr/local/lib/` (si está sin restricciones)
5. `/usr/lib/`
<div data-gb-custom-block data-tag="hint" data-style='danger'>
Si hay barras inclinadas en el nombre y no es un framework, la forma de secuestrarlo sería:
* Si el binario está **sin restricciones** y luego es posible cargar algo desde el CWD o `/usr/local/lib` (o abusando de una de las variables de entorno mencionadas)
</div>
<div data-gb-custom-block data-tag="hint" data-style='info'>
Nota: No hay archivos de configuración para **controlar la búsqueda de dlopen**.
Nota: Si el ejecutable principal es un binario **set\[ug]id o firmado con permisos**, entonces **todas las variables de entorno se ignoran**, y solo se puede usar una ruta completa ([verificar restricciones de DYLD\_INSERT\_LIBRARIES](macos-dyld-hijacking-and-dyld\_insert\_libraries.md#check-dyld\_insert\_librery-restrictions) para obtener información más detallada)
Nota: Las plataformas de Apple utilizan archivos "universales" para combinar bibliotecas de 32 bits y 64 bits. Esto significa que no hay **rutas de búsqueda separadas para 32 bits y 64 bits**.
Nota: En las plataformas de Apple, la mayoría de las dylibs del sistema operativo se **combinan en la caché de dyld** y no existen en disco. Por lo tanto, llamar a **`stat()`** para verificar si una dylib del sistema operativo existe **no funcionará**. Sin embargo, **`dlopen_preflight()`** utiliza los mismos pasos que **`dlopen()`** para encontrar un archivo mach-o compatible.
</div>
**Verificar rutas**
Veamos todas las opciones con el siguiente código:
```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;
}
Si lo compilas y lo ejecutas, puedes ver dónde se buscó sin éxito cada biblioteca. Además, podrías filtrar los registros del sistema de archivos:
sudofs_usage|grep"dlopentest"
Secuestro de Ruta Relativa
Si un binario/aplicación privilegiado (como un SUID o algún binario con permisos poderosos) está cargando una biblioteca de ruta relativa (por ejemplo, usando @executable_path o @loader_path) y tiene la Validación de Biblioteca deshabilitada, podría ser posible mover el binario a una ubicación donde el atacante pudiera modificar la biblioteca cargada de ruta relativa, y abusar de ella para inyectar código en el proceso.
Podar variables de entorno DYLD_* y LD_LIBRARY_PATH
En el archivo dyld-dyld-832.7.1/src/dyld2.cpp es posible encontrar la función pruneEnvironmentVariables, la cual eliminará cualquier variable de entorno que empiece con DYLD_ y LD_LIBRARY_PATH=.
También establecerá específicamente a nulo las variables de entorno DYLD_FALLBACK_FRAMEWORK_PATH y DYLD_FALLBACK_LIBRARY_PATH para binarios suid y sgid.
Esta función es llamada desde la función _main del mismo archivo si se apunta a OSX de la siguiente manera:
y esas banderas booleanas se establecen en el mismo archivo en el código:
#ifTARGET_OS_OSX// support chrooting from old kernelbool isRestricted =false;bool libraryValidation =false;// any processes with setuid or setgid bit set or with __RESTRICT segment is restrictedif ( 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 entitlementsif ( ((flags & CS_RESTRICT) == CS_RESTRICT) && usingSIP ) {isRestricted =true;}// Library Validation loosens searching but requires everything to be code signedif ( 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;
Lo que básicamente significa que si el binario es suid o sgid, o tiene un segmento RESTRICT en los encabezados o fue firmado con la bandera CS_RESTRICT, entonces !gLinkContext.allowEnvVarsPrint && !gLinkContext.allowEnvVarsPath && !gLinkContext.allowEnvVarsSharedCache es verdadero y las variables de entorno son eliminadas.
Ten en cuenta que si CS_REQUIRE_LV es verdadero, entonces las variables no serán eliminadas, pero la validación de la biblioteca verificará que estén utilizando el mismo certificado que el binario original.
Verificar Restricciones
SUID & SGID
# Make it owned by root and suidsudochownroothellosudochmod+shello# Insert the libraryDYLD_INSERT_LIBRARIES=inject.dylib./hello# Remove suidsudochmod-shello
Cree un nuevo certificado en el Llavero y úselo para firmar el binario:
# Apply runtime proetctioncodesign-s<cert-name>--option=runtime./helloDYLD_INSERT_LIBRARIES=inject.dylib./hello#Library won't be injected# Apply library validationcodesign-f-s<cert-name>--option=library./helloDYLD_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'tcodesign-f-s<cert-name>inject.dylibDYLD_INSERT_LIBRARIES=inject.dylib./hello-signed# Apply CS_RESTRICT protectioncodesign-f-s<cert-name>--option=restricthello-signedDYLD_INSERT_LIBRARIES=inject.dylib./hello-signed# Won't work
Ten en cuenta que incluso si hay binarios firmados con la bandera 0x0(none), pueden obtener la bandera CS_RESTRICT dinámicamente al ejecutarse y, por lo tanto, esta técnica no funcionará en ellos.
Puedes verificar si un proc tiene esta bandera con (obtén csops aquí):
csops-status<pid>
y luego verificar si la bandera 0x800 está habilitada.