Skip to content

Windows Object Manager and handle-table attack surface

Ob* Object Manager ve per-process handle table: kernel object'lerin OBJECT_HEADER, reference count ve type descriptor üzerinden nasıl isimlendirilip erişildiği; handle-entry manipülasyonu, object type-confusion ve refcount imbalance'ın neden tekrar eden bir corruption hedefi olduğu ve defender'ın bunu nasıl tespit ettiği.

Mechanism

Handle-to-object indirection ve refcount invariant'ı neden kırılgandır

Windows'ta bir device, file, event, process ya da thread gibi her named kernel entity bir executive object'tir ve her object'in önünde bir OBJECT_HEADER bulunur. Bu header, object'in TypeIndex'ini (bir OBJECT_TYPE descriptor'ına çözülür), güvenlik descriptor'ını ve iki ayrı lifetime sayacını taşır: bir handle count ve daha geniş bir pointer (reference) count. Pointer count, açık handle'ların yanı sıra ObReferenceObject* ile kernel içinde alınmış her counted reference'ın toplamıdır. Kritik invariant şudur: object ancak pointer count sıfıra indiğinde free edilir; her increment tam olarak bir decrement ile dengelenmelidir.

User-mode object'e asla doğrudan pointer ile değil, bir opaque handle ile dokunur. Handle, per-process _HANDLE_TABLE'a bir index'tir; tablo tek bir düz array değil, üç seviyeli bir hierarchy'dir ve tablonun adresi _EPROCESS'in ObjectTable alanında tutulur. Her _HANDLE_TABLE_ENTRY iki şey encode eder: object header'a giden bir encoded pointer ve o handle için verilmiş granted access mask. Bir syscall handle verdiğinde kernel entry'yi çözer, pointer'ı reconstruct eder ve granted access'i istenen operasyona karşı doğrular.

Attack surface tam da bu iki noktadan doğar:

  • Handle-table entry manipulation. Bir arbitrary kernel write primitive'i handle entry'yi hedef alırsa, ya encoded object pointer'ı başka bir object'e yönlendirebilir ya da granted access mask'i yükseltip read-only bir handle'ı full-access yapabilir — kernel bir daha ObpValidate etmeden ona güvenir.
  • Type confusion. Handle-to-object çözümü object'in gerçek TypeIndex'ine güvenir. Bir bug object'i A type'ı beklerken B type'ı gibi yorumlarsa (ya da free edilmiş bir slot'a başka type reallocate olursa), type-specific field'lar yanlış interpret edilir — klasik bir type-confusion.
  • Reference-count imbalance. Nadiren çalışan bir error path'te kaçırılan bir increment/decrement sayacı dengesizleştirir. Over-reference bir resource leak'tir; under-reference ise object'i hâlâ başka client'lar ona pointer tutarken erkenden free ettirir — doğrudan bir use-after-free. Windows 8.1 handle entry'lere per-handle inverted reference count (x64'te 0x7FFF'den geriye sayan) ekledi; bu, biased pointer count değerleri üretir ama temeldeki "her ref bir deref ister" invariant'ını değiştirmez.

Walkthrough

Aşağıdaki adımlar public object-manager internals yazılarından türetilmiş kavramsal bir gözlemdir; struct offset'leri build'e özgüdür ve her hedefte dt nt!_OBJECT_HEADER / dt nt!_HANDLE_TABLE_ENTRY ile doğrulanmalıdır.

1. Object header ve iki sayacı gör. WinDbg'de bir object'ten header'a geç ve lifetime sayaçlarını incele:

kd> !object <object-address>
Object: ...  Type: (...) Event
    HandleCount: 2  PointerCount: 32770

PointerCount'un HandleCount'tan çok büyük görünmesi bir bug değil — Windows 8.1'in inverted-refcount bias'ıdır (her handle pointer count'a 0x7FFF katkı yapar).

2. Handle'ı entry'ye çöz. Per-process tabloyu dolaş ve bir handle'ın hangi object'e ve hangi granted access ile bağlandığını gör:

kd> !handle <handle-value> f <eprocess>
   ... Object: ...  GrantedAccess: 0x1f0003 ...

Buradaki iki güven noktası — entry'nin encoded object pointer'ı ve GrantedAccess — kernel-write ile tamper edilirse istismarın kalbini oluşturur.

3. Imbalance'ı gözlemle. Under-reference'ı laboratuvar ortamında görmek için Microsoft'un tag-based tracing'i kullanılır; !obtrace per-tag reference vs dereference dengesini raporlar:

kd> !obtrace <object-address>
   ...
   Tag: Lky8 References: 1 Dereferences: 2 Under reference by: 1

Bir tag'de dereference'ların reference'ları aşması, object'in erken free edilebileceği anlamına gelir — free sonrası reallocate edilen slot üzerinde bir UAF/type-confusion penceresi açar. Bu, sömürünün mantıksal önkoşuludur; buradaki altitude'da weaponize edilmiş bir tetikleyici verilmez.

Neden under-reference bir UAF'a dönüşür (kavramsal)
refA = ObReferenceObjectByHandle(h)   // pointer count += 1
... error path taken ...
ObDereferenceObject(obj)              // -1
ObDereferenceObject(obj)              // -1  (fazladan: bug)
// pointer count beklenenden 1 düşük -> object free
UseObject(refA)                       // freed memory'e dokunma = UAF

Detection

  • Reference-tracing / !obtrace. Gflags ile object reference tracing'i aç; ObReferenceObjectByHandleWithTag / ObDereferenceObjectWithTag çağrılarına eşleştirilmiş tag'ler, bir tag'de reference vs dereference mismatch'i olarak under/over-reference'ı doğrudan gösterir. "Permanent" trace option'ı object free edildikten sonra bile trace'i saklar — under-reference avında değerli.
  • Cross-view handle consistency. Bir process'in _HANDLE_TABLE'ını dolaş ve handle'ların işaret ettiği object type'larını bağımsız kaynakla karşılaştır; bir entry'nin TypeIndex'i ile hedef object header'ın type'ı uyuşmuyorsa bu bir type-confusion / entry-tamper sinyalidir.
  • GrantedAccess anomalileri. Beklenenden geniş granted access mask taşıyan handle'lar (örn. bir read handle'ının aniden full-access görünmesi) entry manipülasyonuna işaret eder; EDR/telemetry handle duplication ve access upgrade'lerini loglayabilir.
  • Pool / object-header integrity. Memory-forensics (Volatility object scan'leri) pool tag'lerden object'leri oyup çıkarır ve list'lerden düşmüş ya da tutarsız refcount taşıyan object'leri yakalar.
  • Crash telemetry. Object-manager bölgesinde tekrarlayan BAD_POOL / KERNEL_MODE_HEAP_CORRUPTION bugcheck'leri, refcount imbalance kaynaklı UAF'ın klasik gürültüsüdür.

Mitigation

  • Type-safe reference API'leri. Driver'lar ObReferenceObjectByHandlebeklenen OBJECT_TYPE argümanıyla çağırmalı ki type-confusion baştan reddedilsin; her reference'ı tam olarak bir dereference ile eşle (tag'li varyantlar denetimi kolaylaştırır).
  • Handle-table'ı bütünlüğüyle koru. Handle-entry ya da granted-access tampering bir mevcut arbitrary kernel write primitive gerektirir; VBS/HVCI, Kernel CFG, PatchGuard/HyperGuard ve Driver Signature Enforcement o primitive'e ulaşma çıtasını yükseltir.
  • Least privilege on handles. Object'leri minimum gerekli access ile aç ve OBJ_PROTECT_CLOSE gibi attribute'larla kritik handle'ları koru; gereksiz duplicate handle'lardan kaçın.
  • Fuzzing + statik analiz. Refcount imbalance'lar nadir error path'lerde yaşar; driver'ları Driver Verifier (özellikle reference-count doğrulama) ve syscall fuzzing ile sür, böylece kaçırılan deref'ler shipping'ten önce ortaya çıksın.

References