36 KiB
English
•
日本語 •
Portuguese •
Türkçe
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: identifica carregamento de biblioteca via callback, possivelmente para esconder a origem real da chamadaLoadLibraryna call stack.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: 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 emMEM_PRIVATEouMEM_MAPPEDque não pertence a uma imagem conhecida. - Frames
BackedModified: execução emMEM_IMAGEonde 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
.textdo 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_FUNCTIONe 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 novoLoadLibrarypara 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.
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 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.
BYOUD / Spoofing de Metadados de Unwind
O teste usa 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.
Cadeia de Callback Threadpool
Este sample é baseado na ideia do 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.
Proxy Em .text de Imagem
Este teste usa uma shellcode PIC que eu fiz a partir da variação experimental RefinedPool, baseada no projeto LibTPLoadLib. A técnica segue a linha de API proxying apresentada por paranoidninja e também demonstrada em 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.
Code Cave / Imagem Modificada
Esse padrão também é inspirado na variação experimental simples do meu projeto 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.
SilentMoonwalk RDI Com Stack Sintética
Aqui a variação de klezVirus/SilentMoonwalk é empacotada como payload via 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".
Dispatch Estilo MassDriver
Inspirado no padrão de dispatch do 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.
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.
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 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 Inline sem Compressão
Com /inline-stack, a stack volta para o formato em uma única linha, sem compactar repetições.
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.
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.
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.
.\build.ps1
Se utilizar o script, a saída do build é escrita em build\:
StackSentry64.exeCallstackMonitor.dll
Código de terceiros fica em third_party\. Veja 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.
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/memseja passado./max: perfil mais forte e prático para DLL load. Habilita telemetria deep, stack audit, checagens LDR e/auto-enterpor padrão, mas deixa hooks de memória/API, wait e threadpool desligados a menos que sejam pedidos explicitamente./profile <quick|deep|max>: 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-stacke/full-stack.Origin / proxy analysis:/regtrace,/dispatch-tracee/threadpool-chain-tracepara casos de caller escondido e proxy loading.Network use analysis:/network-use-tracee/net-use-tracepara reutilização de DLL de rede já carregada.Remote / multi-process:/follow-remotee/net-resetpara loaders que movem execução para outro processo.Extra telemetry / integrity:/etw,/ldr-integrity,/unwind,/stack-audit,/memory-audit,/byoud-tracee/shadow-stack.Aggressive / low-level hooks:/mem,/tp,/waite/xhooksdireto.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:
.\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.textdo 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.
.\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:
.\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 injetaCallstackMonitor.dllno 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\<process.exe - pid>\ 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:
.\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:
.\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:
.\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:
.\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:
.\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
.\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,/livee/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.
.\build\StackSentry64.exe
.\build\StackSentry64.exe /help
.\build\StackSentry64.exe /version
.\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:
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.jsoneevents.jsonlguardam 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\emodified_network_modules\guardam bytes preservados para análise posterior.network_trace.json,byoud_trace.json,shadow_stack_trace.jsoneetw_timeline.jsonaparecem quando os modos correspondentes são usados.children\guarda artefatos por PID quando/follow-remoteacompanha 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:
{
"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/WaitForMultipleObjectseTp*existem, mas ficam fora de/deepe/maxporque 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-remotedepende de hooks user-mode observando a preparação de execução remota. Cadeias completas de direct/indirect syscalls ainda podem contornar esses hooks./net-resetpode 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-auditroda 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
EntryPointfora da imagem do módulo ou fora deMEM_IMAGEcomo "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
/memestá 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.















