
Un framework de hooking d'API pour intercepter et surveiller les applications Windows
ApiScope est un outil de recherche Windows x64 pour tracer des appels API sélectionnés dans un nouveau processus ou un processus en cours. Il suit les événements de chargement de DLL via l'API du débogueur Windows et installe des hooks avant que le débogué ne continue après chaque événement.
Hooks inclus :
ntdll.dll!NtCreateFile (enregistre le chemin cible)ntdll.dll!NtOpenFile (enregistre le chemin cible)ntdll.dll!NtReadFilentdll.dll!NtWriteFilentdll.dll!NtClosentdll.dll!NtOpenKey (enregistre le chemin de la clé)ntdll.dll!NtSetValueKeyntdll.dll!NtQueryValueKeybcrypt.dll!BCryptOpenAlgorithmProviderPrérequis :
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 est récupéré à un commit épinglé et lié uniquement dans
apiscope.exe. Le fichier apiscope-hooks.dll injecté reste sans importation.
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]
Les noms de hooks sont toujours qualifiés par le module. La correspondance de module est insensible à la casse ; la correspondance d'export est sensible à la casse.
.\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
Les événements du terminal restent lisibles tandis que --output peut copier le texte ou le JSONL vers un fichier. --quiet supprime le miroir des événements dans le terminal. --color auto|always|never contrôle la couleur du terminal (par défaut auto : activé pour un TTY, désactivé en cas de redirection ou sous NO_COLOR) ; la couleur n'apparaît jamais dans les fichiers --output. Sur un TTY, ApiScope affiche également un pied de page en direct (événements, pertes, taux et compteurs par hook) qui se redessine en place ; contrôlez-le avec --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)
Les événements JSONL contiennent des métadonnées génériques et des champs locaux au hook :
{"schema_version":1,"sequence":1,"module":"bcrypt.dll","api":"BCryptOpenAlgorithmProvider","hook":"bcrypt.dll!BCryptOpenAlgorithmProvider","fields":{"flags":0,"result":"STATUS_SUCCESS (0x00000000)"}}
Voir SCHEMA.md pour l'enveloppe des événements et les encodages de champs par type (les pointeurs et les états s'affichent sous forme de chaînes hexadécimales 0x).
Appuyez sur Ctrl+C ou Ctrl+Break pour restaurer les hooks actifs, libérer l'instrumentation distante et se détacher. La cible continue de s'exécuter. En cas de sortie naturelle, ApiScope affiche l'état de la cible en décimal et en hexadécimal, suivi d'un résumé de session (événements, pertes et compteurs par hook) sur stderr.
NtCreateFile, NtOpenFile et NtOpenKey résolvent le path cible à partir de OBJECT_ATTRIBUTES et signalent le handle résultant. ApiScope enregistre chaque ouverture réussie et annote les opérations ultérieures sur le même handle — lectures, écritures, accès aux valeurs de registre et le NtClose correspondant — avec le path résolu, afin que l'activité soit lisible sans suivre manuellement les handles. NtClose supprime également le handle, et les ouvertures relatives sont résolues via un handle root_directory précédemment vu.
[*] 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
Le path sur les événements de lecture et d'écriture est corrélé à partir du handle, et non observé sur l'appel lui-même.
Chaque hook est un fichier sous src/apiscope-hooks/hooks/ et déclare ensemble son module source, son export, son gestionnaire, son emplacement de trampoline, sa convention d'appel et sa signature :
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 découvre les descripteurs à mise en page fixe exportés par la DLL de hook. Aucune liste d'API centrale ni schéma d'événement côté lanceur n'est requis.
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
Le contrôleur crée une section basée sur un fichier de pagination et la mappe dans les deux processus. Les threads de hook publient dans un anneau de capacité fixe multi-producteur en utilisant des numéros de séquence par emplacement. Un contournement non patché de NtSetEvent envoie un réveil coalescé pour chaque lot en attente, et le contrôleur draine les événements par lots. Les producteurs n'attendent jamais le lecteur ; un anneau plein abandonne et compte l'événement. Les aperçus de tampon utilisent toujours un contournement non patché de NtReadVirtualMemory afin que les pointeurs cibles invalides échouent sans planter le hook.
cmake/ Configuration des dépendances
docs/ Schéma d'événement et référence de format
scripts/ Validation et tests d'intégration
src/apiscope/ CLI, débogueur, mappeur, patcher et rendu
src/apiscope-hooks/ DLL de hooks sans importation et hooks autonomes
src/include/ Contrats partagés
tests/ Cibles de tests unitaires et d'intégration
ntdll.dll!NtClose lorsque ce hook est actif ; sans cela, un handle fermé qui est réutilisé conserve son chemin précédent.DebugSetProcessKillOnExit(FALSE) maintient la cible en vie, mais les hooks et l'instrumentation mappée restent résidents jusqu'à la sortie de la cible.Utilisez ApiScope uniquement sur les systèmes et processus que vous êtes autorisé à inspecter. Voir SECURITY.md, CONTRIBUTING.md et ROADMAP.md.
MIT. Les composants tiers conservent leurs licences ; voir THIRD_PARTY_NOTICES.md.