DirectComposition — Kernel Research via WinDbg Полный ресерч архитектуры DirectComposition в ядре Windows (ARM64), полученный через живое disassembly в WinDbg. Исследование проводилось на Parallels Desktop VM (Windows ARM64) с kernel debugging через кастомный windbg_agent.dll MCP. Архитектура: 3-Layer System DirectComposition — это 3-уровневая система в ядре Windows: Код (Text): User32.dll / DComp.dll (user-mode) | v win32kbase / win32kfull (session-space kernel) | | v v dxgkrnl.sys DWM.exe (compositor process) | v GPU Driver DDI Спойлер: Layer 1: Win32K — Composition Object & HWND Target NtUserCreateDCompositionHwndTarget (win32kfull) — главный syscall для создания composition target: Код (Text): 1. TestWindowForCompositionTarget — валидация HWND 2. CreateSharedSystemVisualObject — alloc 0xB8 bytes, создает shared resource object 3. AttachWindowCompositionTarget — bind к HWND: ├── EnterCrit ├── ValidateHwnd ├── Проверка стиля окна: (style & 0xFFFF2FFF) == 0x29D ├── Win32HM_LockIntoThread → PsGetCurrentProcess vs IoThreadToProcess │ └── Если не тот процесс → STATUS_ACCESS_DENIED (0xC0000022) └── _AttachWindowCompositionTarget: ├── W32GetUserSessionState ├── GetProp(atom=0xA542) → CHwndTargetProp │ └── Если нет → CWindowProp::CreateWindowProp<CHwndTargetProp> ├── CWindowProp::SetProp ├── CHwndTargetProp::SetSystemVisual: │ ├── ApiSetEditionNotifyDwmForSystemVisualCreation │ └── ObReferenceObjectByPointer(type=CompositionObject) └── CreateVisRgnTracker (type 0 → flag 4, type 1 → flag 8) 4. CompositionObject::CreateHandle → ObOpenObjectByPointer → handle CompositionObject::Create (win32kbase) — создание kernel объекта: Код (Text): ObCreateObject(0xB0 bytes) → W32GetCurrentWin32kSessionId → store at [obj+8] → ZwAllocateLocallyUniqueId → LUID at [obj+0x10] → virtual init callback → ObInsertObject → handle Validation: CompositionObjectType (w4-1) must be <= 5 → 6 object types Disassembly CompositionObject::Create (win32kbase!d622c9b0): Код (Text): pacibsp stp x19,x20,[sp,#-0x20]! stp fp,lr,[sp,#-0x10]! mov fp,sp mov x19,x0 ; CompositionObjectType ... mov w5,#0xB0 ; object size = 0xB0 bytes mov x4,#0 ; no ParseContext mov w3,#1 ; ObjectBody size present mov x2,x1 ; OBJECT_ATTRIBUTES ldr x1,[...ObjectType] ; CompositionObjectType ptr sxtb w0,w0 ; sign-extend alloc type bl nt!ObCreateObject ; ★ create kernel object ... bl nt!PsGetCurrentProcess bl nt!PsGetProcessSessionId str w0,[x8,#8] ; store SessionId at obj+8 ... add x0,x8,#0x10 ; LUID at obj+0x10 bl nt!ZwAllocateLocallyUniqueId ; ★ assign LUID ... bl nt!ObInsertObject ; ★ insert into handle table Спойлер: Layer 2: ShouldComposeWindow & ComposeWindowIfNeeded ShouldComposeWindow — решает, нужно ли окно композить: Код (Text): if ([window+0x18] == NULL || [window+0x18] == arg1): return false if IsWindowBeingDestroyed: return false if !([window_state+0x1F] & 0x10): return false // bit 4 = "has composition" if IsTopLevelWindow: return true if IsDesktopWindow: return true return false Disassembly ShouldComposeWindow (win32kfull!d6641f30): Код (Text): pacibsp stp fp,lr,[sp,#-0x10]! mov fp,sp mov x11,x0 ; window object ldr x8,[x11,#0x18] ; composition target ptr mov w12,#0 ; result = false cmp x8,#0 ccmpne x8,x1,#0 ; compare with expected value bne skip ; if target != null && != expected → skip bl IsWindowBeingDestroyed cbnz w0,skip ; being destroyed → skip ldr x8,[x11,#0x28] ; window internal state ldrb w8,[x8,#0x1F] ; flags byte tbz w8,#4,skip ; ★ bit 4 not set → not composed → skip mov x0,x11 bl IsTopLevelWindow cbnz w0,compose_yes mov x0,x11 bl IsDesktopWindow cbz w0,skip compose_yes: mov w12,#1 ; result = true skip: mov w0,w12 ComposeWindowIfNeeded — main composition pipeline: Код (Text): 1. IsToplevelWindowDesktopComposed? ├── YES: ComposeWindow(type=5 if arg==0, else 0xD) │ DirtyVisRgnTrackers │ ReferenceDwmApiPort → get DWM LPC port │ IncrementDWMWindowUniqueness │ → if port exists: LPC message to DWM via nt!LpcRequestPort │ (message size 0x3C, type 0x40000016, data 0x16) ├── NO + IsDesktopWindow: │ → IsWindowDesktopComposed? │ → YES: goto ComposeWindow path └── NO: → IsChildWindowDpiBoundaryDesktopComposed? → YES: goto ComposeWindow path → NO: return error Disassembly ComposeWindowIfNeeded (win32kfull!d6525ac8): Код (Text): pacibsp stp x19,x20,[sp,#-0x20]! ... mov x19,x0 ; window mov w20,w1 ; compose type bl IsToplevelWindowDesktopComposed cbz w0,check_desktop ; not top-level composed → check desktop ... ; === Compose path === cmp w20,#0 mov w9,#5 mov w8,#0xD cseleq w1,w9,w8 ; type = 5 if arg==0, else 0xD mov x0,x19 bl ComposeWindow ; ★ compose the window mov w21,w0 mov x0,x19 bl DirtyVisRgnTrackers ; ★ mark visual region dirty ... bl ReferenceDwmApiPort ; ★ get DWM communication port ... ; === Build LPC message and send to DWM === movi v16.16b,#0 ldr w8,[...msg_type] ; 0x3C0014 → type 0x40000016 str w8,[sp,#0x10] mov w8,#-0x8000 strh w8,[sp,#0x14] ; HWND handle ... bl nt!LpcRequestPort ; ★ send LPC message to DWM bl nt!ObDereferenceObject ; release port reference ComposeWindow — ядро composition: Код (Text): if (type & 1): // compose operation if IsDesktopWindow: session state path check window_state[0x1F] bit 4 (composed flag) if window_state[0x1A] & 8: // has sprite → UpdateWindowSpriteMonitor if window_state[0xE8] & 2: // already layered → UnsetLayeredWindow(type) else: → AtomicExecutionCheck guard → xxxSetLayeredWindow (make layered) → SetLayeredWindowAttributes(alpha=0xFF) → sets window_state[0xE8] |= 2 // mark as layered → ReferenceDwmApiPort → DwmAsyncChildStyleChange notification Спойлер: Layer 3: CSwapChainProp — Swap Chain → Composition Surface CSwapChainProp::SetCompositionSurfaceObj — простая функция: Код (Text): if ([prop+0x18] != NULL): MicrosoftTelemetryAssertTriggeredArgsKM(assert) // warning: already set [prop+0x18] = composition_surface_obj // просто store указателя Disassembly (win32kfull!d66b6c20): Код (Text): pacibsp stp x19,x20,[sp,#-0x10]! stp fp,lr,[sp,#-0x10]! mov fp,sp mov x19,x0 ; this (CSwapChainProp) ldr x8,[x19,#0x18] ; existing surface obj mov x20,x1 ; new surface obj cbz x8,store ; if null → ok to set ; --- Assert if already set --- adrp x8,... add x0,x8,#0xEC0 ; assert message mov w2,#0x5C ; line number mov w1,#0x20000 ; severity bl MicrosoftTelemetryAssertTriggeredArgsKM store: str x20,[x19,#0x18] ; ★ store surface object pointer UserSetWindowedSwapChain — syscall привязки swap chain к окну: Код (Text): 1. EnterCrit → ValidateHwnd 2. Проверка стиля: (style & ~0xD001) - 0x29D must have no bits in 0xFFFFFFFD 3. IsWindowBeingDestroyed → reject 4. IsWindowDesktopComposed → check 5. DxgkReferenceCompositionObject(handle) → get dxgkrnl composition object 6. CWindowProp::GetProp<CSwapChainProp> → check existing ├── No existing prop: │ Win32AllocPoolZInit(0x28) → alloc CSwapChainProp (40 bytes) │ init vtable (LegacyInputDispatcher::vftable+0x418+0x108) │ CSwapChainProp::SetCompositionSurfaceObj │ CWindowProp::SetProp → store on window │ CreateVisRgnTracker(type=2) → tracker for DWM └── Existing prop: update surface object Disassembly UserSetWindowedSwapChain (win32kfull!d66b6c68): Код (Text): pacibsp ... bl EnterCrit bl ValidateHwnd ... mov w9,#-0xD001 ldrh w8,[x8,#0x2A] ; window style and w8,w8,w9 sub w8,w8,#0x29D tst w8,#0xFFFFFFFD ; ★ style validation beq error_exit bl IsWindowBeingDestroyed cbnz w0,error_exit bl IsWindowDesktopComposed ... ; --- Reference dxgkrnl composition object --- mov w3,#1 ; access mode mov w2,#1 mov w1,#1 mov x0,x22 ; handle bl dxgkrnl!DxgkReferenceCompositionObject ; ★ cross-module call ... ; --- Get or create CSwapChainProp --- bl CWindowProp::GetProp<CSwapChainProp> ... cbnz x19,existing_prop ; already exists → update ... ; --- Allocate new prop --- mov x0,#0x28 ; 40 bytes bl Win32AllocPoolZInit ; alloc + zero ... ; --- Setup vtable and store --- ldr x8,...vtable str x8,[x19] ; vtable stp xzr,xzr,[x19,#8] stp xzr,xzr,[x19,#0x18] ; zero surface ptr bl CSwapChainProp::SetCompositionSurfaceObj bl CWindowProp::SetProp ; ★ attach to window ... bl CreateVisRgnTracker ; ★ create DWM tracker Спойлер: Layer 4: CApplicationChannel — MIL Command Batch Protocol CApplicationChannel::Commit — основной метод коммита: Код (Text): 1. Increment commit counter at [channel+0x198] 2. CMilCommandBatchParser::ValidateAndTranslateHandles (MIL batch handle translation) └── CET check via KscpCfgCheckUserCallTargetEs 3. NotifyCommitMustBeLastForFrame (if flagged) 4. Collect batch data from [channel+0xB0] → store in pending list 5. BuildNinjaBatch: ├── Input: pending batch + sync object └── Output: built CMilProtocolBlock 6. SubmitBatch → send to DWM (see below) Disassembly CApplicationChannel::Commit (win32kbase!d6233e40): Код (Text): pacibsp ... mov x19,x0 ; this (CApplicationChannel) ldr w26,[x19,#0x198] ; commit counter ... add w21,w26,#1 ; increment str w21,[x19,#0x198] ; store new counter ... ; === Handle validation === ldrb w8,[x19,#0x109] ; channel flags tbnz w8,#2,validate_handles ... validate_handles: add x0,sp,#0x58 bl CMilCommandBatchParser::ValidateAndTranslateHandles ... ; === Notify frame === ldrb w8,[sp,#0x10] ; mustBeLastForFrame flag cbz w8,check_batch mov w1,#1 mov x0,x19 bl NotifyCommitMustBeLastForFrame ... ; === Build batch === mov w3,w26 ; commit id add x2,sp,#0x20 add x1,sp,#0x18 mov x0,x19 bl BuildNinjaBatch ; ★ build MIL command batch ... ; === Submit batch === ldr x2,[sp,#0x20] ; batch data mov x0,x19 ; channel and w3,w8,#1 ; flags bl SubmitBatch ; ★ submit to DWM SubmitBatch — отправка batch в DWM: Код (Text): 1. Optional: KeQueryPerformanceCounter for profiling 2. KeEnterCriticalRegion 3. ExAcquireResourceSharedLite (channel lock at [channel+0x28]+0x10) 4. Check pending count > 0 at [channel+0x18] 5. Acquire connection resource lock 6. Check connection active flag at [conn+0x94] 7. ExpInterlockedPushEntrySList → push batch to SLIST at [conn+0x60] (lock-free producer-consumer queue) 8. KeSetEvent → signal DWM via event at [conn+0x58]+8 Disassembly SubmitBatch (win32kbase!d623b040): Код (Text): pacibsp ... mov x20,x0 ; this (CApplicationChannel) mov x19,x1 ; batch object mov x23,x2 ; batch data uxtb w8,w3 ; profiling flag mov x21,x4 ; sync object ... ; === Acquire channel lock === bl nt!KeEnterCriticalRegion ldr x0,[x8,...] ; ERESOURCE at channel+0x28+0x10 mov w1,#1 ; shared bl nt!ExAcquireResourceSharedLite ... ; === Acquire connection lock === ldr x22,[x8,#0x10] ; connection resource bl nt!KeEnterCriticalRegion ldr x0,[x8,...] bl nt!ExAcquireResourceSharedLite ldr w22,[x21,#0x94] ; ★ connection active flag ... ; === Push to SLIST (lock-free!) === mov x1,x19 ; batch entry add x0,x21,#0x60 ; SLIST head at conn+0x60 bl nt!ExpInterlockedPushEntrySList ; ★ atomic push ... ; === Signal DWM === ldr x8,[x21,#0x58] ; event object mov w2,#0 mov w1,#1 ; increment ldr x0,[x8,#8] ; KEVENT bl nt!KeSetEvent ; ★ wake DWM thread Ключевое: Коммуникация с DWM — это lock-free SLIST (interlocked singly-linked list) + KeSetEvent. App проталкивает batch через SLIST, DWM просыпается по событию и забирает. Никаких LPC здесь — это быстрый путь. LPC используется для window composition нотификаций. Спойлер: Layer 5: dxgkrnl — CompositionSurfaceObject (GPU) NtCreateCompositionSurfaceHandle — syscall создания GPU composition surface: Код (Text): KeEnterCriticalRegion ObCreateObject(0xD8 bytes) → CompositionSurfaceObject PsGetCurrentProcess → PsGetProcessSessionId → store at [obj+8] ZwAllocateLocallyUniqueId → LUID at [obj+0x18] CompositionSurfaceObject::ObjectInit(obj+0x10) ObInsertObject → return handle Disassembly (dxgkrnl!d2193f10): Код (Text): pacibsp ... mov x19,x0 ; OBJECT_ATTRIBUTES mov w21,w1 ; desired access mov x20,x2 ; output handle ptr ... bl nt!KeEnterCriticalRegion ... mov w7,#0 ; Unknown mov w6,#0 ; Unknown mov w5,#0xD8 ; ★ object body size = 0xD8 bytes (GPU surface) mov x4,#0 ; no ParseContext mov w3,#1 ; ObjectBody present mov x2,x19 ; OBJECT_ATTRIBUTES ldr x1,[...ObjectType] ; CompositionSurfaceObjectType mov w0,#1 ; alloc type bl nt!ObCreateObject ; ★ create GPU composition surface object ... bl nt!PsGetCurrentProcess bl nt!PsGetProcessSessionId str w0,[x8,#8] ; store SessionId ... add x0,x8,#0x18 ; LUID buffer bl nt!ZwAllocateLocallyUniqueId ; ★ assign unique ID ... add x2,x0,#0x10 mov x1,#0 bl CompositionSurfaceObject::ObjectInit ; ★ init GPU surface ... bl nt!ObInsertObject ; ★ insert, get handle CompositionSurfaceObject::Create — альтернативный путь: Код (Text): Тот же паттерн: ObCreateObject → session ID → LUID → ObjectInit → ObInsertObject Размер объекта: 0xD8 bytes ObjectInit: stores allocation attributes, initializes GPU-side resources NtBindCompositionSurface — привязка surface к GPU: Код (Text): 1. Copy 0x520 bytes from user buffer (bind parameters) via RtlCopyFromUser 2. KeEnterCriticalRegion 3. DxgkGetWin32kImportTable → virtual call for win32k validation 4. DxgkGetSessionTokenManager → get session token 5. Virtual dispatch on token object (vtable+0x20) for validation 6. GPU driver DDI calls to bind the surface to hardware Disassembly NtBindCompositionSurface (dxgkrnl!d2193c20): Код (Text): pacibsp ... mov x23,x0 ; composition surface handle str w1,[x26,#0x20] ; bind flags stp w1,w2,[x26,#4] ; store params ... ; === Copy bind params from user mode === mov x2,#0x520 ; ★ 0x520 bytes of bind parameters mov w1,#0 bl _memset_spec_unaligned_zva ; zero local buffer mov x2,#0x520 mov x1,x21 ; user buffer bl RtlCopyFromUser ; ★ copy from user space ... ; === Cross-module validation === bl DxgkGetWin32kImportTable ; get win32k function table ldr x8,[x0,#0x260] ; validation function mov x15,x8 bl nt!KscpCfgCheckUserCallTargetEs ; CET check blr x15 ; ★ virtual call to win32k validator ... bl DxgkGetSessionTokenManager ; get token manager ... ldr x8,[x20] ; vtable ldr x8,[x8,#0x20] ; validate method mov x15,x8 bl nt!KscpCfgCheckUserCallTargetEs blr x15 ; ★ token validation Спойлер: Layer 6: CConnection — Session/Process Connection Model CConnection::GetDefaultConnection: Код (Text): Path 1: [current_process_win32k+0x100] → per-process connection Path 2: W32GetDCompSessionState+0x10 → session global connection Both guarded by ExAcquireResourceExclusiveLite + atomic ldaddal refcount Disassembly GetDefaultConnection (win32kbase!d6231470): Код (Text): ; === Path 1: per-process === ldr x8,[...offset_0x100] ; per-process connection ptr cbz x8,path2 ; null → try session path ... ldaddal w9,w8,[x8+refcount] ; ★ atomic increment refcount ... ret ; === Path 2: session global === path2: bl W32GetDCompSessionState ldr x8,[x0,#0x10] ; session connection cbz x8,return_null ... ldaddal w9,w8,[x8+refcount] ; ★ atomic increment refcount DCompositionSessionInitialize — при создании сессии: Код (Text): CConnection::OnSessionCreation → W32GetDCompSessionState → alloc 0x218 bytes for CSynchronizationManager → CSynchronizationManager::Initialize → store at session_state+0x18 Disassembly DCompositionSessionInitialize (win32kbase!d636da00): Код (Text): ... bl CConnection::OnSessionCreation ... bl W32GetDCompSessionState ... mov x0,#0x218 ; ★ CSynchronizationManager size = 0x218 bytes bl Win32AllocPoolZInit ; allocate ... bl CSynchronizationManager::Initialize str x0,[session_state,#0x18] ; ★ store at offset 0x18 DCompSession State Structure: Код (Text): offset 0x00: ??? (header) offset 0x08: ??? offset 0x10: CConnection* (session connection to DWM) offset 0x18: CSynchronizationManager* (cross-process sync) offset 0x20: ERESOURCE (resource lock) Спойлер: Layer 7: DWM Initialization DCompositionDwmInitialize — DWM-side initialization: Код (Text): 1. GetDefaultConnection → CConnection 2. Acquire push lock at connection+0x150 3. Set flag at connection+0x158 (DWM initialized) 4. Optionally: EmitSetBlurredWallpaperSurface Disassembly (win32kbase!d636d630): Код (Text): ... bl GetDefaultConnection ; get session connection ... ; === Acquire push lock === ldar w8,[x0,#0x158] ; read DWM init flag cbnz w8,already_init ; already initialized → skip ... ; acquire push lock at conn+0x150 add x1,sp,#0x8 mov w2,#1 add x0,x0,#0x150 bl RtlAcquirePushLockExclusive ; ★ exclusive lock ... ; set flag str w8,[x19,#0x158] ; ★ mark DWM initialized ... bl EmitSetBlurredWallpaperSurface ; optional wallpaper surface Complete Data Flow Summary Код (Text): App calls DCompositionCreateDevice → kernel: CompositionObject::Create (ObCreateObject + session+LUID) → kernel: handle via ObOpenObjectByPointer App creates visual tree + sets target → kernel: NtUserCreateDCompositionHwndTarget → kernel: CHwndTargetProp (atom 0xA542) binds CompositionObject to HWND → kernel: DWM notified via ApiSetEditionNotifyDwmForSystemVisualCreation App creates swap chain / surface → kernel: NtCreateCompositionSurfaceHandle (dxgkrnl ObCreateObject 0xD8 bytes) → kernel: NtBindCompositionSurface → GPU driver DDI App commits → kernel: CApplicationChannel::Commit → BuildNinjaBatch (MIL protocol commands) → SubmitBatch → ExpInterlockedPushEntrySList → KeSetEvent → DWM wakes, dequeues batch, processes visual tree Window changes → ShouldComposeWindow (checks composed flag bit 4) → ComposeWindowIfNeeded → ComposeWindow (layered window setup) → LPC message to DWM via LpcRequestPort DWM composites → Reads composition surface from dxgkrnl → GPU renders visual tree → Presents via CompositionSurfaceObject → independent flip Security Model Process ownership: Код (Text): PsGetCurrentProcess == IoThreadToProcess(thread) — только процесс-владелец окна может создать composition target Session isolation: SessionId записывается в каждый CompositionObject и CompositionSurfaceObject Handle-based access: Все операции идут через NT object manager handles CET/PAC: Все indirect calls защищены Код (Text): KscpCfgCheckUserCallTargetEs + ARM64 PAC (pacibsp/autibsp) Key Syscalls Map SyscallModulePurposeNtUserCreateDCompositionHwndTargetwin32kfullCreate composition target for HWNDNtUserDestroyDCompositionHwndTargetwin32kfullDestroy composition targetNtUserGetDCompositionHwndBitmapwin32kfullGet composition bitmapNtUserGetResizeDCompositionSynchronizationObjectwin32kfullResize sync objectNtCreateCompositionSurfaceHandledxgkrnlCreate GPU composition surfaceNtBindCompositionSurfacedxgkrnlBind surface to GPUNtUnBindCompositionSurfacedxgkrnlUnbind surfaceNtNotifyPresentToCompositionSurfacedxgkrnlPresent frameNtQueryCompositionSurfaceStatisticsdxgkrnlQuery surface stats Спойлер: Key Kernel Structures CompositionObject (win32kbase, 0xB0 bytes): Код (Text): +0x00: vtable / type info +0x08: ULONG SessionId +0x10: LUID (Locally Unique ID) +0x18: ... (object body) CompositionSurfaceObject (dxgkrnl, 0xD8 bytes): Код (Text): +0x00: vtable / type info +0x08: ULONG SessionId +0x10: ObjectInit data +0x18: LUID (Locally Unique ID) CHwndTargetProp (stored via window property atom 0xA542): Код (Text): +0x00: vtable +0x08: ??? +0x10: ??? +0x18: CompositionObject* (per-slot, indexed by target type) CSwapChainProp (stored via window property, 0x28 bytes): Код (Text): +0x00: vtable +0x08: ??? +0x10: ??? +0x18: CompositionSurfaceObject* (GPU surface pointer) +0x20: ULONG flags CApplicationChannel: Код (Text): +0x00: vtable +0x18: pending batch count (atomic) +0x28: CConnection* (back-ptr to session connection) +0x30: flags byte +0x109: channel flags (validation required etc.) +0x188: CCriticalSection (batch submission lock) +0x198: ULONG commit counter +0x1D0: pre-commit callback list +0x1D8: post-commit callback list CConnection: Код (Text): +0x10: ERESOURCE (resource lock) +0x58: KEVENT* (DWM wake event, obj at [+8]) +0x60: SLIST_HEADER (batch queue head) +0x94: ULONG active flag +0x150: push lock (DWM init) +0x158: ULONG DWM initialized flag Overlay Implications Для оверлеев DirectComposition дает: Hardware-accelerated composition через DWM — каждый визуал — отдельный CompositionObject с собственным GPU surface Independent flip — CompositionSurfaceObject позволяет GPU page flipping без GDI Visual tree — parent-child отношения между визуалами, трансформации, clip regions — всё обрабатывается DWM на GPU Cross-process safe — DWM процесс делает финальный composite, изолируя app от screen pixels No GDI dependency — полностью минует GDI/rendering pipeline Исследование проведено через live kernel debugging на Windows ARM64 (Parallels Desktop VM) с использованием WinDbg MCP agent (windbg_agent.dll). Все адреса и размеры структур актуальны для данного билда Windows.