NtSetTimer2
Arms a Timer2 object with a due time, optional period and a T2_SET_PARAMETERS block describing callback and flags.
Prototype
NTSTATUS NtSetTimer2( HANDLE TimerHandle, PLARGE_INTEGER DueTime, PLARGE_INTEGER Period, PT2_SET_PARAMETERS Parameters );
Arguments
| Name | Type | Dir | Description |
|---|---|---|---|
| TimerHandle | HANDLE | in | Handle to a timer previously created with NtCreateTimer2. |
| DueTime | PLARGE_INTEGER | in | Absolute (positive) or relative (negative, in 100ns units) initial expiry time. |
| Period | PLARGE_INTEGER | in | Optional periodic interval in milliseconds; NULL or 0 for one-shot. |
| Parameters | PT2_SET_PARAMETERS | in | Structure with TolerableDelay, optional NoWakeTolerance and the apc/callback descriptor. |
Syscall IDs by Windows version
| Windows version | Syscall ID | Build |
|---|---|---|
| Win10 1507 | 0x192 | win10-1507 |
| Win10 1607 | 0x19B | win10-1607 |
| Win10 1703 | 0x1A1 | win10-1703 |
| Win10 1709 | 0x1A4 | win10-1709 |
| Win10 1803 | 0x1A6 | win10-1803 |
| Win10 1809 | 0x1A7 | win10-1809 |
| Win10 1903 | 0x1A8 | win10-1903 |
| Win10 1909 | 0x1A8 | win10-1909 |
| Win10 2004 | 0x1AE | win10-2004 |
| Win10 20H2 | 0x1AE | win10-20h2 |
| Win10 21H1 | 0x1AE | win10-21h1 |
| Win10 21H2 | 0x1B0 | win10-21h2 |
| Win10 22H2 | 0x1B0 | win10-22h2 |
| Win11 21H2 | 0x1B9 | win11-21h2 |
| Win11 22H2 | 0x1BD | win11-22h2 |
| Win11 23H2 | 0x1BD | win11-23h2 |
| Win11 24H2 | 0x1C0 | win11-24h2 |
| Server 2016 | 0x19B | winserver-2016 |
| Server 2019 | 0x1A7 | winserver-2019 |
| Server 2022 | 0x1B6 | winserver-2022 |
| Server 2025 | 0x1C0 | winserver-2025 |
Kernel module
Related APIs
Syscall stub
4C 8B D1 mov r10, rcx B8 C0 01 00 00 mov eax, 0x1C0 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
Undocumented notes
Where the legacy `NtSetTimer` took the APC routine and context as separate `PTIMER_APC_ROUTINE` / `PVOID` parameters, `NtSetTimer2` collapses everything that *modifies* the armed-state into a single `T2_SET_PARAMETERS` block. The struct carries `Version`, a `TolerableDelay` (used by the coalescing scheduler), an optional `NoWakeTolerance` (avoids waking the CPU from C-states), and a union choosing between *workitem callback* (used by `SetThreadpoolTimer`) and *APC callback* (used when the timer drives an alertable wait). The kernel side is `ExpSetTimer2` → `KiSetTimer2` and the timer is rearmed via `KiTimerExpiration` running on the `PRCB`'s second-generation timer queue. Coalescing is the real reason Microsoft introduced Timer2: a stream of small timers across many threads can be merged at expiry to reduce wakeups by an order of magnitude.
Common malware usage
In sleep-mask designs that adopted Timer2 (Cronos / Zilean and several private red-team kits) `NtSetTimer2` is the call that *actually weaponises* the timer: the `T2_SET_PARAMETERS::Callback.Routine` field points at the first ROP gadget of the encryption chain, and a short `DueTime` (typically 1-5 ms relative) plus a non-zero `Period` makes the chain self-rearm. Setting `NoWakeTolerance` to a high value reduces CPU wakeups, making the implant's idle posture indistinguishable from a normal threadpool consumer such as Edge or Teams. Some **PoolParty** variants pass `Parameters` that point into a *different process*' memory via a smuggled work-item, abusing the threadpool worker factory to deliver execution.
Detection opportunities
Telemetry-wise `NtSetTimer2` is the chokepoint because `NtCreateTimer2` is too noisy in legitimate apps. ETW-TI (`Microsoft-Windows-Threat-Intelligence`) does *not* currently emit a dedicated event for Timer2 arming; ETW `Microsoft-Windows-Kernel-Process` and `Microsoft-Windows-Threading` together let you reconstruct it. The strong signal is: `Parameters->Callback.Routine` lying in unbacked / `MEM_PRIVATE` RWX memory, short `DueTime` (< 100 ms) combined with a `Period` that matches an Ekko-derived 50-100 ms cadence, and a calling thread whose entry point itself is private-memory. CrowdStrike, Defender for Endpoint and Elastic all detect Ekko *via the consequence* — the alertable wait inside RWX memory — rather than this syscall itself.
Direct syscall examples
asmx64 direct stub (Win11 24H2)
; Direct syscall stub for NtSetTimer2 (SSN 0x1C0 on Win11 24H2 / Server 2025)
NtSetTimer2 PROC
mov r10, rcx ; syscall convention
mov eax, 1C0h ; SSN — drifts; resolve dynamically for portability
syscall
ret
NtSetTimer2 ENDPcModern sleep-mask arming (Cronos-style)
// Arm the Timer2 created earlier; APC delivery will run the ROP chain that
// flips implant memory between encrypted RW and executable RX.
typedef struct _T2_SET_PARAMETERS_V0 {
ULONG Version;
ULONG Reserved;
LONG64 NoWakeTolerance;
} T2_SET_PARAMETERS, *PT2_SET_PARAMETERS;
LARGE_INTEGER due = { .QuadPart = -50000 }; // 5 ms relative
LARGE_INTEGER period = { .QuadPart = 50 }; // 50 ms
T2_SET_PARAMETERS p = { .Version = 0, .NoWakeTolerance = 50 * 10000 };
NTSTATUS st = NtSetTimer2(hTimer, &due, &period, &p);rustSetThreadpoolTimer wrapper
// Cargo: windows-sys = "0.59" (Win32_System_Threading, Win32_Foundation)
use windows_sys::Win32::System::Threading::{SetThreadpoolTimer, PTP_TIMER};
use windows_sys::Win32::Foundation::FILETIME;
unsafe fn arm(t: PTP_TIMER) {
let mut due: FILETIME = std::mem::zeroed();
// Negative => relative 100ns; -50_000 == 5 ms.
let rel: i64 = -50_000;
due.dwLowDateTime = (rel as u64) as u32;
due.dwHighDateTime = ((rel as u64) >> 32) as u32;
SetThreadpoolTimer(t, &due, 50 /* period ms */, 100 /* tolerance ms */);
}MITRE ATT&CK mappings
Last verified: 2026-05-20