QueueUserAPC — Исследование внутренних механизмов ядра Windows (ARM64)

Дата публикации 3 июн 2026 в 00:50
QueueUserAPC — Исследование внутренних механизмов ядра Windows (ARM64)
Среда отладки:
Windows 11 Build 26100, ARM64 (Parallels Desktop VM)
Метод: Live Kernel Debugging через WinDbg → socat serial bridge → MCP
Дата: Июнь 2026
1. Цепочка вызовов (Call Chain)
Код (Text):
  1.  
  2. Пользовательский режим:
  3.   kernel32!QueueUserAPC(pfnAPC, hThread, dwData)
  4.     ↓
  5.   ntdll!NtQueueApcThread(ThreadHandle, ApcRoutine, ApcContext, Arg1, Arg2)
  6.     ↓ (syscall)
  7. Режим ядра:
  8.   nt!NtQueueApcThread              → 0xFFFFF801`B4923410
  9.     ↓ (thin wrapper)
  10.   nt!NtQueueApcThreadEx2            → 0xFFFFF801`B4B00AD0
  11.     ├── nt!ObReferenceObjectByHandle → получает ETHREAD по хэндлу
  12.     ├── nt!ExAllocatePool2            → выделяет KAPC (0x58 байт, тег 'PasP')
  13.     ├── nt!KeInitializeApc           → заполняет структуру KAPC
  14.     └── nt!KeInsertQueueApc          → вставляет APC в очередь потока
  15.          ├── nt!KiInsertQueueApc       → манипуляция со списком (LIST_ENTRY)
  16.          ├── nt!KiSignalThreadForApc   → пробуждение целевого потока
  17.          └── nt!KiExitDispatcher       → диспетчеризация потоков
  18. Доставка APC (при возврате из ядра):
  19.   nt!KiDeliverApc                  → 0xFFFFF801`B448A030
  20.     ├── Kernel APC: вызывает KernelRoutine напрямую
  21.     └── User APC: вызывает nt!KiInitializeUserApc
  22.          └── Модифицирует trap frame → переход в ntdll!KiUserApcDispatcher
  23.               └── Вызывает пользовательский回调 (ApcRoutine)
  24.  
2. nt!NtQueueApcThread — Тонкая обёртка
Код (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  ; прямой переход
Анализ: Функция состоит из 7 инструкций. Перемаппит параметры ABI и прыгает в расширенную версию. Никаких собственных проверок — вся логика в NtQueueApcThreadEx2.
Маппинг параметров NtQueueApcThread → NtQueueApcThreadEx2:
NtQueueApcThreadРегистрNtQueueApcThreadEx2
ThreadHandle (x0)x0ThreadHandle
x1 = 0UserApcReserveHandle = NULL
w2 = 0Flags = 0
ApcRoutine (x1)x3ApcRoutine
ApcContext (x2)x4SystemArgument1
Arg1 (x3)x5SystemArgument2
Arg2 (x4)x6SystemArgument3
3. nt!NtQueueApcThreadEx2 — Основная логика
Адрес:
0xFFFFF801`B4B00AD0 (самая длинная функция в цепочке)
3.1 Валидация флагов
Код (Text):
  1. fffff801`b4b00b10  tst   w24, #0xFFFEFFFE     ; проверить лишние биты в Flags
  2. fffff801`b4b00b14  bne   invalid_parameter      ; → STATUS_INVALID_PARAMETER
  3. fffff801`b4b00b18  tbnz  w24, #0, check_reserve  ; бит 0 = UserApcReserveHandle
  4.                                                      ; (NtQueueApcThread передаёт 0)
3.2 Получение ETHREAD по хэндлу
Код (Text):
  1. fffff801`b4b00b2c  ldr   x2, [x21]             ; CurrentThread для access check
  2. fffff801`b4b00b30  mov   x5, #0                ; HandleInformation = NULL
  3. fffff801`b4b00b34  add   x4, sp, #0x10         ; &Object (выходной параметр)
  4. fffff801`b4b00b38  mov   w3, w20               ; ProcessorMode (User/Kernel)
  5. fffff801`b4b00b3c  mov   w1, #0x10             ; DesiredAccess = THREAD_SET_CONTEXT
  6. fffff801`b4b00b40  bl    nt!ObReferenceObjectByHandle
DesiredAccess = 0x10 = THREAD_SET_CONTEXT — для APC достаточно этого права доступа к потоку.
3.3 Проверка terminated-флага
Код (Text):
  1. fffff801`b4b00b4c  ldr   w8, [x22, #0x6C]      ; ETHREAD+0x6C (CrossThreadFlags)
  2. fffff801`b4b00b50  tbz   w8, #0xA, continue      ; бит 10 = PS_CROSS_THREAD_FLAGS_TERMINATED
  3.                                                      ; если поток уже завершён — ошибка
  4. fffff801`b4b00b54  ldr   w19, =0xC0000008       ; STATUS_INVALID_HANDLE
  5. fffff801`b4b00b58  mov   x0, x22                ; ETHREAD
  6. fffff801`b4b00b5c  bl    nt!ObDereferenceObject  ; освободить ссылку
  7. fffff801`b4b00b60  mov   w0, w19                ; вернуть ошибку
Ключевой момент: Проверка ETHREAD+0x6C бит 10 (PS_CROSS_THREAD_FLAGS_TERMINATED). Если целевой поток уже завершён — APC не ставится в очередь, возвращается STATUS_INVALID_HANDLE.
3.4 Проверка WoW64 / ARM64EC
Код (Text):
  1. fffff801`b4b00b84  ldr   x8, [xpr, #0x988]     ; текущий EPROCESS (CurrentThread→Process)
  2. fffff801`b4b00b88  ldr   x9, [x8, #0xB0]        ; EPROCESS+0xB0 → KPROCESS (Pcb)
  3. fffff801`b4b00b8c  ldr   x8, [x9, #0x300]       ; KPROCESS+0x300 (WoW64Process)
  4. fffff801`b4b00b90  cbnz  x8, check_arch          ; если WoW64 — дополнительная проверка
KPROCESS+0x300 = WoW64Process. Если процесс является WoW64 (x86 на ARM64), выполняется проверка архитектуры целевого потока:
Код (Text):
  1. fffff801`b4b00c84  ldrh  w9, [x9, #0x7AC]       ; KPROCESS+0x7AC (Machine)
  2. fffff801`b4b00c88  mov   w8, #0x1C4              ; IMAGE_FILE_MACHINE_ARM64EC
  3. fffff801`b4b00c8c  cmp   w9, #0x14C              ; IMAGE_FILE_MACHINE_I386
  4. fffff801`b4b00c90  ccmpne w9, w8, #4             ; или ARM64EC?
  5. fffff801`b4b00c94  bne   continue_path           ; если нет — пропустить проверку
3.5 CAS-блокировка потока (UserApcReserve path)
Код (Text):
  1. ; При использовании UserApcReserveHandle (NtQueueApcThreadEx):
  2. fffff801`b4b00bc0  ldr   x0, [sp, #0x10]         ; Object pointer
  3. fffff801`b4b00bc4  mov   w9, #1                  ; desired = 1
  4. fffff801`b4b00bc8  mov   w8, #0                  ; expected = 0
  5. fffff801`b4b00bcc  casal w8, w9, [x0]            ; Compare-And-Swap (acquire)
  6. fffff801`b4b00bd0  cbz   w8, acquired            ; если было 0 — захватили
  7. fffff801`b4b00bd4  bl    nt!ObDereferenceObject   ; не удалось — освободить
  8. fffff801`b4b00bd8  ldr   w19, =0xC00000F0        ; STATUS_INVALID_PARAMETER_1
CAS-механизм: Атомарная операция casal (Compare-And-Swap, Acquire semantics) гарантирует, что только один поток может одновременно ставить APC через reserve handle.
3.6 Выделение KAPC
Код (Text):
  1. ; Path при UserApcReserveHandle == NULL (QueueUserAPC):
  2. fffff801`b4b00be0  ldr   w2, =0x50617370         ; Pool tag = 'PasP'
  3. fffff801`b4b00be4  mov   x1, #0x58               ; Size = 0x58 = sizeof(KAPC)
  4. fffff801`b4b00be8  mov   x0, #0x41               ; PoolType = NonPagedPoolNx
  5. fffff801`b4b00bec  bl    nt!ExAllocatePool2       ; выделить из пула
Pool Tag: 'PasP' (0x50617370) — тег для пользовательских APC. Легко отслеживать через PoolMon.
Размер: 0x58 (88 байт) = sizeof(KAPC)
Тип пула: NonPagedPoolNx (0x41) — невыграждаемая память без исполнения
3.7 Инициализация KAPC — вызов KeInitializeApc
Код (Text):
  1. fffff801`b4b00c34  ldr   x7, [sp, #0x18]        ; SystemArgument2 (ApcContext)
  2. fffff801`b4b00c38  mov   w6, w23                 ; ApcMode = 1 (UserMode)
  3. fffff801`b4b00c3c  mov   x5, x26                 ; NormalRoutine = ApcRoutine
  4. fffff801`b4b00c40  mov   x4, x21                 ; RundownRoutine
  5. fffff801`b4b00c44  mov   w2, #0                  ; ApcStateIndex = OriginalApcEnvironment
  6. fffff801`b4b00c48  mov   x1, x22                 ; Thread = ETHREAD
  7. fffff801`b4b00c4c  mov   x0, x20                 ; KAPC object
  8. fffff801`b4b00c50  bl    nt!KeInitializeApc
3.8 Вставка APC в очередь — вызов KeInsertQueueApc
Код (Text):
  1. fffff801`b4b00c58  ldr   x1, [sp, #0x20]        ; SystemArgument1
  2. fffff801`b4b00c5c  mov   w3, #0                  ; Increment = 0
  3. fffff801`b4b00c60  mov   x2, x27                 ; SystemArgument2
  4. fffff801`b4b00c64  mov   x0, x20                 ; KAPC
  5. fffff801`b4b00c68  bl    nt!KeInsertQueueApc
Если KeInsertQueueApc возвращает FALSE (поток уже уничтожается):
Код (Text):
  1. fffff801`b4b00cf0  mov   x0, x20                ; KAPC
  2. fffff801`b4b00cf4  mov   x15, x21               ; RundownRoutine
  3. fffff801`b4b00cf8  bl    nt!KscpCfgCheckUserCallTargetEs  ; PAC проверка
  4. fffff801`b4b00cfc  blr   x15                     ; вызвать RundownRoutine для очистки
  5. fffff801`b4b00d00  mov   w19, #0xC0000001       ; STATUS_UNSUCCESSFUL
4. nt!KeInitializeApc — Заполнение структуры KAPC
Адрес:
0xFFFFF801`B44720F0
Код (Text):
  1. nt!KeInitializeApc:
  2. fffff801`b44720f0  mov   w8, #0x12             ; Type = 0x12 (ApcObject)
  3. fffff801`b44720f4  strb  w8, [x0]              ; KAPC.Type = 0x12
  4. fffff801`b44720f8  mov   w8, #0x58             ; Size = 88 байт
  5. fffff801`b44720fc  strb  w8, [x0, #2]          ; KAPC.Size = 0x58
  6. fffff801`b4472100  cmp   w2, #2                ; ApcStateIndex == AttachedApcEnvironment?
  7. fffff801`b4472104  beq   attached_env          ; специальный путь
  8. fffff801`b4472108  mov   w8, w2                ;
  9. fffff801`b447210c  strb  w8, [x0, #0x50]       ; KAPC.ApcStateIndex
  10. fffff801`b4472110  cbz   x5, no_normal_routine ; NormalRoutine == NULL?
  11. fffff801`b4472114  str   x1, [x0, #8]          ; KAPC.Thread = ETHREAD
  12. fffff801`b4472118  csel  w8, wzr, w6, eq       ; если NormalRoutine == NULL: ApcMode = 0
  13. fffff801`b447211c  stp   x3, x4, [x0, #0x20]  ; KAPC.KernelRoutine, KAPC.RundownRoutine
  14. fffff801`b4472120  str   x5, [x0, #0x30]       ; KAPC.NormalRoutine
  15. fffff801`b4472124  strb  w8, [x0, #0x51]       ; KAPC.ApcMode (1 = UserMode)
  16. fffff801`b4472128  csel  x8, xzr, x7, eq       ; если NormalRoutine == NULL: Context = 0
  17. fffff801`b447212c  str   x8, [x0, #0x38]       ; KAPC.NormalContext
  18. fffff801`b4472130  strb  wzr, [x0, #0x52]      ; KAPC.Inserted = FALSE
  19. fffff801`b4472134  strb  wzr, [x0, #1]         ; KAPC.CallbackDataContext = 0
  20. fffff801`b4472138  ret
Примечание: Если NormalRoutine == NULL, APC помечается как kernel-only (ApcMode = 0), даже если передан ApcMode = 1. Это prevents пользовательские APC без callback.
5. nt!KeInsertQueueApc — Вставка APC в очередь
Адрес:
0xFFFFF801`B4472150
Это самая сложная функция. Ключевые операции:
5.1 ETW трассировка
Код (Text):
  1. fffff801`b4472170  adrp  x8, ...               ; ETW GUID
  2. fffff801`b4472174  ldr   x10, [x8, #0xE10]    ; EtwTiLogInsertQueueUserApc провайдер
  3. fffff801`b447218c  cbz   x10, skip_etw         ; если ETW не активен — пропустить
  4. fffff801`b4472190  ldr   x8, [x10, #0x20]      ;
  5. fffff801`b4472194  mov   x2, #0x3000            ; keyword mask
  6. fffff801`b44721a0  bl    nt!EtwpLevelKeywordEnabled
5.2 Подъём IRQL и захват спин-блокировки потока
Код (Text):
  1. fffff801`b447221c  mov   w0, #2                ; DISPATCH_LEVEL
  2. fffff801`b4472220  bl    nt!KfRaiseIrql          ; поднять IRQL
  3. fffff801`b4472244  add   x27, x19, #0x40        ; KTHREAD+0x40 (ThreadLock)
  4. fffff801`b4472248  swpa  x21, x8, [x27]         ; Atomic swap: захват блокировки
  5.                                                   ; x21 = 1, x8 = предыдущее значение
  6. fffff801`b4472250  cbz   x8, lock_acquired       ; если было 0 — захватили
  7. ; Spin-wait loop (блокировка занята):
  8. fffff801`b4472258  ldr   w8, [x26, #0x28C]      ; Prcb spin count
  9. fffff801`b4472268  dmb   ishst                   ; Data Memory Barrier
  10. fffff801`b447226c  yield                          ; CPU hint (сэкономить энергию)
  11. fffff801`b4472270  ldr   x8, [x27]              ; перечитать блокировку
  12. fffff801`b4472274  cbnz  x8, spin_wait           ; если всё ещё занята — крутиться
Механизм блокировки:
KTHREAD+0x40 = ThreadLock
(спин-блокировка).
Используется инструкция SWPA (Store Word Pair Atomic) — атомарный обмен с acquire-семантикой.
Ожидание (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
  3. fffff801`b44722a0  mov   x0, x20                ; KAPC
  4. fffff801`b44722a4  stp   x8, x25, [x20, #0x40]  ; KAPC.SystemArgument1/2
  5. fffff801`b44722a8  bl    nt!KiInsertQueueApc     ; вставить в связный список
Перед вызовом: Устанавливается KAPC.Inserted = 1 и записываются SystemArgument1/2.
5.4 Пробуждение целевого потока (KiSignalThreadForApc)
После вставки APC в список, KeInsertQueueApc проверяет, нужно ли разбудить целевой поток:
Код (Text):
  1. ; Проверка: является ли целевой поток текущим?
  2. fffff801`b44722b0  ldrsb w8, [x20, #0x50]       ; KAPC.ApcStateIndex
  3. fffff801`b44722b8  ldrb  w9, [x19, #0x26A]      ; KTHREAD.ApcStateIndex
  4. fffff801`b44722bc  cmp   w8, w9                  ; APC state == Thread state?
  5. fffff801`b44722c4  ldr   x8, [x23, #8]          ; CurrentThread
  6. fffff801`b44722c8  cmp   x19, x8                ; target == current?
  7. fffff801`b44722d0  cbnz  w10, signal_thread      ; если ApcMode != 0 → сигналить
Для целевого потока на другом процессоре:
Код (Text):
  1. fffff801`b44722ec  strb  w21, [x19, #0xB9]      ; KTHREAD+0xB9 = KernelApcPending = 1
  2. fffff801`b44722f0  cbz   w24, no_signal          ; если IRQL не поднят — пропустить IPI
Если поток ждёт (Waiting state):
Код (Text):
  1. fffff801`b4472450  add   x8, x19, #0xB9        ; &KTHREAD.KernelApcPending
  2. fffff801`b4472454  stlrb  w21, [x8]              ; store-release: KernelApcPending = 1
  3. ; Проверка состояния потока:
  4. fffff801`b447245c  ldrsb w8, [x19, #0x17C]      ; KTHREAD+0x17C (State)
  5. fffff801`b4472464  cmp   w8, #2                  ; == Waiting?
  6. fffff801`b4472468  beq   send_ipi                ; отправить IPI
  7. fffff801`b447246c  cmp   w8, #5                  ; == DeferredReady?
  8. fffff801`b4472484  ldr   x8, [x20, #0x30]       ; KAPC.NormalRoutine
  9. fffff801`b4472488  cbnz  x8, check_ready         ; если есть NormalRoutine
  10. fffff801`b4b00c68  bl    nt!KiSignalThread       ; разбудить поток
Для потока на другом процессоре отправляется программное прерывание:
Код (Text):
  1. fffff801`b4472530  ldr   w8, [x19, #0x238]      ; KTHREAD+0x238 (WaitIrql)
  2. fffff801`b4472548  bl    nt!KiSendSoftwareInterrupt  ; отправить IPI
  3. ; или:
  4. fffff801`b4472550  mov   w0, #1                  ; APC_LEVEL request
  5. fffff801`b4472554  bl    nt!HalRequestSoftwareInterrupt
5.5 Освобождение блокировки и диспетчеризация
Код (Text):
  1. fffff801`b4472334  stlr  xzr, [x8]              ; store-release: ThreadLock = 0
  2. fffff801`b4472344  bl    nt!KiExitDispatcher        ; диспетчеризация
  3. fffff801`b447234c  mov   w0, w20                    ; вернуть TRUE/FALSE
6. nt!KiInsertQueueApc — Манипуляция со связным списком
Адрес:
0xFFFFF801`B448A6F0
Это низкоуровневая функция, работающая напрямую с LIST_ENTRY в ETHREAD.
6.1 Выбор APC-списка
Код (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       ; если ApcStateIndex != 0 → special
  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`b448a708  ldrsb w8, [x9]               ; ApcStateIndex
  10. fffff801`b448a70c  add   x13, x10, #0x90         ; x13 = KTHREAD.ApcState base
  11. ; Если ApcMode != 0 (User APC):
  12. fffff801`b448a790  add   x13, x10, #0x278        ; x13 = KTHREAD.SavedApcState base
  13. fffff801`b448a794  b     continue_path
6.2 Вычисление адреса списка по ApcMode
Код (Text):
  1. ; x13 = APC state base (ApcState или SavedApcState)
  2. ; w12 = KAPC.ApcMode (0 = Kernel, 1 = User)
  3. fffff801`b448a724  add   x9, x13, w12, sxtw #4   ; x9 = base + (ApcMode * 16)
  4.                                                   ; Kernel: base + 0x00 (ApcListHead[0])
  5.                                                   ; User:   base + 0x10 (ApcListHead[1])
Формула:
Код (Text):
  1. ListHead = KAPC_STATE_BASE + (ApcMode * sizeof(LIST_ENTRY))
ApcModeOffset от ApcStateOffset от KTHREADТип APC
0 (Kernel)+0x00+0x090Kernel APC
1 (User)+0x10+0x0A0User APC
6.3 Вставка в связный список
Код (Text):
  1. ; Поиск конца списка (FIFO — вставка в tail):
  2. fffff801`b448a744  add   x8, x13, w12, sxtw #4   ; x8 = &ApcListHead[ApcMode]
  3. fffff801`b448a748  ldr   x10, [x8, #8]           ; x10 = ListHead.Blink (последний элемент)
  4. fffff801`b448a74c  cmp   x10, x8                 ; список пуст? (Blink == Head?)
  5. fffff801`b448a750  bne   list_not_empty
  6. ; Список пуст — первая вставка:
  7. fffff801`b448a798  stp   x9, x8, [x11]           ; KAPC.ApcListEntry.Flink = Head
  8.                                                   ; KAPC.ApcListEntry.Blink = Head
  9. fffff801`b448a79c  str   x11, [x8]               ; Head.Flink = KAPC
  10. fffff801`b448a7a0  str   x11, [x9, #8]           ; Head.Blink = KAPC
  11. fffff801`b448a7a4  ret
  12. ; Список не пуст — вставка в конец:
  13. fffff801`b448a754  ldr   x9, [x10]               ; x9 = last_entry.Flink (== Head)
  14. fffff801`b448a758  add   x11, x0, #0x10          ; x11 = &KAPC.ApcListEntry
  15. fffff801`b448a75c  ldr   x8, [x9, #8]            ; проверка целостности: Blink
  16. fffff801`b448a760  cmp   x8, x10
  17. fffff801`b448a764  bne   list_corrupt             ; BRK #0xF003 (bugcheck)
  18. fffff801`b448a768  stp   x9, x10, [x11]          ; KAPC.Flink = Head, KAPC.Blink = last
  19. fffff801`b448a76c  str   x11, [x9, #8]           ; Head.Blink = KAPC
  20. fffff801`b448a770  str   x11, [x10]              ; last.Flink = KAPC
  21. fffff801`b448a774  ret
Порядок: FIFO (First In, First Out) — новые APC добавляются в конец списка.
7. nt!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
Код (Text):
  1. fffff801`b448a0b4  add   x21, x20, #0x90         ; x21 = KTHREAD.ApcState
  2. fffff801`b4b00b0  add   x26, x20, #0x90         ; KTHREAD.ApcState (kernel list base)
  3. fffff801`b448a0d0  ldr   x8, [x26]               ; ApcListHead[0].Flink (kernel)
  4. fffff801`b448a0d4  cmp   x8, x26                 ; список пуст?
  5. fffff801`b448a0d8  beq   user_apc_check          ; пустой → перейти к user APC
  6. ; Поднять IRQL и захватить ThreadLock:
  7. fffff801`b448a0e0  bl    nt!KfRaiseIrql           ; DISPATCH_LEVEL
  8. fffff801`b448a0e4  add   x22, x20, #0x40         ; ThreadLock
  9. fffff801`b448a0ec  swpa  x24, x8, [x22]          ; захват ThreadLock
  10. ; Извлечь KAPC из списка:
  11. fffff801`b448a118  ldr   x19, [x20, #0x90]       ; первый KAPC (kernel list)
  12. fffff801`b448a130  ldr   x9, [x19, #0x20]        ; KAPC.KernelRoutine
  13. fffff801`b448a134  ldr   x21, [x19, #0x10]       ; KAPC.ApcListEntry.Flink
  14. fffff801`b448a138  str   x9, [sp, #0x30]         ; сохранить KernelRoutine
  15. fffff801`b448a144  ldr   x8, [x19, #0x30]        ; KAPC.NormalRoutine
  16. fffff801`b448a14c  ldr   x8, [x19, #0x38]        ; KAPC.NormalContext
  17. ; Проверка: NormalRoutine == NULL (kernel-only APC)?
  18. fffff801`b448a154  cbz   x9, normal_routine_path  ; да → специальная обработка
  19. ; Удалить из списка (unlink):
  20. fffff801`b448a168  ldr   x10, [x19]              ; KAPC.ApcListEntry.Flink
  21. fffff801`b448a178  ldr   x9, [x19, #8]           ; KAPC.ApcListEntry.Blink
  22. fffff801`b448a188  str   x10, [x9]               ; Blink.Flink = Flink
  23. fffff801`b448a18c  str   x9, [x10, #8]           ; Flink.Blink = Blink
  24. ; Освободить ThreadLock и понизить IRQL:
  25. fffff801`b448a198  stlr  xzr, [x22]              ; ThreadLock = 0
  26. fffff801`b448a19c  bl    nt!KfLowerIrql           ; вернуться к предыдущему IRQL
  27. ; Вызвать KernelRoutine через PAC:
  28. fffff801`b448a1dc  mov   x15, x21                ; KernelRoutine
  29. fffff801`b448a1e0  bl    nt!KscpCfgCheckUserCallTargetEs  ; PAC проверка
  30. fffff801`b448a1e4  blr   x15                     ; вызвать KernelRoutine(KAPC, ...)
7.3 Доставка User APC
Код (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`b448a390  add   x26, x20, #0xA0        ; save list head
  5. fffff801`b448a394  cmp   x8, x9                 ; список пуст?
  6. fffff801`b448a398  beq   no_user_apcs           ; пустой → выход
  7. ; Поиск первого user APC с NormalRoutine:
  8. fffff801`b448a404  ldr   x8, [x26]              ; traverse list
  9. fffff801`b448a40c  cmp   x8, x26                ; end of list?
  10. fffff801`b448a410  beq   user_apc_done
  11. ; Извлечь KAPC и его поля:
  12. fffff801`b448a414  sub   x25, x8, #0x10         ; KAPC = entry - 0x10
  13. fffff801`b448a41c  ldr   x9, [x25, #0x20]       ; KAPC.KernelRoutine
  14. fffff801`b448a44c  ldr   x9, [x25, #0x30]       ; KAPC.NormalRoutine
  15. fffff801`b448a45c  ldr   x9, [x25, #0x38]       ; KAPC.NormalContext
  16. fffff801`b448a470  ldr   x9, [x25, #0x40]       ; KAPC.SystemArgument1
  17. fffff801`b448a480  ldr   x9, [x25, #0x48]       ; KAPC.SystemArgument2
7.4 KiInitializeUserApc — подготовка user-mode перехода
Код (Text):
  1. ; Подготовка вызова KiInitializeUserApc:
  2. fffff801`b448a50c  ldp   x5, x4, [sp, #0x10]     ; SystemArgument1, SystemArgument2
  3. fffff801`b448a510  mov   w6, w21                 ; flags
  4. fffff801`b448a514  ldr   x3, [sp, #0x20]         ; NormalContext
  5. fffff801`b448a518  mov   x1, x19                 ; ExceptionFrame / TrapFrame
  6. fffff801`b448a51c  ldr   x0, [sp, #0x48]         ; TrapFrame
  7. fffff801`b448a520  bl    nt!KiInitializeUserApc
Параметры KiInitializeUserApc:
ПараметрРегистрЗначение
TrapFramex0Адрес trap frame потока
ExceptionFramex1Адрес exception frame
NormalRoutinex2Адрес APC callback (user-mode)
NormalContextx3Контекст APC (ApcContext)
SystemArgument1x4Arg1 (из NtQueueApcThread)
SystemArgument2x5Arg2
Flagsw6Флаги доставки
8. nt!KiInitializeUserApc — Подготовка user-mode перехода
Адрес:
0xFFFFF801`B44B70C0
Функция модифицирует trap frame так, что при возврате из ядра в user-mode, выполнение начинается не с адреса прерывания, а с ntdll!KiUserApcDispatcher.
8.1 Определение machine type и setting up CONTEXT
Код (Text):
  1. fffff801`b44b7144  bl    nt!PsGetProcessMachine    ; получить архитектуру процесса
  2. fffff801`b44b7148  mov   w8, #0xAA64              ; IMAGE_FILE_MACHINE_ARM64
  3. fffff801`b44b714c  cmp   w8, w0, uxth #0          ; ARM64 native?
  4. fffff801`b44b7150  bne   not_arm64_native          ; нет → другой путь
  5. ; ARM64 native + XState:
  6. fffff801`b44b7158  adrp  x8, ...
  7. fffff801`b44b715c  ldr   w8, [x8, #0xB9C]        ; XState feature flag
  8. fffff801`b44b7160  tbz   w8, #0, not_arm64_native
  9. fffff801`b44b7164  ldr   w24, ...                 ; CONTEXT_EX flag
8.2 Манипуляция trap frame
Код (Text):
  1. fffff801`b44b7184  ldr   x8, [x19, #0x98]        ; KTHREAD.TrapFrame->Sp (User stack)
  2. fffff801`b44b7188  str   x8, [x26, #0x38]        ; сохранить оригинальный SP
  3. fffff801`b44b718c  sub   x21, x8, #0x10          ; выделить место на стеке
  4. ...
  5. fffff801`b44b7278  ldr   x2, [x26, #0x18]        ; CONTEXT size
  6. fffff801`b44b727c  str   w24, [x2]               ; CONTEXT flags
  7. fffff801`b44b7280  ldr   x1, [x26, #0x40]        ; source context
  8. fffff801`b44b7284  mov   x0, x19                 ; TrapFrame
  9. fffff801`b44b7288  bl    nt!KeContextFromKframes   ; заполнить CONTEXT из trap frame
8.3 Перенаправление возврата в user-mode
Код (Text):
  1. ; Установить новый PC (Program Counter) в trap frame:
  2. fffff801`b44b72d0  ldp   x8, x0, [x26, #0x60]    ; dispatcher address и stack layout
  3. fffff801`b44b72d4  stp   x8, x20, [x0, #8]       ; записать параметры APC
  4. fffff801`b44b72d8  ldr   x8, [x26, #0x70]
  5. fffff801`b44b72dc  str   x8, [x0, #0x18]
  6. fffff801`b44b72e0  ldr   x8, [x26, #0x78]
  7. fffff801`b44b72e4  str   x8, [x0]                ; NormalRoutine
  8. fffff801`b44b72e8  str   w22, [x0, #0x20]        ; flags
  9. fffff801`b44b72ec  str   x0, [x19, #0x98]        ; обновить SP в trap frame
Результат: Trap frame модифицирован так, что:
  • PC → ntdll!KiUserApcDispatcher
  • SP → уменьшен, на стеке размещены параметры APC
  • Восстановление оригинального контекста произойдёт через ntdll!NtContinue
8.4 Выбор адреса возврата по архитектуре
Код (Text):
  1. fffff801`b44b72f0  ldrh  w9, [x27, #0x7AC]       ; EPROCESS.Machine
  2. fffff801`b44b72f4  mov   w8, #0x8664              ; IMAGE_FILE_MACHINE_AMD64
  3. fffff801`b44b72f8  cmp   w9, w8
  4. fffff801`b44b72fc  adrp  x8, ...
  5. fffff801`b44b7304  ldr   x9, [x8, #0x28]          ; ARM64 dispatcher address
  6. fffff801`b44b7308  ldr   x8, [x8, #0x268]         ; x64/ARM64EC dispatcher address
  7. fffff801`b44b730c  cselne x8, x9, x8             ; выбрать нужный адрес
  8. fffff801`b44b7310  str   x8, [x19, #0x148]        ; KTHREAD+0x148 = Dispatcher address
9. Структуры данных
9.1 KAPC (0x58 байт)
СмещениеРазмерПолеОписание
+0x001Type0x12 (ApcObject)
+0x011AllFlagsБит 0: CallbackDataContext
+0x021Size0x58 (88 байт)
+0x031SpareByte1Зарезервировано
+0x044SpareLong0Зарезервировано
+0x088ThreadУказатель на KTHREAD
+0x1016ApcListEntryLIST_ENTRY (связный список)
+0x208KernelRoutineФункция ядра (очистка)
+0x288RundownRoutineФункция при rundown
+0x308NormalRoutineПользовательский callback
+0x388NormalContextКонтекст для NormalRoutine
+0x408SystemArgument1Аргумент 1
+0x488SystemArgument2Аргумент 2
+0x501ApcStateIndex0=Original, 1=Attached, 2=Attached
+0x511ApcMode0=KernelMode, 1=UserMode
+0x521InsertedФлаг: APC в списке? (0/1)
9.2 KAPC_STATE (0x30 байт)
СмещениеРазмерПолеОписание
+0x0016ApcListHead[0]Kernel APC list (LIST_ENTRY)
+0x1016ApcListHead[1]User APC list (LIST_ENTRY)
+0x208ProcessУказатель на KPROCESS
+0x281InProgressFlagsKernelApcInProgress, SpecialApcInProgress
+0x291KernelApcPendingФлаг: есть kernel APC для доставки
+0x2A1UserApcPendingAllБит 0: SpecialUserApcPending, Бит 1: UserApcPending
9.3 APC-поля в KTHREAD
СмещениеПолеОписание
+0x040ThreadLockСпин-блокировка для APC операций (SWPA)
+0x088TrapFrameУказатель на текущий trap frame
+0x090ApcStateKAPC_STATE (текущее состояние APC)
+0x0B0ApcState.ProcessKPROCESS (владелец APC state)
+0x0B8ApcState.InProgressFlagsФлаги выполнения APC
+0x0B9KernelApcPendingФлаг: kernel APC ожидает доставки
+0x0BAUserApcPendingAllФлаг: user APC ожидает доставки
+0x148ApcStatePointerМассив указателей на ApcState/SavedApcState
+0x26AApcStateIndexТекущий APC state index (0=Original)
+0x278SavedApcStateKAPC_STATE (сохранённый при KeStackAttach)
10. Диаграмма потока выполнения
Код (Text):
  1.  
  2.                         ┌──────────────────────┐
  3.                         │  kernel32            │
  4.                         │  QueueUserAPC()      │
  5.                         └──────────┬───────────┘
  6.                                    │
  7.                         ┌──────────▼───────────┐
  8.                         │  ntdll               │
  9.                         │  NtQueueApcThread()   │
  10.                         │  (syscall #x1F4)     │
  11.                         └──────────┬───────────┘
  12.                                    │
  13.                     ═══════════════╪═══════════════
  14.                           Kernel boundary
  15.                     ═══════════════╪═══════════════
  16.                                    │
  17.                         ┌──────────▼───────────┐
  18.                         │  nt!NtQueueApcThread  │  ← Thin wrapper
  19.                         │  Rearrange ABI args   │
  20.                         └──────────┬───────────┘
  21.                                    │
  22.                         ┌──────────▼───────────┐
  23.                         │ nt!NtQueueApcThreadEx2│  ← Основная логика
  24.                         │                       │
  25.                         │ 1. Validate flags     │
  26.                         │ 2. ObRefByHandle→ETHREAD│
  27.                         │ 3. Check terminated   │
  28.                         │ 4. Check WoW64/EC     │
  29.                         │ 5. CAS lock (reserve) │
  30.                         │ 6. ExAllocatePool2    │  ← KAPC (0x58, 'PasP')
  31.                         │ 7. KeInitializeApc    │
  32.                         │ 8. KeInsertQueueApc   │
  33.                         └──────────┬───────────┘
  34.                                    │
  35.                    ┌───────────────┼───────────────┐
  36.                    │               │               │
  37.          ┌─────────▼──┐  ┌────────▼──────┐  ┌─────▼──────────┐
  38.          │KiInsertQueue│  │SignalThread   │  │KiExitDispatcher │
  39.          │Apc          │  │ForApc        │  │                 │
  40.          │             │  │              │  │ Переключение    │
  41.          │ Insert into │  │ Wake target  │  │ контекста       │
  42.          │ LIST_ENTRY  │  │ thread       │  │                 │
  43.          └─────────────┘  └──────┬───────┘  └────────────────┘
  44.                                  │
  45.                     ┌────────────▼─────────────┐
  46.                     │   Target thread wakes     │
  47.                     │   (returns from wait)     │
  48.                     └────────────┬──────────────┘
  49.                                  │
  50.                     ┌────────────▼──────────────┐
  51.                     │  nt!KiDeliverApc          │
  52.                     │                           │
  53.                     │  1. Process Kernel APCs   │  ← ApcListHead[0] (+0x90)
  54.                     │     → Call KernelRoutine  │
  55.                     │                           │
  56.                     │  2. Process User APCs     │  ← ApcListHead[1] (+0xA0)
  57.                     │     → KiInitializeUserApc │
  58.                     └────────────┬──────────────┘
  59.                                  │
  60.                     ═════════════╪════════════════
  61.                        User-mode boundary
  62.                     ═════════════╪════════════════
  63.                                  │
  64.                     ┌────────────▼──────────────┐
  65.                     │  ntdll!KiUserApcDispatcher│
  66.                     │                           │
  67.                     │  Call ApcRoutine(Context, │
  68.                     │       Arg1, Arg2)         │
  69.                     │                           │
  70.                     │  NtContinue() → restore   │
  71.                     └───────────────────────────┘
  72.  
11. Сравнение с CreateRemoteThread
КритерийQueueUserAPCCreateRemoteThread
Выделение объектаExAllocatePool2 (0x58 байт)ObCreateObjectEx (ETHREAD ~0x770 байт)
Pool tag'PasP''Thre' / 'CrP'
IRQLDISPATCH_LEVEL (2)DPC_LEVEL (3) в KeStartThread
БлокировкаThreadLock (SWPA @ KTHREAD+0x40)PushLock (EPROCESS+0x1B8)
СтекНе создаётсяPspSetupUserStack (новый стек)
TEBНе создаётсяMmCreateTeb
Rundown ProtectionНетExAcquireRundownProtection
Protected Process checkНетPspIsProcessReadyForRemoteThread
Размер кода~200 инструкций (Ex2)~1500+ инструкций (PspAllocateThread)
Сложность внедренияНизкаяВысокая
ОбнаружениеPoolMon: 'PasP' тегиPoolMon: 'Thre' теги + новый поток
12. Практические выводы
  1. QueueUserAPC значительно проще CreateRemoteThread — не создаёт поток, стек, TEB. Просто выделяет KAPC (88 байт) и вставляет в связный список целевого потока.
  2. Нет проверки Protected Process — в отличие от CreateRemoteThread (PspIsProcessReadyForRemoteThread), QueueUserAPC не проверяет флаг ProtectedProcess в EPROCESS+0x168. Это может позволить APC-инъекцию в защищённые процессы.
  3. CAS-блокировка вместо Rundown Protection — QueueUserAPC использует атомарный Compare-And-Swap (casal), а не ExAcquireRundownProtection. Это легче, но менее надёжно при конкуренции.
  4. Тег пула 'PasP' — легко отслеживать через PoolMon / WPA. Может быть использован для обнаружения APC-инъекций.
  5. DISPATCH_LEVEL IRQL — KeInsertQueueApc поднимает IRQL до DISPATCH_LEVEL и использует спин-блокировку (SWPA). Это блокирует все потоковые переключения на текущном процессоре.
  6. FIFO порядок доставки — APC добавляются в конец списка и доставляются в порядке очереди.
  7. Kernel APC доставляются раньше User APC — KiDeliverApc сначала обрабатывает все kernel APC, затем user APC. Это гарантирует, что kernel APC не блокируются user APC.
  8. PAC (Pointer Authentication) — при вызове KernelRoutine выполняется KscpCfgCheckUserCallTargetEs — проверка PAC кода. Это защитный механизм ARM64, предотвращающий ROP-атаки.
  9. User-mode переход через KiInitializeUserApc — trap frame модифицируется так, что поток "возвращается" в ntdll!KiUserApcDispatcher, а не по оригинальному адресу. После выполнения APC callback вызывается NtContinue для восстановления контекста.
  10. IPI для пробуждения — если целевой поток ждёт на другом процессоре, отправляется Inter-Processor Interrupt (KiSendSoftwareInterrupt или HalRequestSoftwareInterrupt).
  11. ETHREAD+0x6C бит 10 — CrossThreadFlags.Terminated. Проверяется перед постановкой APC в очередь. Если поток уже завершён — возвращает STATUS_INVALID_HANDLE.
  12. WoW64/ARM64EC проверка — при инъекции APC в WoW64-процесс проверяется архитектура целевого потока. Несовпадение x86/ARM64/ARM64EC может привести к блокировке.
13. Ключевые адреса (Build 26100 ARM64)
ФункцияАдрес
nt!NtQueueApcThread0xFFFFF801`B4923410
nt!NtQueueApcThreadEx0xFFFFF801`B4B00AA0
nt!NtQueueApcThreadEx20xFFFFF801`B4B00AD0
nt!KeInitializeApc0xFFFFF801`B44720F0
nt!KeInsertQueueApc0xFFFFF801`B4472150
nt!KiInsertQueueApc0xFFFFF801`B448A6F0
nt!KiSignalThreadForApc0xFFFFF801`B448A810
nt!KiDeliverApc0xFFFFF801`B448A030
nt!KiInitializeUserApc0xFFFFF801`B44B70C0
nt!KiExitDispatcher0xFFFFF801`B449A918
nt!KiSignalThread0xFFFFF801`B449B940
nt!KiSendSoftwareInterrupt0xFFFFF801`B4477240
nt!HalRequestSoftwareInterrupt0xFFFFF801`B4409F60
nt!ObReferenceObjectByHandle0xFFFFF801`B4AA6B70
nt!ExAllocatePool20xFFFFF801`B4CAC000

0 25
galenkane

galenkane
Active Member

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