NtCancelTimer
Annule un armement NtSetTimer en attente et indique si le timer était encore actif à l'annulation.
Prototype
NTSTATUS NtCancelTimer( HANDLE TimerHandle, PBOOLEAN CurrentState );
Arguments
| Name | Type | Dir | Description |
|---|---|---|---|
| TimerHandle | HANDLE | in | Handle vers un timer créé par NtCreateTimer / NtOpenTimer avec l'accès TIMER_MODIFY_STATE. |
| CurrentState | PBOOLEAN | out | Optionnel ; reçoit TRUE si le timer était encore en attente (annulation effective), FALSE s'il avait déjà expiré. |
IDs de syscalls par version de Windows
| Version de Windows | ID de syscall | Build |
|---|---|---|
| Win10 1507 | 0x61 | win10-1507 |
| Win10 1607 | 0x61 | win10-1607 |
| Win10 1703 | 0x61 | win10-1703 |
| Win10 1709 | 0x61 | win10-1709 |
| Win10 1803 | 0x61 | win10-1803 |
| Win10 1809 | 0x61 | win10-1809 |
| Win10 1903 | 0x61 | win10-1903 |
| Win10 1909 | 0x61 | win10-1909 |
| Win10 2004 | 0x61 | win10-2004 |
| Win10 20H2 | 0x61 | win10-20h2 |
| Win10 21H1 | 0x61 | win10-21h1 |
| Win10 21H2 | 0x61 | win10-21h2 |
| Win10 22H2 | 0x61 | win10-22h2 |
| Win11 21H2 | 0x61 | win11-21h2 |
| Win11 22H2 | 0x61 | win11-22h2 |
| Win11 23H2 | 0x61 | win11-23h2 |
| Win11 24H2 | 0x61 | win11-24h2 |
| Server 2016 | 0x61 | winserver-2016 |
| Server 2019 | 0x61 | winserver-2019 |
| Server 2022 | 0x61 | winserver-2022 |
| Server 2025 | 0x61 | winserver-2025 |
Module noyau
APIs liées
Stub du syscall
4C 8B D1 mov r10, rcx B8 61 00 00 00 mov eax, 0x61 F6 04 25 08 03 FE 7F 01 test byte ptr [0x7FFE0308], 1 75 03 jne short +3 0F 05 syscall C3 ret CD 2E int 2Eh C3 ret
Notes non documentées
NtCancelTimer désarme un timer précédemment armé par NtSetTimer. Le paramètre out `CurrentState` indique si l'annulation a couru contre l'expiration : TRUE signifie que le timer était encore dans la liste pending du dispatcher (annulation gagnée), FALSE signifie que le timer s'est déclenché avant le syscall (et toute routine APC a déjà été mise en file ou exécutée). Le SSN `0x61` est gelé sur toutes les builds Windows 10/11. La routine noyau `KeCancelTimer` la sous-tend.
Usage courant par les malwares
Couplé à NtSetTimer dans les designs d'implant *sleep mask* et *delayed execution*. Cycle de vie typique : NtCreateTimer + NtSetTimer (période + APC) → travail de l'implant → NtCancelTimer + NtClose au teardown pour supprimer la mise à feu APC périodique qui sinon continuerait à dispatcher dans du shellcode libéré. Certains implants utilisent aussi NtCancelTimer pour *replanifier dynamiquement* les intervalles de sommeil — annuler l'armement courant et appeler NtSetTimer avec une nouvelle DueTime en réponse à une commande C2. Pas une primitive offensive en soi ; le signal vient du *couplage* avec NtSetTimer / NtQueueApcThread.
Opportunités de détection
Comme les autres primitives event/timer, NtCancelTimer est du trafic légitime à fort volume. Le signal de détection est la *paire* : NtSetTimer avec un `TimerApcRoutine` non-NULL pointant dans une région RWX ou non-backed, suivi d'un NtCancelTimer sur le même handle lors de la sortie du processus ou du teardown. Le provider ETW `Microsoft-Windows-Kernel-EventTracing` et le flag legacy `KernelTimer` de `Microsoft-Windows-Kernel-Process` donnent du contexte.
Exemples de syscalls directs
asmx64 direct stub
; Direct syscall stub for NtCancelTimer (SSN 0x61, stable Win10 1507+)
NtCancelTimer PROC
mov r10, rcx ; TimerHandle
mov eax, 61h ; SSN
syscall
ret
NtCancelTimer ENDPcSleep-mask teardown
// Implant shutdown: disarm the periodic APC timer that ticks our sleep mask.
#include <windows.h>
#include <winternl.h>
typedef NTSTATUS (NTAPI *pNtCancelTimer)(HANDLE, PBOOLEAN);
void TeardownSleepMask(HANDLE hTimer) {
BOOLEAN wasPending = FALSE;
pNtCancelTimer fn = (pNtCancelTimer)GetProcAddress(
GetModuleHandleA("ntdll.dll"), "NtCancelTimer");
fn(hTimer, &wasPending);
// wasPending == FALSE => the APC has already queued; flush APCs with a brief alertable wait.
NtClose(hTimer);
}rustAdaptive reschedule
// Cargo: ntapi = "0.4"
use ntapi::ntexapi::{NtCancelTimer, NtSetTimer};
use winapi::shared::ntdef::{BOOLEAN, HANDLE, LARGE_INTEGER};
unsafe fn reschedule(timer: HANDLE, new_due_100ns: i64) {
let mut was_pending: BOOLEAN = 0;
NtCancelTimer(timer, &mut was_pending);
let mut due: LARGE_INTEGER = std::mem::zeroed();
*due.QuadPart_mut() = -new_due_100ns; // negative = relative
let mut prev: BOOLEAN = 0;
NtSetTimer(timer, &mut due, None, std::ptr::null_mut(), 0, 0, &mut prev);
}Mappings MITRE ATT&CK
Last verified: 2026-05-20