![StackSentry](/static/StackSentry.png)
`English``日本語``Portuguese``Türkçe`       ![Release](https://img.shields.io/badge/release-v0.8-000000?style=for-the-badge)

StackSentry nasceu de uma ideia simples que foi ficando cada vez mais teimosa: se um loader tenta esconder a origem de um `LoadLibrary`, a call stack provavelmente ainda deixa alguma pista. Este projeto é uma ferramenta de pesquisa em user-mode x64 para triagem de memória, análise de loaders e detecção de carregamento sensível de DLLs. Ela inicia um processo alvo, injeta uma DLL de monitoramento leve e observa eventos importantes no momento em que acontecem, tentando responder uma pergunta direta:
**quem realmente provocou esse carregamento ou uso de rede?**
> [!Important] > |**Este projeto ainda está em desenvolvimento**. Portanto, bugs, falsos positivos ou falsos negativos podem ocorrer. | > |:--------------------------------| > | O StackSentry foi desenvolvido principalmente para facilitar a análise de loaders e payloads em laboratório.
A ferramenta em si não instala driver e não faz alteração persistente no sistema, compare resultados com ferramentas auxiliares e considere o resultado como evidência de triagem, não como veredito absoluto. |
## A Ideia O foco principal do StackSentry é triagem rápida de código executando em memória. A lógica por trás do projeto é bem prática: um C2, RAT ou loader fileless pode esconder o arquivo em disco, criptografar o próprio corpo enquanto dorme e montar uma call stack bonita, mas uma hora ele precisa carregar ou falar com uma dll de rede. No Windows, isso normalmente passa por DLLs como `ws2_32.dll`, `wininet.dll`, `winhttp.dll`, `dnsapi.dll` ou por APIs que vivem nelas. Se essas DLLs são carregadas ou usadas a partir de uma origem estranha, vale parar e olhar. Esse padrão não saiu do nada. Ele vem diretamente de ideias já usadas em detecção comportamental, como estas regras da Elastic: - [`defense_evasion_library_loaded_via_a_callback_function.toml`](https://github.com/elastic/protections-artifacts/blob/6e9ee22c5a7f57b85b0cb063adba9a3c72eca348/behavior/rules/windows/defense_evasion_library_loaded_via_a_callback_function.toml): identifica carregamento de biblioteca via callback, possivelmente para esconder a origem real da chamada `LoadLibrary` na call stack. - [`defense_evasion_network_module_loaded_from_suspicious_unbacked_memory.toml`](https://github.com/elastic/protections-artifacts/blob/6e9ee22c5a7f57b85b0cb063adba9a3c72eca348/behavior/rules/windows/defense_evasion_network_module_loaded_from_suspicious_unbacked_memory.toml): identifica carregamento de módulo de rede quando a stack da thread contém frames fora de imagens executáveis conhecidas, um padrão comum em execução em memória. - [`defense_evasion_library_loaded_from_a_spoofed_call_stack.toml`](https://github.com/elastic/protections-artifacts/blob/6e9ee22c5a7f57b85b0cb063adba9a3c72eca348/behavior/rules/windows/defense_evasion_library_loaded_from_a_spoofed_call_stack.toml): detecta carregamento de biblioteca a partir de uma call stack possivelmente alterada para esconder a fonte real da chamada. Justamente por existirem regras desse tipo, alguns loaders começaram a evitar o caminho óbvio. Em vez de chamar `LoadLibrary` direto a partir de memória privada, tentam esconder a origem usando callbacks, gadgets em módulos legítimos, threadpool, dispatch entre threads, execução em imagem modificada ou até metadados de unwind. O StackSentry pega essa ideia e tenta aprofundar em modo de laboratório: não só dizer que uma DLL sensível foi carregada, mas também mostrar a origem provável, a memória envolvida, o estado da stack, os dumps úteis e, quando possível, o caminho que o loader tentou esconder. ## O Que Ele Procura - Carregamento de DLLs sensíveis (`ws2_32`, `wininet`, `winhttp`, `dnsapi`, CLR/.NET e afins) a partir de stacks suspeitas. - Frames `Unbacked`: execução em `MEM_PRIVATE` ou `MEM_MAPPED` que não pertence a uma imagem conhecida. - Frames `BackedModified`: execução em `MEM_IMAGE` onde bytes vivos divergem do arquivo em disco. - Origem escondida por proxy/gadget, callback, thread start, APC, VEH, threadpool, dispatch entre threads ou execução anterior na `.text` do alvo. - Stack spoofing: return frame sem callsite plausível, stack truncada, stack sintética ou caller visível “limpo” demais para o evento. - BYOUD/unwind spoofing: mudanças temporais em `.pdata`, `.xdata`, `.rdata`, `RUNTIME_FUNCTION` e tabelas dinâmicas de unwind. - Uso real de APIs de rede, quando `/network-use-trace` é habilitado, mesmo se a DLL já estava carregada e não existe um novo `LoadLibrary` para observar. - Metadados LDR/loader fora do esperado, incluindo entrypoint hijack e inconsistências de módulo. - Memory audit: imagens modificadas, mappings executáveis órfãos, regiões privadas executáveis e artefatos em memória com dumps focados. - Correlação e artefatos: `events.jsonl`, `summary.json`, `memory.json`, `network_trace.json`, `byoud_trace.json`, origin dumps, module dumps, hashes, entropia e strings úteis. - E outras checagens menores de telemetria, integridade, ETW opcional, saída compacta e modos de laboratório. ## Galeria de Detecção Os comandos dos samples e os resumos esperados de call stack estão documentados em [samples/README.md](samples/README.md).
Já vou adiantando: todos os samples são uma "bomba russa", eu fiz eles apenas para testes locais, então não espere grande coisa.
Abaixo estão alguns padrões que o StackSentry consegue mostrar no terminal sem precisar de kernel driver. Algumas imagens ainda mostram saídas tiradas na fase `v0.8`. Depois disso eu fiz melhorias de console, compactação de stack e redução de ruído, mas não achei honesto chamar isso de `v0.9` só por causa de polimento visual. Então, se algum detalhe de formatação estiver um pouco diferente da versão atual, é por isso. > [!Important] > A maioria das imagens abaixo mostra apenas um recorte do resultado de detecção. Teste para ver o resultado completo. ### SilentMoonwalk Com Stack Sintética Foi usada uma variação do projeto [klezVirus/SilentMoonwalk](https://github.com/klezVirus/SilentMoonwalk) para carregar uma DLL de rede com uma stack sintética. Todos os frames visíveis parecem módulos legítimos, mas a validação de callsite e o origin tracing ainda ligam o DLL load ao código que preparou a chamada. ![Detecção de stack sintética SilentMoonwalk](static/stack-03-silentmoon-synthetic.png) ### BYOUD / Spoofing de Metadados de Unwind O teste usa [klezVirus/byoud](https://github.com/klezVirus/byoud) transformado em shellcode RDI. O bootstrap carrega DLLs como `wininet.dll` e `mscoree.dll`, mas o estágio que importa aqui usa BYOUD para esconder o carregamento de `ws2_32.dll`: em vez de mexer no endereço de retorno, manipula metadados de unwind do Windows. O StackSentry captura a divergência temporal antes do carregamento sensível e reporta a tabela/estrutura que saiu do esperado. ![Detecção BYOUD RDI](static/stack-05-byoud-rdi-1.png) ![Detecção BYOUD RDI](static/stack-05-byoud-rdi-2.png) ### Cadeia de Callback Threadpool Este sample é baseado na ideia do [klezvirus/ThreadPoolExecChain](https://github.com/klezvirus/ThreadPoolExecChain): uma cadeia threadpool/proxy faz o carregamento acontecer em um contexto que parece mais natural. O relatório preserva o contexto da cadeia e marca os frames modificados que aparecem no caminho. ![Detecção de cadeia threadpool](static/stack-07-threadpool-chain.png) ### Proxy Em `.text` de Imagem Este teste usa uma shellcode PIC que eu fiz a partir da variação experimental [RefinedPool](https://github.com/Vith0r/RefinedPool/tree/main/RefinedPool), baseada no projeto [LibTPLoadLib](https://github.com/AlmondOffSec/LibTPLoadLib). A técnica segue a linha de API proxying apresentada por [paranoidninja](https://0xdarkvortex.dev/hiding-in-plainsight/) e também demonstrada em [paranoidninja/Proxy-DLL-Loads](https://github.com/paranoidninja/Proxy-DLL-Loads). Por ser PIC, ela pode ficar na `.text` do loader e chamar `LoadLibrary` via proxy, adicionando mais um elemento à stack por meio de um gadget existente em `nvwgf2umx.dll`. A stack final aponta para um trecho aparentemente limpo, mas o register/origin tracing liga o DLL load de volta para a região `.text` que iniciou o fluxo. Isso prova que a detecção consegue acontecer mesmo em padrões bem específicos como este exemplo. ![Detecção de proxy em .text](static/stack-01-text-section-proxy.png) ### Code Cave / Imagem Modificada Esse padrão também é inspirado na variação experimental simples do meu projeto [RefinedPool](https://github.com/Vith0r/RefinedPool/tree/main/RefinedPool), transformado em shellcode PIC. O carregamento sensível passa por *bytes escritos em um code cave de imagem*. O interessante aqui é que o StackSentry consegue preservar o módulo modificado e o mapa dos bytes alterados, entregando isso no dump, o que eu acho ser realmente útil. ![Detecção de code cave em imagem modificada](static/stack-02-code-cave-modified-image.png) ### SilentMoonwalk RDI Com Stack Sintética Aqui a variação de [klezVirus/SilentMoonwalk](https://github.com/klezVirus/SilentMoonwalk) é empacotada como payload via [Donut](https://github.com/TheWover/donut)/RDI. O bootstrap pode carregar DLLs como `wininet.dll` e `mscoree.dll`, mas o estágio relevante da técnica é o carregamento de `ws2_32.dll` com stack sintética. A primeira imagem mostra os alertas de carregamento de DLL, a segunda mostra a origem provável voltando para a região executável "escondida". ![Detecção de SilentMoonwalk RDI sintético](static/stack-04-silentmoon-rdi-synthetic-1.png) ![Detecção de SilentMoonwalk RDI sintético](static/stack-04-silentmoon-rdi-synthetic-2.png) ### Dispatch Estilo MassDriver Inspirado no padrão de dispatch do [Sizeable-Bingus/MassDriver](https://github.com/Sizeable-Bingus/MassDriver), uma worker thread aparentemente limpa executa `LoadLibraryA`. O `/dispatch-trace` liga o carregamento ao requester que postou a estrutura de dispatch. É uma detecção de um padrão bem específico, mas achei interessante de realizar. ![Detecção de dispatch estilo MassDriver](static/stack-06-massdriver-dispatch.png) ### Network Use Trace em Payload C2 Este exemplo usa `/network-use-trace` para mostrar o caso em que o carregamento da DLL de rede não é a única coisa importante. O payload também precisa usar APIs de rede, e o StackSentry tenta atribuir quem chamou `connect`, `WSAConnect`, `send`, `recv`, WinHTTP/WinINet e afins. Isso acaba sendo útil porque o output pode revelar o destino real, como domínio, IP/porta, e até serviços de terceiros usados no fluxo, como o `pastebin.com` nesse teste. A stack da imagem está compactada de propósito para não virar um bloco gigante de `system.ni.dll` repetido.
Se preferir ver a stack completa em uma linha, como nas prints antigas, use `/inline-stack`, mas se quiser os frames linha por linha com offsets, use `/full-stack`. ![Detecção de network-use trace em payload C2](static/stack-08-network-use-trace-c2.png) ### Modos de Visualização da Stack Além das detecções em si, a saída atual do StackSentry tenta deixar a análise menos cansativa. A mesma stack pode ser mostrada de formas diferentes dependendo do que você quer fazer: #### Stack Compactada Esse é o formato padrão atual. Frames repetidos são agrupados como `[module.dll xN]`, o que reduz muito o ruído em payloads que passam por runtimes grandes como .NET. ![Stack compactada](static/stack-09-compressed-stack.png) #### Stack Completa com Offsets Com `/full-stack`, cada frame é mostrado em uma linha separada, com offset do módulo. É um modo interessante quando você quer auditar exatamente onde cada retorno caiu. ![Stack completa com offsets](static/stack-10-full-stack.png) #### Stack Inline sem Compressão Com `/inline-stack`, a stack volta para o formato em uma única linha, sem compactar repetições. ![Stack inline sem compressão](static/stack-11-inline-stack.png) #### Eventos Limpos com Verbose O modo padrão prioriza alertas para não lotar o terminal. Com `/verbose`, também aparecem carregamentos limpos com `score=0`, esses eventos ficam em azul. Isso ajuda a confirmar rapidamente que um DLL load foi visto e registrado, mesmo quando ele não é suspeito por si só. Mesmo sem `/verbose`, eventos de `score=0` continuam salvos em `events.jsonl` e `memory.json`. ![Evento limpo em modo verbose](static/stack-12-verbose-clean-load.png) #### BackedModified e Memory Audit Quando o retorno cai dentro de uma DLL real, mas os bytes daquela região não batem com o arquivo em disco, o StackSentry não trata isso como um frame limpo. Ele marca a stack como `BackedModified`/`captured-modified` e o Memory Audit registra o módulo, a região e o detalhe temporal da alteração. ![BackedModified e memory audit](static/stack-13-backed-modified-memory-audit.png) ## Build Para facilitar a compilação do projeto, deixei o arquivo `build.ps1`. Ele usa o Microsoft Visual Studio/MSVC para compilar. Caso você não use exatamente o mesmo ambiente, não deve ser difícil adaptar, o script é curto, e olhando ele você já vai entender o que precisa ser chamado. ```powershell .\build.ps1 ``` Se utilizar o script, a saída do build é escrita em `build\`: - `StackSentry64.exe` - `CallstackMonitor.dll` Código de terceiros fica em `third_party\`. Veja [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md) para créditos e notas de licença. ## Comandos de Teste Os comandos recomendados e os comandos exatos dos samples estão em [samples/README.md](samples/README.md).
Se estiver em dúvida, comece por lá, deixei o básico para uma primeira passada, o perfil forte, os modos de stack e os exemplos que eu uso para validar as prints da galeria. ## Perfis Principais - `/quick`: perfil estável de triagem de DLL load. Bom para primeiras execuções, baseline benigno e baixa quantidade de ruído. - `/deep`: perfil de hunting para DLL load. Habilita hooks estáveis de callback/thread-start, hooks de tabela unwind, checagens de integridade LDR, dumps, carving e uma janela de correlação maior. Telemetria de memória/API continua desligada, a menos que `/mem` seja passado. - `/max`: perfil mais forte e prático para DLL load. Habilita telemetria deep, stack audit, checagens LDR e `/auto-enter` por padrão, mas deixa hooks de memória/API, wait e threadpool desligados a menos que sejam pedidos explicitamente. - `/profile `: seleciona um perfil por nome. Se você só quer testar um loader e não quer pensar muito, eu começaria com `/max`. Depois disso, adicionaria `/hunt`, `/network-use-trace` ou flags específicas conforme o resultado. Flags explícitas de hook continuam sendo aditivas, então `/quick /mem`, `/deep /mem` e `/max /mem` são válidos. Use `/max /tp /wait` somente quando quiser o conjunto experimental mais agressivo de hooks. ## Grupos de Comandos Caso queira saber todos os possíveis argumentos, utilize `/features`.
A saída completa de `/features` é agrupada por intenção para usuários novos não precisarem tratar toda flag como igualmente importante: - `Common options`: pasta de saída, timeout, keep-alive, automação de stdin e verbosidade. - `Output style`: controles quiet/plain/live/color, supressão de output do alvo, `/inline-stack` e `/full-stack`. - `Origin / proxy analysis`: `/regtrace`, `/dispatch-trace` e `/threadpool-chain-trace` para casos de caller escondido e proxy loading. - `Network use analysis`: `/network-use-trace` e `/net-use-trace` para reutilização de DLL de rede já carregada. - `Remote / multi-process`: `/follow-remote` e `/net-reset` para loaders que movem execução para outro processo. - `Extra telemetry / integrity`: `/etw`, `/ldr-integrity`, `/unwind`, `/stack-audit`, `/memory-audit`, `/byoud-trace` e `/shadow-stack`. - `Aggressive / low-level hooks`: `/mem`, `/tp`, `/wait` e `/xhooks` direto. - `Advanced config`: regras/configurações customizadas. ## Origin Tracing Técnicas de DLL load por proxy podem fazer a call stack final parecer limpa, inclusive colocando um gadget de imagem existente entre o código real do loader e `LoadLibrary`. O StackSentry mantém os perfis padrão focados e com pouco ruído, mas adiciona modos opt-in para esses casos: ```powershell .\build\StackSentry64.exe /run .\samples\sample_03_text_section_proxy.exe /max /origin-trace /no-target-output /timeout 9000 .\build\StackSentry64.exe /run .\samples\sample_03_text_section_proxy.exe /max /regtrace /no-target-output /timeout 9000 ``` - `/origin-trace`: correlaciona DLL loads posteriores com contexto recente de callback/thread/APC. - `/regtrace`: habilita o caminho de origin-trace e envolve thread starts na `.text` do alvo para ligar um DLL load escondido por proxy de volta ao código original quando essa origem está ausente na stack final. - `/proxy-trace`: alias para `/regtrace`. Nota técnica: atualmente `/regtrace` evita intencionalmente o rastreamento completo de registradores em módulos de imagem não principais com mais de 32 MB. Memória executável privada, inicializações de thread, transições executáveis dinâmicas e correlação de origem continuam sendo rastreadas; essa omissão só evita a instrumentação completa e cara de imagens enormes usadas como gadget-carrier. Eu precisei fazer isso porque esse tracing estava atrapalhando mais análises normais do que ajudando. Se você realmente precisar aumentar esse teto em um caso de laboratório, é só uma constante pequena no código. Hooks de threadpool continuam explícitos. Adicione `/tp` somente quando quiser telemetria de `TpAllocWork/TpPostWork`. Quando `/regtrace` correlaciona um load limpo via gadget de volta para código executável `MEM_IMAGE`, o StackSentry escreve um artefato em `origin_regions\`. Isso é separado de `dumps\`: não é uma alocação unbacked, e sim uma janela focada de memória ao redor da origem `.text` rastreada, com um JSON lateral contendo VA/RVA da origem, caller gadget visível, SHA256, entropia e strings selecionadas. O hook de `LdrLoadDll` também registra evidências de origem vindas de argumentos `UNICODE_STRING` vivos e blocos de parâmetros proxy. Isso cobre bypasses simples que entram em `LoadLibrary*` depois do prologue instrumentado, mas ainda chegam em `ntdll!LdrLoadDll`. ## Integridade LDR O StackSentry também detecta hijack de entrypoint do loader. Isso cobre técnicas como LdrShuffle/EPI, onde um módulo ainda parece legítimo nas listas do PEB loader, mas seu `LDR_DATA_TABLE_ENTRY.EntryPoint` é alterado para código controlado pelo atacante. ```powershell .\build\StackSentry64.exe /run target.exe /ldr-integrity /timeout 10000 ``` `/deep` e `/max` habilitam essa checagem. `/quick` a mantém desligada. O analyzer reporta o módulo hijacked, entrypoint atual, entrypoint esperado do PE, tipo/proteção de memória do entrypoint atual e contexto como `OriginalBase` suspeito quando já existe uma anomalia de entrypoint. ## Remote Follow e Net Reset Esses são modos de laboratório opt-in e não fazem parte de `/quick`, `/deep` ou `/max` por padrão: ```powershell .\build\StackSentry64.exe /run loader.exe /max /follow-remote /timeout 15000 .\build\StackSentry64.exe /run loader.exe /max /follow-remote /net-reset /timeout 15000 ``` - `/follow-remote`: observa handles de processo remoto e injeta `CallstackMonitor.dll` no processo remoto quando execução remota está prestes a acontecer. - `/net-reset`: tenta descarregar DLLs de rede monitoradas já carregadas para que um payload precise recarregar seu próprio módulo de rede, tornando o caller visível para o StackSentry. Isso pode funcionar ou não, então espere possíveis erros. Artefatos de processos remotos são escritos em `children\\` dentro da pasta principal da execução. `events.jsonl` continua agregado, e cada evento inclui o PID que o produziu. ## Network Use Trace `/network-use-trace` cobre o caso em que um payload não precisa chamar `LoadLibrary` para uma DLL de rede monitorada porque o host, loader ou runtime já carregou esse módulo. Em vez de perguntar apenas quem carregou `wininet.dll`, `ws2_32.dll`, `winhttp.dll` ou `dnsapi.dll`, o StackSentry também pergunta quem está usando as APIs de rede desses módulos. Claro, ele também pode ser usado mesmo quando o loader/shellcode carrega a DLL de rede por conta própria: ```powershell .\build\StackSentry64.exe /run target.exe /max /network-use-trace /timeout 10000 .\build\StackSentry64.exe /run target.exe /max /hunt /network-use-trace /timeout 15000 ``` O monitor instala hooks em um conjunto focado de APIs como `connect`, `WSAConnect`, `send`, `recv`, `getaddrinfo`, `DnsQuery_*`, `InternetOpenUrl*`, `InternetConnect*`, `HttpOpenRequest*`, `HttpSendRequest*` e chamadas comuns de request/read/write do WinHTTP. O analyzer então classifica o endereço caller e a stack como em um evento de DLL load. Casos de alto sinal incluem APIs de rede chamadas a partir de `MEM_PRIVATE` executável, código image-backed modificado, metadados de unwind adulterados ou stack spoofing/incomum. Aqui vai uma observação honesta: dependendo do programa analisado, você pode enfrentar erros, volume alto de eventos e falsos positivos. Esse modo foi pensado para loaders e payloads em laboratório, não para prometer uma experiência perfeita em programas grandes já previamente alterados. Findings aparecem em `== Network Use Details ==` e são escritos em `network_trace.json`, `memory.json` e `summary.json`. Esse modo não faz parte de `/hunt` por padrão: ele é forte, mas pode ser verboso, então prefiro que o analista habilite explicitamente quando quiser provar uso real de APIs de rede. ## Memory Audit `/memory-audit` é um scan opt-in do estado vivo de memória inspirado na maravilhosa pesquisa Moneta, de Forrest Orr, sobre artefatos maliciosos em memória. Ele tenta complementar o modelo de evento/call-stack do StackSentry perguntando como o processo se parece em memória antes de o StackSentry terminar ou desanexar dele: ```powershell .\build\StackSentry64.exe /run target.exe /max /memory-audit /timeout 10000 .\build\StackSentry64.exe /run target.exe /max /regtrace /memory-audit /timeout 10000 ``` O audit verifica regiões `MEM_IMAGE` ausentes das listas de loader/módulos, mappings de imagem sem nome de arquivo mapeado disponível, páginas privadas executáveis dentro de imagens, headers PE apagados ou fortemente alterados, permissões executáveis em seções não executáveis e regiões privadas ou mapeadas executáveis que parecem payloads. Findings de alta confiança aparecem em `== Memory Audit ==` e podem criar dumps focados em `memory_audit\`. O contexto de hunting de menor confiança vai ficar em `memory_audit.json` sem gerar alerta por padrão, porque componentes modernos do Windows e ferramentas residentes podem criar páginas privadas ou modificadas de forma legítima. Esse modo intencionalmente não é habilitado por `/quick`, `/deep` ou `/max`. ## BYOUD e Pesquisa de Shadow Stack `/byoud-trace` é um modo de laboratório para casos de DLL load que manipulam metadados de unwind x64 do Windows em vez de endereços de retorno óbvios. Ele observa APIs de tabela unwind, mudanças de proteção de memória ao redor de `.pdata`/`.xdata`/`.rdata` e divergência temporal de metadados antes de chamadas sensíveis do loader: ```powershell .\build\StackSentry64.exe /run target.exe /max /byoud-trace /regtrace /timeout 12000 ``` `/hunt` inclui `/byoud-trace` porque o corpus atual de testes BYOUD fornece prova repetível por divergência temporal de metadados de unwind, o que eu achei útil demais para deixar escondido atrás de uma flag separada. `/shadow-stack` é diferente. Ele é exposto apenas como switch de pesquisa/teste para sistemas onde o Windows expõe *estado de shadow stack CET/HSP em user-mode*. Ele captura frames de retorno CET, compara esses frames contra a stack clássica como uma sequência ordenada e reporta frames escondidos, ausentes ou fora de ordem. Ele não está incluído em `/hunt`, não é contado como cobertura madura e pode não produzir findings em sistemas onde `XSTATE_CET_U` não está disponível. Para ser bem honesto eu precisei testar isso no notebook de um amigo, e não pude realizar muitos testes, então espere possíveis problemas: ```powershell .\build\StackSentry64.exe /run target.exe /max /shadow-stack /stack-audit /regtrace /timeout 12000 ``` Quando funciona, findings aparecem em `== Shadow Stack Trace ==` e são escritos em `shadow_stack_trace.json`. Quando a plataforma não expõe o estado CET necessário, as camadas normais `/regtrace`, `/stack-audit`, `/memory-audit` e `/byoud-trace` continuam carregando a detecção. ## Timeline ETW `/etw` é um modo de laboratório opt-in que inicia um trace kernel com krabsetw antes de a thread principal do alvo ser retomada. Ele registra eventos de processo, thread e image-load para o PID primário e PIDs filhos cujo processo pai já está sendo rastreado. Para ser realista, não é o recurso mais útil do projeto, mas achei interessante de adicionar: ```powershell .\build\StackSentry64.exe /run target.exe /max /etw /timeout 10000 .\build\StackSentry64.exe /run loader.exe /max /follow-remote /etw /timeout 15000 ``` Isso não substitui as detecções da DLL de monitoramento. Ele fornece uma timeline com respaldo do kernel para responder perguntas como qual processo filho apareceu, qual DLL foi mapeada naquele momento e se a execução remota do payload bate com um estágio suspeito do loader. A timeline é escrita em `etw_timeline.json` e resumida na saída final do console. Coleta kernel ETW pode exigir privilégios elevados, se o Windows recusar o trace, o StackSentry reporta `/etw` como indisponível e continua a análise normal. ## Hooks Individuais ```powershell .\build\StackSentry64.exe /run target.exe /xhooks .\build\StackSentry64.exe /run target.exe /mem /unwind ``` `/xhooks` habilita os hooks mais estáveis de callback/thread-start (`CreateThread` e `QueueUserAPC`). `/origin-trace` adiciona correlação de origem, e `/regtrace` adiciona o caminho mais pesado de tracing de thread-start na `.text` do alvo. `/mem` habilita telemetria ruidosa de stack para APIs de memória (`NtAllocateVirtualMemory`, `NtProtectVirtualMemory`, `NtMapViewOfSection`, writes, criação de thread e queue de APC) e intencionalmente não é habilitado por nenhum perfil principal. `/etw` adiciona telemetria kernel de timeline processo/thread/image-load via krabsetw. `/tp` e `/wait` são separados porque hooks `Tp*` e `WaitFor*` podem desestabilizar alguns alvos. E sim, eles realmente podem desestabilizar, então use conforme o alvo. Argumentos legados (`-e`, `--out`, `--rules`, `--timeout-ms` e `--experimental-hooks`) ainda funcionam. ## Saída de Console A saída de console é agrupada por bloco de análise (`DLL LOAD ANALYSIS`, `MEMORY API TELEMETRY`, `CALLBACK/THREAD ANALYSIS` e seções relacionadas). Por padrão, o console mostra apenas alertas para que findings de DLL load não fiquem enterrados em telemetria rotineira. Use `/verbose` quando quiser ver eventos sem alerta também. `events.jsonl` continua recebendo o stream completo de eventos. Na prática, o console tenta não virar um romance. O resumo fica legível, e o detalhe bruto continua nos arquivos JSON para quem quiser cavar depois. Algumas flags úteis para controlar a saída: - `/no-target-output`: não mistura stdout/stderr do alvo no console do StackSentry. - `/inline-stack`: imprime a stack completa em uma linha, sem compactar frames repetidos. - `/full-stack`: imprime frames um por linha com offset de módulo e desativa a compactação tipo `[module xN]`. - `/quiet`: escreve artefatos e reduz a UI do console. - `/plain`, `/live` e `/no-color`: ajustam animação/cor porque sim. No final, o bloco `Memory` lista cada DLL monitorada carregada com o endereço base do módulo carregado e o endereço selecionado de caller/origem na stack. Esses endereços também são escritos em `memory.json` e `summary.json`. ```powershell .\build\StackSentry64.exe .\build\StackSentry64.exe /help .\build\StackSentry64.exe /version ``` ```powershell .\build\StackSentry64.exe /run target.exe /live /verbose .\build\StackSentry64.exe /run target.exe /plain /no-color .\build\StackSentry64.exe /run target.exe /quiet ``` ## Saídas Cada execução cria uma pasta por processo dentro do diretório escolhido em `/out`: ```text out\loader\loader_binary.exe - 24216\ ``` O console já tenta entregar o que importa primeiro, então você não precisa abrir todo JSON para entender uma execução simples. Mas, quando quiser validar uma detecção ou comparar com outra ferramenta, vale olhar os artefatos: - `summary.json`, `memory.json` e `events.jsonl` guardam o resumo, os eventos e as decisões que sustentaram o alerta. - `origin_regions\` guarda janelas focadas da região que o origin tracing associou ao carregamento escondido. - `dumps\`, `memory_audit\`, `modified_modules\` e `modified_network_modules\` guardam bytes preservados para análise posterior. - `network_trace.json`, `byoud_trace.json`, `shadow_stack_trace.json` e `etw_timeline.json` aparecem quando os modos correspondentes são usados. - `children\` guarda artefatos por PID quando `/follow-remote` acompanha execução em outro processo. Um detalhe que vale atenção: quando o StackSentry preserva um módulo modificado, ele também escreve um arquivo `.tag` ao lado do dump.
Esse `.tag` é um mapa de diferença simples, com offsets/bytes alterados em relação ao arquivo no disco. Para casos de code cave, module stomping ou imagem temporariamente modificada, isso costuma ser mais útil do que só ter o módulo inteiro dumpado. Exit codes: - `0`: nenhum alerta. - `10`: pelo menos um alerta foi gerado. - `1`/`2`: erro de runtime, crash do alvo, erro de argumento ou erro de configuração. ## Regras `config\rules.json` mostra o formato suportado: ```json { "schema_version": 3, "network_modules": ["ws2_32.dll", "wininet.dll", "winhttp.dll", "dnsapi.dll", "iphlpapi.dll"], "dotnet_modules": ["clr.dll", "coreclr.dll", "mscoree.dll", "System.Management.Automation.dll"], "alert_on_unbacked_executable": true, "alert_on_backed_modified": true, "dump_suspicious_regions": true, "analyze_dumps": true, "carve_embedded_pe": true, "module_integrity_enabled": true, "enable_sleep_hooks": true, "enable_msgwait_hook": true, "enable_wait_object_hooks": false, "enable_thread_start_hooks": false, "enable_threadpool_hooks": false, "experimental_hooks": false, "memory_api_hooks": false, "unwind_integrity": true, "unwind_table_hooks": false, "origin_trace": false, "register_trace": false, "follow_remote": false, "net_reset": false, "etw_telemetry": false, "ldr_integrity": false, "dispatch_trace": false, "threadpool_chain_trace": false, "stack_audit": false, "memory_audit": false, "byoud_trace": false, "shadow_stack": false, "network_use_trace": false, "callsite_validation": true, "alert_on_unwind_tamper": true, "correlation_window_ms": 5000, "max_dump_bytes": 16777216, "long_sleep_ms": 1000 } ``` Use outro arquivo com `--rules path\to\rules.json`. O campo antigo `watch_dlls` ainda é aceito como alias de compatibilidade. Provavelmente você nem vai ver isso em uso, porque basicamente só eu testei esse caminho, e porque não remover? tô cansado. ## Limitações Atuais Eu poderia tentar vender esse projeto como se ele resolvesse tudo, mas seria desonesto. Depois de centenas de testes, ficou claro que ele é forte no que se propõe, mas ainda enfrenta problemas dependendo do alvo. Como todo projeto desse tipo, existem limitações reais, algumas óbvias, outras nem tanto: - Isto ainda é instrumentação em user-mode. Um alvo forte pode obviamente detectar/remover hooks ou usar caminhos de execução que não são observados. - Hooks de `WaitForSingleObject/WaitForMultipleObjects` e `Tp*` existem, mas ficam fora de `/deep` e `/max` porque causaram instabilidade em alguns alvos de teste. - Hooks de API de memória podem ser ruidosos em ambientes com engines benignos de injeção/hook, então são habilitados apenas quando `/mem` é passado. - `Register tracing` é telemetria de laboratório opt-in. Ele foi desenhado para revelar origens escondidas por proxy/gadget em loaders, não para produzir um trace completo de instruções. - `/follow-remote` depende de hooks user-mode observando a preparação de execução remota. Cadeias completas de direct/indirect syscalls ainda podem contornar esses hooks. - `/net-reset` pode falhar quando uma DLL é importada estaticamente, segurada por refcount ou em uso ativo. Caso isso ocorra, uma falha de reset é reportada e a análise continua normalmente. - `/etw` é telemetria de contexto, não prova de detecção por si só. Pode exigir elevação e perder eventos que ocorreram antes do início do trace. Não tem muito o que fazer aqui. - `/memory-audit` roda enquanto o alvo ainda está vivo, normalmente no timeout/keep-alive. Se o alvo sair antes disso, tem grande chance de não existir espaço de endereçamento vivo para escanear. - Integridade LDR trata `EntryPoint` fora da imagem do módulo ou fora de `MEM_IMAGE` como "prova principal". `OriginalBase` é reportado como contexto porque esse campo é sensível ao layout em diferentes builds do Windows e não deve ser confiado sozinho. Eu percebi isso testando em outro notebook, então deixei esse ponto mais conservador. - Zydis melhora o decoding de instruções, mas validação de callsite ainda é heurística porque um endereço de retorno sozinho "não prova o histórico real de control-flow". - Comparação de módulos é "inspirada no PE-sieve", mas claramente simplificada: compara seções executáveis e de unwind contra disco e não tenta modelar todo caso legítimo de relocação/hook. - O memory audit é inspirado em classes de artefatos estilo Moneta, mas é conservador no console: evidência fraca de página privada é escrita como contexto de hunting a menos que esteja ligada a uma anomalia mais forte. - `/shadow-stack` é um switch de pesquisa para experimentos CET/HSP e pode ficar silencioso quando o alvo ou a plataforma não expõe estado de shadow stack em user-mode. - Stack spoofing avançado não é impossível de contornar em ring3. A ferramenta aumenta o custo correlacionando callbacks, callsites, integridade de módulo, bytes capturados de callsite, metadados de unwind e telemetria opcional de memória/API quando `/mem` está habilitado. ## Licença Este projeto é distribuído sob a licença MIT (Modify It Tonight). Use, modifique, quebre em laboratório, arrume, compare, publique resultado, faça o que precisar.
Se ele economizar algumas horas de análise, já fico feliz! (Mas aceito um café, se quiser). Código de terceiros mantém suas próprias licenças e créditos em [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).