APC Queue (Постановка) — Исследование механизмов ядра Windows (ARM64)

Дата публикации 3 июн 2026 в 02:00
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)
WinAPINT APISyscallНазначение
QueueUserAPCNtQueueApcThread0x1F4Классическая постановка user APC
QueueUserAPC2NtQueueApcThreadEx0x1F5Расширенная версия (reserve handle)
NtQueueApcThreadEx20x1F6Полная версия (флаги + reserve)
CreateTimerQueueTimerNtSetTimerExТаймер → APC callback при срабатывании
ReadFileExNtReadFile (w/ ApcRoutine)I/O completion через APC
WriteFileExNtWriteFile (w/ ApcRoutine)I/O completion через APC
SleepExNtDelayExecutionAlertable wait → доставка pending APC
WaitForSingleObjectExNtWaitForSingleObjectAlertable wait → доставка pending APC
WaitForMultipleObjectsExNtWaitForMultipleObjectsAlertable wait → доставка pending APC
SignalObjectAndWaitNtSignalAndWaitForSingleObjectAlertable wait → доставка pending APC
MsgWaitForMultipleObjectsExNtUserMsgWaitForMultipleObjectsExAlertable wait + сообщение
Важно: Функции ожидания (SleepEx, WaitFor*Ex) не ставят APC в очередь — они лишь триггерят доставку уже поставленных APC через alertable wait.
1.2 Режим ядра (Kernel-Mode)
ФункцияАдресТип APCКонтекст
KeInitializeApc + KeInsertQueueApc0xFFFFF801`B44720F0 / 0xFFFFF801`B4472150Kernel / UserРучная постановка (драйверы)
KeInitializeThreadedDpc0xFFFFF801`B4709D90DPC → threadDPC с потоком (имитация APC)
IoAllocateWorkItem + IoQueueWorkItem0xFFFFF801`B444C600 / 0xFFFFF801`B444CA30Work ItemОчередь рабочих элементов
ExQueueWorkItem0xFFFFF801`B457D900Work ItemУстаревший интерфейс (system worker)
PsWrapApcWow64Thread0xFFFFF801`B473A970User (WoW64)Обёртка APC для x86→ARM64/x64
IopQueueThreadApc (внутренняя)KernelI/O completion APC
2. Цепочка вызовов QueueUserAPC
Код (Text):
  1.  
  2. Пользовательский режим:
  3.   kernel32!QueueUserAPC(pfnAPC, hThread, dwData)
  4.     ↓
  5.   ntdll!NtQueueApcThread(ThreadHandle, ApcRoutine, ApcContext, Arg1, Arg2)
  6.     ↓ (syscall #0x1F4)
  7. Режим ядра:
  8.   nt!NtQueueApcThread              → 0xFFFFF801`B4923410  (thin wrapper, 7 инструкций)
  9.     ↓
  10.   nt!NtQueueApcThreadEx2           → 0xFFFFF801`B4B00AD0  (основная логика)
  11.     ├── ObReferenceObjectByHandle  → ETHREAD по хэндлу (DesiredAccess = THREAD_SET_CONTEXT)
  12.     ├── Проверка terminated        → ETHREAD+0x6C бит 10
  13.     ├── Проверка WoW64/ARM64EC     → KPROCESS+0x300, KPROCESS+0x7AC
  14.     ├── ExAllocatePool2             → KAPC (0x58 байт, тег 'PasP', NonPagedPoolNx)
  15.     ├── KeInitializeApc            → заполнение структуры KAPC
  16.     └── KeInsertQueueApc           → вставка + пробуждение потока
  17.          ├── KfRaiseIrql(DISPATCH_LEVEL)
  18.          ├── SWPA ThreadLock (KTHREAD+0x40)
  19.          ├── KiInsertQueueApc       → LIST_ENTRY манипуляция (FIFO)
  20.          ├── KiSignalThreadForApc   → пробуждение / IPI
  21.          ├── stlr ThreadLock = 0    → освобождение
  22.          └── KiExitDispatcher       → диспетчеризация
  23.  
3. NtQueueApcThread → NtQueueApcThreadEx → NtQueueApcThreadEx2
3.1 nt!NtQueueApcThread — 7 инструкций
Код (Text):
  1. nt!NtQueueApcThread:
  2. fffff801`b4923410  mov  x5, x3          ; x5 = Arg1
  3. fffff801`b4923414  mov  x6, x4          ; x6 = Arg2
  4. fffff801`b4923418  mov  x4, x2          ; x4 = ApcContext
  5. fffff801`b492341c  mov  x3, x1          ; x3 = ApcRoutine
  6. fffff801`b4923420  mov  w2, #0          ; Flags = 0
  7. fffff801`b4923424  mov  x1, #0          ; UserApcReserveHandle = NULL
  8. fffff801`b4923428  b    nt!NtQueueApcThreadEx2
3.2 nt!NtQueueApcThreadEx — 11 инструкций
Код (Text):
  1. nt!NtQueueApcThreadEx:
  2. fffff801`b4b00aa0  mov  x6, x5          ; x6 = Arg2
  3. fffff801`b4b00aa4  mov  x8, x2          ; x8 = ApcContext (temp)
  4. fffff801`b4b00aa8  cmp  x1, #1          ; UserApcReserveHandle == 1? (special)
  5. fffff801`b4b00aac  mov  x5, x4          ; x5 = Arg1
  6. fffff801`b4b00ab0  mov  x4, x3          ; x4 = ApcRoutine
  7. fffff801`b4b00ab4  mov  x3, x8          ; x3 = ApcContext
  8. fffff801`b4b00ab8  cseteq w2            ; if (handle == 1): Flags = 1
  9. fffff801`b4b00abc  cselne x1, x1, xzr   ; if (handle != 1): x1 = handle, else NULL
  10. fffff801`b4b00ac0  b    nt!NtQueueApcThreadEx2
3.3 Маппинг параметров
QueueUserAPCNtQueueApcThreadNtQueueApcThreadEx2
hThreadThreadHandle (x0)ThreadHandle (x0)
UserApcReserveHandle = NULL (x1)
Flags = 0 (w2)
pfnAPCApcRoutine (x1)ApcRoutine → SystemArgument1 (x3→x26)
dwDataApcContext (x2)ApcContext → SystemArgument2 (x4→x27)
4. KeInitializeApc — Заполнение KAPC
Адрес:
0xFFFFF801`B44720F0
Код (Text):
  1. nt!KeInitializeApc:
  2. ; Тип и размер объекта
  3. fffff801`b44720f0  mov   w8, #0x12             ; Type = ApcObject (0x12)
  4. fffff801`b44720f4  strb  w8, [x0]              ; KAPC.Type = 0x12
  5. fffff801`b44720f8  mov   w8, #0x58             ; Size = 88 байт
  6. fffff801`b44720fc  strb  w8, [x0, #2]          ; KAPC.Size = 0x58
  7. ; Проверка ApcStateIndex
  8. fffff801`b4472100  cmp   w2, #2                ; AttachedApcEnvironment?
  9. fffff801`b4472104  beq   attached_env          ; → использовать thread's index
  10. ; Обычный путь
  11. fffff801`b4472108  mov   w8, w2
  12. fffff801`b447210c  strb  w8, [x0, #0x50]       ; KAPC.ApcStateIndex
  13. fffff801`b4472110  cbz   x5, no_normal_routine ; NormalRoutine == NULL?
  14. ; Заполнение полей
  15. fffff801`b4472114  str   x1, [x0, #8]          ; KAPC.Thread = ETHREAD
  16. fffff801`b4472118  csel  w8, wzr, w6, eq       ; если NormalRoutine=NULL → ApcMode=0
  17. fffff801`b447211c  stp   x3, x4, [x0, #0x20]  ; KernelRoutine, RundownRoutine
  18. fffff801`b4472120  str   x5, [x0, #0x30]       ; NormalRoutine
  19. fffff801`b4472124  strb  w8, [x0, #0x51]       ; ApcMode (0=Kernel, 1=User)
  20. fffff801`b4472128  csel  x8, xzr, x7, eq       ; если NULL → Context = 0
  21. fffff801`b447212c  str   x8, [x0, #0x38]       ; NormalContext
  22. fffff801`b4472130  strb  wzr, [x0, #0x52]      ; Inserted = FALSE
  23. fffff801`b4472134  strb  wzr, [x0, #1]         ; CallbackDataContext = 0
  24. fffff801`b4472138  ret
Ключевое: Если NormalRoutine == NULL, APC помечается как kernel-only (ApcMode = 0), даже если передали ApcMode = 1.
5. KeInsertQueueApc — Основной механизм постановки
Адрес:
0xFFFFF801`B4472150
5.1 ETW трассировка
Код (Text):
  1. ; Проверка: активен ли ETW-провайдер для APC?
  2. fffff801`b4472170  adrp  x8, ...
  3. fffff801`b4472174  ldr   x10, [x8, #0xE10]    ; EtwTiLogInsertQueueUserApc
  4. fffff801`b447218c  cbz   x10, skip_etw         ; если NULL → пропустить
  5. fffff801`b4472194  mov   x2, #0x3000            ; keyword mask
  6. fffff801`b44721a0  bl    nt!EtwpLevelKeywordEnabled
ETW событие: Если включено — логируется каждый APC insert. Можно отслеживать через Event Tracing.
5.2 Подъём IRQL и захват ThreadLock
Код (Text):
  1. fffff801`b447221c  mov   w0, #2                ; DISPATCH_LEVEL
  2. fffff801`b4472220  bl    nt!KfRaiseIrql          ; поднять IRQL до 2
  3. ; Захват ThreadLock через атомарный SWPA:
  4. fffff801`b4472244  add   x27, x19, #0x40        ; KTHREAD+0x40 (ThreadLock)
  5. fffff801`b4472248  swpa  x21, x8, [x27]         ; Atomic swap: x21=1, x8=prev
  6.                                                   ; x21 = 1 (запереть)
  7.                                                   ; x8 = предыдущее значение
  8. fffff801`b4472250  cbz   x8, lock_acquired       ; было 0 → захватили!
  9. ; Spin-wait loop (блокировка занята другим):
  10. fffff801`b4472258  ldr   w8, [x26, #0x28C]      ; Prcb spin count
  11. fffff801`b447225c  add   w25, w25, #1           ; счётчик попыток
  12. fffff801`b4472260  tst   w8, w25                 ; проверить spin count
  13. fffff801`b4472268  dmb   ishst                   ; Data Memory Barrier
  14. fffff801`b447226c  yield                          ; CPU hint (снизить功耗)
  15. fffff801`b4472270  ldr   x8, [x27]              ; перечитать ThreadLock
  16. fffff801`b4472274  cbnz  x8, spin_wait           ; занято → крутиться
Механизм: KTHREAD+0x40 = ThreadLock. Инструкция SWPA (Store Word Pair Atomic, Acquire semantics) — атомарный обмен. Spin-wait использует yield + DMB для cooperative spinning.
5.3 Вызов KiInsertQueueApc (вставка в список)
Код (Text):
  1. fffff801`b4472298  ldr   x8, [sp, #0x18]        ; SystemArgument1
  2. fffff801`b447229c  strb  w21, [x20, #0x52]      ; KAPC.Inserted = TRUE (1)
  3. fffff801`b44722a0  mov   x0, x20                ; KAPC
  4. fffff801`b44722a4  stp   x8, x25, [x20, #0x40]  ; KAPC.SystemArgument1/2
  5. fffff801`b44722a8  bl    nt!KiInsertQueueApc     ; вставить в связный список
5.4 Определение необходимости пробуждения
Код (Text):
  1. ; Проверка: APC state совпадает?
  2. fffff801`b44722b0  ldrsb w8, [x20, #0x50]       ; KAPC.ApcStateIndex
  3. fffff801`b44722b8  ldrb  w9, [x19, #0x26A]      ; KTHREAD.ApcStateIndex
  4. fffff801`b44722bc  cmp   w8, w9                  ; совпадают?
  5. ; Проверка: целевой поток = текущий?
  6. fffff801`b44722c4  ldr   x8, [x23, #8]          ; CurrentThread
  7. fffff801`b44722c8  cmp   x19, x8                ; target == current?
  8. ; Если User APC и поток не текущий → сигналить
  9. fffff801`b44722d0  cbnz  w10, signal_thread      ; ApcMode != 0 → сигналить
5.5 Пробуждение целевого потока
Код (Text):
  1. ; Установка флага KernelApcPending:
  2. fffff801`b44722ec  strb  w21, [x19, #0xB9]      ; KTHREAD+0xB9 = KernelApcPending = 1
  3. ; Если целевой поток на другом процессоре — отправить IPI:
  4. fffff801`b44722f4  mrs   x8, DAIF               ; проверить маски прерываний
  5. fffff801`b44722f8  tbnz  w8, #7, ...             ; если IRQ masked → пропустить IPI
  6. ; Отправка программного прерывания:
  7. fffff801`b4472530  ldr   w8, [x19, #0x238]      ; KTHREAD+0x238 (WaitIrql)
  8. fffff801`b4472548  bl    nt!KiSendSoftwareInterrupt  ; отправить IPI
  9. ; или:
  10. fffff801`b4472550  mov   w0, #1                  ; APC_LEVEL request
  11. fffff801`b4472554  bl    nt!HalRequestSoftwareInterrupt
5.6 Освобождение блокировки и диспетчеризация
Код (Text):
  1. fffff801`b447232c  add   x8, x26, #0x40        ; &ThreadLock
  2. fffff801`b4472334  stlr  xzr, [x8]              ; store-release: ThreadLock = 0
  3. fffff801`b4472338  mov   w2, #1                  ; WaitReason
  4. fffff801`b447233c  mov   w1, #0                  ; Increment
  5. fffff801`b4472340  mov   x0, x23                 ; Prcb
  6. fffff801`b4472344  bl    nt!KiExitDispatcher     ; диспетчеризация
  7. fffff801`b447234c  mov   w0, w20                 ; вернуть TRUE/FALSE
6. KiInsertQueueApc — Манипуляция со связным списком
Адрес:
0xFFFFF801`B448A6F0
6.1 Выбор списка по ApcStateIndex
Код (Text):
  1. fffff801`b448a6f0  ldr   x10, [x0, #8]         ; x10 = KAPC.Thread (KTHREAD)
  2. fffff801`b448a6f4  ldrsb w8, [x0, #0x50]        ; w8 = KAPC.ApcStateIndex
  3. fffff801`b448a6f8  add   x9, x10, #0x26A         ; &KTHREAD.ApcStateIndex
  4. fffff801`b448a6fc  cbnz  w8, special_state       ; != 0 → special path
  5. ; ApcStateIndex == 0 (OriginalApcEnvironment):
  6. fffff801`b448a700  ldrb  w8, [x10, #0x26A]       ; thread's ApcStateIndex
  7. fffff801`b448a704  cbnz  w8, attached_thread     ; поток attached → +0x278
  8. ; Нормальный путь: KTHREAD+0x90 (ApcState)
  9. fffff801`b448a70c  add   x13, x10, #0x90         ; x13 = KTHREAD.ApcState
6.2 Вычисление списка по ApcMode
Код (Text):
  1. ; w12 = KAPC.ApcMode (0 = Kernel, 1 = User)
  2. fffff801`b448a724  add   x9, x13, w12, sxtw #4   ; base + (ApcMode * 16)
  3.                                                   ; Kernel: base + 0x00
  4.                                                   ; User:   base + 0x10
Формула:
Код (Text):
  1. ListHead = ApcStateBase + (ApcMode * sizeof(LIST_ENTRY))
ApcModeOffset от ApcStateOffset от KTHREADСписок
0 (Kernel)+0x00+0x090Kernel APC List
1 (User)+0x10+0x0A0User APC List
6.3 Вставка (FIFO — в конец списка)
Код (Text):
  1. ; Список пуст (Blink == Head):
  2. fffff801`b448a798  stp   x9, x8, [x11]           ; KAPC.Flink = Head, Blink = Head
  3. fffff801`b448a79c  str   x11, [x8]               ; Head.Flink = KAPC
  4. fffff801`b448a7a0  str   x11, [x9, #8]           ; Head.Blink = KAPC
  5. ; Список не пуст — вставка после последнего:
  6. fffff801`b448a754  ldr   x9, [x10]               ; last_entry.Flink
  7. fffff801`b448a758  add   x11, x0, #0x10          ; &KAPC.ApcListEntry
  8. fffff801`b448a75c  ldr   x8, [x9, #8]            ; проверка целостности
  9. fffff801`b448a760  cmp   x8, x10                  ; Blink == last?
  10. fffff801`b448a764  bne   corrupt                   ; BRK #0xF003 (BugCheck)
  11. fffff801`b448a768  stp   x9, x10, [x11]          ; KAPC.Flink = Head, Blink = last
  12. fffff801`b448a76c  str   x11, [x9, #8]           ; Head.Blink = KAPC
  13. fffff801`b4472270  str   x11, [x10]              ; last.Flink = KAPC
6.4 Установка UserApcPending флага
Код (Text):
  1. ; Для user APC: установить UserApcPendingAll бит 1
  2. fffff801`b448a7b8  ldrb  w8, [x10, #0xBA]        ; KTHREAD+0xBA (UserApcPendingAll)
  3. fffff801`b448a7c0  add   x9, x13, w12, sxtw #4   ; list head
  4. fffff801`b448a7c8  orr   w8, w8, #2              ; установить бит 1 (UserApcPending)
  5. fffff801`b448a7cc  strb  w8, [x10, #0xBA]        ; записать обратно
7. KiDeliverApc — Доставка APC
Адрес:
0xFFFFF801`B448A030
Вызывается при возврате из ядра в user-mode (KiSystemServiceExit / KiExceptionExit).
7.1 Подготовка
Код (Text):
  1. fffff801`b448a08c  ldr   x20, [xpr, #0x988]     ; KTHREAD текущего потока
  2. fffff801`b448a090  ldr   x8, [x20, #0x88]        ; KTHREAD+0x88 (TrapFrame)
  3. fffff801`b448a094  strb  wzr, [x20, #0xB9]       ; KernelApcPending = 0
  4. fffff801`b448a098  str   x2, [x20, #0x88]        ; новый trap frame
  5. fffff801`b448a09c  str   x8, [sp, #0x58]         ; старый trap frame → стек
7.2 Доставка Kernel APC (список +0x90)
Код (Text):
  1. ; Проверка kernel APC списка:
  2. fffff801`b448a0b4  add   x21, x20, #0x90         ; KTHREAD.ApcState
  3. fffff801`b448a0d0  ldr   x8, [x26]               ; ApcListHead[0].Flink
  4. fffff801`b448a0d4  cmp   x8, x26                 ; список пуст?
  5. fffff801`b448a0d8  beq   user_apc_check          ; пустой → user APC
  6. ; Захват ThreadLock:
  7. fffff801`b448a0e0  bl    nt!KfRaiseIrql           ; DISPATCH_LEVEL
  8. fffff801`b448a0ec  swpa  x24, x8, [x22]          ; захват ThreadLock (SWPA)
  9. ; Извлечь первый KAPC из списка:
  10. fffff801`b448a118  ldr   x19, [x20, #0x90]       ; первый KAPC
  11. fffff801`b448a130  ldr   x9, [x19, #0x20]        ; KernelRoutine
  12. fffff801`b448a134  ldr   x21, [x19, #0x10]       ; Flink (следующий)
  13. fffff801`b448a144  ldr   x8, [x19, #0x30]        ; NormalRoutine
  14. ; Unlink из списка:
  15. fffff801`b448a168  ldr   x10, [x19]              ; KAPC.Flink
  16. fffff801`b448a178  ldr   x9, [x19, #8]           ; KAPC.Blink
  17. fffff801`b448a188  str   x10, [x9]               ; Blink→Flink
  18. fffff801`b448a18c  str   x9, [x10, #8]           ; Flink→Blink
  19. ; Освободить блокировку:
  20. fffff801`b448a198  stlr  xzr, [x22]              ; ThreadLock = 0
  21. fffff801`b448a19c  bl    nt!KfLowerIrql           ; восстановить IRQL
  22. ; Вызвать KernelRoutine через PAC:
  23. fffff801`b448a1a8  strb  w24, [x20, #0xB8]       ; KernelApcInProgress = 1
  24. fffff801`b448a1dc  mov   x15, x21                ; KernelRoutine
  25. fffff801`b448a1e0  bl    nt!KscpCfgCheckUserCallTargetEs  ; PAC проверка
  26. fffff801`b448a1e4  blr   x15                     ; вызвать KernelRoutine(KAPC, ...)
7.3 Доставка User APC (список +0xA0)
Код (Text):
  1. ; Проверка user APC списка:
  2. fffff801`b448a388  add   x9, x20, #0xA0         ; KTHREAD+0xA0 (User APC list)
  3. fffff801`b448a38c  ldr   x8, [x9]               ; User ApcListHead.Flink
  4. fffff801`b448a394  cmp   x8, x9                 ; список пуст?
  5. fffff801`b448a398  beq   no_user_apcs           ; пустой → выход
  6. ; Поиск первого APC с NormalRoutine:
  7. fffff801`b448a414  sub   x25, x8, #0x10         ; KAPC = entry - 0x10
  8. fffff801`b448a41c  ldr   x9, [x25, #0x20]       ; KernelRoutine
  9. fffff801`b448a44c  ldr   x9, [x25, #0x30]       ; NormalRoutine
  10. fffff801`b448a45c  ldr   x9, [x25, #0x38]       ; NormalContext
  11. fffff801`b448a470  ldr   x9, [x25, #0x40]       ; SystemArgument1
  12. fffff801`b448a480  ldr   x9, [x25, #0x48]       ; SystemArgument2
7.4 KiInitializeUserApc — переход в user-mode
Код (Text):
  1. fffff801`b448a50c  ldp   x5, x4, [sp, #0x10]     ; SystemArgument1, SystemArgument2
  2. fffff801`b448a510  mov   w6, w21                 ; flags
  3. fffff801`b448a514  ldr   x3, [sp, #0x20]         ; NormalContext
  4. fffff801`b448a518  mov   x1, x19                 ; ExceptionFrame
  5. fffff801`b448a51c  ldr   x0, [sp, #0x48]         ; TrapFrame
  6. fffff801`b448a520  bl    nt!KiInitializeUserApc
Параметры:
ПараметрРегистрЗначение
TrapFramex0Адрес trap frame
ExceptionFramex1Адрес exception frame
NormalRoutinex2APC callback (user-mode)
NormalContextx3ApcContext
SystemArgument1x4Arg1
SystemArgument2x5Arg2
Flagsw6Флаги доставки
7.5 KiInitializeUserApc — модификация trap frame
Адрес: 0xFFFFF801`B44B70C0
Код (Text):
  1. ; Определение архитектуры:
  2. fffff801`b44b7144  bl    nt!PsGetProcessMachine    ; Machine type
  3. fffff801`b44b7148  mov   w8, #0xAA64              ; ARM64
  4. fffff801`b44b714c  cmp   w8, w0, uxth #0          ; native ARM64?
  5. ; Подготовка CONTEXT:
  6. fffff801`b44b7184  ldr   x8, [x19, #0x98]        ; TrapFrame->Sp (user stack)
  7. fffff801`b44b7188  str   x8, [x26, #0x38]        ; сохранить оригинальный SP
  8. fffff801`b44b718c  sub   x21, x8, #0x10          ; выделить место на стеке
  9. ; Заполнение CONTEXT из trap frame:
  10. fffff801`b44b7278  ldr   x2, [x26, #0x18]        ; CONTEXT size
  11. fffff801`b44b7280  ldr   x1, [x26, #0x40]        ; source context
  12. fffff801`b44b7284  mov   x0, x19                 ; TrapFrame
  13. fffff801`b44b7288  bl    nt!KeContextFromKframes   ; CONTEXT ← trap frame
  14. ; Выбор dispatcher address по архитектуре:
  15. fffff801`b44b72f0  ldrh  w9, [x27, #0x7AC]       ; EPROCESS.Machine
  16. fffff801`b44b72f4  mov   w8, #0x8664             ; AMD64
  17. fffff801`b44b72f8  cmp   w9, w8
  18. fffff801`b44b7304  ldr   x9, [x8, #0x28]         ; ARM64 dispatcher
  19. fffff801`b44b7308  ldr   x8, [x8, #0x268]        ; x64/ARM64EC dispatcher
  20. fffff801`b44b730c  cselne x8, x9, x8             ; выбрать нужный
  21. fffff801`b44b7310  str   x8, [x19, #0x148]       ; KTHREAD+0x148 = dispatcher
Результат: Trap frame модифицирован:
  • PC → ntdll!KiUserApcDispatcher
  • SP → уменьшен, параметры APC на стеке
  • Восстановление через ntdll!NtContinue
8. WinAPI, использующие APC внутри
8.1 Функции постановки APC
ФункцияNT вызовKernel tagОписание
QueueUserAPCNtQueueApcThread'PasP'Ставит user APC в очередь целевого потока
QueueUserAPC2NtQueueApcThreadEx'PasP'Расширенная версия (reserve handle, флаги)
8.2 Функции, триггерящие доставку (alertable wait)
Эти функции не ставят APC — они переводят поток в alertable wait, что позволяет доставить уже поставленные APC.
ФункцияNT вызовМеханизм доставки
SleepEx(alertable=TRUE)NtDelayExecutionПри пробуждении → KiDeliverApc
WaitForSingleObjectExNtWaitForSingleObjectAlertable wait → KiDeliverApc
WaitForMultipleObjectsExNtWaitForMultipleObjectsAlertable wait → KiDeliverApc
SignalObjectAndWaitNtSignalAndWaitForSingleObjectSignal + alertable wait → KiDeliverApc
MsgWaitForMultipleObjectsExNtUserMsgWaitForMultipleObjectsExMsg + alertable wait → KiDeliverApc
WaitForThreadpoolTimerCallbacksTP timer → APC delivery path
8.3 Функции I/O с APC completion
ФункцияNT вызовМеханизм
ReadFileExNtReadFile (ApcRoutine ≠ NULL)I/O completion → kernel APC → user callback
WriteFileExNtWriteFile (ApcRoutine ≠ NULL)I/O completion → kernel APC → user callback
DeviceIoControl (overlapped)NtDeviceIoControlFileAPC completion при overlapped I/O
Механизм I/O APC: ReadFileEx/WriteFileEx передают ApcRoutine в NtReadFile/NtWriteFile. Когда I/O завершается, I/O manager ставит kernel APC (IopQueueThreadApc), который в свою очередь вызывает пользовательский completion routine.
8.4 Функции, НЕ использующие APC
ФункцияПочему нет
Sleep (без Ex)Не alertable — APC не доставляются
WaitForSingleObject (без Ex)Не alertable
ReadFile (без Ex)Нет ApcRoutine — completion через event или IOCP
CreateThreadСоздаёт новый поток, не использует APC
9. Ранжирование методов стелс-выполнения
От самого незаметного к наиболее обнаружимому:
#МетодОбнаружениеОсобенности
1APC InjectionОчень низкоеНет нового потока, стека, TEB. Тег 'PasP'. Не проверяет Protected Process.
2Threaded DPCНизкоеРаботает на DISPATCH_LEVEL. Не создаёт поток. Только kernel-mode.
3Timer + DPCНизкоеОтложенное выполнение. KeSetTimerEx + DPC callback.
4Work Items (IoQueueWorkItem)СреднееВыполняется в system worker thread. Видно в очереди.
5Thread Context HijackingСреднееПерехват已有 потока. Модификация контекста. Обнаруживается ETW.
6CreateRemoteThreadВысокоеНовый ETHREAD (~0x770 байт), стек, TEB. Protected Process check. Тег 'Thre'.
10. Структуры данных
10.1 KAPC (0x58 = 88 байт)[/SIZE]
СмещениеРазмерПолеОписание
+0x001Type0x12 (ApcObject)
+0x011AllFlagsБит 0: CallbackDataContext
+0x021Size0x58 (88 байт)
+0x088ThreadУказатель на KTHREAD
+0x1016ApcListEntryLIST_ENTRY (связный список)
+0x208KernelRoutineФункция ядра (очистка)
+0x288RundownRoutineФункция при rundown потока
+0x308NormalRoutineПользовательский callback
+0x388NormalContextКонтекст APC
+0x408SystemArgument1Аргумент 1
+0x488SystemArgument2Аргумент 2
+0x501ApcStateIndex0=Original, 1/2=Attached
+0x511ApcMode0=KernelMode, 1=UserMode
+0x521InsertedФлаг: APC в списке (0/1)
10.2 APC-поля в KTHREAD
СмещениеПолеОписание
+0x040ThreadLockСпин-блокировка (SWPA atomic)
+0x088TrapFrameУказатель на текущий trap frame
+0x090ApcStateKAPC_STATE (текущее состояние)
+0x0B9KernelApcPendingФлаг: kernel APC ожидает
+0x0BAUserApcPendingAllБит 0: SpecialUserApcPending, Бит 1: UserApcPending
+0x148ApcStatePointerМассив указателей ApcState/SavedApcState
+0x26AApcStateIndexТекущий APC state (0=Original)
+0x278SavedApcStateKAPC_STATE при KeStackAttach
11. Диаграмма постановки APC
Код (Text):
  1.  
  2.                     ┌──────────────────────┐
  3.                     │  User-Mode            │
  4.                     │                      │
  5.                     │  QueueUserAPC()      │  ← постановка
  6.                     │  QueueUserAPC2()     │  ← постановка (расширенная)
  7.                     │                      │
  8.                     │  SleepEx(T)          │  ← alertable wait (доставка)
  9.                     │  WaitForSingleObjEx  │  ← alertable wait (доставка)
  10.                     │  ReadFileEx          │  ← I/O completion (постановка + доставка)
  11.                     │  WriteFileEx         │  ← I/O completion (постановка + доставка)
  12.                     └──────────┬───────────┘
  13.                                │ syscall
  14.                     ══════════╪═════════════
  15.                     Kernel boundary
  16.                     ══════════╪═════════════
  17.                                │
  18.                     ┌──────────▼───────────┐
  19.                     │  nt!NtQueueApcThread  │  7 инструкций, remap ABI
  20.                     └──────────┬───────────┘
  21.                                │
  22.                     ┌──────────▼───────────┐
  23.                     │ nt!NtQueueApcThreadEx2│  Основная логика
  24.                     │                       │
  25.                     │ 1. Validate flags     │  tst w24, #0xFFFEFFFE
  26.                     │ 2. ObRefByHandle      │  → ETHREAD (THREAD_SET_CONTEXT)
  27.                     │ 3. Check terminated   │  ETHREAD+0x6C бит 10
  28.                     │ 4. Check WoW64/EC     │  KPROCESS+0x300
  29.                     │ 5. CAS lock           │  casal (reserve path)
  30.                     │ 6. ExAllocatePool2    │  KAPC (0x58, 'PasP')
  31.                     │ 7. KeInitializeApc    │  заполнение KAPC
  32.                     │ 8. KeInsertQueueApc   │  ──────────────────┐
  33.                     └──────────┬───────────┘                     │
  34.                                │                                  ▼
  35.                     ┌──────────▼───────────┐     ┌──────────────────────────┐
  36.                     │   nt!KiInsertQueueApc │     │  nt!KeInsertQueueApc     │
  37.                     │                       │     │                          │
  38.                     │ Выбор ApcState:       │     │ 1. ETW trace             │
  39.                     │   +0x90 (Original)    │     │ 2. KfRaiseIrql(2)        │
  40.                     │   +0x278 (Attached)   │     │ 3. SWPA ThreadLock       │
  41.                     │                       │     │ 4. KAPC.Inserted = TRUE  │
  42.                     │ Выбор списка:         │     │ 5. KiInsertQueueApc      │
  43.                     │   +0x00 (Kernel)      │     │ 6. SignalThreadForApc    │
  44.                     │   +0x10 (User)        │     │ 7. stlr ThreadLock = 0   │
  45.                     │                       │     │ 8. KiExitDispatcher      │
  46.                     │ Вставка: FIFO (tail)  │     └──────────────────────────┘
  47.                     │                       │
  48.                     │ Для User: установить  │
  49.                     │ UserApcPending (0xBA) │
  50.                     └──────────────────────┘
  51.                     ┌──────────────────────┐
  52.                     │  Доставка APC         │
  53.                     │                      │
  54.                     │  KiDeliverApc         │
  55.                     │  ├── Kernel APC list  │  +0x90 (ApcListHead[0])
  56.                     │  │   → KernelRoutine  │  через PAC проверку
  57.                     │  │                    │
  58.                     │  └── User APC list    │  +0xA0 (ApcListHead[1])
  59.                     │      → KiInitializeUserApc
  60.                     │         → Модификация trap frame
  61.                     │         → PC = ntdll!KiUserApcDispatcher
  62.                     │         → SP = уменьшен (параметры)
  63.                     │                      │
  64.                     │  ntdll!KiUserApcDispatcher
  65.                     │  ├── ApcRoutine(Context, Arg1, Arg2)
  66.                     │  └── NtContinue() → восстановление контекста
  67.                     └──────────────────────┘
  68.  
12. Ключевые адреса (Build 26100 ARM64)
ФункцияАдрес
nt!NtQueueApcThread0xFFFFF801`B4923410
nt!NtQueueApcThreadEx0xFFFFF801`B4B00AA0
nt!NtQueueApcThreadEx20xFFFFF801`B4B00AD0
nt!KeInitializeApc0xFFFFF801`B44720F0
nt!KeInsertQueueApc0xFFFFF801`B4472150
nt!KiInsertQueueApc0xFFFFF801`B448A6F0
nt!KiDeliverApc0xFFFFF801`B448A030
nt!KiInitializeUserApc0xFFFFF801`B44B70C0
nt!KiExitDispatcher0xFFFFF801`B449A918
nt!PsWrapApcWow64Thread0xFFFFF801`B473A970
nt!KfRaiseIrql0xFFFFF801`B4406730
nt!KfLowerIrql0xFFFFF801`B44065F0
nt!ObReferenceObjectByHandle0xFFFFF801`B4AA6B70
nt!ExAllocatePool20xFFFFF801`B4CAC000
nt!KscpCfgCheckUserCallTargetEs0xFFFFF801`B4CF4040

0 27
galenkane

galenkane
Active Member

Регистрация:
13 янв 2017
Публикаций:
4