APC Queue (Постановка) — Исследование механизмов ядра Windows (ARM64)
Среда отладки: Windows 11 Build 26100, ARM64 (Parallels Desktop VM)
Метод: Live Kernel Debugging через WinDbg → socat serial bridge → MCP
Дата: Июнь 2026
1. Все пути постановки APC в очередь
Каждый APC в Windows проходит через одну и ту же точку входа в ядро — KeInsertQueueApc. Ниже перечислены все способы попадания к этой функции.
1.1 Пользовательский режим (User-Mode → Kernel)
Важно: Функции ожидания (SleepEx, WaitFor*Ex) не ставят APC в очередь — они лишь триггерят доставку уже поставленных APC через alertable wait.
WinAPI NT API Syscall Назначение QueueUserAPC NtQueueApcThread 0x1F4 Классическая постановка user APC QueueUserAPC2 NtQueueApcThreadEx 0x1F5 Расширенная версия (reserve handle) — NtQueueApcThreadEx2 0x1F6 Полная версия (флаги + reserve) CreateTimerQueueTimer NtSetTimerEx — Таймер → APC callback при срабатывании ReadFileEx NtReadFile (w/ ApcRoutine) — I/O completion через APC WriteFileEx NtWriteFile (w/ ApcRoutine) — I/O completion через APC SleepEx NtDelayExecution — Alertable wait → доставка pending APC WaitForSingleObjectEx NtWaitForSingleObject — Alertable wait → доставка pending APC WaitForMultipleObjectsEx NtWaitForMultipleObjects — Alertable wait → доставка pending APC SignalObjectAndWait NtSignalAndWaitForSingleObject — Alertable wait → доставка pending APC MsgWaitForMultipleObjectsEx NtUserMsgWaitForMultipleObjectsEx — Alertable wait + сообщение
1.2 Режим ядра (Kernel-Mode)
2. Цепочка вызовов QueueUserAPC
Функция Адрес Тип APC Контекст KeInitializeApc + KeInsertQueueApc 0xFFFFF801`B44720F0 / 0xFFFFF801`B4472150 Kernel / User Ручная постановка (драйверы) KeInitializeThreadedDpc 0xFFFFF801`B4709D90 DPC → thread DPC с потоком (имитация APC) IoAllocateWorkItem + IoQueueWorkItem 0xFFFFF801`B444C600 / 0xFFFFF801`B444CA30 Work Item Очередь рабочих элементов ExQueueWorkItem 0xFFFFF801`B457D900 Work Item Устаревший интерфейс (system worker) PsWrapApcWow64Thread 0xFFFFF801`B473A970 User (WoW64) Обёртка APC для x86→ARM64/x64 IopQueueThreadApc (внутренняя) — Kernel I/O completion APC
3. NtQueueApcThread → NtQueueApcThreadEx → NtQueueApcThreadEx2Код (Text):
Пользовательский режим: kernel32!QueueUserAPC(pfnAPC, hThread, dwData) ↓ ntdll!NtQueueApcThread(ThreadHandle, ApcRoutine, ApcContext, Arg1, Arg2) ↓ (syscall #0x1F4) Режим ядра: nt!NtQueueApcThread → 0xFFFFF801`B4923410 (thin wrapper, 7 инструкций) ↓ nt!NtQueueApcThreadEx2 → 0xFFFFF801`B4B00AD0 (основная логика) ├── ObReferenceObjectByHandle → ETHREAD по хэндлу (DesiredAccess = THREAD_SET_CONTEXT) ├── Проверка terminated → ETHREAD+0x6C бит 10 ├── Проверка WoW64/ARM64EC → KPROCESS+0x300, KPROCESS+0x7AC ├── ExAllocatePool2 → KAPC (0x58 байт, тег 'PasP', NonPagedPoolNx) ├── KeInitializeApc → заполнение структуры KAPC └── KeInsertQueueApc → вставка + пробуждение потока ├── KfRaiseIrql(DISPATCH_LEVEL) ├── SWPA ThreadLock (KTHREAD+0x40) ├── KiInsertQueueApc → LIST_ENTRY манипуляция (FIFO) ├── KiSignalThreadForApc → пробуждение / IPI ├── stlr ThreadLock = 0 → освобождение └── KiExitDispatcher → диспетчеризация
3.1 nt!NtQueueApcThread — 7 инструкций
3.2 nt!NtQueueApcThreadEx — 11 инструкцийКод (Text):
nt!NtQueueApcThread: fffff801`b4923410 mov x5, x3 ; x5 = Arg1 fffff801`b4923414 mov x6, x4 ; x6 = Arg2 fffff801`b4923418 mov x4, x2 ; x4 = ApcContext fffff801`b492341c mov x3, x1 ; x3 = ApcRoutine fffff801`b4923420 mov w2, #0 ; Flags = 0 fffff801`b4923424 mov x1, #0 ; UserApcReserveHandle = NULL fffff801`b4923428 b nt!NtQueueApcThreadEx2
3.3 Маппинг параметровКод (Text):
nt!NtQueueApcThreadEx: fffff801`b4b00aa0 mov x6, x5 ; x6 = Arg2 fffff801`b4b00aa4 mov x8, x2 ; x8 = ApcContext (temp) fffff801`b4b00aa8 cmp x1, #1 ; UserApcReserveHandle == 1? (special) fffff801`b4b00aac mov x5, x4 ; x5 = Arg1 fffff801`b4b00ab0 mov x4, x3 ; x4 = ApcRoutine fffff801`b4b00ab4 mov x3, x8 ; x3 = ApcContext fffff801`b4b00ab8 cseteq w2 ; if (handle == 1): Flags = 1 fffff801`b4b00abc cselne x1, x1, xzr ; if (handle != 1): x1 = handle, else NULL fffff801`b4b00ac0 b nt!NtQueueApcThreadEx2
4. KeInitializeApc — Заполнение KAPC
QueueUserAPC NtQueueApcThread NtQueueApcThreadEx2 hThread ThreadHandle (x0) ThreadHandle (x0) — — UserApcReserveHandle = NULL (x1) — — Flags = 0 (w2) pfnAPC ApcRoutine (x1) ApcRoutine → SystemArgument1 (x3→x26) dwData ApcContext (x2) ApcContext → SystemArgument2 (x4→x27)
Адрес: 0xFFFFF801`B44720F0
Ключевое: Если NormalRoutine == NULL, APC помечается как kernel-only (ApcMode = 0), даже если передали ApcMode = 1.Код (Text):
nt!KeInitializeApc: ; Тип и размер объекта fffff801`b44720f0 mov w8, #0x12 ; Type = ApcObject (0x12) fffff801`b44720f4 strb w8, [x0] ; KAPC.Type = 0x12 fffff801`b44720f8 mov w8, #0x58 ; Size = 88 байт fffff801`b44720fc strb w8, [x0, #2] ; KAPC.Size = 0x58 ; Проверка ApcStateIndex fffff801`b4472100 cmp w2, #2 ; AttachedApcEnvironment? fffff801`b4472104 beq attached_env ; → использовать thread's index ; Обычный путь fffff801`b4472108 mov w8, w2 fffff801`b447210c strb w8, [x0, #0x50] ; KAPC.ApcStateIndex fffff801`b4472110 cbz x5, no_normal_routine ; NormalRoutine == NULL? ; Заполнение полей fffff801`b4472114 str x1, [x0, #8] ; KAPC.Thread = ETHREAD fffff801`b4472118 csel w8, wzr, w6, eq ; если NormalRoutine=NULL → ApcMode=0 fffff801`b447211c stp x3, x4, [x0, #0x20] ; KernelRoutine, RundownRoutine fffff801`b4472120 str x5, [x0, #0x30] ; NormalRoutine fffff801`b4472124 strb w8, [x0, #0x51] ; ApcMode (0=Kernel, 1=User) fffff801`b4472128 csel x8, xzr, x7, eq ; если NULL → Context = 0 fffff801`b447212c str x8, [x0, #0x38] ; NormalContext fffff801`b4472130 strb wzr, [x0, #0x52] ; Inserted = FALSE fffff801`b4472134 strb wzr, [x0, #1] ; CallbackDataContext = 0 fffff801`b4472138 ret
5. KeInsertQueueApc — Основной механизм постановки
Адрес: 0xFFFFF801`B4472150
5.1 ETW трассировка
ETW событие: Если включено — логируется каждый APC insert. Можно отслеживать через Event Tracing.Код (Text):
; Проверка: активен ли ETW-провайдер для APC? fffff801`b4472170 adrp x8, ... fffff801`b4472174 ldr x10, [x8, #0xE10] ; EtwTiLogInsertQueueUserApc fffff801`b447218c cbz x10, skip_etw ; если NULL → пропустить fffff801`b4472194 mov x2, #0x3000 ; keyword mask fffff801`b44721a0 bl nt!EtwpLevelKeywordEnabled
5.2 Подъём IRQL и захват ThreadLock
Механизм: KTHREAD+0x40 = ThreadLock. Инструкция SWPA (Store Word Pair Atomic, Acquire semantics) — атомарный обмен. Spin-wait использует yield + DMB для cooperative spinning.Код (Text):
fffff801`b447221c mov w0, #2 ; DISPATCH_LEVEL fffff801`b4472220 bl nt!KfRaiseIrql ; поднять IRQL до 2 ; Захват ThreadLock через атомарный SWPA: fffff801`b4472244 add x27, x19, #0x40 ; KTHREAD+0x40 (ThreadLock) fffff801`b4472248 swpa x21, x8, [x27] ; Atomic swap: x21=1, x8=prev ; x21 = 1 (запереть) ; x8 = предыдущее значение fffff801`b4472250 cbz x8, lock_acquired ; было 0 → захватили! ; Spin-wait loop (блокировка занята другим): fffff801`b4472258 ldr w8, [x26, #0x28C] ; Prcb spin count fffff801`b447225c add w25, w25, #1 ; счётчик попыток fffff801`b4472260 tst w8, w25 ; проверить spin count fffff801`b4472268 dmb ishst ; Data Memory Barrier fffff801`b447226c yield ; CPU hint (снизить功耗) fffff801`b4472270 ldr x8, [x27] ; перечитать ThreadLock fffff801`b4472274 cbnz x8, spin_wait ; занято → крутиться
5.3 Вызов KiInsertQueueApc (вставка в список)
5.4 Определение необходимости пробужденияКод (Text):
fffff801`b4472298 ldr x8, [sp, #0x18] ; SystemArgument1 fffff801`b447229c strb w21, [x20, #0x52] ; KAPC.Inserted = TRUE (1) fffff801`b44722a0 mov x0, x20 ; KAPC fffff801`b44722a4 stp x8, x25, [x20, #0x40] ; KAPC.SystemArgument1/2 fffff801`b44722a8 bl nt!KiInsertQueueApc ; вставить в связный список
5.5 Пробуждение целевого потокаКод (Text):
; Проверка: APC state совпадает? fffff801`b44722b0 ldrsb w8, [x20, #0x50] ; KAPC.ApcStateIndex fffff801`b44722b8 ldrb w9, [x19, #0x26A] ; KTHREAD.ApcStateIndex fffff801`b44722bc cmp w8, w9 ; совпадают? ; Проверка: целевой поток = текущий? fffff801`b44722c4 ldr x8, [x23, #8] ; CurrentThread fffff801`b44722c8 cmp x19, x8 ; target == current? ; Если User APC и поток не текущий → сигналить fffff801`b44722d0 cbnz w10, signal_thread ; ApcMode != 0 → сигналить
5.6 Освобождение блокировки и диспетчеризацияКод (Text):
; Установка флага KernelApcPending: fffff801`b44722ec strb w21, [x19, #0xB9] ; KTHREAD+0xB9 = KernelApcPending = 1 ; Если целевой поток на другом процессоре — отправить IPI: fffff801`b44722f4 mrs x8, DAIF ; проверить маски прерываний fffff801`b44722f8 tbnz w8, #7, ... ; если IRQ masked → пропустить IPI ; Отправка программного прерывания: fffff801`b4472530 ldr w8, [x19, #0x238] ; KTHREAD+0x238 (WaitIrql) fffff801`b4472548 bl nt!KiSendSoftwareInterrupt ; отправить IPI ; или: fffff801`b4472550 mov w0, #1 ; APC_LEVEL request fffff801`b4472554 bl nt!HalRequestSoftwareInterrupt
6. KiInsertQueueApc — Манипуляция со связным спискомКод (Text):
fffff801`b447232c add x8, x26, #0x40 ; &ThreadLock fffff801`b4472334 stlr xzr, [x8] ; store-release: ThreadLock = 0 fffff801`b4472338 mov w2, #1 ; WaitReason fffff801`b447233c mov w1, #0 ; Increment fffff801`b4472340 mov x0, x23 ; Prcb fffff801`b4472344 bl nt!KiExitDispatcher ; диспетчеризация fffff801`b447234c mov w0, w20 ; вернуть TRUE/FALSE
Адрес: 0xFFFFF801`B448A6F0
6.1 Выбор списка по ApcStateIndex
6.2 Вычисление списка по ApcModeКод (Text):
fffff801`b448a6f0 ldr x10, [x0, #8] ; x10 = KAPC.Thread (KTHREAD) fffff801`b448a6f4 ldrsb w8, [x0, #0x50] ; w8 = KAPC.ApcStateIndex fffff801`b448a6f8 add x9, x10, #0x26A ; &KTHREAD.ApcStateIndex fffff801`b448a6fc cbnz w8, special_state ; != 0 → special path ; ApcStateIndex == 0 (OriginalApcEnvironment): fffff801`b448a700 ldrb w8, [x10, #0x26A] ; thread's ApcStateIndex fffff801`b448a704 cbnz w8, attached_thread ; поток attached → +0x278 ; Нормальный путь: KTHREAD+0x90 (ApcState) fffff801`b448a70c add x13, x10, #0x90 ; x13 = KTHREAD.ApcState
Формула:Код (Text):
; w12 = KAPC.ApcMode (0 = Kernel, 1 = User) fffff801`b448a724 add x9, x13, w12, sxtw #4 ; base + (ApcMode * 16) ; Kernel: base + 0x00 ; User: base + 0x10Код (Text):
ListHead = ApcStateBase + (ApcMode * sizeof(LIST_ENTRY))6.3 Вставка (FIFO — в конец списка)
ApcMode Offset от ApcState Offset от KTHREAD Список 0 (Kernel) +0x00 +0x090 Kernel APC List 1 (User) +0x10 +0x0A0 User APC List
6.4 Установка UserApcPending флагаКод (Text):
; Список пуст (Blink == Head): fffff801`b448a798 stp x9, x8, [x11] ; KAPC.Flink = Head, Blink = Head fffff801`b448a79c str x11, [x8] ; Head.Flink = KAPC fffff801`b448a7a0 str x11, [x9, #8] ; Head.Blink = KAPC ; Список не пуст — вставка после последнего: fffff801`b448a754 ldr x9, [x10] ; last_entry.Flink fffff801`b448a758 add x11, x0, #0x10 ; &KAPC.ApcListEntry fffff801`b448a75c ldr x8, [x9, #8] ; проверка целостности fffff801`b448a760 cmp x8, x10 ; Blink == last? fffff801`b448a764 bne corrupt ; BRK #0xF003 (BugCheck) fffff801`b448a768 stp x9, x10, [x11] ; KAPC.Flink = Head, Blink = last fffff801`b448a76c str x11, [x9, #8] ; Head.Blink = KAPC fffff801`b4472270 str x11, [x10] ; last.Flink = KAPC
7. KiDeliverApc — Доставка APCКод (Text):
; Для user APC: установить UserApcPendingAll бит 1 fffff801`b448a7b8 ldrb w8, [x10, #0xBA] ; KTHREAD+0xBA (UserApcPendingAll) fffff801`b448a7c0 add x9, x13, w12, sxtw #4 ; list head fffff801`b448a7c8 orr w8, w8, #2 ; установить бит 1 (UserApcPending) fffff801`b448a7cc strb w8, [x10, #0xBA] ; записать обратно
Адрес: 0xFFFFF801`B448A030
Вызывается при возврате из ядра в user-mode (KiSystemServiceExit / KiExceptionExit).
7.1 Подготовка
7.2 Доставка Kernel APC (список +0x90)Код (Text):
fffff801`b448a08c ldr x20, [xpr, #0x988] ; KTHREAD текущего потока fffff801`b448a090 ldr x8, [x20, #0x88] ; KTHREAD+0x88 (TrapFrame) fffff801`b448a094 strb wzr, [x20, #0xB9] ; KernelApcPending = 0 fffff801`b448a098 str x2, [x20, #0x88] ; новый trap frame fffff801`b448a09c str x8, [sp, #0x58] ; старый trap frame → стек
7.3 Доставка User APC (список +0xA0)Код (Text):
; Проверка kernel APC списка: fffff801`b448a0b4 add x21, x20, #0x90 ; KTHREAD.ApcState fffff801`b448a0d0 ldr x8, [x26] ; ApcListHead[0].Flink fffff801`b448a0d4 cmp x8, x26 ; список пуст? fffff801`b448a0d8 beq user_apc_check ; пустой → user APC ; Захват ThreadLock: fffff801`b448a0e0 bl nt!KfRaiseIrql ; DISPATCH_LEVEL fffff801`b448a0ec swpa x24, x8, [x22] ; захват ThreadLock (SWPA) ; Извлечь первый KAPC из списка: fffff801`b448a118 ldr x19, [x20, #0x90] ; первый KAPC fffff801`b448a130 ldr x9, [x19, #0x20] ; KernelRoutine fffff801`b448a134 ldr x21, [x19, #0x10] ; Flink (следующий) fffff801`b448a144 ldr x8, [x19, #0x30] ; NormalRoutine ; Unlink из списка: fffff801`b448a168 ldr x10, [x19] ; KAPC.Flink fffff801`b448a178 ldr x9, [x19, #8] ; KAPC.Blink fffff801`b448a188 str x10, [x9] ; Blink→Flink fffff801`b448a18c str x9, [x10, #8] ; Flink→Blink ; Освободить блокировку: fffff801`b448a198 stlr xzr, [x22] ; ThreadLock = 0 fffff801`b448a19c bl nt!KfLowerIrql ; восстановить IRQL ; Вызвать KernelRoutine через PAC: fffff801`b448a1a8 strb w24, [x20, #0xB8] ; KernelApcInProgress = 1 fffff801`b448a1dc mov x15, x21 ; KernelRoutine fffff801`b448a1e0 bl nt!KscpCfgCheckUserCallTargetEs ; PAC проверка fffff801`b448a1e4 blr x15 ; вызвать KernelRoutine(KAPC, ...)
7.4 KiInitializeUserApc — переход в user-modeКод (Text):
; Проверка user APC списка: fffff801`b448a388 add x9, x20, #0xA0 ; KTHREAD+0xA0 (User APC list) fffff801`b448a38c ldr x8, [x9] ; User ApcListHead.Flink fffff801`b448a394 cmp x8, x9 ; список пуст? fffff801`b448a398 beq no_user_apcs ; пустой → выход ; Поиск первого APC с NormalRoutine: fffff801`b448a414 sub x25, x8, #0x10 ; KAPC = entry - 0x10 fffff801`b448a41c ldr x9, [x25, #0x20] ; KernelRoutine fffff801`b448a44c ldr x9, [x25, #0x30] ; NormalRoutine fffff801`b448a45c ldr x9, [x25, #0x38] ; NormalContext fffff801`b448a470 ldr x9, [x25, #0x40] ; SystemArgument1 fffff801`b448a480 ldr x9, [x25, #0x48] ; SystemArgument2
Параметры:Код (Text):
fffff801`b448a50c ldp x5, x4, [sp, #0x10] ; SystemArgument1, SystemArgument2 fffff801`b448a510 mov w6, w21 ; flags fffff801`b448a514 ldr x3, [sp, #0x20] ; NormalContext fffff801`b448a518 mov x1, x19 ; ExceptionFrame fffff801`b448a51c ldr x0, [sp, #0x48] ; TrapFrame fffff801`b448a520 bl nt!KiInitializeUserApc
7.5 KiInitializeUserApc — модификация trap frame
Параметр Регистр Значение TrapFrame x0 Адрес trap frame ExceptionFrame x1 Адрес exception frame NormalRoutine x2 APC callback (user-mode) NormalContext x3 ApcContext SystemArgument1 x4 Arg1 SystemArgument2 x5 Arg2 Flags w6 Флаги доставки
Адрес: 0xFFFFF801`B44B70C0
Результат: Trap frame модифицирован:Код (Text):
; Определение архитектуры: fffff801`b44b7144 bl nt!PsGetProcessMachine ; Machine type fffff801`b44b7148 mov w8, #0xAA64 ; ARM64 fffff801`b44b714c cmp w8, w0, uxth #0 ; native ARM64? ; Подготовка CONTEXT: fffff801`b44b7184 ldr x8, [x19, #0x98] ; TrapFrame->Sp (user stack) fffff801`b44b7188 str x8, [x26, #0x38] ; сохранить оригинальный SP fffff801`b44b718c sub x21, x8, #0x10 ; выделить место на стеке ; Заполнение CONTEXT из trap frame: fffff801`b44b7278 ldr x2, [x26, #0x18] ; CONTEXT size fffff801`b44b7280 ldr x1, [x26, #0x40] ; source context fffff801`b44b7284 mov x0, x19 ; TrapFrame fffff801`b44b7288 bl nt!KeContextFromKframes ; CONTEXT ← trap frame ; Выбор dispatcher address по архитектуре: fffff801`b44b72f0 ldrh w9, [x27, #0x7AC] ; EPROCESS.Machine fffff801`b44b72f4 mov w8, #0x8664 ; AMD64 fffff801`b44b72f8 cmp w9, w8 fffff801`b44b7304 ldr x9, [x8, #0x28] ; ARM64 dispatcher fffff801`b44b7308 ldr x8, [x8, #0x268] ; x64/ARM64EC dispatcher fffff801`b44b730c cselne x8, x9, x8 ; выбрать нужный fffff801`b44b7310 str x8, [x19, #0x148] ; KTHREAD+0x148 = dispatcher
8. WinAPI, использующие APC внутри
- PC → ntdll!KiUserApcDispatcher
- SP → уменьшен, параметры APC на стеке
- Восстановление через ntdll!NtContinue
8.1 Функции постановки APC
8.2 Функции, триггерящие доставку (alertable wait)
Функция NT вызов Kernel tag Описание QueueUserAPC NtQueueApcThread 'PasP' Ставит user APC в очередь целевого потока QueueUserAPC2 NtQueueApcThreadEx 'PasP' Расширенная версия (reserve handle, флаги)
Эти функции не ставят APC — они переводят поток в alertable wait, что позволяет доставить уже поставленные APC.
8.3 Функции I/O с APC completion
Функция NT вызов Механизм доставки SleepEx(alertable=TRUE) NtDelayExecution При пробуждении → KiDeliverApc WaitForSingleObjectEx NtWaitForSingleObject Alertable wait → KiDeliverApc WaitForMultipleObjectsEx NtWaitForMultipleObjects Alertable wait → KiDeliverApc SignalObjectAndWait NtSignalAndWaitForSingleObject Signal + alertable wait → KiDeliverApc MsgWaitForMultipleObjectsEx NtUserMsgWaitForMultipleObjectsEx Msg + alertable wait → KiDeliverApc WaitForThreadpoolTimerCallbacks — TP timer → APC delivery path
Механизм I/O APC: ReadFileEx/WriteFileEx передают ApcRoutine в NtReadFile/NtWriteFile. Когда I/O завершается, I/O manager ставит kernel APC (IopQueueThreadApc), который в свою очередь вызывает пользовательский completion routine.
Функция NT вызов Механизм ReadFileEx NtReadFile (ApcRoutine ≠ NULL) I/O completion → kernel APC → user callback WriteFileEx NtWriteFile (ApcRoutine ≠ NULL) I/O completion → kernel APC → user callback DeviceIoControl (overlapped) NtDeviceIoControlFile APC completion при overlapped I/O
8.4 Функции, НЕ использующие APC
9. Ранжирование методов стелс-выполнения
Функция Почему нет Sleep (без Ex) Не alertable — APC не доставляются WaitForSingleObject (без Ex) Не alertable ReadFile (без Ex) Нет ApcRoutine — completion через event или IOCP CreateThread Создаёт новый поток, не использует APC
От самого незаметного к наиболее обнаружимому:
10. Структуры данных
# Метод Обнаружение Особенности 1 APC Injection Очень низкое Нет нового потока, стека, TEB. Тег 'PasP'. Не проверяет Protected Process. 2 Threaded DPC Низкое Работает на DISPATCH_LEVEL. Не создаёт поток. Только kernel-mode. 3 Timer + DPC Низкое Отложенное выполнение. KeSetTimerEx + DPC callback. 4 Work Items (IoQueueWorkItem) Среднее Выполняется в system worker thread. Видно в очереди. 5 Thread Context Hijacking Среднее Перехват已有 потока. Модификация контекста. Обнаруживается ETW. 6 CreateRemoteThread Высокое Новый ETHREAD (~0x770 байт), стек, TEB. Protected Process check. Тег 'Thre'.
10.1 KAPC (0x58 = 88 байт)[/SIZE]
10.2 APC-поля в KTHREAD
Смещение Размер Поле Описание +0x00 1 Type 0x12 (ApcObject) +0x01 1 AllFlags Бит 0: CallbackDataContext +0x02 1 Size 0x58 (88 байт) +0x08 8 Thread Указатель на KTHREAD +0x10 16 ApcListEntry LIST_ENTRY (связный список) +0x20 8 KernelRoutine Функция ядра (очистка) +0x28 8 RundownRoutine Функция при rundown потока +0x30 8 NormalRoutine Пользовательский callback +0x38 8 NormalContext Контекст APC +0x40 8 SystemArgument1 Аргумент 1 +0x48 8 SystemArgument2 Аргумент 2 +0x50 1 ApcStateIndex 0=Original, 1/2=Attached +0x51 1 ApcMode 0=KernelMode, 1=UserMode +0x52 1 Inserted Флаг: APC в списке (0/1)
11. Диаграмма постановки APC
Смещение Поле Описание +0x040 ThreadLock Спин-блокировка (SWPA atomic) +0x088 TrapFrame Указатель на текущий trap frame +0x090 ApcState KAPC_STATE (текущее состояние) +0x0B9 KernelApcPending Флаг: kernel APC ожидает +0x0BA UserApcPendingAll Бит 0: SpecialUserApcPending, Бит 1: UserApcPending +0x148 ApcStatePointer Массив указателей ApcState/SavedApcState +0x26A ApcStateIndex Текущий APC state (0=Original) +0x278 SavedApcState KAPC_STATE при KeStackAttach
12. Ключевые адреса (Build 26100 ARM64)Код (Text):
┌──────────────────────┐ │ User-Mode │ │ │ │ QueueUserAPC() │ ← постановка │ QueueUserAPC2() │ ← постановка (расширенная) │ │ │ SleepEx(T) │ ← alertable wait (доставка) │ WaitForSingleObjEx │ ← alertable wait (доставка) │ ReadFileEx │ ← I/O completion (постановка + доставка) │ WriteFileEx │ ← I/O completion (постановка + доставка) └──────────┬───────────┘ │ syscall ══════════╪═════════════ Kernel boundary ══════════╪═════════════ │ ┌──────────▼───────────┐ │ nt!NtQueueApcThread │ 7 инструкций, remap ABI └──────────┬───────────┘ │ ┌──────────▼───────────┐ │ nt!NtQueueApcThreadEx2│ Основная логика │ │ │ 1. Validate flags │ tst w24, #0xFFFEFFFE │ 2. ObRefByHandle │ → ETHREAD (THREAD_SET_CONTEXT) │ 3. Check terminated │ ETHREAD+0x6C бит 10 │ 4. Check WoW64/EC │ KPROCESS+0x300 │ 5. CAS lock │ casal (reserve path) │ 6. ExAllocatePool2 │ KAPC (0x58, 'PasP') │ 7. KeInitializeApc │ заполнение KAPC │ 8. KeInsertQueueApc │ ──────────────────┐ └──────────┬───────────┘ │ │ ▼ ┌──────────▼───────────┐ ┌──────────────────────────┐ │ nt!KiInsertQueueApc │ │ nt!KeInsertQueueApc │ │ │ │ │ │ Выбор ApcState: │ │ 1. ETW trace │ │ +0x90 (Original) │ │ 2. KfRaiseIrql(2) │ │ +0x278 (Attached) │ │ 3. SWPA ThreadLock │ │ │ │ 4. KAPC.Inserted = TRUE │ │ Выбор списка: │ │ 5. KiInsertQueueApc │ │ +0x00 (Kernel) │ │ 6. SignalThreadForApc │ │ +0x10 (User) │ │ 7. stlr ThreadLock = 0 │ │ │ │ 8. KiExitDispatcher │ │ Вставка: FIFO (tail) │ └──────────────────────────┘ │ │ │ Для User: установить │ │ UserApcPending (0xBA) │ └──────────────────────┘ ┌──────────────────────┐ │ Доставка APC │ │ │ │ KiDeliverApc │ │ ├── Kernel APC list │ +0x90 (ApcListHead[0]) │ │ → KernelRoutine │ через PAC проверку │ │ │ │ └── User APC list │ +0xA0 (ApcListHead[1]) │ → KiInitializeUserApc │ → Модификация trap frame │ → PC = ntdll!KiUserApcDispatcher │ → SP = уменьшен (параметры) │ │ │ ntdll!KiUserApcDispatcher │ ├── ApcRoutine(Context, Arg1, Arg2) │ └── NtContinue() → восстановление контекста └──────────────────────┘
Функция Адрес nt!NtQueueApcThread 0xFFFFF801`B4923410 nt!NtQueueApcThreadEx 0xFFFFF801`B4B00AA0 nt!NtQueueApcThreadEx2 0xFFFFF801`B4B00AD0 nt!KeInitializeApc 0xFFFFF801`B44720F0 nt!KeInsertQueueApc 0xFFFFF801`B4472150 nt!KiInsertQueueApc 0xFFFFF801`B448A6F0 nt!KiDeliverApc 0xFFFFF801`B448A030 nt!KiInitializeUserApc 0xFFFFF801`B44B70C0 nt!KiExitDispatcher 0xFFFFF801`B449A918 nt!PsWrapApcWow64Thread 0xFFFFF801`B473A970 nt!KfRaiseIrql 0xFFFFF801`B4406730 nt!KfLowerIrql 0xFFFFF801`B44065F0 nt!ObReferenceObjectByHandle 0xFFFFF801`B4AA6B70 nt!ExAllocatePool2 0xFFFFF801`B4CAC000 nt!KscpCfgCheckUserCallTargetEs 0xFFFFF801`B4CF4040
APC Queue (Постановка) — Исследование механизмов ядра Windows (ARM64)
Дата публикации 3 июн 2026 в 02:00