
Um mecanismo de análise estática zero-symbol que extrai e ranqueia matematicamente a superfície de ataque do Windows RPC usando um modelo de risco baseado em AHP.
Estática vs dinâmica, leia isto primeiro. Vale a pena deixar clara desde o início a linha entre análise dinâmica e estática: esta ferramenta faz sua classificação puramente por análise estática. Ela lê as estruturas MIDL / NDR compiladas diretamente do binário e nunca executa nada.
Triagem estática para a superfície de ataque RPC do Windows. Aponte-a para uma pasta de binários PE; ela encontra todos os que registram um servidor RPC, recupera as assinaturas de métodos NDR de cada interface, os bindings de transporte e os flags de registro diretamente das estruturas MIDL compiladas, e então classifica cada interface por alcance x perigo, com um comprovante aritmético completo anexado a cada pontuação para que você possa conferir a matemática manualmente.
As ferramentas RPC existentes recuperam sem problemas as assinaturas de métodos de uma interface e seus flags de registro. O que nenhuma delas faz é pegar ambos e responder à única pergunta que realmente decide onde você gasta seu tempo: dado que posso alcançar esta interface e dado o que seus métodos aceitam como entrada, com que urgência devo examiná-la em comparação com todo o resto na máquina? Essa lacuna é o que esta ferramenta preenche.
Filtra a pasta de destino para que o Ghidra só faça auto-análise em binários que realmente registram um servidor RPC (eles importam rpcrt4.dll e chamam uma das APIs RpcServerRegisterIf\*). No System32, isso é a diferença entre uma tarde e uma semana.
Extrai, por interface: UUID, a cadeia RPC_SERVER_INTERFACE / MIDL_SERVER_INFO, o autoritativo DispatchTableCount, o opnum de cada método + direções de parâmetros + opcodes NDR decodificados, os flags de registro (R9), a presença do callback de segurança ("bouncer"), o descritor de segurança (melhor esforço) e os bindings de endpoint / transporte.
Classifica cada interface limpa em dois eixos independentes e os multiplica em um composto de 0 a 100, dividido em faixas Crítico / Alto / Moderado / Baixo.
Explica-se: cada pontuação vem com uma string de comprovante listando cada componente e a aritmética que produziu o número final.
target dir --(pefile filter) --> only RPC-registering PEs
--(Ghidra headless auto-analysis) --> analyzed program DB
--(extract_rpc_interfaces.py script) --> interfaces + NDR + flags + endpoints
--(two-axis AHP ranking engine) --> ranked interfaces + receipts
--> single JSON report
Dependência Zero de Símbolos: Um diferencial técnico crucial deste mecanismo é que ele opera inteiramente sem símbolos de depuração. Ao percorrer programaticamente as tabelas de despacho e analisar os bytecodes NDR (Network Data Representation) brutos e as estruturas MIDL compiladas diretamente da memória, a ferramenta elimina a necessidade dos arquivos .pdb da Microsoft ou de consultas ao endpoint mapper em tempo real. Isso garante que o mecanismo funcione de fábrica em binários System32 de produção, sem símbolos, exatamente como são distribuídos.
Ghidra 11.x (usa o support/analyzeHeadless incluído). Precisa de um JDK 17+ no PATH.
Python 3.8+ no lado do driver, com pefile.
O script de extração roda sob o Jython 2.7 incluído do Ghidra - sem imports de terceiros, nada para instalar ali.
Alvos: arquivos PE Windows x64. A análise em si é independente do SO (o Ghidra é multiplataforma), então você não precisa executar isso no Windows.
git clone https://github.com/talha-nazeef-ahmed/RPC-Triage
cd RPC-Triage/
python -m pip install -r requirements.txt # just pefile
requirements.txt: pefile>=2023.2.7
Tudo é conduzido por orchestrator.py: ele filtra, importa, analisa e executa o extrator para você.
python orchestrator.py \
-t \"C:\Windows\System32\" \
-g \"C:\ghidra_12.1.2_PUBLIC\" \
-s \".\extract_rpc_interfaces.py\" \
-o \".\out\report.json\" \
--stagedir \".\out\staged\" \
--projdir \".\out\ghidra_proj\" \
--projname RPC_Atlas
Primeira execução vs re-execução (importante). A primeira execução faz a parte lenta: filtra, importa os binários correspondentes, executa a auto-análise completa e então grava um marcador .analysisComplete em --projdir. Toda execução posterior contra o mesmo projeto pula a importação/análise (-process -noanalysis) e apenas re-executa o script sobre os programas já analisados. Portanto, analisar o System32 é um custo único, e iterar na saída é barato. Se a análise for interrompida, o marcador não é gravado; exclua o projeto parcial (.rep / .gpr) e comece do zero.
Um arquivo JSON: uma lista de binários, cada um com um array Interfaces. Uma execução completa sobre o System32 está incluída neste repositório em output/FullBatchRun.json; é a saída bruta, sem curadoria, para que você veja exatamente o que a ferramenta produz em escala. Por interface:
Tags; o portão de higiene de dados:
Clean: recuperada de forma limpa; classificada normalmente.
Needs-Review: classificada, mas a varredura da tabela de despacho ultrapassou a contagem armazenada (geralmente um bloco de thunk no final). A pontuação é real, mas carrega [Provisional]; verifique a contagem de métodos antes de citá-la.
Diagnostics / Diagnostics (2): a linha é um artefato de extração (uma string ASCII erroneamente capturada como UUID e/ou um ponteiro MIDL corrompido). Não pontuada (Rank: N/A). Elas são mantidas de propósito: relatam a saúde da ferramenta, não são superfície de ataque.
A nota FLAG: Walked X != Stored Y. O DispatchTableCount armazenado é autoritativo e é o que toda pontuação usa. A contagem percorrida é uma verificação cruzada independente de executabilidade; quando as duas divergem (normalmente um 2x limpo), a interface é marcada como Needs-Review para que você saiba que deve inspecioná-la. Isso nunca altera uma pontuação silenciosamente.
Esta é a parte que torna uma pontuação discutível:
Moderate/35 | Gate:35 [ncacn_np:41, MultiEndpointBonus:15, HasBouncer:-46, BouncerIsNotCaching:25] | Surface:100 [HasBogusStruct:1x(opnums 5):[in]:61, HasCallerSizedBuffer:5x(opnums 0,6,11):[in]:49, InPtrs:6:18, Count:12:6 -> raw:134 [capped to 100] * 1.0 -> 100] | (35 * 100) / 100 = 35 [Provisional]
Leia da esquerda para a direita:
Moderate/35: faixa e pontuação composta.
Gate:35 [...]: o eixo de alcance. Ele parte da base de transporte (ncacn_np:41, um named pipe) e então lista cada modificador de registro com sua contribuição sinalizada: MultiEndpointBonus:15 (registrada em vários transportes), HasBouncer:-46 (um callback de segurança está presente, o que reduz o alcance) e BouncerIsNotCaching:+25. Somados e então limitados a [5,100] -> 35.
Surface:100 [...]: o eixo de perigo. Cada sinal disparado é Nome:contagem x(opnums):direção:peso, ex.: HasBogusStruct disparou em 1 parâmetro (opnums 5) e seu peso é 61. Depois uma contribuição baseada em contagem: InPtrs:6:18 = 6 ponteiros de entrada controlados pelo chamador contribuindo com +18, Count:12:6 = 12 métodos adicionando +6. O é a soma antes do limite, , é o multiplicador de confiança (cai para 0.5 quando as assinaturas são incertas); Surface final .
Um segundo exemplo mostrando o limite e o desconto de baixa confiança:
Low/3 | Gate:5 [Dynamic / epmapper:29, LocalCallOnly:-100, SecureOnly:-65, HasBouncer:-46, BouncerIsNotCaching:25] | Surface:50 [... -> raw:140 [capped to 100] * 0.5 -> 50] | (5 * 50) / 100 = 3
Somente LocalCallOnly:-100 leva o gate abaixo de zero, então ele é limitado ao piso de 5; as assinaturas eram incertas, então Surface é reduzida pela metade (* 0.5); o composto termina em 3. Espaço de entrada perigoso, mas efetivamente inalcançável -> corretamente despriorizado.
Crítico >= 75, Alto >= 50, Moderado >= 25, Baixo caso contrário (uma interface com superfície recuperada zero é Baixo independentemente do gate). Os limites se aplicam ao composto; o modelo por trás deles está em docs/Surface_Scoring_Methadology.md.
Todas as três tabelas de pesos (bases de transporte, modificadores de gate, sinais de superfície) são derivadas com o Processo de Hierarquia Analítica; comparações pareadas, pesos de média geométrica e uma razão de consistência medida. A derivação completa, as matrizes, os números de consistência e as notas trabalhadas manualmente estão em docs/Surface_Scoring_Methadology.md.
A extração não é apenas autoconfirmada: as interfaces que esta ferramenta recupera foram verificadas de forma cruzada com um extrator de IDL RPC independente e bem estabelecido (o parser RpcServer do NtObjectManager, de James Forshaw) executado nos mesmos binários. Dumps de referência para lsass, samsrv e winlogon estão em validation/, e o passo a passo completo está em validation/VALIDATION.md. Compare qualquer um deles com o binário correspondente em output/FullBatchRun.json: os UUIDs de interface, as contagens de métodos/opnums e as direções por parâmetro coincidem (por exemplo, a interface 12E65DD8-... do winlogon mostra cinco métodos, Proc0-Proc4, em ambos). A ferramenta de referência para na recuperação do IDL; esta ferramenta pega a mesma superfície recuperada e adiciona a classificação por alcance x perigo por cima. Sem afiliação com aquele projeto; é usada puramente como uma verificação independente de ground-truth.
Somente estática. Nada é invocado. O alcance é inferido pelo registro, não em tempo de execução.
Pontuações Needs-Review são provisórias até que a contagem de métodos seja inspecionada.
Esta ferramenta faz parte da minha pesquisa contínua sobre ALPC/RPC e internals do Windows. Publicarei mais descobertas e lançarei ferramentas complementares em um futuro próximo. Se você achou isso útil, considere seguir-me no GitHub ou nas minhas redes sociais abaixo para ser notificado sobre futuros lançamentos.
Uma ferramenta estática de triagem / mapeamento para pesquisa de vulnerabilidades em sistemas que você possui. Ela relata a superfície de ataque, não vulnerabilidades. Qualquer coisa que você venha a encontrar nas interfaces que ela destaca deve passar por divulgação coordenada (MSRC) antes de qualquer detalhe público.
MIT
| flag | significado |
|---|
-t / --target | pasta de binários para escanear |
-g / --ghidra | pasta de instalação do Ghidra (a que contém support/analyzeHeadless) |
-s / --script | caminho para extract_rpc_interfaces.py |
-o / --output | caminho do relatório JSON a ser gravado |
--stagedir | pasta para onde os binários RPC filtrados são copiados (mantida) |
--projdir | pasta para o projeto Ghidra persistente |
--projname | nome do projeto Ghidra (ex.: RPC_Atlas) |
| campo | significado |
|---|
CallSite | endereço da chamada RpcServerRegisterIf\* |
Tag / TagDesc | categoria de qualidade dos dados (veja abaixo) |
Rank | \"{Tier}/{Composite}\", ex.: Critical/91 |
RankDetail | o comprovante completo da pontuação (veja abaixo) |
UUID | UUID da interface |
InterfaceAddress / DispatchAddress | endereços das estruturas recuperadas |
FunctionsCount | contagem autoritativa de métodos armazenada; pode trazer (FLAG: Walked X != Stored Y) |
Endpoints | bindings de transporte / endpoint |
Security | HasBouncer, SecurityDescriptor, SecureOnly, LocalCallOnly |
Methods | lista de parâmetros por opnum com opcodes NDR decodificados |
raw:134[capped to 100]* 1.0(35 * 100) / 100 = 35 [Provisional]: composto = Gate x Surface / 100. Alcance e perigo são multiplicados, não calculados como média, porque o perigo só importa se você puder alcançá-lo: uma interface maximamente perigosa que você não pode tocar não deve flutuar para o topo. O flag [Provisional] no final alerta que a varredura dinâmica de memória não correspondeu exatamente à contagem de métodos armazenada, o que significa que um humano deve verificar os limites.