
Бессимвольный механизм статического анализа, который извлекает и математически ранжирует поверхность атаки Windows RPC с помощью модели риска на основе AHP.
Статический или динамический: сначала прочтите это. Стоит сразу прояснить разницу между динамическим и статическим анализом: этот инструмент выполняет ранжирование исключительно на основе статического анализа. Он читает скомпилированные структуры MIDL / NDR прямо из бинарного файла и никогда ничего не исполняет.
Статическая триаж-система для поверхности атак Windows RPC. Укажите ей папку с PE-бинарниками; она находит каждый из них, который регистрирует RPC-сервер, восстанавливает NDR-сигнатуры методов каждого интерфейса, привязки транспорта и флаги регистрации прямо из скомпилированных структур MIDL, а затем ранжирует каждый интерфейс по достижимости x опасности, прикрепляя к каждой оценке полную арифметическую квитанцию, чтобы вы могли проверить вычисления вручную.
Существующие RPC-инструменты без проблем восстанавливают сигнатуры методов интерфейса и его флаги регистрации. Но ни один из них не берёт оба этих аспекта и не отвечает на единственный вопрос, который на самом деле определяет, куда вы потратите своё время: если я могу достичь этот интерфейс и учитывая, что его методы принимают на вход, насколько срочно мне нужно его изучить по сравнению со всем остальным на машине? Именно этот пробел заполняет данный инструмент.
Фильтрует целевую папку, чтобы Ghidra автоматически анализировал только те бинарники, которые действительно регистрируют RPC-сервер (они импортируют rpcrt4.dll и вызывают один из API RpcServerRegisterIf\*). На System32 это разница между полуднем и неделей.
Извлекает для каждого интерфейса: UUID, цепочку RPC_SERVER_INTERFACE / MIDL_SERVER_INFO, авторитетный DispatchTableCount, opnum каждого метода + направления параметров + декодированные NDR-опкоды, флаги регистрации (R9), наличие security-callback («bouncer»), дескриптор безопасности (best-effort) и привязки endpoint / transport.
Ранжирует каждый чистый интерфейс по двум независимым осям и перемножает их в один композитный балл от 0 до 100, распределяемый по категориям Critical / High / Moderate / Low.
Объясняет себя: каждая оценка сопровождается строкой-квитанцией, перечисляющей каждый компонент и арифметику, которая привела к итоговому числу.
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
Независимость от символов: Ключевое техническое отличие этого движка в том, что он работает полностью без отладочных символов. Программно обходя таблицы диспетчеризации и разбирая сырые NDR-байткоды (Network Data Representation) и скомпилированные структуры MIDL напрямую из памяти, инструмент обходит необходимость в .pdb-файлах Microsoft или живых запросах к endpoint mapper. Это гарантирует, что движок работает из коробки на обрезанных, продакшн-бинарниках System32 ровно в том виде, в каком они поставляются.
Ghidra 11.x (использует встроенный support/analyzeHeadless). Требуется JDK 17+ в PATH.
Python 3.8+ на стороне драйвера, с pefile.
Скрипт извлечения работает под встроенным в Ghidra Jython 2.7 — никаких сторонних импортов, устанавливать там нечего.
Целевые объекты: Windows x64 PE-файлы. Сам анализ не зависит от ОС (Ghidra кроссплатформенный), так что запускать это на 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
Всем управляет orchestrator.py: он фильтрует, импортирует, анализирует и запускает экстрактор за вас.
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
| флаг | значение |
|---|---|
-t / --target | папка с бинарниками для сканирования |
-g / --ghidra | папка установки Ghidra (та, что содержит support/analyzeHeadless) |
-s / --script | путь к extract_rpc_interfaces.py |
-o / --output | путь для записи JSON-отчёта |
--stagedir | папка, в которую копируются отфильтрованные RPC-бинарники (сохраняется) |
--projdir | папка для постоянного проекта Ghidra |
--projname | имя проекта Ghidra (например, RPC_Atlas) |
Первый запуск vs повторный запуск (важно). Первый запуск выполняет медленную часть: фильтрует, импортирует подходящие бинарники, запускает полный автоанализ, а затем помещает маркер .analysisComplete в --projdir. Каждый последующий запуск с тем же проектом пропускает импорт/анализ (-process -noanalysis) и просто заново запускает скрипт по уже проанализированным программам. Таким образом, анализ System32 — это разовая трата времени, а итерации по результатам дёшевы. Если анализ был прерван, маркер не записывается, и инструмент удаляет частичный проект (.rep / .gpr) и начинает заново.
Один JSON-файл: список бинарников, каждый со своим массивом Interfaces. Полный прогон по System32 включён в этот репозиторий в output/FullBatchRun.json — это сырой, необработанный вывод, чтобы вы могли точно увидеть, что инструмент выдаёт в масштабе. Для каждого интерфейса:
| поле | значение |
|---|---|
CallSite | адрес вызова RpcServerRegisterIf\* |
Tag / TagDesc | категория качества данных (см. ниже) |
Rank | \"{Tier}/{Composite}\", например Critical/91 |
RankDetail | полная квитанция с оценкой (см. ниже) |
UUID | UUID интерфейса |
InterfaceAddress / DispatchAddress | восстановленные адреса структур |
FunctionsCount | авторитетное сохранённое количество методов; может содержать (FLAG: Walked X != Stored Y) |
Endpoints | привязки транспорта / endpoint |
Security | HasBouncer, SecurityDescriptor, SecureOnly, LocalCallOnly |
Methods | список параметров для каждого opnum с декодированными NDR-опкодами |
Теги; шлюз гигиены данных:
Clean: восстановлен чисто; ранжируется обычным образом.
Needs-Review: ранжирован, но обход таблицы диспетчеризации превысил сохранённое количество (обычно это хвостовой блок thunk). Оценка реальна, но содержит [Provisional]; проверьте количество методов, прежде чем ссылаться на него.
Diagnostics / Diagnostics (2): строка является артефактом извлечения (ASCII-строка, ошибочно принятая за UUID, и/или повреждённый MIDL-указатель). Не оценивается (Rank: N/A). Они сохранены намеренно: они сообщают о состоянии инструмента, а не являются поверхностью атаки.
Примечание об FLAG: Walked X != Stored Y.** Сохранённый DispatchTableCount является авторитетным, и именно его использует каждая оценка. Обойдённое количество — это независимая перекрёстная проверка исполняемости; когда эти два значения расходятся (обычно ровно в 2 раза), интерфейс получает тег Needs-Review, чтобы вы знали, что его стоит просмотреть глазами. Он никогда не меняет оценку молча.
Это та часть, которая делает оценку доказуемой:
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]
Читайте её слева направо: