
Фреймворк для перехвата API и мониторинга Windows-приложений
ApiScope — это исследовательский инструмент для Windows x64, предназначенный для трассировки избранных API-вызовов в новом или уже запущенном процессе. Он отслеживает события загрузки DLL через отладчик Windows и устанавливает перехватчики до того, как отлаживаемая программа продолжит выполнение после каждого такого события.
Включённые перехватчики:
ntdll.dll!NtCreateFile (записывает целевой путь)ntdll.dll!NtOpenFile (записывает целевой путь)ntdll.dll!NtReadFilentdll.dll!NtWriteFilentdll.dll!NtClosentdll.dll!NtOpenKey (записывает путь к ключу)ntdll.dll!NtSetValueKeyntdll.dll!NtQueryValueKeybcrypt.dll!BCryptOpenAlgorithmProviderТребования:
cmake -S . -B build -A x64
cmake --build build --config Release
ctest --test-dir build -C Release --output-on-failure
powershell -ExecutionPolicy Bypass -File .\scripts\validate-apiscope-hooks.ps1 .\build\bin\Release\apiscope-hooks.dll
powershell -ExecutionPolicy Bypass -File .\scripts\smoke.ps1 -SkipBuild
Zydis v4.1.1 загружается по зафиксированному коммиту и линкуется только в apiscope.exe. Внедряемая apiscope-hooks.dll остаётся свободной от импорта.
apiscope.exe [--help | --version | --list-hooks]
apiscope.exe run -k <module!export|all> [-k <module!export>] [-f text|jsonl] [-o <path>] [-q] -- <program> [args...]
apiscope.exe attach -p <pid> -k <module!export|all> [-k <module!export>] [-f text|jsonl] [-o <path>] [-q]
Имена хуков всегда указываются с именем модуля. Поиск модуля нечувствителен к регистру; поиск экспорта чувствителен к регистру.
.\apiscope.exe run `
--hook ntdll.dll!NtCreateFile `
--hook bcrypt.dll!BCryptOpenAlgorithmProvider `
-- C:\path\app.exe
.\apiscope.exe attach --pid 4242 --hook all
.\apiscope.exe run --hook all --format jsonl --output trace.jsonl --quiet -- app.exe
Терминальные события остаются читаемыми, а --output при необходимости дублирует текст или JSONL в файл. --quiet отключает зеркалирование событий в терминал. --color auto|always|never управляет цветом в терминале (по умолчанию auto: включён для TTY, выключен при перенаправлении вывода или при NO_COLOR); цвет никогда не появляется в файлах --output. На TTY ApiScope также отображает живую нижнюю панель статуса (количество событий, потерь, скорость и счётчики по каждому хуку), которая перерисовывается на месте; управление через --status auto|always|never.
[*] ntdll.dll!NtWriteFile ----------
timestamp : 2026-06-07T17:44:23.3834340Z
thread_id : 3508
sequence : 3
file_handle : 0x000000000000008C
length : 16
buffer_ascii : Hello, ApiScope!
result : STATUS_SUCCESS (0x00000000)
События в формате JSONL содержат общие метаданные и поля, специфичные для хука:
{"schema_version":1,"sequence":1,"module":"bcrypt.dll","api":"BCryptOpenAlgorithmProvider","hook":"bcrypt.dll!BCryptOpenAlgorithmProvider","fields":{"flags":0,"result":"STATUS_SUCCESS (0x00000000)"}}
См. SCHEMA.md для описания конверта события и кодировок полей каждого типа (указатели и статусы отображаются как шестнадцатеричные строки с 0x).
Нажмите Ctrl+C или Ctrl+Break, чтобы восстановить активные хуки, освободить удалённую инструментовку и отключиться. Целевой процесс продолжит выполнение. При естественном выходе ApiScope выводит статус целевого процесса в десятичном и шестнадцатеричном виде, за которым следует сводка сессии (количество событий, потерь и счётчики по каждому хуку) в stderr.
NtCreateFile, NtOpenFile и NtOpenKey извлекают целевой path из OBJECT_ATTRIBUTES и сообщают полученный дескриптор. ApiScope записывает каждое успешное открытие и annotирует последующие операции с тем же дескриптором — чтения, записи, доступ к значениям реестра и соответствующий NtClose — с указанием разрешённого path, чтобы действия были читаемы без ручного отслеживания дескрипторов. NtClose также удаляет дескриптор, а относительные открытия разрешаются через ранее виденный дескриптор root_directory.
[*] ntdll.dll!NtCreateFile ----------
sequence : 2
path : test_file.txt
file_handle : 0x000000000000008C
result : STATUS_SUCCESS (0x00000000)
[*] ntdll.dll!NtReadFile ----------
sequence : 3
file_handle : 0x000000000000008C
buffer_ascii : Hello, ApiScope!
result : STATUS_SUCCESS (0x00000000)
path : test_file.txt
path в событиях чтения и записи берётся из привязки к дескриптору, а не наблюдается в самом вызове.
Каждый хук представляет собой один файл в src/apiscope-hooks/hooks/ и объявляет свой исходный модуль, экспорт, обработчик, слот трамплина, соглашение вызова и сигнатуру вместе:
DEFINE_API_HOOK(
BCryptOpenAlgorithmProvider,
"bcrypt.dll",
"BCryptOpenAlgorithmProvider",
NTSTATUS,
WINAPI,
PVOID* Algorithm,
const wchar_t* AlgorithmId,
const wchar_t* Implementation,
ULONG Flags) {
TraceEvent event;
InitializeTraceEvent(&event, "bcrypt.dll", "BCryptOpenAlgorithmProvider");
AddTraceUInt32(&event, "flags", Flags);
NTSTATUS result = CALL_ORIGINAL(
BCryptOpenAlgorithmProvider,
Algorithm,
AlgorithmId,
Implementation,
Flags);
AddTraceStatus(&event, "result", result);
EmitTraceEvent(&event);
return result;
}
apiscope.exe --list-hooks обнаруживает дескрипторы с фиксированной структурой, экспортируемые DLL хуков. Центральный список API или схема событий на стороне запускающего процесса не требуются.
sequenceDiagram
actor User
participant ApiScope as apiscope.exe
participant Debugger as Windows debugger API
participant Target as Target process
participant Modules as Loaded DLLs
participant Hooks as apiscope-hooks.dll
participant Ring as Shared-memory ring
User->>ApiScope: run program or attach PID
alt run
ApiScope->>Debugger: CreateProcess(DEBUG_ONLY_THIS_PROCESS)
else attach
ApiScope->>Debugger: DebugActiveProcess(PID)
end
ApiScope->>Debugger: DebugSetProcessKillOnExit(FALSE)
Debugger-->>ApiScope: CREATE_PROCESS_DEBUG_EVENT
ApiScope->>Target: Map import-free hook image
ApiScope->>Ring: Create and initialize bounded ring
ApiScope->>Target: Map shared section with NtMapViewOfSection