Threat hunting (VII): cazando sin salir de casa. Creación de procesos

Ver entradas anteriores: I: intro 1, II: intro 2, III: Kibana, IV: Grafiki, V: Jupyter Notebooks, VI: Creando nuestra víctima.

Buenas cazadores, ¿qué tal va la caza?

Espero que hayáis tenido tiempo para jugar con vuestro laboratorio y cada vez os sintáis más cómodos realizando consultas y analizado los datos.

Como dije en el artículo anterior, ahora toca bajarse al barro y empezar a entender qué es lo que está pasando en nuestro laboratorio. En este caso vamos a hablar de la creación de procesos, qué ocurre cuando se crea un proceso, qué formas hay de crearlos y los rastros que deja a su paso dicha creación.

Entendiendo el entorno

Windows se organiza en capas en lo que a interacción con el sistema se refiere.

Las capas superiores son con las que interactúa el usuario o los programas que él lanza, las capas inferiores las que utiliza el propio sistema operativo para funcionar.

Por motivos de seguridad, las capas superiores están bien documentadas y Windows ofrece facilidades para interactuar con ellas, pero con las capas inferiores la cosa cambia, no están documentadas y debido a la complejidad de su funcionamiento, es muy difícil o directamente no posible por seguridad.

En esta representación de Pavel Yosifovich, se puede observar como en las capas superiores, los procesos del usuario o los servicios interactúan con las “Subsystem DLLs” que ofrece el sistema y que están bien documentadas.

Estas librerías son las conocidas “kernel32.dll”, “user32.dll”, “netapi32.dll”… que nos permiten interactuar con el sistema operativo para prácticamente cualquier cosa que pueda necesitar un usuario o un programa. Más tarde, esa librería se encargará de enviar la información en el formato adecuado a la capa inferior y de esta manera abstraer al usuario o programador de la complejidad de las capas inferiores, y así también, proteger las capas inferiores de usos ilegítimos.

Creación de procesos

El tema de este artículo es sobre la creación de procesos, una de las tareas más infravaloradas en un sistema operativo, y ahora que ya tenemos la información de contexto es posible explicar un poco por encima.

La forma más común de crear un proceso es usando las funciones que exporta “win32.dll”.

CreateprocessA()

Esta es la función más común cuando se trata de ejecutar procesos y esta es su estructura. Esta función crea un nuevo proceso con el mismo contexto de seguridad que el proceso que la ejecuta.

CreateProcessAsUser() y CreateProcessWithTokenW()

Estas funciones nos permiten ejecutar un proceso en el contexto de seguridad de otro usuario. Para ejecutar estas funciones, será necesario pasarle, además de todos los parámetros de “CreateProcessA()”el “Access token” del usuario que queremos que asigne los privilegios al proceso.

CreateProcessWithLogonW()

En este caso, esta función permite ejecutar un proceso bajo el contexto de seguridad de otro usuario, pero esta vez habrá que pasarle a la función el usuario, contraseña y dominio del usuario a impersonar.

ShellExecute y ShellExecuteEx

Son funciones simplificadas para la ejecución de procesos que ofrece Windows. Trabajar con estas funciones permiten menos flexibilidad que con las anteriores, pero son más sencillas.

Sysmon

Para realizar su trabajo, Sysmon escucha ciertas funciones del sistema para registrar cuando un proceso las utiliza y generar sus eventos.

Para la generación de eventos de tipo 1 Sysmon monitoriza 6 APIs, que como os podéis imaginar son las mencionadas anteriormente. Ahora vamos a hacer algunas pruebas.

Pruebas

Para entender mejor qué es lo que pasa, dejo un pequeño programa en C++ muy simplificado que utiliza inicialmente la función “CreateProcess” y que yo he llamado “CreadorDeProcesos”.

#include <iostream>
#include <windows.h>
using namespace std;

int main()
{
PROCESS_INFORMATION pi;
STARTUPINFO si = { sizeof(si) };
TCHAR name[] = TEXT("cmd");
BOOLEAN success = CreateProcess(nullptr, name, nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si, &pi);
WaitForSingleObject(pi.hProcess, INFINITE);
DWORD code;
GetExitCodeProcess(pi.hProcess, &code);
return 0;
}

Este programa, una vez compilado y ejecutado, lanzará el proceso “cmd”, es decir, la terminal de comandos de Windows. Y una vez lo ha lanzado se podrá ver lo siguiente con “Process Explorer”.

El proceso “cmd.exe” es marcado como proceso hijo de “CreadorDeProcesos.exe” y relacionado con el usuario “luisf”, que es quien lanza el creador de procesos.

Esto es el comportamiento normal: el token de seguridad que tiene el proceso padre es heredado por los procesos hijos y en el laboratorio se ve así.

La siguiente prueba es con la función “CreateProcessWithLogonW”. Como se mencionó antes, esta función permite crear un proceso impersonando a otro usuario.

Este sería un código de ejemplo:

#include <iostream>
#include <windows.h>
using namespace std;

int main()
{
PROCESS_INFORMATION pi;
STARTUPINFO si = { sizeof(si) };
TCHAR name[] = TEXT("cmd");
TCHAR user[] = TEXT("JuanitoP");
TCHAR pass[] = TEXT("SuperContraseña");

BOOLEAN success = CreateProcessWithLogonW(user, NULL, pass, LOGON_WITH_PROFILE, nullptr, name, 0, nullptr, nullptr, &si, &pi);
WaitForSingleObject(pi.hProcess, INFINITE);
DWORD code;
GetExitCodeProcess(pi.hProcess, &code);
return 0;
}

Como se puede ver en el código, esta vez es necesario pasarle a la función el usuario y la contraseña del usuario que se quiere impersonar.

Igualmente ejecuta una consola de comandos, pero cuando en la consola se ejecuta “whoami”, el resultado es diferente.

Este proceso ha sido lanzado en un contexto de seguridad del usuario “JuanitoP” y Sysmon registra esta misma información.

Si os fijáis, es el usuario “JuanitoP” el que ha ejecutado el proceso, aunque fuera “luisf” quien ejecutó el “CreadorDeProcesos”, y no hay ni rastro del usuario “luisf”.

¿Qué ha pasado internamente?

Internamente, se ha realizado un login en el sistema para poder generar un “AccessToken” del usuario “JuanitoP”, y con ese token se ha creado el proceso hijo.

Si ha habido un login, habrá un evento de ello, ¿no? Pues efectivamente, sí, hay un evento “4624” indicando que se ha realizado un login con el usuario “luisf” sobre el usuario “JuanitoP”.  También podemos ver que el tipo de login es de tipo 2, interactivo, es decir mediante credenciales.

Cómo se ha podido ver, Windows permite la ejecución de procesos bajo el contexto de seguridad de otro usuario, siempre y cuando tengamos un “AccessToken” de ese usuario, pero en todos los casos el proceso padre no ha sido modificado, aunque esté relacionado con otro usuario.

Ahora os quiero contar otra cosa interesante relacionada con la creación de procesos. Hay una ley universal en Windows Internals que dice que un proceso no puede crear otro proceso con un nivel de integridad superior al que él tiene.

Es decir, si un proceso tiene integridad media, nunca podrá crear un proceso hijo de integridad alta. Sorprendente, ¿verdad?

Seguro que alguno está pensando:

Pero … cuando yo lanzo una aplicación como Administrador desde Explorer, Explorer tiene integridad media y el proceso que he lanzado tiene integridad alta… Lo que dices no tiene sentido.

Manos a la obra, voy a intentar explicar lo que ocurre cuando se ejecuta una elevación de privilegios.

Elevación de privilegios

En este caso, voy a utilizar la función “ShellExecuteEx” para lanzar un proceso con el comando “runas”, que permita lanzar un proceso con privilegios elevados, solo si el usuario que ejecuta el proceso padre tiene los privilegios para hacerlo, evidentemente.

int main()
{
SHELLEXECUTEINFO shExInfo = { 0 };
shExInfo.cbSize = sizeof(shExInfo);
shExInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
shExInfo.hwnd = 0;
shExInfo.lpVerb = TEXT("runas");
shExInfo.lpFile = TEXT("C:\\Windows\\System32\\cmd.exe");
shExInfo.lpDirectory = 0;
shExInfo.nShow = SW_SHOW;
shExInfo.hInstApp = 0;

if (ShellExecuteEx(&shExInfo))
{
WaitForSingleObject(shExInfo.hProcess, INFINITE);
CloseHandle(shExInfo.hProcess);
}
}

Cuando se ejecuta la aplicación, se crea un proceso de “cmd.exe” y esto es lo que muestra “Process Explorer”.

Efectivamente, un proceso de integridad media es padre de otro de integridad alta, pero lo que ha pasado por detrás es muy diferente a la creación de procesos que vimos antes.

Para facilitar esta “magia” está el servicio “APPInfo” o “Información de la aplicación” en castellano. Si se ve la descripción del servicio, es así:

Aquí está el secreto: en su tarea facilitadora, la aplicación que quiere ejecutar un proceso con privilegios elevados llamará a este servicio pidiéndole su ayuda. AppInfo será el encargado de crear el proceso “consent.exe”, la famosa ventana de UAC que preguntará al usuario si quiere ejecutar un proceso elevado en caso de tener privilegios, o le pedirá las credenciales de un usuario privilegiado en caso de no tener privilegios el usuario.

Una vez “consent.exe” tiene el consentimiento del usuario, devolverá a “AppInfo” su visto bueno, y será “AppInfo” quien ejecute el proceso con privilegios elevados con la función “CreateProcessAsUser”, y le asignará privilegios elevados ya que él está corriendo con privilegios de sistema.

Y ahora  viene la clave de la mágia. ¿Por qué Process Explorer muestra que el padre del proceso es “CreadorDeProcesos” si en realidad lo ha ejecutado “AppInfo”? Esto ocurre porque una de las tareas de “AppInfo”, ademas de lanzar el proceso, es realizar un cambio en el proceso para que parezca que lo ha lanzado “CreadorDeProcesos” y ¡voilà! Tenemos un proceso de integridad alta hijo de uno de integridad media.

A esta técnica se la conoce como “Parent Process ID Spoofing” y hablaremos de ella en el artículo siguiente, ya que los atacantes la controlan para engañar a los cazadores.

Espero que os haya gustado, y en el próximo articulo utilizaremos todo este conocimiento para entender mejor a los atacantes y sus técnicas.

No quiero perder la oportunidad de agradecer a Pavel Yosifovich todo lo que he aprendido de él sobre estos temas y lo paciente que es con las dudas. Todo un genio.

Un saludo y ¡Feliz caza!

Ver también en: