
Un motor de análisis estático de cero símbolos que extrae y clasifica matemáticamente la superficie de ataque de Windows RPC mediante un modelo de riesgo basado en AHP.
Estático vs dinámico, lea esto primero. Vale la pena ser claro sobre la línea entre el análisis dinámico y estático desde el principio: esta herramienta realiza su clasificación puramente mediante análisis estático. Lee las estructuras MIDL / NDR compiladas directamente del binario y nunca ejecuta nada.
Triaje estático para la superficie de ataque RPC de Windows. Apúntalo a una carpeta de binarios PE; encuentra todos los que registran un servidor RPC, recupera las firmas de métodos NDR de cada interfaz, los enlaces de transporte y las banderas de registro directamente de las estructuras MIDL compiladas, y luego clasifica cada interfaz por accesibilidad x peligro con un recibo aritmético completo adjunto a cada puntuación para que puedas comprobar las cuentas a mano.
Las herramientas RPC existentes recuperan sin problema las firmas de métodos de una interfaz y sus banderas de registro. Lo que ninguna hace es tomar ambas y responder a la única pregunta que realmente decide dónde inviertes tu tiempo: dado que puedo alcanzar esta interfaz, y dado lo que sus métodos aceptan como entrada, ¿con qué urgencia debería examinarla en comparación con todo lo demás en la máquina? Ese vacío es lo que esto llena.
Filtra la carpeta de destino para que Ghidra solo auto-analice los binarios que realmente registran un servidor RPC (importan rpcrt4.dll y llaman a una de las API RpcServerRegisterIf\*). En System32, eso es la diferencia entre una tarde y una semana.
Extrae, por interfaz: UUID, la cadena RPC_SERVER_INTERFACE / MIDL_SERVER_INFO, el DispatchTableCount autoritativo, el opnum de cada método + direcciones de parámetros + opcodes NDR decodificados, las banderas de registro (R9), la presencia de callback de seguridad ("bouncer"), el descriptor de seguridad (mejor esfuerzo) y los enlaces de endpoint / transporte.
Clasifica cada interfaz limpia en dos ejes independientes y los multiplica en un compuesto de 0-100, en cubos Critical / High / Moderate / Low.
Se explica a sí misma: cada puntuación incluye una cadena de recibo que enumera cada componente y la aritmética que produjo el 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
Dependencia cero de símbolos: Un diferenciador técnico crítico de este motor es que opera enteramente sin símbolos de depuración. Al recorrer programáticamente las tablas de despacho y analizar los bytecodes NDR (Network Data Representation) crudos y las estructuras MIDL compiladas directamente desde memoria, la herramienta evita la necesidad de los archivos .pdb de Microsoft o consultas en vivo al endpoint mapper. Esto garantiza que el motor funcione desde el primer momento en binarios System32 de producción, sin símbolos, exactamente como se distribuyen.
Ghidra 11.x (utiliza el support/analyzeHeadless incluido). Necesita un JDK 17+ en PATH.
Python 3.8+ en el lado del controlador, con pefile.
El script de extracción se ejecuta bajo el Jython 2.7 incluido con Ghidra: sin importaciones de terceros, nada que instalar ahí.
Objetivos: archivos PE de Windows x64. El análisis en sí es independiente del SO (Ghidra es multiplataforma), por lo que no es necesario ejecutarlo en 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
Todo está controlado por orchestrator.py: filtra, importa, analiza y ejecuta el extractor por ti.
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 | carpeta de binarios a escanear |
-g / --ghidra | carpeta de instalación de Ghidra (la que contiene support/analyzeHeadless) |
-s / --script | ruta a extract_rpc_interfaces.py |
-o / --output | ruta del informe JSON a escribir |
--stagedir | carpeta donde se copian los binarios RPC filtrados (se conserva) |
--projdir | carpeta para el proyecto Ghidra persistente |
--projname | nombre del proyecto Ghidra (p. ej. RPC_Atlas) |
Primera ejecución vs re-ejecución (importante). La primera ejecución hace la parte lenta: filtra, importa los binarios coincidentes, ejecuta el auto-análisis completo y luego deja un marcador .analysisComplete en --projdir. Cada ejecución posterior contra el mismo proyecto omite la importación/análisis (-process -noanalysis) y solo vuelve a ejecutar el script sobre los programas ya analizados. Así que analizar System32 es un costo único e iterar sobre la salida es barato. Si el análisis se interrumpe, el marcador no se escribe y se elimina el proyecto parcial (.rep / .gpr) y se empieza de nuevo.
Un archivo JSON: una lista de binarios, cada uno con un array Interfaces. Una ejecución completa sobre System32 se incluye en este repositorio en output/FullBatchRun.json; es la salida cruda, sin curar, para que puedas ver exactamente qué produce la herramienta a escala. Por interfaz:
| campo | significado |
|---|---|
CallSite | dirección de la llamada RpcServerRegisterIf\* |
Tag / TagDesc | cubo de calidad de datos (ver más abajo) |
Rank | \"{Nivel}/{Compuesto}\", p. ej. Critical/91 |
RankDetail | el recibo de puntuación completo (ver más abajo) |
UUID | UUID de la interfaz |
InterfaceAddress / DispatchAddress | direcciones de estructura recuperadas |
FunctionsCount | recuento de métodos almacenado autoritativo; puede llevar (FLAG: Walked X != Stored Y) |
Endpoints | enlaces de transporte / endpoint |
Security | HasBouncer, SecurityDescriptor, SecureOnly, LocalCallOnly |
Methods | lista de parámetros por opnum con opcodes NDR decodificados |
Etiquetas; la puerta de higiene de datos:
Clean: recuperada limpiamente; clasificada normalmente.
Needs-Review: clasificada, pero el recorrido de la tabla de despacho superó el recuento almacenado (normalmente un bloque thunk final). La puntuación es real pero lleva [Provisional]; verifica el recuento de métodos antes de citarlo.
Diagnostics / Diagnostics (2): la fila es un artefacto de extracción (una cadena ASCII mal interpretada como UUID y/o un puntero MIDL corrupto). Sin puntuar (Rank: N/A). Se conservan a propósito: informan de la salud de la herramienta, no son superficie de ataque.
La FLAG: Walked X != Stored Y nota. El DispatchTableCount almacenado es autoritativo y es lo que usa cada puntuación. El recuento recorrido es una verificación cruzada independiente de ejecutabilidad; cuando ambos no coinciden (comúnmente un 2x limpio), la interfaz se etiqueta como Needs-Review para que sepas que debes echarle un vistazo. Nunca cambia una puntuación en silencio.
Esta es la parte que hace que una puntuación sea defendible:
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]
Léelo de izquierda a derecha: