Consiguiendo persistencia invisible al editor del registro de Windows (regedit)

Hoy vamos a ver como, mediante algunas llamadas al API nativa de Windows, podemos crear valores en el registro que Regedit no puede mostrar o exportar. Esta técnica se recogía en un paper de eWhite Hats y con la misma se puede conseguir persistencia casi "invisible" e incluso almacenar de binarios "fileless".

En este post nos centraremos en el primer caso, básicamente, la posibilidad de escribir un valor en la clave Run que Regedit no puede mostrar, pero que sin embargo Windows leerá correctamente cuando la compruebe justo después de reiniciar.

// HIDDEN_KEY_LENGTH doesn't matter as long as it is non-zero.
// Length is needed to delete the key
#define HIDDEN_KEY_LENGTH 11
void createHiddenRunKey(const WCHAR* runCmd) {
LSTATUS openRet = 0;
NTSTATUS setRet = 0;
HKEY hkResult = NULL;
UNICODE_STRING ValueName = { 0 };
wchar_t runkeyPath[0x100] = L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run";
wchar_t runkeyPath_trick[0x100] = L"\0\0SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run";
if (!NtSetValueKey) {
HMODULE hNtdll = LoadLibraryA("ntdll.dll");
NtSetValueKey = (_NtSetValueKey)GetProcAddress(hNtdll, "NtSetValueKey");
}
ValueName.Buffer = runkeyPath_trick;
ValueName.Length = 2 * HIDDEN_KEY_LENGTH;
ValueName.MaximumLength = 0;
if (!(openRet = RegOpenKeyExW(HKEY_CURRENT_USER, runkeyPath, 0, KEY_SET_VALUE, &hkResult))) {
if (!(setRet = NtSetValueKey(hkResult, &ValueName, 0, REG_SZ, (PVOID)runCmd, wcslen(runCmd) * 2)))
printf("SUCCESS setting hidden run value!\n");
else
printf("FAILURE setting hidden run value! (setRet == 0x%X, GLE() == %d)\n", setRet, GetLastError());
RegCloseKey(hkResult);
}
else {
printf("FAILURE opening RUN key in registry! (openRet == 0x%X, GLE() == %d)\n", openRet, GetLastError());
}
}

En la función anterior, NtSetValueKey se pasa a UNICODE_STRING ValueName. ValueName.Buffer normalmente se establecería en "SOFTWARE\Microsoft\Windows\CurrentVersion\Run" para establecer un valor de la clave Run.
En su lugar, anteponemos esta cadena con dos NULOS WCHAR ("\0\0") para que ValueName.Buffer sea "\0\0SOFTWARE\Microsoft\Windows\CurrentVersion\Run”

Cuando Regedit intenta abrir la clave Run mostrará un mensaje de error:


Después de hacer clic en Aceptar, el valor no se mostrará:


Intentar exportar la clave y escribirla en un archivo tampoco funcionará:


El archivo exportado contiene el valor para OneDrive, pero no el valor oculto:


Otro lugar común para verificar los programas que se ejecutan automáticamente al iniciar el sistema es el Administrador de tareas. En las versiones más recientes de Windows, la pestaña "Inicio" ha sido añadida al Administrador de tareas. Esta técnica no añade un entrada a la pestaña de inicio del administrador de tareas.

Además, hay un maravillosa herramienta que podemos compilar y usar disponible en el Github de eWhite Hats:

https://github.com/ewhitehats/InvisiblePersistence/

Uso:

1) InvisibleRegKeys.exe persist [ruta al ejecutable de malware para que sea autorun]
  • Esto agregará un valor oculto a HKCU\Software\Microsoft\Windows\CurrentVersion\Run.
  • El valor será mshta.exe, o intérprete de Microsoft Scripting Host, que toma Javascript como argumento y lo ejecuta.
  • El Javascript hace referencia a la clave de registro HKCU\software\WUV (creada por InvisibleRegKeys).
  • Esa clave tiene el valor "Tethering".
  • Tethering es un blob de Javascript que decodifica (XOR) otro blob de Javascript, que a su vez se decodifica (Base64) en un script de Powershell, que hace un VirtualAllocs RWX y ejecuta sc (ver InvisibleRegKeys.h), un pequeño blob con el shellcode.
  • El shellcode se está ejecutando en el contexto del proceso powershell.exe.
  • El shellcode lee el valor predeterminado para HKCU\software\WUV, que contiene un búfer oculto.
  • El búfer aparecerá como "(valor no establecido)" en Regedit.
  • En realidad es el malware codificado (XOR) con una clave que se genera cuando se ejecuta InvisibleRegKeys, y que se graba en el blob Shellcode.
  • El shellcode decodifica el malware y lo carga (con un pequeño cargador de PE) en el contexto del proceso de PowerShell.
  • En ese punto, el malware puede inyectarse en otro proceso y llamar al proceso de salida para matar a Powershell. O el módulo puede regresar desde su punto de entrada, momento en el cual el shellcode sale del proceso. Después de 20 minutos, el proceso Powershell saldrá de cualquier manera (el modo de espera (1200) en InvisibleRegKeys.h controla esto).
  • Echa un vistazo a pe_loader.cpp. Este es el mismo cargador que usa el shellcode. Asigna el doble de memoria que el archivo PE normalmente necesitaría para cargarse, y copia el archivo PE en el espacio adicional. Esto le permite cargar reflexivamente el PE al realizar la inyección del proceso.
2) InvisibleRegKeys.exe unpersist
  • Elimina el valor oculto en HKCU\Software\Microsoft\Windows\CurrentVersion\Run.
  • Borra HKCU\software\WUV.
Por último, remarcar que estas técnicas se basan en errores de cómo Regedit lee y muestra valores en el registro. Otras herramientas no tienen los mismos fallos y se pueden usar para verificar la salida de regedit:
  • Autoruns de Sysinternals, por ejemplo, mostrará correctamente los valores de la clave Run. 
  • Las herramientas forenses como FTK Registry Viewer pueden ver los buffers ocultos almacenados en valores si se realiza una copia de las secciones del registro en disco. 
  • Debido a que los archivos de la sección están en uso mientras se ejecuta Windows, se puede usar una herramienta como HoboCopy para hacer una copia de los hive files.
  • El RegDelNull de SysInternals puede escanear el registro en busca de entradas con NULL bytes y tiene la opción de borrar esas entradas.
CONCLUSIÓN

Debido a la multitud de tipos de datos que se pueden almacenar en un valor de registro, es difícil mostrar estos valores de forma sólida y completa. Pero el máximo culpable no es Regedit sino la especificación de windows para los hive files.

Los formatos complejos con tipos que pueden ser tanto strings como datos conducen casi inevitablemente a errores.

Para agregar complejidad, el registro puede además interactuar no solo con la API de Windows típica, sino también con APIs nativas de nivel inferior.

Por estas razones, se cree que se pueden explotar muchos más errores en Regedit y que será un gran objetivo porque puede proporcionar nuevas técnicas para tareas comunes de malware sin necesidad de elevar privilegios...

Comentarios