Varias veces al año, los investigadores de Elastic Security Labs tienen la libertad de elegir y profundizar en los proyectos que más les gusten, ya sea solos o en equipo. Este tiempo se conoce internamente como proyectos "On-Week". Este es el primero de un serial centrado en la tecnología de depuración de viajes en el tiempo (TTD) desarrollada por Microsoft que se exploró en detalle durante una reciente sesión de On-Week.
A pesar de haber hecho público desde hace varios años, el conocimiento de TTD y su potencial está muy subestimado dentro de la comunidad de seguridad de la información. Esperamos que este serial de dos partes pueda ayudar a arrojar algo de luz sobre cómo TTD puede ser útil para la depuración de programas, la investigación y explotación de vulnerabilidades y el análisis de malware.
Esta investigación implicó primero comprender el funcionamiento interno de la TTD y luego evaluar algunos usos interesantes aplicables que se pueden hacer de ella. Esta publicación se centrará en cómo los investigadores profundizan en el TTD, compartiendo su metodología junto con algunos hallazgos interesantes. En la segunda parte se detallará el uso aplicable de TTD con fines de análisis de malware e integración con Elastic Security.
Fondo
Time Travel Debugging es una herramienta desarrollada por Microsoft Research que permite a los usuarios registrar la ejecución y navegar libremente por el tiempo de ejecución en modo de usuario de un binario. El TTD se basa en dos tecnologías: Nirvana para la traducción binaria e iDNA para el proceso de lectura/escritura de trazas. Disponibles desde Windows 7, los componentes internos de TTD se detallaron por primera vez en un documento disponible públicamente. Desde entonces, tanto Microsoft como investigadores independientes lo cubrieron con gran detalle. Por esta razón, no exploraremos en profundidad los entresijos de ambas tecnologías. En cambio, los investigadores de Elastic investigaron el ecosistema (o los ejecutables, DLL y controladores) que hacen que la implementación de TTD funcione. Esto condujo a algunos hallazgos interesantes sobre TTD, pero también sobre el propio Windows, ya que TTD aprovecha algunas técnicas (no documentadas) para funcionar según lo previsto en casos especiales, como los procesos protegidos.
Pero, ¿por qué investigar el TTD? Aparte de la pura curiosidad, es probable que uno de los posibles usos previstos para la tecnología sea descubrir errores en entornos de producción. Cuando los errores son difíciles de desencadenar o reproducir, tener un entorno del tipo "grabar una vez y reproducir siempre" ayuda a compensar esa dificultad, que es exactamente lo que TTD implementa cuando se combina con WinDbg.
Las herramientas de depuración como WinDbg siempre fueron una inmensa fuente de información a la hora de revertir componentes de Windows, ya que proporcionan información adicional comprensible, normalmente en texto sin formato. Las herramientas de depuración (especialmente los depuradores) deben cooperar con el sistema operativo subyacente, lo que podría implicar interfaces de depuración o capacidades no reveladas previamente del sistema operativo. TTD se ajusta a ese patrón.
Información general de alto nivel
TTD funciona creando primero una grabación que rastrea cada instrucción ejecutada por una aplicación y la almacena en una base de datos (con el sufijo .run). Los seguimientos grabados se pueden reproducir a voluntad mediante el depurador WinDbg, que en el primer acceso indexará el archivo .run archivo, lo que permite una navegación más rápida a través de la base de datos. Para poder realizar un seguimiento de la ejecución de procesos arbitrarios, TTD inyecta una DLL responsable de registrar la actividad a petición que le permite registrar procesos generándolos, pero también puede anexar a un proceso que ya se está ejecutando.
TTD se puede descargar gratuitamente como parte del paquete WinDbg Preview en MS Store. Se puede usar directamente desde WinDbg Preview (también conocido como WinDbgX), pero es un componente independiente que se encuentra en C:\Program Files\WindowsApps\Microsoft.WinDbg_<version></version>_<arch>__8wekyb3d8bbwe\amd64\ttd
para la arquitectura x64, en la que nos centraremos en esta publicación. Las versiones x86 y arm64 también están disponibles para su descarga en MS Store.
El paquete consta de dos archivos EXE (TTD.exe y TTDInject.exe) y un puñado de archivos DLL. Esta investigación se centra en la principal DLL responsable de todo lo que no está relacionado con el Nirvana/iDNA (es decir, responsable de la gestión de sesiones, la comunicación con los controladores, la inyección de DLL y más): ttdrecord.dll
_Note: La mayor parte de esta investigación se realizó empleando dos versiones de la DLL ttdrecord: principalmente en una versión 2018 (1.9.106.0 SHA256=aca1786a1f9c96bbe1ea9cef0810c4d164abbf2c80c9ecaf0a1ab91600da6630), y primera versión 2022 (10.0.19041.1 SHA256=1FF7F54A4C865E4FBD63057D5127A73DA30248C1FF28B99FF1A43238071CBB5C). Se descubrió que las versiones anteriores tenían más símbolos, lo que ayudó a acelerar el proceso de ingeniería inversa. A continuación, readaptamos las estructuras y los nombres de las funciones a la versión más reciente. Por lo tanto, es posible que algunas de las estructuras explicadas aquí no sean las mismas si está tratando de reproducir en versiones más recientes. _
Examen de las características de TTD
Parámetros de la línea de comandos
Los lectores deben tener en cuenta que TTD.exe actúa esencialmente como un envoltorio para ttdrecord! ExecuteTTTracerCommandLine:
HRESULT wmain()
{
v28 = 0xFFFFFFFFFFFFFFFEui64;
hRes = CoInitializeEx(0i64, 0);
if ( hRes >= 0 )
{
ModuleHandleW = GetModuleHandleW(L"TTDRecord.dll");
[...]
TTD::DiagnosticsSink::DiagnosticsSink(DiagnosticsSink, &v22);
CommandLineW = GetCommandLineW();
lpDiagnosticsSink = Microsoft::WRL::Details::Make<TTD::CppToComDiagnosticsSink,TTD::DiagnosticsSink>(&v31, DiagnosticsSink);
hRes = ExecuteTTTracerCommandLine(*lpDiagnosticsSink, CommandLineW, 2i64);
[...]
La última línea del extracto de código anterior muestra una llamada a ExecuteTTTracerCommandLine , que toma un número entero como último argumento. Este argumento corresponde a los modos de rastreo deseados, que son: - 0 -> FullTracingMode, - 1 -> UnrestrictedTracing y - 2 -> Standalone (el modo codificado para la versión pública de TTD.exe)
Al forzar la ejecución de TTD en modo de seguimiento completo, se revelan las opciones disponibles, que incluyen algunas capacidades ocultas, como el resetear de procesos (-parent) y el seguimiento automático hasta el resetear (-onLaunch) para programas y servicios.
Al volcar el conjunto completo de opciones de TTDRecord.dll se revelaron interesantes opciones de línea de comandos ocultas, como:
-persistent Trace programs or services each time they are started (forever). You must specify a full path to the output location with -out.
-delete Stop future tracing of a program previously specified with -onLaunch or -persistent. Does not stop current tracing. For -plm apps you can only specify the package (-delete <package>) and all apps within that package will be removed from future tracing
-initialize Manually initialize your system for tracing. You can trace without administrator privileges after the system is initialized.
El proceso de configuración de Nirvana requiere que TTD configure el campo InstrumentationCallback en el _EPROCESS de destino. Esto se logra a través de la llamada al sistema (no documentada pero conocida) NtSetInformationProcess(ProcessInstrumentationCallback) (ProcessInstrumentationCallback, que tiene un valor de 40). Debido a las posibles participaciones de seguridad, invocar esta llamada al sistema requiere privilegios elevados. Curiosamente, el indicador -initialize también insinuaba que TTD podría implementar como un servicio de Windows. Dicho servicio sería responsable de dirigir las solicitudes de rastreo a procesos arbitrarios. Esto se puede confirmar ejecutándolo y viendo el mensaje de error resultante:
A pesar de que es fácilencontrar evidencia que confirma la existencia de TTDService.exe , el archivo no se proporcionó como parte del paquete público, por lo que, aparte de señalar que TTD puede ejecutar como un servicio, no lo cubriremos en esta publicación.
Inyección de proceso TTD
Como se explicó, un archivo de seguimiento TTD se puede crear a partir del TTD.exe binario independiente o a través de un TTDService.exe de servicio (privado), los cuales deben ejecutar en un contexto privilegiado. Sin embargo, esos son solo pícher e inyectar la DLL de grabación (llamada TTDRecordCPU.dll) es el trabajo de otro proceso: TTDInject.exe.
TTDInject.exe es otro ejecutable notablemente más grande que TTD.exe, pero con un objetivo bastante simple: preparar la sesión de trazado. En una vista demasiado simplificada, TTD.exe iniciará primero el proceso para que se registre en un estado suspendido. A continuación, generará TTDInject.exe, pasándole todos los argumentos necesarios para preparar la sesión. Tenga en cuenta que TTDInject también puede generar el proceso directamente dependiendo del modo de rastreo que mencionamos anteriormente, por lo tanto, estamos describiendo el comportamiento más común (es decir, cuando se genera desde TTD.exe).
TTDInject creará un hilo para ejecutar TTDLoader! InjectThread en el proceso grabado, que luego de varias validaciones cargará a su vez la biblioteca responsable de registrar toda la actividad del proceso, TTDRecordCPU.dll.
A partir de ese momento, se registrarán todas las instrucciones, los accesos a la memoria, las excepciones activadas o los estados de CPU encontrados durante la ejecución.
Una vez que se comprendió el flujo de trabajo general de TTD, quedó claro que es posible poca o ninguna manipulación luego de la inicialización de la sesión. Por lo tanto, se prestó mayor atención a los argumentos respaldados por ttdrecord.dll. Gracias al formato de función de manipulación de C++, se puede recuperar una gran cantidad de información crítica de los propios nombres de las funciones, lo que hace que el análisis del analizador de argumentos de la línea de comandos sea relativamente sencillo. Una marca interesante que se descubrió fue PplDebuggingToken. Esa marca está oculta y solo está disponible en el modo sin restricciones.
La existencia de esta bandera planteó preguntas de inmediato: TTD se diseñó primero en torno a Windows 7 y 8, y en Windows 8.1+. El concepto de nivel de protección se agregó a los procesos, dictando que los procesos solo pueden abrir identificadores a un proceso con un nivel de protección igual o inferior. Es un simple byte en la estructura _EPROCESS en el kernel y, por lo tanto, no se puede modificar directamente desde el modo de usuario.
Los valores del byte de nivel de protección son bien conocidos y se resumen en la tabla siguiente.
El subsistema de la autoridad local de seguridad (lsass.exe) en Windows se puede configurar para que se ejecute como Protected Process Light, cuyo objetivo es limitar el alcance de un intruso que obtiene privilegios máximos en un host. Al actuar a nivel del kernel, ningún proceso en modo de usuario puede abrir un identificador a lsass, sin importar cuán privilegiado sea.
Pero la marca PplDebuggingToken parece sugerir lo contrario. Si existiera tal bandera, sería el sueño de cualquier pentester/red teamer: un token (mágico) que les permitiera inyectar en procesos protegidos y grabarlos, volcar su memoria o más. El analizador de línea de comandos parece implicar que el contenido de la marca de comando es una mera cadena ancha. ¿Podría tratar de una puerta trasera PPL?
Persiguiendo el token de depuración PPL
Volviendo a ttdrecord.dll, la opción de línea de comandos PplDebuggingToken se analiza y almacena en una estructura de contexto junto con todas las opciones necesarias para crear la sesión TTD. El valor se puede rastrear hasta varias ubicaciones, una interesante es TTD::InitializeForAttach, cuyo comportamiento se simplifica en el siguiente pseudocódigo:
ErrorCode TTD::InitializeForAttach(TtdSession *ctx)
{
[...]
EnableDebugPrivilege(GetCurrentProcess()); // [1]
HANDLE hProcess = OpenProcess(0x101040u, 0, ctx->dwProcessId);
if(hProcess == INVALID_HANDLE_VALUE)
{
goto Exit;
}
[...]
HMODULE ModuleHandleW = GetModuleHandleW(L"crypt32.dll");
if ( ModuleHandleW )
pfnCryptStringToBinaryW = GetProcAddress(ModuleHandleW, "CryptStringToBinaryW"); // [2]
if ( ctx->ProcessDebugInformationLength ) // [3]
{
DecodedProcessInformationLength = ctx->ProcessDebugInformationLength;
DecodedProcessInformation = std::vector<unsigned char>(DecodedProcessInformationLength);
wchar_t* b64PplDebuggingTokenArg = ctx->CmdLine_PplDebugToken;
if ( *pfnCryptStringToBinaryW )
{
if( ERROR_SUCCESS == pfnCryptStringToBinaryW( // [4]
b64PplDebuggingTokenArg,
DecodedProcessInformationLength,
CRYPT_STRING_BASE64,
DecodedProcessInformation.get(),
&DecodedProcessInformationLength,
0, 0))
{
Status = NtSetInformationProcess( // [5]
NtGetCurrentProcess(),
ProcessDebugAuthInformation,
DecodedProcessInformation.get(),
DecodedProcessInformationLength);
}
[...]
Luego de habilitar el indicador SeDebugPrivilege para el proceso actual ([1]) y obtener un identificador para el proceso que se va a anexar a ([2]), la función resuelve una función genérica exportada que se usa para realizar operaciones de cadena: crypt32! CryptStringToBinaryW. En este caso, se emplea para decodificar el valor codificado en base64 de la opción de contexto PplDebuggingToken si lo proporcionó la línea de comandos ( [3], [4]). A continuación, el valor descodificado se usa para invocar la llamada del sistema NtSetInformationProcess(ProcessDebugAuthInformation) ([5]). El token no parece usar en ningún otro lugar, lo que nos hizo examinar esa llamada al sistema.
La clase de información de proceso ProcessDebugAuthInformation se agregó en RS4. Un vistazo rápido a ntoskrnl muestra que esta llamada al sistema simplemente pasa el búfer a CiSetInformationProcess ubicado en ci.dll, que es el archivo DLL del controlador de integridad de código. A continuación, el búfer se pasa a ci!CiSetDebugAuthInformation con argumentos totalmente controlados.
En el diagrama siguiente se resume a alto nivel dónde ocurre esto en el flujo de ejecución de TTD.
El flujo de ejecución en CiSetDebugAuthInformation es bastante simple: el búfer con el PplDebuggingToken decodificado en base64 y su longitud se pasan como argumentos para el análisis y la validación a ci!SbValidateAndParseDebugAuthToken. En caso de que la validación tenga éxito, y luego de una validación adicional, un identificador para el proceso que realiza la llamada al sistema (recuerde que todavía estamos manejando la llamada al sistema nt! NtSetInformationProcess) se insertará en un objeto de información de depuración de proceso y, a continuación, se almacenará en una entrada de lista global.
Pero, ¿cómo es eso interesante? Porque solo se accede a esta lista en una única ubicación: en ci!CiCheckProcessDebugAccessPolicy, y esta función se alcanza durante una llamada al sistema NtOpenProcess. Y, como el nombre de la marca recién descubierta sugirió anteriormente, cualquier proceso cuyo PID se encuentre en esa lista omitiría la aplicación del nivel de protección. Esto se confirmó prácticamente en una sesión de KD estableciendo un punto de interrupción de acceso en esa lista (en nuestra versión de ci.dll este se encontraba en ci + 364d8). También habilitamos PPL en LSASS y escribimos un script de PowerShell simple que desencadenaría una llamada al sistema NtOpenProcess:
Al romper ante la llamada a nt! PsTestProtectedProcessIncompatibility in nt! PspProcessOpen, podemos confirmar que nuestro proceso de PowerShell intenta dirigir a lsass.exe, que es un proceso PPL:
Ahora, para confirmar la teoría inicial de lo que haría el argumento PplDebuggingToken al forzar el valor de retorno de la llamada a nt! PsTestProtectedProcessIncompatibilidad:
Nos detenemos ante la instrucción que sigue el llamado a nt! PsTestProtectedProcessIncompatibility (que solo llama a CI!CiCheckProcessDebugAccessPolicy) y forzar el valor devuelto a 0 (como se mencionó anteriormente, un valor de 1 significa incompatible):
¡Éxito! Obtuvimos un identificador para LSASS a pesar de ser PPL, lo que confirma nuestra teoría. En resumen, si podemos encontrar un "valor válido" (profundizaremos en eso pronto) pasará la verificación de SbValidateAndParseDebugAuthToken() en ci!CiSetDebugAuthInformation(), y tendríamos una omisión universal de PPL. Si esto suena demasiado bueno para ser verdad, es principalmente porque lo es, pero confirmarlo requiere desarrollar una mejor comprensión de lo que CI.dll está haciendo.
Descripción de las directivas de integridad del código
Las restricciones basadas en la integridad del código, como las que usa AppLocker, se pueden aplicar a través de directivas, que en su forma legible por humanos son archivos XML. Hay dos tipos de pólizas: básicas y complementarias. Se pueden encontrar ejemplos del aspecto de las directivas base en su formato XML en "C:\Windows\schemas\CodeIntegrity\ExamplePolicies". Este es el aspecto de una directiva base en su formato XML (tomado de "C:\Windows\schemas\CodeIntegrity\ExamplePolicies\AllowAll.xml"), que revela la mayoría de los detalles que nos interesan claramente en texto sin formato.
<?xml version="1.0" encoding="utf-8"?>
<SiPolicy xmlns="urn:schemas-microsoft-com:sipolicy">
<VersionEx>1.0.1.0</VersionEx>
<PolicyID>{A244370E-44C9-4C06-B551-F6016E563076}</PolicyID>
<BasePolicyID>{A244370E-44C9-4C06-B551-F6016E563076}</BasePolicyID>
<PlatformID>{2E07F7E4-194C-4D20-B7C9-6F44A6C5A234}</PlatformID>
<Rules>
<Rule><Option>Enabled:Unsigned System Integrity Policy</Option></Rule>
<Rule><Option>Enabled:Advanced Boot Options Menu</Option></Rule>
<Rule><Option>Enabled:UMCI</Option></Rule>
<Rule><Option>Enabled:Update Policy No Reboot</Option></Rule>
</Rules>
<!--EKUS-- >
<EKUs />
<!--File Rules-- >
<FileRules>
<Allow ID="ID_ALLOW_A_1" FileName="*" />
<Allow ID="ID_ALLOW_A_2" FileName="*" />
</FileRules>
<!--Signers-- >
<Signers />
<!--Driver Signing Scenarios-- >
<SigningScenarios>
<SigningScenario Value="131" ID="ID_SIGNINGSCENARIO_DRIVERS_1" FriendlyName="Auto generated policy on 08-17-2015">
<ProductSigners>
<FileRulesRef><FileRuleRef RuleID="ID_ALLOW_A_1" /></FileRulesRef>
</ProductSigners>
</SigningScenario>
<SigningScenario Value="12" ID="ID_SIGNINGSCENARIO_WINDOWS" FriendlyName="Auto generated policy on 08-17-2015">
<ProductSigners>
<FileRulesRef><FileRuleRef RuleID="ID_ALLOW_A_2" /></FileRulesRef>
</ProductSigners>
</SigningScenario>
</SigningScenarios>
<UpdatePolicySigners />
<CiSigners />
<HvciOptions>0</HvciOptions>
<Settings>
<Setting Provider="PolicyInfo" Key="Information" ValueName="Name">
<Value><String>AllowAll</String></Value>
</Setting>
<Setting Provider="PolicyInfo" Key="Information" ValueName="Id">
<Value><String>041417</String></Value>
</Setting>
</Settings>
</SiPolicy>
Las directivas con formato XML se pueden compilar en un formato binario mediante el cmdlet de PowerShell ConvertFrom-CiPolicy:
Las políticas básicas permiten un detalle fino, con la capacidad de restringir por nombre, ruta, hash o firmante (con o sin EKU específico); sino también en su modo de acción (Auditoría o Aplicado).
Las Políticas Suplementarias se diseñaron como una extensión de las Políticas Base para proporcionar más flexibilidad permitiendo, por ejemplo, que las políticas se apliquen (o no) a un grupo específico de estaciones de trabajo o servidores. Por lo tanto, son más específicos, pero también pueden ser más permisivos de lo que debería ser la Política Base. Curiosamente, antes de 2016, las pólizas complementarias no estaban vinculadas a un dispositivo específico, lo que permitía derivaciones mitigadas corregidas por MS16-094 y MS16-100 que fueron ampliamente cubiertas por los medios de comunicación.
Teniendo en cuenta esa información, es posible volver a ci!SbValidateAndParseDebugAuthToken con más claridad: la función sigue esencialmente tres pasos: 1. Llame a ci!SbParseAndVerifySignedSupplementalPolicy para analizar el búfer de entrada de la llamada al sistema y determinar si se trata de una política complementaria 2 firmada válidamente. Llame a ci!SbIsSupplementalPolicyBoundToDevice para comparar el DeviceUnlockId de la directiva complementaria con el del sistema actual; estos valores se pueden recuperar fácilmente mediante la llamada del sistema NtQuerySystemEnvironmentValueEx con el GUID {EAEC226F-C9A3-477A-A826-DDC716CDC0E3}
3. Por último, extraiga dos variables de la política: un número entero (DWORD) que corresponde al nivel de protección y una autorización de depuración (UNICODE_STRING).
Dado que es posible crear archivos de políticas (a través de secuencias de comandos XML o PowerShell), el paso 3 no es un problema. Tampoco lo es el paso 2, ya que el DeviceUnlockId se puede forjar con el NtSetSystemEnvironmentValueEx({EAEC226F-C9A3-477A-A826-DDC716CDC0E3})
de llamada del sistema siempre que tengamos el privilegio SeSystemEnvironmentPrivilege. Sin embargo, debe tener que UnlockId es un valor volátil que se restaurará al resetear.
Sin embargo, omitir el paso 1 es prácticamente imposible, ya que requiere: - poseer la clave privada de un certificado propiedad de Microsoft con el OID particular 1.3.6.1.4.1.311.10.3.6(es decir, MS NT5 Lab (szOID_NT5_CRYPTO)) - y que el certificado mencionado anteriormente no debe ser revocado o caducado
Entonces, ¿dónde nos deja eso? Ahora confirmamos que, contrariamente a la sabiduría convencional, los procesos PPL pueden ser abiertos por otro proceso sin el paso adicional de cargar un controlador de kernel. Sin embargo, también hay que destacar que este caso de uso es un nicho, ya que sólo Microsoft (literalmente) tiene las claves para emplear esta técnica en máquinas muy específicas. Sin embargo, este caso sigue siendo un gran ejemplo de un uso de CI con fines de depuración.
TTD ofensivo
Nota: Como recordatorio, TTD.exe requiere privilegios elevados que asumen todas las técnicas que se analizan a continuación.
A lo largo de esta investigación, descubrimos algunos casos de uso ofensivo y defensivo potencialmente interesantes de TTD.
Seguimiento != Depuración
¡TTD no es un depurador! Por lo tanto, funcionará perfectamente sin ser detectado para los procesos que realizan una verificación básica anti-depuración, como el uso de IsDebuggerPresent() (o cualquier otra forma que dependa de PEB. BeingDebugged). La siguiente captura de pantalla ilustra este detalle haciendo que TTD se anexe a un proceso simple de bloc de notas:
Desde un depurador podemos comprobar el campo BeingDebugged ubicado en el bloc de notas PEB, que muestra que la bandera no está configurada:
El curioso caso de ProcLaunchMon
Otro truco interesante puesto a disposición por TTD es abusar del controlador incorporado de Windows ProcLaunchMon.sys. Cuando se ejecuta como un servicio (p. ej. TTDService.exe), ttdrecord.dll creará la instancia de servicio, cargará el controlador y se comunicará con el dispositivo disponible en .\com_microsoft_idna_ProcLaunchMon para registrar los clientes recién rastreados.
El propio controlador se empleará para monitorear los nuevos procesos creados por el servicio TTD y, a continuación, suspender esos procesos directamente desde el kernel, evitando así cualquier protección que únicamente monitorear la creación de procesos con el indicador de creación CREATE_SUSPENDED (como se menciona aquí , por ejemplo). Desarrollamos un cliente básico de controlador de dispositivo para esta investigación, que se puede encontrar aquí.
CreateDump.exe
Otro dato curioso: aunque no forma parte estrictamente de TTD, el paquete WinDbgX proporciona un binario firmado por .NET cuyo nombre resume perfectamente su funcionalidad: createdump.exe. Este binario se encuentra en "C:\Archivos de programa\WindowsApps\Microsoft.WinDbg_*\createdump.exe".
Este binario se puede usar para crear una instantánea y volcar el contexto de un proceso proporcionado como argumento, en el linaje directo de otros LOLBAS.
Una vez más, esto pone de manifiesto la necesidad de evitar depender de firmas estáticas y entradas de listas de bloqueo de nombres de archivo para proteger contra ataques como el volcado de credenciales y favorecer enfoques más robustos como RunAsPPL, Credential Guard o Credential Hardening de Elastic Endpoint.
TTD defensivo
Bloqueo de TTD
Aunque TTD es una característica extremadamente útil, los casos en los que sería necesario habilitarla en máquinas que no son de desarrollo o de prueba (como servidores de producción o estaciones de trabajo) son raros. Aunque esto parece en gran medida no documentado en el momento de escribir este artículo, ttdrecord.dll permite un escenario de salida temprana simplemente creando o actualizando una clave del Registro ubicada en "HKEY_LOCAL_MACHINE\Software\Microsoft\TTD" y actualizando el valor de DWORD32 RecordingPolicy a 2. Intentos posteriores de emplear cualquier servicio TTD (TTD.exe, TTDInject.exe, TTDService.exe) se detendrá y se generará un evento ETW para realizar un seguimiento de los intentos.
Detección de TTD
Impedir el uso de TTD puede ser demasiado extremo para todos los entornos, sin embargo, existen varios indicadores para detectar el uso de TTD. Un proceso que se está rastreando tiene las siguientes propiedades:
- Un subproceso ejecutará el código desde TTDRecordCPU.dll, que se puede verificar usando un simple comando incorporado de Windows: tasklist /m TTDRecordCPU.dll
- Aunque esto se pueda omitir, el PID principal del proceso registrado (o el primero, en caso de que el rastreo recursivo esté habilitado), sería TTD.exe sí mismo:
- Además, el _KPROCESS. El puntero InstrumentationCallback se establecería para aterrizar en la sección BSS de TTDRecordCPU.dll del ejecutable:
Por lo tanto, la detección del seguimiento de TTD se puede lograr a través de los métodos de modo de usuario y modo kernel.
Conclusión
Con esto concluye la primera parte de esta investigación "On-Week" centrada en el TTD. Al profundizar en los aspectos internos del ecosistema TTD, se revelaron algunos mecanismos muy interesantes y menos conocidos integrados en Windows, que son necesarios para que TTD funcione para ciertos casos extremos, como el seguimiento de procesos PPL.
A pesar de que esta investigación no reveló una nueva puerta trasera secreta para atacar los procesos de PPL, sí mostró una técnica inexplorada integrada en Windows para hacerlo. En todo caso, esta investigación destaca la importancia de un modelo basado en criptografía estable (de aquí a CI.dll), y cómo puede aportar mucha flexibilidad, al tiempo que mantiene un alto nivel de seguridad, cuando se implementa adecuadamente.
La segunda parte de este serial estará menos orientado a la investigación y será más práctica, con el lanzamiento de una pequeña herramienta que también desarrollamos como parte de On-Week. Esto ayuda en el proceso de análisis binario a través de TTD, empleando el espacio aislado de Windows.
Reconocimiento
Como esta investigación ya estaba concluida y el artículo en progreso, el autor se enteró de una investigación que cubría un tema similar y hallazgos con respecto a esa misma técnica (token de depuración PPL). Esa investigación fue realizada por Lucas George (de la compañía Synacktiv), quien presentó sus hallazgos en SSTIC 2022.