Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
241 changes: 223 additions & 18 deletions PersistenceSniper/PersistenceSniper.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,8 @@ function Find-AllPersistence {
'BootExecute',
'NetshHelperDLL',
'SetupExecute',
'PlatformExecute'
'PlatformExecute',
'LibraryAbuseCOM'
)]
$PersistenceMethod = 'All',

Expand Down Expand Up @@ -397,7 +398,85 @@ function Find-AllPersistence {
return $false
}
}

function Get-APPID {
param(
[String]
$RootPath = $null,
[String]
$AppCLSID = $null
)

$appName = ""

if ($null -ne $AppCLSID) {
$appIDList = Get-ItemProperty "$RootPath\Software\Classes\appid\*"
foreach ($app in $appIDList) {
if ($null -ne $app.AppID -and $app.AppID -eq $AppCLSID) {
$appName = $app.PsChildName
break
}
}
}
return $appName
}
function Get-CLSIDPayload {
param(
[String]
$RootPath = $null,
[String]
$CLSID = $null
)

$dll = ""

$registryKey = (Get-ItemProperty "$rootPath\Software\Classes\CLSID\$CLSID\InprocServer32")
if ($null -eq $registryKey) {
$registryKey = (Get-ItemProperty "$rootPath\Software\Classes\CLSID\$CLSID\LocalServer32")
}
if ($null -eq $registryKey) {
$registryKey = (Get-ItemProperty "Registry::HKEY_LOCAL_MACHINE\Software\Classes\CLSID\$CLSID\InprocServer32")
}
if ($null -eq $registryKey) {
$registryKey = (Get-ItemProperty "Registry::HKEY_LOCAL_MACHINE\Software\Classes\CLSID\$CLSID\LocalServer32")
}

if ($null -eq $registryKey) {
$appName = ""
$AppId = (Get-ItemProperty "$rootPath\Software\Classes\CLSID\$CLSID")."AppId"
if ($null -ne $AppId) {
if (-not $AppId.Contains("{")) {
$AppId = "{$AppId}"
}
$appName = Get-APPID -RootPath $rootPath -CLSID $AppId

}
else {
$AppId = (Get-ItemProperty "Registry::HKEY_LOCAL_MACHINE\Software\Classes\CLSID\$CLSID")."AppId"
if ($null -ne $AppId) {
if (-not $AppId.Contains("{")) {
$AppId = "{$AppId}"
}
$appName = Get-APPID -RootPath "Registry::HKEY_LOCAL_MACHINE" -AppCLSID $AppId
}
}

if ($appName -ne "") {
$dll = $appName
}

}

else {
$dll = $registryKey.'(default)'

if ($null -ne $dll) {
if ($dll.EndsWith("mscoree.dll") -and $null -ne $registryKey.'CodeBase' -and $registryKey.'CodeBase' -ne "") {
$dll = $registryKey.'CodeBase'
}
}
}
return $dll
}
function Parse-NetUser {
<#
.SYNOPSIS
Expand Down Expand Up @@ -1628,25 +1707,99 @@ function Find-AllPersistence {
}

function Get-ScheduledTasks {

Write-Verbose -Message "$hostname - Getting scheduled tasks"
$tasks = Get-ScheduledTask
if ($tasks) {
foreach ($task in $tasks) {
$propPath = $task.TaskPath
$propPath += $task.TaskName
$path = ($task.Actions).Execute + " " + ($task.Actions).Arguments
if ($task.UserId -eq 'SYSTEM') {

$tasksPath = "$env:windir\System32\Tasks"

$taskFileList = Get-ChildItem -Path $tasksPath -Recurse | Where-Object { ! $_.PSIsContainer }

$ErrorActionPreference = "SilentlyContinue"

foreach ($taskFile in $taskFileList) {

[xml] $xmlTask = Get-Content $taskFile.FullName

$propPath = $taskFile.FullName.Replace($tasksPath, "")

if ($null -ne $xmlTask.Task.Principals.Principal.UserId) {
$userID = $xmlTask.Task.Principals.Principal.UserId
if (($userID -eq 'SYSTEM') -or ($userID -eq 'S-1-5-18') -or ($userID -eq 'S-1-5-19') -or ($userID -eq 'S-1-5-20')) {
$access = 'System'
}
else {
$access = 'User'
}
}
elseif ($null -ne $xmlTask.Task.Principals.Principal.GroupId) {
$groupID = $xmlTask.Task.Principals.Principal.GroupId
if (($groupID -eq 'SYSTEM') -or ($groupID -eq 'S-1-5-18') -or ($groupID -eq 'S-1-5-19') -or ($groupID -eq 'S-1-5-20')) {
$access = 'System'
}
else {
$access = 'User'
}
}
else {
$access = "Unknow"
}

if ($null -ne $xmlTask.Task.Actions.Exec.Command) {
$command = $xmlTask.Task.Actions.Exec.Command
$value = $command + " " + $xmlTask.Task.Actions.Exec.Arguments
}

elseif ($null -ne $xmlTask.Task.Actions.ComHandler.ClassId) {
$ClassId = $xmlTask.Task.Actions.ComHandler.ClassId
if (-not $ClassId.Contains("{")) {
$ClassId = "{$ClassId}"
}

$PersistenceObject = New-PersistenceObject -Hostname $hostname -Technique 'Scheduled Task' -Classification 'MITRE ATT&CK T1053.005' -Path $propPath -Value $path -AccessGained $access -Note "Scheduled tasks run executables or actions when certain conditions, such as user log in or machine boot up, are met." -Reference 'https://attack.mitre.org/techniques/T1053/005/'
$null = $persistenceObjectArray.Add($PersistenceObject)
}
$rootPath = $null

if ($access -eq "System") {
$rootPath = "Registry::HKEY_LOCAL_MACHINE"
}

elseif ($null -ne $userID) {
if ($userID.Contains("\")) {
$domain, $username = $userID.Split("\", 2)
$objUser = New-Object System.Security.Principal.NTAccount($domain, $username)
$userID = $objUser.Translate([System.Security.Principal.SecurityIdentifier]).Value
}
$rootPath = "Registry::HKEY_USERS\$userID"

}
elseif ($null -ne $groupID) {
if ($groupID.Contains("\")) {
$domain, $username = $userID.Split("\", 2)
$objUser = New-Object System.Security.Principal.NTAccount($domain, $username)
$groupID = $objUser.Translate([System.Security.Principal.SecurityIdentifier]).Value
}
$rootPath = "Registry::HKEY_USERS\$groupID"
}

if ($null -ne $rootPath) {
$dll = Get-CLSIDPayload -RootPath $rootPath -CLSID $ClassId
if ($dll -eq "") {
$value = $ClassId
}
else {
$value = $dll
}
}

else {
$value = "Unknow"
}
}

$PersistenceObject = New-PersistenceObject -Hostname $hostname -Technique 'Scheduled Task' -Classification 'MITRE ATT&CK T1053.005' -Path $propPath -Value $value -AccessGained $access -Note "Scheduled tasks run executables or actions when certain conditions, such as user log in or machine boot up, are met." -Reference 'https://attack.mitre.org/techniques/T1053/005/'
$null = $persistenceObjectArray.Add($PersistenceObject)

}
Write-Verbose -Message ''

}

function Get-BitsJobsNotifyCmdLine {
Expand Down Expand Up @@ -2141,6 +2294,57 @@ function Find-AllPersistence {
Write-Verbose -Message ''
}

function Get-LibraryAbuseCOM {
Write-Verbose -Message "$hostname - Checking if users' start menu folder contains .library-ms or junction folder artifacts..."
$userDirectories = Get-ChildItem -Path "$env:SystemDrive:\Users\"
foreach ($directory in $userDirectories) {
$fullPath = $directory.FullName
$starMenuDirectory = Get-ChildItem -Path "$fullPath\AppData\Roaming\Microsoft\Windows\Start Menu\" -Recurse -ErrorAction SilentlyContinue
foreach ($entry in $starMenuDirectory) {
$CLSID = $null
$entryName = $entry.Name
$entryPath = $entry.FullName

$match = [regex]::Match($entryName, "\.\{[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}\}", [Text.RegularExpressions.RegexOptions]::IgnoreCase)
if ($entry.PSIsContainer -and $match.Success) {
Write-Verbose -Message "$hostname - [!] Found a folder: $entryPath!"
$matchPattern = $match.Value
$CLSID = $matchPattern.Substring(1, $matchPattern.length-1)
}

if (!$entry.PSIsContainer -and $entryName.ToLower().EndsWith('.library-ms')) {
$fileContent = Get-Content -Path $entryPath
$match = [regex]::Match($fileContent, "\<url\>.*(shell:::|knownfolder:::|\.)\{[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}\}\<\/url\>", [Text.RegularExpressions.RegexOptions]::IgnoreCase)
if ($match.Success) {
Write-Verbose -Message "$hostname - [!] Found a file: $entryPath!"
$matchPattern = $match.Value
$clsidMatch = [regex]::Match($matchPattern, "\{[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}\}", [Text.RegularExpressions.RegexOptions]::IgnoreCase)
$CLSID = $clsidMatch.Value
}
}
if ($null -ne $CLSID) {
$objUser = New-Object System.Security.Principal.NTAccount($hostname, $directory.Name)
$userID = $objUser.Translate([System.Security.Principal.SecurityIdentifier]).Value
if ($null -ne $userID -or $userID -ne "") {
$rootPath = "Registry::HKEY_USERS\$userID"
}
else {
$rootPath = "Registry::HKEY_LOCAL_MACHINE"
}
$dll = Get-CLSIDPayload -RootPath $rootPath -CLSID $CLSID
if ($dll -ne "") {
$value = $dll
}
else {
$value = $CLSID
}
$PersistenceObject = New-PersistenceObject -Hostname $hostname -Technique 'Library Abuse COM' -Classification 'MITRE ATT&CK T1546.015' -Path "$entryPath" -Value "$value" -AccessGained 'User' -Note "The library file or junction folder under .\AppData\Roaming\Microsoft\Windows\Start Menu\ in a user's folder is accessed by explorer.exe every time that user logs in and interacts with the Start Menu. This behavior could be abused to achieve persistence using the Component Object Model." -Reference 'https://attack.mitre.org/techniques/T1546/015/'
$null = $persistenceObjectArray.Add($PersistenceObject)
}
}
}
Write-Verbose -Message ''
}

function Out-EventLog {

Expand Down Expand Up @@ -2214,6 +2418,7 @@ function Find-AllPersistence {
'NetshHelperDLL' = $null
'SetupExecute' = $null
'PlatformExecute' = $null
'LibraryAbuseCOM' = $null
}

# Collect the keys in a separate list
Expand Down Expand Up @@ -2305,7 +2510,8 @@ function Find-AllPersistence {
Get-NetshHelperDLL
Get-SetupExecute
Get-PlatformExecute

Get-LibraryAbuseCOM

if ($IncludeHighFalsePositivesChecks.IsPresent) {
Write-Verbose -Message "$hostname - You have used the -IncludeHighFalsePositivesChecks switch, this may generate a lot of false positives since it includes checks with results which are difficult to filter programmatically..."
Get-AppPaths
Expand Down Expand Up @@ -2468,11 +2674,6 @@ function Find-AllPersistence {
Get-RDPWDSStartupPrograms
break
}
'ScheduledTasks' {
Get-ScheduledTasks
break
}

'Screensaver' {
Get-Screensaver
break
Expand Down Expand Up @@ -2558,6 +2759,10 @@ function Find-AllPersistence {
Get-PlatformExecute
break
}
'LibraryAbuseCOM' {
Get-LibraryAbuseCOM
break
}
}
}

Expand Down