
Фреймворк для перехвата 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
loop CREATE_PROCESS / LOAD_DLL events
Debugger-->>ApiScope: Module base and file handle
ApiScope->>Modules: Register module name and base
alt ntdll.dll loaded
ApiScope->>Target: Build unpatched NtReadVirtualMemory bypass
end
ApiScope->>Modules: Resolve module.dll!Export and forwarders
alt export and dependencies are loaded
ApiScope->>Hooks: Resolve handler and trampoline slot
ApiScope->>Target: Allocate trampoline and patch export
else dependency is not loaded yet
ApiScope->>ApiScope: Keep hook pending
end
ApiScope->>Debugger: ContinueDebugEvent
end
Target->>Hooks: Call patched API
Hooks->>Target: CALL_ORIGINAL through trampoline
Hooks->>Ring: Publish bounded TLV event
opt first event in a pending batch
Hooks->>Target: Signal reader through unpatched NtSetEvent
end
Ring-->>ApiScope: Drain event batches
ApiScope-->>User: Readable text and optional JSONL
opt UNLOAD_DLL_DEBUG_EVENT
Debugger-->>ApiScope: Module unloaded
ApiScope->>Target: Release associated hook state
ApiScope->>Debugger: ContinueDebugEvent
end
alt target exits
Debugger-->>ApiScope: EXIT_PROCESS_DEBUG_EVENT and exit status
ApiScope->>Ring: Drain queued events
ApiScope-->>User: Print decimal and hexadecimal exit status
else Ctrl+C or Ctrl+Break
User->>ApiScope: Stop tracing
ApiScope->>Target: DebugBreakProcess
ApiScope->>Target: Restore hooks and free instrumentation
ApiScope->>Debugger: DebugActiveProcessStop
ApiScope-->>User: Target continues running
end
Контроллер создаёт секцию с поддержкой файла подкачки и отображает её в оба процесса. Потоки хуков публикуют события в кольцо фиксированной ёмкости с несколькими производителями, используя порядковые номера для каждого слота. Обходной путь без патча (NtSetEvent) отправляет одно объединённое пробуждение для каждого ожидающего пакета, а контроллер обрабатывает события пакетами. Производители никогда не ждут читателя; полное кольцо отбрасывает событие и увеличивает счётчик потерь. Предпросмотр буферов по-прежнему использует обходной путь через не патченный NtReadVirtualMemory, чтобы неверные указатели цели не приводили к сбою хука.
cmake/ Dependency configuration
docs/ Event schema and format reference
scripts/ Validation and runtime smoke tests
src/apiscope/ CLI, debugger, mapper, patcher, and renderer
src/apiscope-hooks/ Import-free hook DLL and standalone hooks
src/include/ Shared contracts
tests/ Unit and runtime test targets
ntdll.dll!NtClose, если этот хук активен; без него закрытый дескриптор, который используется повторно, сохраняет свой предыдущий путь.DebugSetProcessKillOnExit(FALSE) оставляет цель в живых, но хуки и отображённая инструментовка остаются в памяти до выхода цели.Используйте ApiScope только на системах и процессах, которые вы уполномочены проверять. См. SECURITY.md, CONTRIBUTING.md и ROADMAP.md.
MIT. Сторонние компоненты сохраняют свои лицензии; см. THIRD_PARTY_NOTICES.md.