Skip to content

Commit

Permalink
updated to version 1.13.0
Browse files Browse the repository at this point in the history
  • Loading branch information
last-byte committed Oct 5, 2023
1 parent 98544e9 commit c965ab3
Show file tree
Hide file tree
Showing 5 changed files with 295 additions and 22 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
# Changelog
## 1.13.0
Features:
- Detection for RID hijacking
- Detection for the Suborner technique
Fixes:
- Fixed a bug which regarding module-wide string comparisons (see issue #19).

## 1.12.1
Fixes:
- Fixed a bug which prevented the detection of the Utilman.exe hijacking in the Accessibility Tools persistence detection.
Expand Down
Binary file added PersistenceSniper.zip
Binary file not shown.
Binary file modified PersistenceSniper/PersistenceSniper.psd1
Binary file not shown.
304 changes: 284 additions & 20 deletions PersistenceSniper/PersistenceSniper.psm1
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<#PSScriptInfo
.VERSION 1.12.1
.VERSION 1.13.0
.GUID 3ce01128-01f1-4503-8f7f-2e50deb56ebc
Expand All @@ -27,7 +27,7 @@
.EXTERNALSCRIPTDEPENDENCIES
.RELEASENOTES
This release fixes a bug in the Accesibility Tools persistence detection, which up to version 1.12.0 did not check for Utilman.exe hijacking.
This release implements detection for RID hijacking and the Suborner technique. It also fixes a module-wide bug regarding string comparisons (see issue #19).
.PRIVATEDATA
Expand Down Expand Up @@ -152,7 +152,9 @@ function Find-AllPersistence
'ServiceControlManagerSD',
'OfficeAiHijacking',
'RunExAndRunOnceEx',
'DotNetStartupHooks'
'DotNetStartupHooks',
'RIDHijacking',
'SubornerAttack'
)]
$PersistenceMethod = 'All',

Expand Down Expand Up @@ -405,6 +407,203 @@ function Find-AllPersistence
return $false
}
}

function Parse-NetUser {
<#
.SYNOPSIS
Parses the net user command output into a single list.
Author: Jake Miller (@LaconicWolf)
.DESCRIPTION
Accepts the output of net user via the pipeline and parses into a
single list.
.EXAMPLE
PS C:\> net user | Parse-NetUser
Name
------
Administrator
DefaultAccount
Dwight
Guest
Jake
WDAGUtilityAccount
#>

foreach ($item in $input) {
if ($item -eq ""){
continue
}
if ($item -match 'User accounts for') {
continue
}
elseif ($item -match '----') {
continue
}
elseif ($item -match 'The command completed') {
continue
}
$contentArray = @()
foreach ($line in $item) {
while ($line.Contains(" ")){
$line = $line -replace ' ',' '
}
$contentArray += $line.Split(' ')
}

foreach($content in $contentArray) {
$content = $content -replace '"',''
if ($content.Length -ne 0) {
New-Object -TypeName PSObject -Property @{"Name" = $content.Trim()}
}
}
}
}


function ElevateTo-System
{

$signature = @"
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct TokPriv1Luid
{
public int Count;
public long Luid;
public int Attr;
}
public const int SE_PRIVILEGE_ENABLED = 0x00000002;
public const int TOKEN_QUERY = 0x00000008;
public const int TOKEN_ADJUST_PRIVILEGES = 0x00000020;
public const UInt32 STANDARD_RIGHTS_REQUIRED = 0x000F0000;
public const UInt32 STANDARD_RIGHTS_READ = 0x00020000;
public const UInt32 TOKEN_ASSIGN_PRIMARY = 0x0001;
public const UInt32 TOKEN_DUPLICATE = 0x0002;
public const UInt32 TOKEN_IMPERSONATE = 0x0004;
public const UInt32 TOKEN_QUERY_SOURCE = 0x0010;
public const UInt32 TOKEN_ADJUST_GROUPS = 0x0040;
public const UInt32 TOKEN_ADJUST_DEFAULT = 0x0080;
public const UInt32 TOKEN_ADJUST_SESSIONID = 0x0100;
public const UInt32 TOKEN_READ = (STANDARD_RIGHTS_READ | TOKEN_QUERY);
public const UInt32 TOKEN_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED | TOKEN_ASSIGN_PRIMARY |
TOKEN_DUPLICATE | TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_QUERY_SOURCE |
TOKEN_ADJUST_PRIVILEGES | TOKEN_ADJUST_GROUPS | TOKEN_ADJUST_DEFAULT |
TOKEN_ADJUST_SESSIONID);
public const string SE_TIME_ZONE_NAMETEXT = "SeTimeZonePrivilege";
public const int ANYSIZE_ARRAY = 1;
[StructLayout(LayoutKind.Sequential)]
public struct LUID
{
public UInt32 LowPart;
public UInt32 HighPart;
}
[StructLayout(LayoutKind.Sequential)]
public struct LUID_AND_ATTRIBUTES {
public LUID Luid;
public UInt32 Attributes;
}
public struct TOKEN_PRIVILEGES {
public UInt32 PrivilegeCount;
[MarshalAs(UnmanagedType.ByValArray, SizeConst=ANYSIZE_ARRAY)]
public LUID_AND_ATTRIBUTES [] Privileges;
}
[DllImport("advapi32.dll", SetLastError=true)]
public extern static bool DuplicateToken(IntPtr ExistingTokenHandle, int
SECURITY_IMPERSONATION_LEVEL, out IntPtr DuplicateTokenHandle);
[DllImport("advapi32.dll", SetLastError=true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SetThreadToken(
IntPtr PHThread,
IntPtr Token
);
[DllImport("advapi32.dll", SetLastError=true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool OpenProcessToken(IntPtr ProcessHandle,
UInt32 DesiredAccess, out IntPtr TokenHandle);
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool LookupPrivilegeValue(string host, string name, ref long pluid);
[DllImport("kernel32.dll", ExactSpelling = true)]
public static extern IntPtr GetCurrentProcess();
[DllImport("advapi32.dll", ExactSpelling = true)]
public static extern IntPtr RevertToSelf();
[DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
public static extern bool AdjustTokenPrivileges(IntPtr htok, bool disall,
ref TokPriv1Luid newst, int len, IntPtr prev, IntPtr relen);
"@

$currentPrincipal = New-Object Security.Principal.WindowsPrincipal ([Security.Principal.WindowsIdentity]::GetCurrent())
if ($currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) -ne $true) {
throw "Run the Command as an Administrator"
break
}

Add-Type -MemberDefinition $signature -Name AdjPriv -Namespace AdjPriv
$adjPriv = [AdjPriv.AdjPriv]
[long]$luid = 0

$tokPriv1Luid = New-Object AdjPriv.AdjPriv+TokPriv1Luid
$tokPriv1Luid.Count = 1
$tokPriv1Luid.Luid = $luid
$tokPriv1Luid.Attr = [AdjPriv.AdjPriv]::SE_PRIVILEGE_ENABLED

$retVal = $adjPriv::LookupPrivilegeValue($null,"SeDebugPrivilege",[ref]$tokPriv1Luid.Luid)

[IntPtr]$htoken = [IntPtr]::Zero
$retVal = $adjPriv::OpenProcessToken($adjPriv::GetCurrentProcess(),[AdjPriv.AdjPriv]::TOKEN_ALL_ACCESS,[ref]$htoken)


$tokenPrivileges = New-Object AdjPriv.AdjPriv+TOKEN_PRIVILEGES
$retVal = $adjPriv::AdjustTokenPrivileges($htoken,$false,[ref]$tokPriv1Luid,12,[IntPtr]::Zero,[IntPtr]::Zero)

if (-not ($retVal)) {
[System.Runtime.InteropServices.Marshal]::GetLastWin32Error()
break
}

$process = (Get-Process -Name winlogon)
[IntPtr]$hwinlogontoken = [IntPtr]::Zero
$retVal = $adjPriv::OpenProcessToken($process.Handle,([AdjPriv.AdjPriv]::TOKEN_IMPERSONATE -bor [AdjPriv.AdjPriv]::TOKEN_DUPLICATE),[ref]$hwinlogontoken)

[IntPtr]$dulicateTokenHandle = [IntPtr]::Zero
$retVal = $adjPriv::DuplicateToken($hwinlogontoken,2,[ref]$dulicateTokenHandle)

$retval = $adjPriv::SetThreadToken([IntPtr]::Zero,$dulicateTokenHandle)
if (-not ($retVal)) {
[System.Runtime.InteropServices.Marshal]::GetLastWin32Error()
}
return 0
}

function RevertTo-Self
{

$signature = @"
[DllImport("advapi32.dll", ExactSpelling = true)]
public static extern IntPtr RevertToSelf();
"@
$adjPriv = [AdjPriv.AdjPriv]
$retval = $adjPriv::RevertToSelf()
if (-not ($retVal)) {
[System.Runtime.InteropServices.Marshal]::GetLastWin32Error()
}
return 0
}

function Get-RunAndRunOnce
{
Expand Down Expand Up @@ -889,7 +1088,7 @@ function Find-AllPersistence
$exePath = $appPath.'(Default)'
if(([System.IO.Path]::IsPathRooted([System.Environment]::ExpandEnvironmentVariables($exePath))) -eq $false)
{
$exePath = "C:\Windows\System32\$exePath"
$exePath = "C:\Windows\System32\$exePath".ToLower()
}
if ($exePath.Contains('powershell') -or $exePath.Contains('cmd') -or -not (Get-AuthenticodeSignature -FilePath $exePath ).IsOSBinary)
{
Expand Down Expand Up @@ -917,6 +1116,7 @@ function Find-AllPersistence
foreach ($key in $keys)
{
$ImagePath = (Get-ItemProperty -Path ($key.pspath)).ImagePath
$ImagePath = $ImagePath.ToLower()
if ($null -ne $ImagePath)
{
if ($ImagePath.Contains('\svchost.exe'))
Expand Down Expand Up @@ -1946,10 +2146,58 @@ function Find-AllPersistence
$PersistenceObject = New-PersistenceObject -Hostname $hostname -Technique '.NET Startup Hooks DLL Sideloading' -Classification 'MITRE ATT&CK T1574.002' -Path $propPath -Value $hook -AccessGained 'User/System' -Note 'The .NET DLLs listed in the DOTNET_STARTUP_HOOKS environment variable are loaded into .NET processes at runtime.' -Reference 'https://persistence-info.github.io/Data/dotnetstartuphooks.html'
$null = $persistenceObjectArray.Add($PersistenceObject)
}

Write-Verbose -Message ''
}

function Get-SubornerAttack
{
$netUsers = net.exe users | Parse-NetUser
$poshUsers = Get-LocalUser | Select-Object Name
$diffUsers = Compare-Object -ReferenceObject $poshUsers -DifferenceObject $netUsers -Property Name
foreach ($user in $diffUsers)
{
Write-Verbose -Message "$hostname - Found hidden account with username $($user.Name)"
$PersistenceObject = New-PersistenceObject -Hostname $hostname -Technique 'Suborner Attack' -Classification 'Uncatalogued Technique N.16' -Path 'N/A' -Value $user.Name -AccessGained 'User/System' -Note 'The Suborner attack involves creating hidden users which are not shown using the "net user" command. They do not appear in the login screen or in lusrmgr.msc and can be found using the Get-LocalUser powershell cmdlet. This technique is usually paired with RID hijacking to achieve stealthy, admin level persistence.' -Reference 'https://r4wsec.com/notes/the_suborner_attack/'
$null = $persistenceObjectArray.Add($PersistenceObject)
}
Write-Verbose -Message ''
}
function Get-RidHijacking
{

Write-Verbose -Message "$hostname - Checking for RID Hijacking"
$success = ElevateTo-System
if($success -ne 0)
{
Write-Verbose -Message "$hostname - Failed to elevate privileges to SYSTEM, RID hijacking checks will be disabled as they require SYSTEM privileges."
return
}

$users = Get-LocalUser | Select-Object Name,Sid
foreach ($user in $users)
{
$userRid = ($user.Sid).ToString().Split('-')[-1]
$registryRid = '00000{0:X}' -f [int]$userRid
$byte1 = [int](Get-ItemPropertyValue -Path "HKLM:\SAM\SAM\Domains\Account\Users\$registryRid" -Name F)[0x30]
$byte2 = [int](Get-ItemPropertyValue -Path "HKLM:\SAM\SAM\Domains\Account\Users\$registryRid" -Name F)[0x31]
$hexRid = '0x{0:X}{1:X}' -f $byte2,$byte1
$decRid = [int32]$hexRid
if($decRid.ToString() -ne $userRid.ToString())
{
Write-Verbose -Message "$hostname - Found username $($user.Name) with hijacked RID $decRid"
$PersistenceObject = New-PersistenceObject -Hostname $hostname -Technique 'RID Hijacking' -Classification 'Uncatalogued Technique N.17' -Path '$user' -Value $decRid -AccessGained 'User/System' -Note 'RID hijacking allows an attacker to covertly replace the RID of a user with the RID of another user, effectively giving the first user all of the privileges of the second user. The second user is usually an Administrator, which allows the first user to gain administrator level privileges while using a non-administrator account.' -Reference 'https://pentestlab.blog/2020/02/12/persistence-rid-hijacking/'
$null = $persistenceObjectArray.Add($PersistenceObject)
}
}
$success = RevertTo-Self
if($success -ne 0)
{
Write-Verbose -Message "$hostname - Failed to revert the token to normal administrator."
}

Write-Verbose -Message ''
}

function Out-EventLog
{

Expand Down Expand Up @@ -2011,6 +2259,10 @@ function Find-AllPersistence
'Office Application Startup' = $null
'Service Control Manager Security Descriptor Manipulation' = $null
'Microsoft Office AI.exe Hijacking' = $null
'RunEx And RunOnceEx' = $null
'DotNet Startup Hooks' = $null
'RID Hijacking' = $null
'Suborner Attack' = $null
}

# Collect the keys in a separate list
Expand Down Expand Up @@ -2093,6 +2345,8 @@ function Find-AllPersistence
Get-MicrosoftOfficeAIHijacking
Get-RunExAndRunOnceEx
Get-DotNetStartupHooks
Get-SubornerAttack
Get-RidHijacking

if($IncludeHighFalsePositivesChecks.IsPresent)
{
Expand Down Expand Up @@ -2359,6 +2613,16 @@ function Find-AllPersistence
Get-DotNetStartupHooks
break
}
'RIDHijacking'
{
Get-RidHijacking
break
}
'SubornerAttack'
{
Get-SubornerAttack
break
}
}
}

Expand Down Expand Up @@ -2429,8 +2693,8 @@ function Find-AllPersistence
# SIG # Begin signature block
# MIIVlQYJKoZIhvcNAQcCoIIVhjCCFYICAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR
# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUv8SskhjkLqiPJmpEgI8DSL48
# twOgghH1MIIFbzCCBFegAwIBAgIQSPyTtGBVlI02p8mKidaUFjANBgkqhkiG9w0B
# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUAY88MwrhfgClD3rN/8nhHFZn
# wtCgghH1MIIFbzCCBFegAwIBAgIQSPyTtGBVlI02p8mKidaUFjANBgkqhkiG9w0B
# AQwFADB7MQswCQYDVQQGEwJHQjEbMBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVy
# MRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEh
# MB8GA1UEAwwYQUFBIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTIxMDUyNTAwMDAw
Expand Down Expand Up @@ -2530,17 +2794,17 @@ function Find-AllPersistence
# ZDErMCkGA1UEAxMiU2VjdGlnbyBQdWJsaWMgQ29kZSBTaWduaW5nIENBIFIzNgIR
# ANqGcyslm0jf1LAmu7gf13AwCQYFKw4DAhoFAKB4MBgGCisGAQQBgjcCAQwxCjAI
# oAKAAKECgAAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIB
# CzEOMAwGCisGAQQBgjcCARUwIwYJKoZIhvcNAQkEMRYEFDcV1wpVYOtCgd4ekcoN
# 0ViwxFRMMA0GCSqGSIb3DQEBAQUABIICAFrbBUQH7C6H+8a5D3xZoaznJZeO22CT
# KKcUssIQu1YeDn6L+v7qyKqHSpc4/HWUF11YrIn0TdIolV+aW+O4LWIpaQVUhkZu
# r8n7U1s4RbWUMnEsDnBfmOQzZWZ++HVnHQStUQOVIdNdk2YpeTx4ttIEf6VxASU5
# FYBEwzm2D6PLjFYoiUDMEilJAGZujc5eVGh1DDe0A99vIAVWmiqLHSD5TUCG5abI
# ZDsIeeZ/DHH7245YWmvDeg9hnI/U2cYxSGMQHnn6vvobdzKNp71dEgcIXLINrguz
# BMwxuCYPvahR06lLtEt0Op7dv4tonl50EanjI7aSiD1XnKTXDrNt2tsANlcDYKdp
# yUq8Kq1x2TOObZ839Fb9RPIoOcScNFHgsJzi4bde/uGsMAuyf2arrHNOVDltiE/s
# QAjrfXMHSes8odq+88MnsouzYESgxfvNWl9MMT3Roaf3nPDfWnO9GpDkFGDY/VEj
# zSFo4JuwMPk0Pn+UhAKpH3sXVf6M/27Mf1qMBDkuXOwFBEpf0RYlK0px02DtkP20
# rrDGPBeUQjbqn7OSsxwfL4VVrpZVDJw+dKlgnZp8/nY+tOGpz4TgzU3+CmAppomO
# UB89VxotemIhyn3bb/v9kqQEXEOW90vhaQmgaoQ5Kz4F490ENWBeM2VeHFQGI6lA
# mcqjmuOk7kuV
# CzEOMAwGCisGAQQBgjcCARUwIwYJKoZIhvcNAQkEMRYEFFiz1pkMOk1L/2oNAa+Q
# BQKpoYlcMA0GCSqGSIb3DQEBAQUABIICAF2TCU7e42deBuQu3ZmyBBWmXp3HsXDN
# dbmS8W+2dWe4noxg2tmwA4l62AJelZeIxlleFUUtcqWF90KUkqgTLFTnTbjUSd5a
# KNKY/TnHqZMAZG4AtxqnJS0OWCMn9ZD3pKd6IzryrylO2IukIinz5ldYG38syVMA
# 6fpsUGOwvG7SrbbtHzXrEuehPwNIy7NG4jZJMAvvPwdJtMt24NdXti8UwRat1Q3K
# ylwBVTc/ZCpBpbYMJZJSx8KiDV5ZdK9raAXOf+sIe6fqOEPpaMoCBu3ZA1B8LheI
# twQgqmH+IEHfWUofggqrZb0pPj+wXqYrEcS/ys1BxzNnknkPk5RRVUgMNOEEUfV5
# 5MxhgJ7FRpvCa8y5wbdio4Xf5dlWHY5a2md/IASbQSNW6heaguP6wLghVDJXj/sE
# dumJNVE3WbdMkPG/UMISQVs9fGKmLEV41IQ8QlEOx6ORuxXoy845ojcu/lyz2MA2
# /wa00IgoGEuMN0TEHMhOs1vnwRMN8R2uyTWHO3ojA4rPEtdpOq7b4f/i7qX8y0Cv
# pSbC3zfqcMSwr6iRRmOthn5Hz1YaHWMKFURGMWT9xXaANbVgYLGVfpNDeJM0yT9N
# t6VNmOtMqBj9M7j7ZIf8rSf+TmT6xqDEcPNOGV8QfQyCxCjfMfdLTnAoofB2Qydn
# quHxlZDEQlbI
# SIG # End signature block
Loading

0 comments on commit c965ab3

Please sign in to comment.