Christophe Alladoum

Mergulho profundo no ecossistema TTD

Este é o primeiro de uma série focada na tecnologia de Depuração de Viagem no Tempo (TTD) desenvolvida pela Microsoft e que foi explorada em detalhes durante um recente período de pesquisa independente.

20 min de leituraPesquisa sobre segurança
Mergulho profundo no ecossistema TTD

Várias vezes por ano, os pesquisadores do Elastic Security Labs têm a liberdade de escolher e se aprofundar em projetos de sua preferência — sozinhos ou em equipe. Esse tempo é chamado internamente de projetos “On-Week”. Este é o primeiro de uma série focada na tecnologia de Depuração de Viagem no Tempo (TTD) desenvolvida pela Microsoft e que foi explorada em detalhes durante uma sessão recente do On-Week.

Apesar de ter se tornado público há vários anos, a conscientização sobre o TTD e seu potencial são muito subestimados na comunidade de segurança da informação. Esperamos que esta série de duas partes possa ajudar a esclarecer como o TTD pode ser útil para depuração de programas, pesquisa e exploração de vulnerabilidades e análise de malware.

Esta pesquisa envolveu primeiro entender o funcionamento interno do TTD e depois avaliar alguns usos aplicáveis interessantes que podem ser feitos dele. Esta postagem se concentrará em como os pesquisadores se aprofundam no TTD, compartilhando sua metodologia junto com algumas descobertas interessantes. A segunda parte detalhará o uso aplicável do TTD para fins de análise de malware e integração com o Elastic Security.

Histórico

Time Travel Debugging é uma ferramenta desenvolvida pela Microsoft Research que permite aos usuários registrar a execução e navegar livremente no tempo de execução do modo de usuário de um binário. O próprio TTD depende de duas tecnologias: Nirvana para a tradução binária e iDNA para o processo de leitura/escrita de traços. Disponível desde o Windows 7, os detalhes internos do TTD foram detalhados pela primeira vez em um artigo disponível publicamente. Desde então, tanto a Microsoft quanto pesquisadores independentes o abordaram detalhadamente. Por esse motivo, não exploraremos os detalhes internos de ambas as tecnologias em profundidade. Em vez disso, os pesquisadores da Elastic investigaram o ecossistema — ou os executáveis, DLLs e drivers — que fazem a implementação do TTD funcionar. Isso levou a algumas descobertas interessantes sobre o TTD, mas também sobre o próprio Windows, já que o TTD utiliza algumas técnicas (não documentadas) para funcionar conforme o esperado em casos especiais, como Processos Protegidos.

Mas por que investigar o TTD? Além da pura curiosidade, é provável que um dos possíveis usos pretendidos para a tecnologia seja descobrir bugs em ambientes de produção. Quando os bugs são difíceis de acionar ou reproduzir, ter um ambiente do tipo “gravar uma vez e reproduzir sempre” ajuda a compensar essa dificuldade, que é exatamente o que o TTD implementa quando acoplado ao WinDbg.

Ferramentas de depuração como o WinDbg sempre foram uma imensa fonte de informações ao reverter componentes do Windows, pois fornecem informações compreensíveis adicionais, geralmente em texto simples. Ferramentas de depuração (especialmente depuradores) devem cooperar com o sistema operacional subjacente, o que pode envolver interfaces de depuração e/ou recursos não divulgados anteriormente do SO. O TTD está em conformidade com esse padrão.

Visão geral de alto nível

O TTD funciona primeiro criando uma gravação que rastreia cada instrução executada por um aplicativo e a armazena em um banco de dados (com o sufixo .run). Os rastros gravados podem ser reproduzidos à vontade usando o depurador WinDbg, que no primeiro acesso indexará o .run arquivo, permitindo uma navegação mais rápida pelo banco de dados. Para poder rastrear a execução de processos arbitrários, o TTD injeta uma DLL responsável por registrar atividades sob demanda, o que lhe permite registrar processos gerando-os, mas também pode se anexar a um processo já em execução.

O TTD pode ser baixado gratuitamente como parte do pacote WinDbg Preview na MS Store. Ele pode ser usado diretamente do WinDbg Preview (também conhecido como WinDbgX), mas é um componente autônomo localizado em C:\Program Files\WindowsApps\Microsoft.WinDbg_<version></version>_<arch>__8wekyb3d8bbwe\amd64\ttd para a arquitetura x64, na qual nos concentraremos neste post. As versões x86 e arm64 também estão disponíveis para download na MS Store.

O pacote consiste em dois arquivos EXE (TTD.exe e TTDInject.exe) e um punhado de DLLs. Esta pesquisa se concentra na principal DLL responsável por tudo o que não está relacionado ao Nirvana/iDNA (ou seja, responsável pelo gerenciamento de sessão, comunicação do driver, injeção de DLL e muito mais): ttdrecord.dll

_Nota: A maior parte desta pesquisa foi feita usando duas versões da DLL ttdrecord: principalmente em uma versão 2018 (1.9.106.0 SHA256=aca1786a1f9c96bbe1ea9cef0810c4d164abbf2c80c9ecaf0a1ab91600da6630), e versão inicial 2022 (10.0.19041.1 SHA256=1FF7F54A4C865E4FBD63057D5127A73DA30248C1FF28B99FF1A43238071CBB5C). Descobriu-se que as versões mais antigas tinham mais símbolos, o que ajudou a acelerar o processo de engenharia reversa. Em seguida, readaptamos as estruturas e os nomes das funções para a versão mais recente. Portanto, algumas das estruturas explicadas aqui podem não ser as mesmas se você estiver tentando reproduzir em versões mais recentes. _

Examinando os recursos do TTD

Parâmetros da linha de comando

Os leitores devem observar que o TTD.exe atua essencialmente como um wrapper 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);
[...]

A linha final do trecho de código acima mostra uma chamada para ExecuteTTTracerCommandLine , que recebe um inteiro como último argumento. Este argumento corresponde aos modos de rastreamento desejados, que são: - 0 -> FullTracingMode, - 1 -> UnrestrictedTracing e - 2 -> Standalone (o modo codificado para a versão pública do TTD.exe)

Forçar o TTD a ser executado no modo de rastreamento completo revela as opções disponíveis, que incluem alguns recursos ocultos, como reprogramação de processos (-parent) e rastreamento automático até a reinicialização (-onLaunch) para programas e serviços.

O despejo do conjunto completo de opções do TTDRecord.dll revelou opções de linha de comando ocultas interessantes, 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.

O processo de configuração do Nirvana requer que o TTD configure o campo InstrumentationCallback no _EPROCESS de destino. Isso é obtido por meio da chamada de sistema (não documentada, mas conhecida) NtSetInformationProcess(ProcessInstrumentationCallback) (ProcessInstrumentationCallback, que tem um valor de 40). Devido à potencial implicação de segurança, invocar esta chamada de sistema requer privilégios elevados. Curiosamente, o sinalizador -initialize também sugeriu que o TTD poderia ser implantado como um serviço do Windows. Esse serviço seria responsável por enviar solicitações de rastreamento para processos arbitrários. Isso pode ser confirmado executando-o e vendo a mensagem de erro resultante:

Embora seja fácilencontrar evidências confirmando a existência do TTDService.exe, o arquivo não foi fornecido como parte do pacote público, então, além de observar que o TTD pode ser executado como um serviço, não o abordaremos neste post.

Injeção de processo TTD

Conforme explicado, um arquivo de rastreamento TTD pode ser criado a partir do binário autônomo TTD.exe ou por meio de um serviço TTDService.exe (privado), ambos os quais devem ser executados em um contexto privilegiado. No entanto, esses são apenas inicializadores e a injeção da DLL de gravação (chamada TTDRecordCPU.dll) é tarefa de outro processo: TTDInject.exe.

TTDInject.exe é outro executável visivelmente maior que TTD.exe, mas com um objetivo bem simples: preparar a sessão de rastreamento. Em uma visão simplificada, o TTD.exe primeiro iniciará o processo a ser gravado em um estado suspenso. Ele então gerará TTDInject.exe, passando-lhe todos os argumentos necessários para preparar a sessão. Observe que o TTDInject também pode gerar o processo diretamente, dependendo do modo de rastreamento mencionado anteriormente. Portanto, estamos descrevendo o comportamento mais comum (ou seja, quando gerado a partir do TTD.exe).

O TTDInject criará um thread para executar TTDLoader!InjectThread no processo gravado, que após várias validações carregará a biblioteca responsável por registrar todas as atividades do processo, TTDRecordCPU.dll.

A partir desse ponto, todas as instruções, acessos à memória, exceções acionadas ou estados de CPU encontrados durante a execução serão registrados.

Depois que o fluxo de trabalho geral do TTD foi compreendido, ficou claro que pouca ou nenhuma manipulação é possível após a inicialização da sessão. Assim, mais atenção foi dada aos argumentos apoiados pelo ttdrecord.dll. Graças ao formato de função de manipulação do C++, muitas informações críticas podem ser recuperadas dos próprios nomes das funções, o que torna a análise do analisador de argumentos da linha de comando relativamente simples. Um sinalizador interessante que foi descoberto foi o PplDebuggingToken. Essa bandeira está oculta e disponível apenas no Modo Irrestrito.

A existência desse sinalizador imediatamente levantou questões: o TTD foi arquitetado primeiro em torno do Windows 7 e 8, e no Windows 8.1+. O conceito de Nível de Proteção foi adicionado aos processos, determinando que os processos só podem abrir identificadores para um processo com Nível de Proteção igual ou inferior. É um byte simples na estrutura _EPROCESS no kernel e, portanto, não pode ser modificado diretamente pelo modo de usuário.

Os valores do byte Nível de Proteção são bem conhecidos e estão resumidos na tabela abaixo.

O subsistema da Autoridade de Segurança Local (lsass.exe) no Windows pode ser configurado para ser executado como Processo Protegido Light, que visa limitar o alcance de um intruso que obtém privilégios máximos em um host. Ao agir no nível do kernel, nenhum processo em modo de usuário pode abrir um identificador para lsass, não importa quão privilegiado seja.

Mas o sinalizador PplDebuggingToken parece sugerir o contrário. Se tal sinalizador existisse, seria o sonho de qualquer pentester/red teamer: um token (mágico) que lhes permitiria injetar em processos protegidos e gravá-los, despejar sua memória e muito mais. O analisador de linha de comando parece implicar que o conteúdo do sinalizador de comando é uma mera string ampla. Isso poderia ser um backdoor do PPL?

Perseguindo o token de depuração PPL

Retornando ao ttdrecord.dll, a opção de linha de comando PplDebuggingToken é analisada e armazenada em uma estrutura de contexto junto com todas as opções necessárias para criar a sessão TTD. O valor pode ser rastreado até vários locais, sendo um interessante o TTD::InitializeForAttach, cujo comportamento é simplificado no seguinte 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);
  }
[...]

Após habilitar o sinalizador SeDebugPrivilege para o processo atual ([1]) e obter um identificador para o processo ao qual anexar ([2]), a função resolve uma função genérica exportada usada para executar operações de string: crypt32!CryptStringToBinaryW. Neste caso, ele é usado para decodificar o valor codificado em base64 da opção de contexto PplDebuggingToken se ele foi fornecido pela linha de comando ([3], [4]). O valor decodificado é então usado para invocar a chamada de sistema NtSetInformationProcess(ProcessDebugAuthInformation) ([5]). O token não parece ser usado em nenhum outro lugar, o que nos fez examinar essa chamada de sistema.

A classe de informações de processo ProcessDebugAuthInformation foi adicionada no RS4. Uma rápida olhada em ntoskrnl mostra que essa chamada de sistema simplesmente passa o buffer para CiSetInformationProcess localizado em ci.dll, que é a DLL do driver de integridade de código. O buffer é então passado para ci!CiSetDebugAuthInformation com argumentos totalmente controlados.

O diagrama a seguir resume em alto nível onde isso acontece no fluxo de execução do TTD.

O fluxo de execução em CiSetDebugAuthInformation é bastante simples: o buffer com o PplDebuggingToken decodificado em base64 e seu comprimento são passados como argumentos para análise e validação para ci!SbValidateAndParseDebugAuthToken. Se a validação for bem-sucedida, e após alguma validação extra, um identificador para o processo que executa a syscall (lembre-se de que ainda estamos manipulando a syscall nt!NtSetInformationProcess) será inserido em um objeto de informações de depuração do processo e então armazenado em uma entrada de lista global.

Mas como isso é interessante? Porque essa lista só é acessada em um único local: em ci!CiCheckProcessDebugAccessPolicy, e essa função é acessada durante uma chamada de sistema NtOpenProcess. E, como o nome do sinalizador recém-descoberto sugeriu anteriormente, qualquer processo cujo PID esteja localizado nessa lista ignoraria a aplicação do Nível de Proteção. Isso foi confirmado praticamente em uma sessão KD ao definir um ponto de interrupção de acesso nessa lista (em nossa versão do ci.dll, ele estava localizado em ci+364d8). Também habilitamos o PPL no LSASS e escrevemos um script simples do PowerShell que acionaria uma chamada de sistema NtOpenProcess:

Ao interromper a chamada para nt!PsTestProtectedProcessIncompatibility em nt!PspProcessOpen, podemos confirmar que nosso processo do PowerShell tenta atingir lsass.exe, que é um processo PPL:

Agora, para confirmar a teoria inicial do que o argumento PplDebuggingToken faria ao forçar o valor de retorno da chamada para nt!PsTestProtectedProcessIncompatibility:

Interrompemos a instrução após a chamada para nt!PsTestProtectedProcessIncompatibility (que chama apenas CI!CiCheckProcessDebugAccessPolicy) e forçamos o valor de retorno para 0 (como mencionado anteriormente, um valor de 1 significa incompatível):

Sucesso! Conseguimos um identificador para LSASS apesar de ser PPL, confirmando nossa teoria. Resumindo, se pudermos encontrar um “valor válido” (falaremos sobre isso em breve), ele passará na verificação de SbValidateAndParseDebugAuthToken() em ci!CiSetDebugAuthInformation(), e teremos um bypass PPL universal. Se isso parece bom demais para ser verdade, é porque realmente é — mas para confirmar isso é preciso desenvolver uma melhor compreensão do que o CI.dll está fazendo.

Compreendendo as políticas de integridade do código

Restrições baseadas na integridade do código, como aquelas usadas pelo AppLocker, podem ser aplicadas por meio de políticas, que em sua forma legível por humanos são arquivos XML. Existem dois tipos de apólices: básica e suplementar. Exemplos de como são as políticas básicas podem ser encontrados no formato XML em "C:\Windows\schemas\CodeIntegrity\ExamplePolicies". É assim que uma Política Base se parece em seu formato XML (retirado de "C:\Windows\schemas\CodeIntegrity\ExamplePolicies\AllowAll.xml"), que revela a maioria dos detalhes em que estamos interessados claramente em texto simples.

<?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>

Políticas formatadas em XML podem ser compiladas em um formato binário usando o cmdlet ConvertFrom-CiPolicy do PowerShell:

As Políticas Base permitem granularidade fina, com a capacidade de restringir por nome, caminho, hash ou signatário (com ou sem EKU específico); mas também em seu modo de ação (Auditoria ou Imposição).

As Políticas Suplementares foram projetadas como uma extensão das Políticas Básicas para fornecer mais flexibilidade, permitindo, por exemplo, que as políticas sejam aplicadas (ou não) a um grupo específico de estações de trabalho ou servidores. Portanto, elas são mais específicas, mas também podem ser mais permissivas do que a Política Base deveria ser. Curiosamente, antes de 2016, as políticas suplementares não estavam vinculadas a um dispositivo específico, permitindo desvios mitigados corrigidos pelos MS16-094 e MS16-100 , que foram amplamente cobertos pela mídia.

Mantendo essas informações em mente, é possível retornar ao ci!SbValidateAndParseDebugAuthToken com mais clareza: a função segue essencialmente três etapas: 1. Chame ci!SbParseAndVerifySignedSupplementalPolicy para analisar o buffer de entrada da syscall e determinar se é uma Política Suplementar assinada de forma válida 2. Chame ci!SbIsSupplementalPolicyBoundToDevice para comparar o DeviceUnlockId da política suplementar com o do sistema atual; esses valores podem ser facilmente recuperados usando a syscall NtQuerySystemEnvironmentValueEx com o GUID {EAEC226F-C9A3-477A-A826-DDC716CDC0E3}3. Por fim, extraia duas variáveis da política: um inteiro (DWORD) que corresponde ao Nível de Proteção e uma Autorização de Depuração (UNICODE_STRING).

Como é possível criar arquivos de política (via XML ou script do PowerShell), a Etapa 3 não é um problema. O Passo 2 também não, pois o DeviceUnlockId pode ser falsificado com a syscall NtSetSystemEnvironmentValueEx({EAEC226F-C9A3-477A-A826-DDC716CDC0E3}) desde que tenhamos o privilégio SeSystemEnvironmentPrivilege. Entretanto, é importante observar que o UnlockId é um valor volátil que será restaurado na reinicialização.

No entanto, ignorar a Etapa 1 é virtualmente impossível, pois requer: - possuir a chave privada para um certificado de propriedade da Microsoft com o OID específico 1.3.6.1.4.1.311.10.3.6(ou seja, MS NT5 Lab (szOID_NT5_CRYPTO)) - e que o certificado acima mencionado não deve ser revogado ou expirado

Então, onde isso nos deixa? Agora confirmamos que, ao contrário do que se pensa, os processos PPL podem ser abertos por outro processo sem a etapa extra de carregar um driver de kernel. No entanto, também deve ser enfatizado que tal caso de uso é de nicho, já que somente a Microsoft (literalmente) detém as chaves para usar essa técnica em máquinas muito específicas. No entanto, esse caso ainda é um ótimo exemplo de uso de CI com lacuna de ar para fins de depuração.

TTD ofensivo

Observação: como lembrete, o TTD.exe requer privilégios elevados que todas as técnicas discutidas abaixo pressupõem.

Ao longo desta pesquisa, descobrimos alguns casos de uso ofensivo e defensivo potencialmente interessantes de TTD.

Rastreamento != Depuração

TTD não é um depurador! Portanto, ele funcionará perfeitamente sem ser detectado para processos que realizam uma verificação antidepuração básica, como usar IsDebuggerPresent() (ou qualquer outra maneira que dependa de PEB.BeingDebugged). A captura de tela a seguir ilustra esse detalhe fazendo com que o TTD seja anexado a um processo simples do bloco de notas:

Em um depurador, podemos verificar o campo BeingDebugged localizado no PEB do bloco de notas, que mostra que o sinalizador não está definido:

O curioso caso do ProcLaunchMon

Outro truque interessante disponibilizado pelo TTD é abusar do driver interno do Windows ProcLaunchMon.sys. Ao executar como um serviço (ou seja, TTDService.exe), ttdrecord.dll criará a instância de serviço, carregará o driver e se comunicará com o dispositivo disponível em .\com_microsoft_idna_ProcLaunchMon para registrar clientes recém-rastreados.

O driver em si será usado para monitorar novos processos criados pelo serviço TTD e então suspender esses processos diretamente do kernel, ignorando assim qualquer proteção que monitore apenas a criação de processos com o sinalizador de criação CREATE_SUSPENDED (como mencionado aqui, por exemplo). Desenvolvemos um cliente de driver de dispositivo básico para esta pesquisa, que pode ser encontrado aqui.

CreateDump.exe

Outro fato interessante: embora não faça parte estritamente do TTD, o pacote WinDbgX fornece um binário assinado pelo .NET cujo nome resume perfeitamente sua funcionalidade: createdump.exe. Este binário está localizado em "C:\Arquivos de Programas\WindowsApps\Microsoft.WinDbg_*\createdump.exe".

Este binário pode ser usado para capturar e despejar o contexto de um processo fornecido como argumento, na linhagem direta de outros LOLBAS.

Isso mais uma vez destaca a necessidade de evitar depender de assinaturas estáticas e entradas de listas de bloqueio de nomes de arquivos para se proteger contra ataques como despejo de credenciais e favorecer abordagens mais robustas, como RunAsPPL, Credential Guard ou Credential Hardening do Elastic Endpoint.

TTD defensivo

Bloqueio TTD

Embora o TTD seja um recurso extremamente útil, são raros os casos em que seria necessário habilitá-lo em máquinas que não sejam de desenvolvimento ou de teste (como servidores de produção ou estações de trabalho). Embora isso pareça em grande parte não documentado no momento em que este artigo foi escrito, o ttdrecord.dll permite um cenário de saída antecipada simplesmente criando ou atualizando uma chave de registro localizada em "HKEY_LOCAL_MACHINE\Software\Microsoft\TTD" e atualizando o valor DWORD32 RecordingPolicy para 2. Outras tentativas de usar qualquer serviço TTD (TTD.exe, TTDInject.exe, TTDService.exe) será interrompido e um evento ETW será gerado para rastrear tentativas.

Detectando TTD

Impedir o uso de TTD pode ser muito extremo para todos os ambientes — no entanto, existem vários indicadores para detectar o uso de TTD. Um processo que está sendo rastreado tem as seguintes propriedades:

  • Um thread executará o código de TTDRecordCPU.dll, que pode ser verificado usando um comando simples integrado do Windows: tasklist /m TTDRecordCPU.dll
  • Embora isso possa ser ignorado, o PID pai do processo gravado (ou o primeiro, caso o rastreamento recursivo esteja habilitado) seria o próprio TTD.exe:

  • Além disso, o ponteiro _KPROCESS.InstrumentationCallback seria definido para pousar na seção BSS TTDRecordCPU.dll do executável:

Portanto, a detecção de rastreamento de TTD pode ser obtida por meio dos métodos Modo Usuário e Modo Kernel.

Conclusão

Isso conclui a primeira parte desta pesquisa “On-Week” focada em TTD. Uma análise mais aprofundada do ecossistema TTD revelou alguns mecanismos muito interessantes e menos conhecidos integrados ao Windows, que são necessários para fazer o TTD funcionar em certos casos extremos — como o rastreamento de processos PPL.

Embora essa pesquisa não tenha revelado um novo backdoor secreto para atacar processos PPL, ela mostrou uma técnica inexplorada incorporada ao Windows para fazer isso. No mínimo, esta pesquisa destaca a importância de um modelo baseado em criptografia forte (aqui por meio do CI.dll) e como ele pode trazer muita flexibilidade — mantendo um alto nível de segurança — quando implementado adequadamente.

A segunda parte desta série será menos voltada para pesquisa e mais prática, com o lançamento de uma pequena ferramenta que também desenvolvemos como parte do On-Week. Isso auxilia no processo de análise binária por meio do TTD, usando o Windows Sandbox.

Reconhecimento

Como esta pesquisa já havia sido concluída e o artigo estava em andamento, o autor tomou conhecimento de uma pesquisa que abordava um assunto semelhante e de descobertas relacionadas à mesma técnica (token de depuração PPL). A pesquisa foi realizada por Lucas George (da empresa Synacktiv), que apresentou suas descobertas no SSTIC 2022.

Compartilhe este artigo