NTFS EA integer underflow + WNF kernel exploit (CVE-2021-31956)¶
NtfsQueryEaUserEaListiçindeki unsigned-length underflow paged pool'u overflow eder ve bu overflow,_WNF_NAME_INSTANCE/_WNF_STATE_DATAobjelerini groom ederek arbitrary kernel read/write'a dönüştürülür.
Mechanism¶
Underflow neden oluşur
NTFS extended attributes (EAs), bir dosyada saklanan key/value çiftleridir.
Query yolu NtQueryEaFile -> NtfsCommonQueryEa -> NtfsQueryEaUserEaList,
caller'ın verdiği bir EA list'i dolaşır ve eşleşen her EA'yı bir output
buffer'a kopyalayarak entry'leri 4-byte boundary üzerinde paketler.
Her FILE_FULL_EA_INFORMATION entry'si NextEntryOffset, EaNameLength ve
EaValueLength taşır. Loop entry'leri yazdıkça output buffer'da kalan yeri
takip eder ve bir sonraki entry'nin 32-bit aligned başlaması için bir
alignment padding uygular:
Remaining-length check'in şekli ea_block_size <= out_buf_length - padding'dir
ve unsigned bir değer üzerinde değerlendirilir. Buffer ilk EA tarafından
tam olarak sıfıra kadar doldurulduğunda, ikinci bir EA işlenirken
out_buf_length - padding, out_buf_length == 0 ve padding sıfırdan farklı
iken hesaplanır. Çıkarma unsigned olduğu için 0 - 2 negatif olmaz —
0xFFFFFFFF'e yakın bir değere wrap eder. Bound check bu durumda
başarısız olması gerekirken geçer ve ikinci EA'nın byte'ları allocation'ın
sonunun ötesine yazılır.
Output buffer paged pool'da yaşar; attacker'ın etkilediği bir size ile
ExAllocatePoolWithQuotaTag üzerinden allocate edilir. Sonuç, bir paged-pool
chunk'ından bir sonrakine controlled-length lineer bir overflow'dur — klasik
bir pool-overflow primitive'i. Exploit'in geri kalan işi pool feng shui'dir:
overflow edilen buffer'ın hemen ardına kullanışlı bir victim object yerleştir
ve tam olarak bir memory-access primitive'i veren field'ı corrupt et.
O victim, Windows Notification Facility (WNF)'dir. Bir _WNF_STATE_DATA
object'i, NtQueryWnfStateData'nın kaç byte döndüreceğini tanımlayan bir
length/AllocatedSize saklar ve bir _WNF_NAME_INSTANCE o data'ya bir pointer
(StateData) taşır. Size'ı corrupt etmek bir query'yi out-of-bounds read'e
çevirir; data pointer'ını corrupt etmek bir update'i arbitrary write'a çevirir.
İkisini chain'lemek tek bir pool byte-overflow'undan temiz bir arbitrary kernel
R/W verir — hiçbir zaman bir function pointer overwrite etmeden.
Walkthrough¶
Bug'a tamamen user mode'dan, dokümante edilmiş NT EA syscall'ları üzerinden ulaşılır.
- İki entry'li bir EA list oluştur ve onu bir dosyaya set et. İlk entry,
kopyalandıktan sonra çalışan
out_buf_lengthtam olarak sıfıra ulaşacak şekilde boyutlandırılır. İkinci entry overflow payload'ını taşır.
// two FILE_FULL_EA_INFORMATION entries chained via NextEntryOffset:
// entry[0]: consumes the output buffer down to length 0
// entry[1]: payload that overflows the next pool chunk
NtSetEaFile(hFile, &iosb, eaBuffer, eaBufferLen);
- Underflow'u tetiklemek için EA'ları query et.
NtQueryEaFile'ı, length'i entry[0] ile eşleşen bir output buffer ile çağır. entry[1] üzerindeki padding çıkarması underflow yapar ve entry[1]'in value'su paged-pool allocation'ının ötesine, komşu chunk'a taşar.
NtQueryEaFile(hFile, &iosb, outBuf, entry0_size,
TRUE /*ReturnSingleEntry*/, eaList, eaListLen,
NULL, FALSE);
-
Paged pool'u WNF state object'leriyle groom et. Çok sayıda WNF state name oluştur (
NtCreateWnfStateName) ve data publish et (NtUpdateWnfStateData); böylece seçilen size'daki_WNF_STATE_DATAallocation'ları EA buffer'ına komşu otursun. Overflow'un controlled bir victim üzerine düşmesi için holes'ları free/realloc et. -
Overflow'u relative bir read'e çevir. Victim
_WNF_STATE_DATA'yı, declare edilen size'ı gerçek allocation'ından büyük olacak şekilde corrupt et.NtQueryWnfStateDataartık out-of-bounds byte'lar döndürür — komşu_WNF_NAME_INSTANCEpointer'larını açığa çıkaran ve pool/KASLR'ı kıran bir info leak. -
Name instance üzerinden arbitrary R/W'ya yükselt. Leak edilen bir
_WNF_NAME_INSTANCEile onunStateData'sını arbitrary bir kernel adresine forge edebilir/yönlendirebilirsin.NtQueryWnfStateDataoradan okur;NtUpdateWnfStateDataoraya yazar. Artık bir arbitrary read ve arbitrary write'ın var. -
Escalate et. R/W'yı mevcut process'in
_EPROCESS'ini bulmak için kullan, ardından ya process'ine bir SYSTEM token kopyala ya da token'ın privilege/integrity field'larını null'la — bkz. access-token-theft-via-arbitrary-kernel-r-w.
Neden bir değil iki EA
Tek bir oversized EA ilk meşru length check'te başarısız olur, çünkü
başlangıçtaki out_buf_length sıfırdan farklıdır ve karşılaştırma dürüsttür.
Underflow yalnızca counter sıfıra sürüldükten sonra bir sonraki iteration'da
ortaya çıkar, bu yüzden wrap'e ulaşmak için chained-EA layout'u gereklidir.
Detection¶
- This bug was found exploited in the wild (discovered by Kaspersky) chained
with a Chrome renderer 0-day for sandbox escape; EDR telemetry of a sandboxed
browser child calling
NtSetEaFile/NtQueryEaFileon its own temp files is anomalous. - Frequent
NtCreate/NtUpdate/NtQueryWnfStateDatabursts immediately after EA operations is a grooming signature. - Pool corruption may surface as
BAD_POOL_HEADER/KERNEL_MODE_HEAP_CORRUPTIONbugchecks on imperfect feng shui.
Mitigation¶
- Apply the Microsoft June 2021 patch (MSRC CVE-2021-31956); the fixed
NtfsQueryEaUserEaListperforms the length math without the unsigned wrap. - Kernel pool hardening (segment heap / pool zeroing introduced in later Windows 10 builds) raises the cost of the WNF grooming step but does not fix the underlying underflow.