
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
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:
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:
Moderate/35: nivel y puntuación compuesta.
Gate:35 [...]: el eje de accesibilidad. Comienza desde la base de transporte (ncacn_np:41, una named pipe), luego lista cada modificador de registro con su contribución con signo: MultiEndpointBonus:15 (registrado en varios transportes), HasBouncer:-46 (hay un callback de seguridad, lo que reduce la accesibilidad) y BouncerIsNotCaching:+25. Se suman y luego se limitan a [5,100] -> 35.
Surface:100 [...]: el eje de peligro. Cada señal activada es Name:count x(opnums):direction:weight, p. ej. HasBogusStruct se activó en 1 parámetro (opnums 5) y su peso es 61. Luego una contribución basada en recuento: InPtrs:6:18 = 6 punteros de entrada controlados por el llamador que contribuyen +18, Count:12:6 = 12 métodos añadidos +6. El es la suma previa al límite, , es el multiplicador de confianza (baja a 0.5 cuando las firmas son inciertas); Surface final .
Un segundo ejemplo que muestra el límite y el descuento por baja confianza:
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
LocalCallOnly:-100 por sí solo lleva la puerta por debajo de cero, por lo que se limita al mínimo de 5; las firmas eran inciertas, así que Surface se reduce a la mitad (* 0.5); el compuesto aterriza en 3. Espacio de entrada peligroso, pero efectivamente inalcanzable -> correctamente despriorizado.
Critical >= 75, High >= 50, Moderate >= 25, Low en caso contrario (una interfaz con superficie recuperada cero es Low independientemente de la puerta). Los umbrales se asientan sobre el compuesto; el modelo que hay detrás está en docs/Surface_Scoring_Methadology.md.
Las tres tablas de pesos (bases de transporte, modificadores de puerta, señales de superficie) se derivan con el Proceso de Jerarquía Analítica; comparaciones por pares, pesos de media geométrica y una razón de consistencia medida. La derivación completa, las matrices, los números de consistencia y las notas trabajadas a mano están en docs/Surface_Scoring_Methadology.md.
Para que la extracción no sea solo auto-confirmatoria, las interfaces que esta herramienta recupera se verificaron de forma cruzada con un extractor de IDL RPC independiente y bien establecido (el analizador RpcServer del NtObjectManager de James Forshaw) ejecutado sobre los mismos binarios. Los volcados de referencia para lsass, samsrv y winlogon están en validation/, y el tutorial completo está en validation/VALIDATION.md. Compara cualquiera de ellos con el binario correspondiente en output/FullBatchRun.json: los UUID de interfaz, los recuentos de método/opnum y las direcciones por parámetro coinciden (por ejemplo, la interfaz 12E65DD8-... de winlogon muestra cinco métodos, Proc0-Proc4, en ambos). La herramienta de referencia se detiene en recuperar el IDL; esta herramienta toma la misma superficie recuperada y le añade la clasificación de accesibilidad x peligro encima. Sin afiliación con ese proyecto, se usa puramente como verificación independiente de verdad fundamental.
Solo estático. Nada se invoca. La accesibilidad se infiere del registro, no en tiempo de ejecución.
Las puntuaciones de Needs-Review son provisionales hasta que se revise visualmente el recuento de métodos.
Esta herramienta forma parte de mi investigación en curso sobre ALPC/RPC y los internals de Windows. Publicaré más hallazgos y lanzaré herramientas complementarias en un futuro próximo. Si te resultó útil, considera seguirme en GitHub o en mis redes sociales de abajo para que te notifiquen de futuros lanzamientos.
Una herramienta estática de triaje / mapeo para la investigación de vulnerabilidades en sistemas que posees. Informa de la superficie de ataque, no de vulnerabilidades. Cualquier cosa que llegues a encontrar en las interfaces que destaca debe pasar por una divulgación coordinada (MSRC) antes de cualquier detalle público.
MIT
| 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) |
| 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 |
raw:134[capped to 100]* 1.0(35 * 100) / 100 = 35 [Provisional]: compuesto = Gate x Surface / 100. La accesibilidad y el peligro se multiplican, no se promedian, porque el peligro solo importa si puedes alcanzarlo: una interfaz máximamente peligrosa a la que no puedes tocar no debe flotar a la cima. El indicador [Provisional] al final advierte que el recorrido dinámico de memoria no coincidió ligeramente con el recuento de métodos almacenado, lo que significa que un humano debe verificar los límites.