NtSetTimer2
Bewaffnet ein Timer2-Objekt mit Fälligkeitszeit, optionaler Periode und einem T2_SET_PARAMETERS-Block mit Callback und Flags.
Prototyp
NTSTATUS NtSetTimer2( HANDLE TimerHandle, PLARGE_INTEGER DueTime, PLARGE_INTEGER Period, PT2_SET_PARAMETERS Parameters );
Argumente
| Name | Type | Dir | Description |
|---|---|---|---|
| TimerHandle | HANDLE | in | Handle auf einen zuvor mit NtCreateTimer2 erzeugten Timer. |
| DueTime | PLARGE_INTEGER | in | Anfänglicher Fälligkeitszeitpunkt absolut (positiv) oder relativ (negativ, in 100ns-Einheiten). |
| Period | PLARGE_INTEGER | in | Optionales periodisches Intervall in Millisekunden; NULL oder 0 für Einmal-Trigger. |
| Parameters | PT2_SET_PARAMETERS | in | Struktur mit TolerableDelay, optionalem NoWakeTolerance und dem APC/Callback-Deskriptor. |
Syscall-IDs pro 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-Modul
Verwandte 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
Undokumentierte Hinweise
Wo das alte `NtSetTimer` die APC-Routine und ihren Kontext als separate `PTIMER_APC_ROUTINE` / `PVOID`-Parameter nahm, fasst `NtSetTimer2` alles, was den bewaffneten Zustand *modifiziert*, in einem einzigen `T2_SET_PARAMETERS`-Block zusammen. Die Struktur trägt `Version`, ein `TolerableDelay` (vom Coalescing-Scheduler genutzt), optionales `NoWakeTolerance` (verhindert das Aufwecken der CPU aus C-States) und eine Union, die zwischen *Workitem-Callback* (von `SetThreadpoolTimer` genutzt) und *APC-Callback* (wenn der Timer einen alertable Wait treibt) wählt. Kernel-seitig ist es `ExpSetTimer2` → `KiSetTimer2`, der Timer wird über `KiTimerExpiration` auf der Second-Generation-Timer-Queue des `PRCB` neu bewaffnet. Coalescing ist der eigentliche Grund für Timer2: ein Strom kleiner Timer über viele Threads kann beim Ablauf gebündelt werden und reduziert Wakeups um eine Größenordnung.
Häufige Malware-Nutzung
In Sleep-Mask-Designs, die Timer2 adoptiert haben (Cronos / Zilean und mehrere private Red-Team-Kits), ist `NtSetTimer2` der Aufruf, der den Timer *tatsächlich scharf macht*: das Feld `T2_SET_PARAMETERS::Callback.Routine` zeigt auf das erste ROP-Gadget der Verschlüsselungskette, und kurze `DueTime` (typisch 1-5 ms relativ) plus eine Nicht-Null-`Period` bringt die Kette dazu, sich selbst neu zu bewaffnen. `NoWakeTolerance` auf hohen Wert zu setzen reduziert CPU-Wakeups und macht die Idle-Haltung des Implants ununterscheidbar von einem normalen Threadpool-Consumer wie Edge oder Teams. Einige **PoolParty**-Varianten übergeben `Parameters`, die über ein geschmuggeltes Work-Item in den Speicher eines *anderen Prozesses* zeigen — sie missbrauchen die Threadpool-Worker-Factory, um Ausführung zu liefern.
Erkennungsmöglichkeiten
Telemetrisch ist `NtSetTimer2` der Engpass, weil `NtCreateTimer2` in legitimen Apps zu rauschig ist. ETW-TI (`Microsoft-Windows-Threat-Intelligence`) emittiert derzeit *kein* dediziertes Event für Timer2-Armierung; ETW `Microsoft-Windows-Kernel-Process` und `Microsoft-Windows-Threading` zusammen erlauben die Rekonstruktion. Das starke Signal: `Parameters->Callback.Routine` in nicht-gemapptem / `MEM_PRIVATE`-RWX-Speicher, kurze `DueTime` (< 100 ms) kombiniert mit einer `Period`, die zur Ekko-typischen Kadenz von 50-100 ms passt, und ein aufrufender Thread, dessen Entry-Point selbst in privatem Speicher liegt. CrowdStrike, Defender for Endpoint und Elastic erkennen Ekko alle *über die Folge* — den alertable Wait in RWX-Speicher — nicht über diesen Syscall selbst.
Direkte Syscall-Beispiele
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