NtSetTimer
Arme un objet timer avec une due time, une période optionnelle et une routine APC optionnelle déclenchée à l'expiration.
Prototype
NTSTATUS NtSetTimer( HANDLE TimerHandle, PLARGE_INTEGER DueTime, PTIMER_APC_ROUTINE TimerApcRoutine, PVOID TimerContext, BOOLEAN ResumeTimer, LONG Period, PBOOLEAN PreviousState );
Arguments
| Name | Type | Dir | Description |
|---|---|---|---|
| TimerHandle | HANDLE | in | Handle vers un timer créé par NtCreateTimer avec l'accès TIMER_MODIFY_STATE. |
| DueTime | PLARGE_INTEGER | in | Temps d'expiration en unités de 100 ns ; négatif = relatif à maintenant, positif = FILETIME UTC absolu. |
| TimerApcRoutine | PTIMER_APC_ROUTINE | in | Routine en mode utilisateur optionnelle, mise en file comme APC sur le thread appelant à l'expiration. |
| TimerContext | PVOID | in | Valeur de contexte passée à TimerApcRoutine comme premier argument. |
| ResumeTimer | BOOLEAN | in | TRUE demande un wake timer capable de réveiller le système depuis la suspension (S1-S3). |
| Period | LONG | in | Période en millisecondes pour un timer récurrent ; 0 le rend one-shot. |
| PreviousState | PBOOLEAN | out | Optionnel ; reçoit l'état signalé précédent du timer. |
IDs de syscalls par version de Windows
| Version de Windows | ID de syscall | Build |
|---|---|---|
| Win10 1507 | 0x62 | win10-1507 |
| Win10 1607 | 0x62 | win10-1607 |
| Win10 1703 | 0x62 | win10-1703 |
| Win10 1709 | 0x62 | win10-1709 |
| Win10 1803 | 0x62 | win10-1803 |
| Win10 1809 | 0x62 | win10-1809 |
| Win10 1903 | 0x62 | win10-1903 |
| Win10 1909 | 0x62 | win10-1909 |
| Win10 2004 | 0x62 | win10-2004 |
| Win10 20H2 | 0x62 | win10-20h2 |
| Win10 21H1 | 0x62 | win10-21h1 |
| Win10 21H2 | 0x62 | win10-21h2 |
| Win10 22H2 | 0x62 | win10-22h2 |
| Win11 21H2 | 0x62 | win11-21h2 |
| Win11 22H2 | 0x62 | win11-22h2 |
| Win11 23H2 | 0x62 | win11-23h2 |
| Win11 24H2 | 0x62 | win11-24h2 |
| Server 2016 | 0x62 | winserver-2016 |
| Server 2019 | 0x62 | winserver-2019 |
| Server 2022 | 0x62 | winserver-2022 |
| Server 2025 | 0x62 | winserver-2025 |
Module noyau
APIs liées
Stub du syscall
4C 8B D1 mov r10, rcx B8 62 00 00 00 mov eax, 0x62 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
Arme un objet timer créé par `NtCreateTimer` via `KeSetTimerEx` / `ExpSetTimer` dans ntoskrnl.exe. La SSN `0x62` est stable sur chaque build Win10/11. `DueTime` est en unités NT de 100 ns (négatif pour relatif). Quand `TimerApcRoutine` est non-NULL, à chaque expiration le noyau met en file un *APC utilisateur normal* sur le **thread ayant appelé `NtSetTimer`** — pas sur un autre thread — et l'APC ne s'exécute que lorsque ce thread entre dans un wait alertable. Un `Period` non nul rend le timer récurrent (sémantique `SetWaitableTimer`) ; les expirations suivantes continuent d'enfiler des APC jusqu'à `NtCancelTimer`.
Usage courant par les malwares
**Le détonateur des sleep masks à la Ekko.** L'implant construit une chaîne : NtCreateTimer → NtSetTimer avec `TimerApcRoutine` pointant sur une série de gadgets ROP `RtlCaptureContext` / `SystemFunction032` / `NtProtectVirtualMemory` → NtWaitForSingleObject(alertable). À l'expiration, l'APC exécute chaque étape ROP tour à tour : capturer le contexte, déchiffrer la région RWX de l'implant, puis à l'expiration suivante rechiffrer + redormir. Le résultat est un beacon dont l'image mémoire à tout instant *inactif* est illisible pour un scanner. Foliage, Cronos et Zilean livrent des variantes ; Havoc active une sleep mask Ekko par défaut. Aussi détourné en beacon de persistance discret — timer périodique + petit APC qui interroge le C2.
Opportunités de détection
ETW Threat Intelligence émet l'événement APC mis en file pour le user APC du noyau à chaque expiration, ce qui signifie que *chaque* tick d'une sleep mask Ekko est en principe journalisable — bien que la plupart des EDR brident le volume. La détection la plus propre est de mémoriser que le RIP du callsite source de `NtSetTimer` est en mémoire non-backed et que l'adresse de la routine APC est dans la même région RWX — un arm-and-fire de timer auto-modifiant depuis de la mémoire anonyme est extrêmement rare en logiciel bénin. L'abus de wake-timer peut aussi être énuméré offline via `powercfg /waketimers`.
Exemples de syscalls directs
asmx64 direct stub
; Direct syscall stub for NtSetTimer (SSN 0x62, stable across Win10/11)
NtSetTimer PROC
mov r10, rcx ; syscall convention
mov eax, 62h ; SSN
syscall
ret
NtSetTimer ENDPcEkko sleep-mask one-shot arm
// hTimer was just created with NtCreateTimer(SynchronizationTimer).
// hEvent is the rendezvous the APC will signal at end of the ROP chain.
LARGE_INTEGER due;
due.QuadPart = -(LONGLONG)(60 * 1000 * 10000); // 60s relative
NtSetTimer(hTimer,
&due,
(PTIMER_APC_ROUTINE)EkkoStage1, // ROP gadget: encrypt → sleep → decrypt
(PVOID)hEvent, // ctx forwarded as APC arg1
FALSE, // not a wake timer
0, // one-shot; we re-arm at end of chain
NULL);
// Implant thread then alertably waits on hEvent; APC fires on expiry.
NtWaitForSingleObject(hEvent, TRUE, NULL);cRecurring beacon timer (T1029 Scheduled Transfer)
// Beacon every 30s with a small callback that issues an HTTPS check-in.
LARGE_INTEGER due;
due.QuadPart = -(LONGLONG)(30 * 1000 * 10000); // first fire +30s
NtSetTimer(hTimer,
&due,
(PTIMER_APC_ROUTINE)BeaconTickApc,
NULL,
FALSE,
30 * 1000, // period 30s
NULL);
for (;;) NtWaitForSingleObject(hTimer, TRUE, NULL); // alertable, APC fires each tickMappings MITRE ATT&CK
Last verified: 2026-05-20