
Бессимвольный механизм статического анализа, который извлекает и математически ранжирует поверхность атаки 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
Первый запуск vs повторный запуск (важно). Первый запуск выполняет медленную часть: фильтрует, импортирует подходящие бинарники, запускает полный автоанализ, а затем помещает маркер .analysisComplete в --projdir. Каждый последующий запуск с тем же проектом пропускает импорт/анализ (-process -noanalysis) и просто заново запускает скрипт по уже проанализированным программам. Таким образом, анализ System32 — это разовая трата времени, а итерации по результатам дёшевы. Если анализ был прерван, маркер не записывается, и инструмент удаляет частичный проект (.rep / .gpr) и начинает заново.
Один JSON-файл: список бинарников, каждый со своим массивом Interfaces. Полный прогон по System32 включён в этот репозиторий в output/FullBatchRun.json — это сырой, необработанный вывод, чтобы вы могли точно увидеть, что инструмент выдаёт в масштабе. Для каждого интерфейса:
Теги; шлюз гигиены данных:
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]
Читайте её слева направо:
Moderate/35: категория и композитный балл.
Gate:35 [...]: ось достижимости. Она начинается с базового транспорта (ncacn_np:41, именованный канал), затем перечисляет каждый модификатор регистрации с его знаковым вкладом: MultiEndpointBonus:15 (зарегистрирован на нескольких транспортах), HasBouncer:-46 (присутствует security-callback, что снижает достижимость) и BouncerIsNotCaching:+25. Суммируется и затем ограничивается диапазоном [5,100] -> 35.
Surface:100 [...]: ось опасности. Каждый сработавший сигнал представлен как Name:count x(opnums):direction:weight, например HasBogusStruct сработал на 1 параметре (opnums 5), и его вес равен 61. Затем идёт вклад на основе количества: InPtrs:6:18 = 6 управляемых вызывающим in-указателей добавляют +18, Count:12:6 = 12 методов добавляют +6. — сумма до ограничения, , — множитель уверенности (снижается до 0.5, когда сигнатуры неопределённы); итоговая Surface .
Второй пример, показывающий ограничение и скидку за низкую уверенность:
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 сам по себе уводит gate ниже нуля, поэтому он ограничивается нижней границей 5; сигнатуры были неопределённы, поэтому Surface уменьшается вдвое (* 0.5); композит оказывается равным 3. Опасное входное пространство, но фактически недостижимое -> корректно понижено в приоритете.
Critical >= 75, High >= 50, Moderate >= 25, иначе Low (интерфейс с нулевой восстановленной поверхностью получает Low независимо от gate). Пороговые значения применяются к композиту; модель, стоящая за ними, описана в docs/Surface_Scoring_Methadology.md.
Все три таблицы весов (базовые транспорты, модификаторы gate, сигналы surface) получены с помощью метода анализа иерархий: попарные сравнения, веса на основе геометрического среднего и измеренный коэффициент согласованности. Полный вывод, матрицы, показатели согласованности и ручные расчёты находятся в docs/Surface_Scoring_Methadology.md.
Чтобы извлечение не было просто самоподтверждающимся, интерфейсы, восстанавливаемые этим инструментом, были перекрёстно проверены с помощью независимого, хорошо зарекомендовавшего себя экстрактора RPC IDL (парсер RpcServer в NtObjectManager Джеймса Форшоу), запущенного на тех же бинарниках. Эталонные дампы для lsass, samsrv и winlogon находятся в validation/, а полное описание процедуры — в validation/VALIDATION.md. Сравните любой из них с соответствующим бинарником в output/FullBatchRun.json: UUID интерфейсов, количество методов/opnum и направления параметров совпадают (например, интерфейс 12E65DD8-... в winlogon показывает пять методов, Proc0-Proc4, в обоих случаях). Эталонный инструмент останавливается на восстановлении IDL; этот инструмент берёт ту же восстановленную поверхность и добавляет к ней ранжирование по достижимости x опасности. Никакой принадлежности к тому проекту нет, он используется исключительно как независимая проверка достоверности.
Только статический анализ. Ничего не исполняется. Достижимость выводится из регистрации, а не в рантайме.
Оценки Needs-Review являются предварительными, пока количество методов не будет проверено глазами.
Этот инструмент — часть моего текущего исследования ALPC/RPC и внутренностей Windows. В ближайшем будущем я опубликую дальнейшие результаты и выпущу сопутствующие инструменты. Если вы нашли это полезным, подумайте о том, чтобы подписаться на меня на GitHub или в моих соцсетях ниже, чтобы получать уведомления о будущих публикациях.
Статический инструмент триажа и картирования для исследования уязвимостей в системах, которыми вы владеете. Он сообщает об поверхности атаки, а не об уязвимостях. Всё, что вы впоследствии найдёте в выделенных им интерфейсах, должно пройти через скоординированное раскрытие (MSRC) до публикации любых деталей.
MIT
| флаг | значение |
|---|
-t / --target | папка с бинарниками для сканирования |
-g / --ghidra | папка установки Ghidra (та, что содержит support/analyzeHeadless) |
-s / --script | путь к extract_rpc_interfaces.py |
-o / --output | путь для записи JSON-отчёта |
--stagedir | папка, в которую копируются отфильтрованные RPC-бинарники (сохраняется) |
--projdir | папка для постоянного проекта Ghidra |
--projname | имя проекта Ghidra (например, RPC_Atlas) |
| поле | значение |
|---|
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-опкодами |
raw:134[capped to 100]* 1.0(35 * 100) / 100 = 35 [Provisional]: композит = Gate x Surface / 100. Достижимость и опасность перемножаются, а не усредняются, потому что опасность имеет значение только если вы можете её достичь: максимально опасный интерфейс, до которого нельзя добраться, не должен всплывать наверх. Флаг [Provisional] в конце предупреждает, что динамический обход памяти немного разошёлся с сохранённым количеством методов, а значит, человек должен проверить границы.