
Un motore di analisi statica zero-symbol che estrae e classifica matematicamente la superficie d'attacco RPC di Windows utilizzando un modello di rischio basato su AHP.
Statico vs dinamico, leggete questo prima. Vale la pena chiarire subito il confine tra analisi dinamica e statica: questo strumento esegue la sua classificazione esclusivamente tramite analisi statica. Legge le strutture MIDL/NDR compilate direttamente dal binario e non esegue mai nulla.
Triage statico per la superficie d'attacco RPC di Windows. Puntatelo su una cartella di eseguibili PE; trova tutti quelli che registrano un server RPC, recupera le firme dei metodi NDR di ogni interfaccia, i binding di trasporto e i flag di registrazione direttamente dalle strutture MIDL compilate, e poi classifica ogni interfaccia in base a raggiungibilità x pericolosità con una ricevuta aritmetica completa allegata a ogni punteggio, così potete verificare i calcoli a mano.
Gli strumenti RPC esistenti recuperano volentieri le firme dei metodi di un'interfaccia e i suoi flag di registrazione. Quello che nessuno di essi fa è prendere entrambi e rispondere all'unica domanda che decide davvero dove spendete il vostro tempo: dato che posso raggiungere questa interfaccia, e dato ciò che i suoi metodi accettano come input, con quanta urgenza dovrei esaminarla rispetto a tutto il resto sulla macchina? È questo vuoto che questo strumento colma.
Filtra la cartella di destinazione in modo che Ghidra analizzi automaticamente solo i binari che registrano effettivamente un server RPC (importano rpcrt4.dll e chiamano una delle API RpcServerRegisterIf\*). Su System32 è la differenza tra un pomeriggio e una settimana.
Estrae, per ogni interfaccia: UUID, la catena RPC_SERVER_INTERFACE / MIDL_SERVER_INFO, l'autorevole DispatchTableCount, l'opnum di ogni metodo + le direzioni dei parametri + gli opcode NDR decodificati, i flag di registrazione (R9), la presenza della security-callback ("bouncer"), il security descriptor (best-effort) e i binding di endpoint / trasporto.
Classifica ogni interfaccia pulita su due assi indipendenti e li moltiplica in un unico punteggio composito 0-100, suddiviso in bucket Critical / High / Moderate / Low.
Si spiega da solo: ogni punteggio include una stringa di ricevuta che elenca ogni componente e l'aritmetica che ha prodotto il numero finale.
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
Dipendenza zero da simboli: Un differenziatore tecnico critico di questo motore è che opera interamente senza simboli di debug. Percorrendo programmaticamente le dispatch table e analizzando i bytecode NDR (Network Data Representation) grezzi e le strutture MIDL compilate direttamente dalla memoria, lo strumento bypassa la necessità dei file .pdb di Microsoft o di query live all'endpoint mapper. Questo garantisce che il motore funzioni out-of-the-box su binari System32 stripped e di produzione, esattamente come vengono distribuiti.
Ghidra 11.x (usa il support/analyzeHeadless incluso). Serve una JDK 17+ nel PATH.
Python 3.8+ sul lato driver, con pefile.
Lo script di estrazione viene eseguito sotto la Jython 2.7 inclusa in Ghidra - nessuna importazione di terze parti, nulla da installare lì.
Obiettivi: file PE Windows x64. L'analisi in sé è indipendente dal sistema operativo (Ghidra è multipiattaforma), quindi non è necessario eseguirla su Windows.
git clone https://github.com/talha-nazeef-ahmed/RPC-Triage
cd RPC-Triage/
python -m pip install -r requirements.txt # solo pefile
requirements.txt: pefile>=2023.2.7
Tutto è gestito da orchestrator.py: filtra, importa, analizza ed esegue l'estrattore per voi.
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 | significato |
|---|---|
-t / --target | cartella dei binari da scansionare |
-g / --ghidra | cartella di installazione di Ghidra (quella che contiene support/analyzeHeadless) |
-s / --script | percorso di extract_rpc_interfaces.py |
-o / --output | percorso del report JSON da scrivere |
--stagedir | cartella in cui vengono copiati i binari RPC filtrati (mantenuta) |
--projdir | cartella per il progetto Ghidra persistente |
--projname | nome del progetto Ghidra (es. RPC_Atlas) |
Prima esecuzione vs esecuzioni successive (importante). La prima esecuzione fa la parte lenta: filtra, importa i binari corrispondenti, esegue l'analisi automatica completa, poi inserisce un marcatore .analysisComplete in --projdir. Ogni esecuzione successiva sullo stesso progetto salta importazione/analisi (-process -noanalysis) e riesegue solo lo script sui programmi già analizzati. Quindi analizzare System32 è un costo una tantum e iterare sull'output è economico. Se l'analisi viene interrotta, il marcatore non viene scritto e lo strumento elimina il progetto parziale (.rep / .gpr) e ricomincia da zero.
Un file JSON: un elenco di binari, ciascuno con un array Interfaces. Un'esecuzione completa su System32 è inclusa in questo repository in output/FullBatchRun.json; è l'output grezzo e non curato, così potete vedere esattamente cosa produce lo strumento su larga scala. Per ogni interfaccia:
| campo | significato |
|---|---|
CallSite | indirizzo della chiamata RpcServerRegisterIf\* |
Tag / TagDesc | bucket di qualità dei dati (vedi sotto) |
Rank | \"{Tier}/{Composite}\", es. Critical/91 |
RankDetail | la ricevuta completa del punteggio (vedi sotto) |
UUID | UUID dell'interfaccia |
InterfaceAddress / DispatchAddress | indirizzi delle strutture recuperate |
FunctionsCount | conteggio metodi stored autorevole; può riportare (FLAG: Walked X != Stored Y) |
Endpoints | binding di trasporto / endpoint |
Security | HasBouncer, SecurityDescriptor, SecureOnly, LocalCallOnly |
Methods | elenco parametri per opnum con opcode NDR decodificati |
Tag; il gate di igiene dei dati:
Clean: recuperato in modo pulito; classificato normalmente.
Needs-Review: classificato, ma la scansione della dispatch table ha superato il conteggio stored (di solito un blocco thunk finale). Il punteggio è reale ma riporta [Provisional]; verificate il conteggio dei metodi prima di citarlo.
Diagnostics / Diagnostics (2): la riga è un artefatto di estrazione (una stringa ASCII agganciata erroneamente come UUID e/o un puntatore MIDL corrotto). Non classificato (Rank: N/A). Questi vengono mantenuti apposta: segnalano lo stato di salute dello strumento, non sono superficie d'attacco.
La nota FLAG: Walked X != Stored Y . Il DispatchTableCount stored è autorevole ed è ciò che usa ogni punteggio. Il conteggio ottenuto dalla scansione è un controllo incrociato indipendente di eseguibilità; quando i due non coincidono (comunemente un 2x pulito) l'interfaccia viene taggata Needs-Review così sapete di controllarla. Non cambia mai silenziosamente un punteggio.
Questa è la parte che rende un punteggio discutibile:
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]
Leggetela da sinistra a destra:
Moderate/35: tier e punteggio composito.