
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
| 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) |
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:
| 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 |
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: