From 467184d6c9a073755c04c852de2c1056846fd83b Mon Sep 17 00:00:00 2001 From: Dave Crim Date: Wed, 18 Apr 2018 16:29:14 -0400 Subject: [PATCH 1/3] Updated SVCAll module and analysis files. Added modules and analysis files for Scheduled Tasks, and a series of Registry and File locations relevant to Malware persistence mechanisms documented by MITRE's ATT&CK matrix. --- ...t-PersistanceFilesAndRegistryKeysStack.ps1 | 33 ++ Analysis/asep/Get-SchedTasksAllStack.ps1 | 42 +++ Analysis/asep/Get-SvcAllStack.ps1 | 56 +-- .../Get-PersistanceFilesAndRegistryKeys.ps1 | 338 ++++++++++++++++++ Modules/ASEP/Get-SchedTasksAll.ps1 | 97 +++++ Modules/ASEP/Get-SvcAll.ps1 | 115 +++++- 6 files changed, 657 insertions(+), 24 deletions(-) create mode 100644 Analysis/asep/Get-PersistanceFilesAndRegistryKeysStack.ps1 create mode 100644 Analysis/asep/Get-SchedTasksAllStack.ps1 create mode 100644 Modules/ASEP/Get-PersistanceFilesAndRegistryKeys.ps1 create mode 100644 Modules/ASEP/Get-SchedTasksAll.ps1 diff --git a/Analysis/asep/Get-PersistanceFilesAndRegistryKeysStack.ps1 b/Analysis/asep/Get-PersistanceFilesAndRegistryKeysStack.ps1 new file mode 100644 index 00000000..db96c00e --- /dev/null +++ b/Analysis/asep/Get-PersistanceFilesAndRegistryKeysStack.ps1 @@ -0,0 +1,33 @@ +<# +.SYNOPSIS +Get-PersistanceFilesAndRegistryKeysStack.ps1 +Requires logparser.exe in path +Pulls stack rank of all Service Failures from acquired Service Failure data + +This script expects files matching the pattern *SvcFail.tsv to be in +the current working directory. +.NOTES +DATADIR PersistanceFilesAndRegistryKeys +#> + +if (Get-Command logparser.exe) { + + $lpquery = @" + SELECT + COUNT(Type) as Quantity, + Type, Set, Path, Value + FROM + *PersistanceFilesAndRegistryKeys.csv + GROUP BY + Type, Set, Path, Value + ORDER BY + Quantity ASC +"@ + + & logparser -stats:off -i:csv -o:csv $lpquery + +} +else { + $ScriptName = [System.IO.Path]::GetFileName($MyInvocation.ScriptName) + "${ScriptName} requires logparser.exe in the path." +} diff --git a/Analysis/asep/Get-SchedTasksAllStack.ps1 b/Analysis/asep/Get-SchedTasksAllStack.ps1 new file mode 100644 index 00000000..5b31121b --- /dev/null +++ b/Analysis/asep/Get-SchedTasksAllStack.ps1 @@ -0,0 +1,42 @@ +<# +.SYNOPSIS +Get-SchedTasksAllStack.ps1 +Requires logparser.exe in path +Pulls stack rank of all Service Failures from acquired Service Failure data + +This script expects files matching the pattern *SvcFail.tsv to be in +the current working directory. +.NOTES +DATADIR SchedTasksAll +#> + +if (Get-Command logparser.exe) { + + $lpquery = @" + SELECT + COUNT(*) as Quantity, + Author, + [Task To Run], + BinaryPath, + dllPath, + BinaryHash, + dllHash + FROM + *SchedTasksAll.csv + GROUP BY + Author, + [Task To Run], + BinaryPath, + dllPath, + BinaryHash, + dllHash + ORDER BY + Quantity ASC +"@ + + & logparser -stats:off -i:csv -o:csv $lpquery + +} else { + $ScriptName = [System.IO.Path]::GetFileName($MyInvocation.ScriptName) + "${ScriptName} requires logparser.exe in the path." +} diff --git a/Analysis/asep/Get-SvcAllStack.ps1 b/Analysis/asep/Get-SvcAllStack.ps1 index 436d46a2..4ef410a4 100644 --- a/Analysis/asep/Get-SvcAllStack.ps1 +++ b/Analysis/asep/Get-SvcAllStack.ps1 @@ -1,30 +1,42 @@ -<# +<# .SYNOPSIS -Get-SvcAllStack.ps1 -A basic stack for services aggregating on Caption and Pathname. -Out put is fairly ugly, it is sorted by Name, rather than count. -Sorting this way means that items with the same Caption, but -different Pathnames will be reported next to each other. Here's -an example: +Get-SvcFailStack.ps1 +Requires logparser.exe in path +Pulls stack rank of all Service Failures from acquired Service Failure data -2 HP Version Control Age... {@{Caption=HP Version Control Agent; PathName="C:\hp\hpsmh\data\cgi-bin\vcagent\vcagent.exe"}, @{Caption=HP Version Control Agent; PathName="C:\hp\hpsmh\data\cgi-bin\vcagent\vcagent.exe"}} -1 HP Version Control Age... {@{Caption=HP Version Control Agent; PathName=C:\hp\hpsmh\data\cgi-bin\vcagent\vcagent.exe}} - -Here we have scan resunts from three systems. All three have a -service named "HP Version Control Agent,", but one of them has -a pathname without double-quotes. A superficial difference. - -Get-Autorunsc.ps1 provides much of the same information, but -Get-SvcAll.ps1 shows Process Ids for running processes and tells -you which account the item is running under. +This script expects files matching the pattern *SvcFail.tsv to be in +the current working directory. .NOTES DATADIR SvcAll #> -$data = $null +if (Get-Command logparser.exe) { -foreach ($file in (ls *svcall.xml)) { - $data += Import-Clixml $file -} + $lpquery = @" + SELECT + COUNT(Name) as Quantity, + Name, + DescriptiveName, + Path, + ServiceDLL, + PathMD5Sum, + ServiceDLLMd5Sum + FROM + *SvcAll.csv + GROUP BY + Name, + DescriptiveName, + Path, + ServiceDLL, + PathMD5Sum, + ServiceDLLMd5Sum + ORDER BY + Quantity ASC +"@ -$data | Select-Object Caption, Pathname | Sort-Object Caption, Pathname | Group-Object Caption, Pathname | Sort-Object Name \ No newline at end of file + & logparser -stats:off -i:csv -o:csv $lpquery + +} else { + $ScriptName = [System.IO.Path]::GetFileName($MyInvocation.ScriptName) + "${ScriptName} requires logparser.exe in the path." +} diff --git a/Modules/ASEP/Get-PersistanceFilesAndRegistryKeys.ps1 b/Modules/ASEP/Get-PersistanceFilesAndRegistryKeys.ps1 new file mode 100644 index 00000000..91db2dff --- /dev/null +++ b/Modules/ASEP/Get-PersistanceFilesAndRegistryKeys.ps1 @@ -0,0 +1,338 @@ +<# +.SYNOPSIS +Get-SvcAll.ps1 returns information about all Windows services. + +.NOTES +The following line is required by Kansa.ps1, which uses it to determine +how to handle the output from this script. +OUTPUT CSV +#> + + +<# + .SYNOPSIS + Hash a file. + + .DESCRIPTION + Powershell introduced the official Get-FileHash in Powershell version 4 + If you have older versions, this function will compute md5 hashes + + .PARAMETER Path + The full path including drive letter to the file you want to hash. + + Example: "C:\windows\system32\lsass.exe" + + .PARAMETER Algorithm + A description of the Algorithm parameter. + + .EXAMPLE + PS C:\> Get-MyFileHash -Path 'Value1' + + .NOTES + Additional information about the function. +#> +function Get-MyFileHash { + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String]$Path, + [String]$Algorithm = 'md5' + ) + + if ($PSVersionTable.PSVersion.Major -ge 4) { + return get-filehash -Path $Path -Algorithm $Algorithm + } + try { + $file = [System.IO.File]::Open($Path, [System.IO.Filemode]::Open, [System.IO.FileAccess]::Read) + } + catch { + #Failed to open file for reading, nothing to do, just return + $return = New-Object -typename PSObject -Property @{ + Hash = "NoHashComputed" + } + return $return + } + try { + $md5 = new-object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider + $hash = ([System.BitConverter]::ToString($md5.ComputeHash($file))) -replace "-", "" + $return = New-Object -TypeName PSObject -Property @{ + Hash = $hash + } + } + finally { + $file.Dispose() + } + $return +} +<# + .SYNOPSIS + Retrieve a registry key value. + + .DESCRIPTION + Provided the hive, and path, retrieve a registry key value. + + NOTE: This function includes built-in error handling and shoud only ever return a string value indicating what happened. + I build this function this way so I didn't have to keep repeating error-handling in the script later. + This may not be the right or proper way to do error handling, but hey, it's my script. + + .PARAMETER hive + Specify which hive you want to query. + + .PARAMETER path + The path to the registry key. + + .PARAMETER key + The specific key withing the registry path that you want to query. + + .EXAMPLE + PS C:\> Get-RegistryKey -hive HKLM -path 'Value2' + + .NOTES + Additional information about the function. +#> + +#Since this is a combined search, we need a good data structure to handle the results +#Type (file/regkey) +#set (list the set/persistence mechanism this is a part of, exe: "AppCertDLL" +#path (path to file or path to registy key ex: c:\Windows\System32\lsass.exe, or hklm:\SYSTEM\ControlSet\ +#value +$Objects = @() + + + + +#Accessibility Features +#https://attack.mitre.org/wiki/Technique/T1015 +$Files = @() +$Files += "c:\Windows\System32\osk.exe" +$Files += "c:\Windows\System32\magnify.exe" +$Files += "c:\Windows\System32\narrator.exe" +$Files += "c:\Windows\System32\displayswitch.exe" +$Files += "c:\Windows\System32\atbroker.exe" +$Files += "c:\Windows\System32\sethc.exe" +$Files += "c:\Windows\System32\utilman.exe" + +$Files | % { + $file = $_ + $hash = (Get-MyFileHash -Path $file -Algorithm md5).hash + $Objects += New-Object -TypeName PSObject -Property @{ + Type = "File" + Set = "AccessibilityFeatures" + Path = $File + Value = $Hash + } +} + +#AppCert DLLs +#https://attack.mitre.org/wiki/Technique/T1182 +#AppCertDLLs value in the Registry key +$Path = "hklm:System\CurrentControlSet\Control\Session Manager\AppCertDLLs\" +if (Test-Path $Path) { + $RegItems = Get-Item $Path + $RegItems.Property | %{ + $Key = $_ + Try { + $Value = (Get-ItemProperty $Path $Key -ErrorAction Stop).$Key + } + Catch [System.Management.Automation.PSArgumentException] { + $Value = "Registry Key Property missing" + } + Catch [System.Management.Automation.ItemNotFoundException] { + $Value = "Registry Key itself is missing" + } + $SearchPath = "$Path\$Key" + $Objects += New-Object -TypeName PSObject -Property @{ + Type = "RegKey" + Set = "AppCertDlls" + Path = $SearchPath + Value = $Value + } + + } +} + + +#AppInitDLLs +#https://attack.mitre.org/wiki/Technique/T1103 +#Manager value in the Registry keys +$Paths = @("hklm:Software\Microsoft\Windows NT\CurrentVersion\Windows", + "hklm:Software\Wow6432Node\Microsoft\Windows NT\CurrentVersion\Windows") +$Key = "AppInit_DLLs" +$Paths | %{ + $Path = $_ + $SearchPath = "$Path\$Key" + Try { + $Value = (Get-ItemProperty $Path $Key -ErrorAction Stop).$Key + } + Catch [System.Management.Automation.PSArgumentException] { + $Value = "Registry Key Property missing" + } + Catch [System.Management.Automation.ItemNotFoundException] { + $Value = "Registry Key itself is missing" + } + $Objects += New-Object -TypeName PSObject -Property @{ + Type = "RegKey" + Set = "AppInitDLLs" + Path = $SearchPath + Value = $Value + } +} + + +#Authentication Package +#Adversaries can use the autostart mechanism provided by LSA Authentication Packages +#for persistence by placing a reference to a binary in the Windows Registry location +#HKLM\SYSTEM\CurrentControlSet\Control\Lsa\ with the key value of "Authentication Packages"=. +#The binary will then be executed by the system when the authentication packages are loaded. +#https://attack.mitre.org/wiki/Technique/T1131 +#HKLM\SYSTEM\CurrentControlSet\Control\Lsa\ with the key value of "Authentication Packages"= +$Path = 'HKLM:SYSTEM\CurrentControlSet\Control\Lsa\' +$Keys = @('Authentication Packages', + 'Notification Packages', + 'Security Packages') +$Keys | %{ + $Key = $_ + $SearchPath = "$Path\$Key" + Try { + $Values = (Get-ItemProperty $Path $Key -ErrorAction Stop).$Key + } + Catch [System.Management.Automation.PSArgumentException] { + $Values = "Registry Key Property missing" + } + Catch [System.Management.Automation.ItemNotFoundException] { + $Values = "Registry Key itself is missing" + } + #Potential future enhancement, split values to compare each individual value. + #For now we'll join via | to get a single string value. + #$Values -split '[, ]' | %{} + $Objects += New-Object -TypeName PSObject -Property @{ + Type = "RegKey" + Set = "AuthenticationPackage" + Path = $SearchPath + Value = $Values -join "|" + } +} + +#Security Support Provider +#Moved here because it's similar / overlapping keys +#https://attack.mitre.org/wiki/Technique/T1101 +#HKLM\SYSTEM\CurrentControlSet\Control\Lsa\Security Packages and +#The first is checked above so we'll check this one here. +#HKLM\SYSTEM\CurrentControlSet\Control\Lsa\OSConfig\Security Packages +$Path = 'HKLM:SYSTEM\CurrentControlSet\Control\Lsa\OSConfig\' +if(Test-Path $Path) { + $Keys = @('Security Packages') + $Keys | %{ + $Key = $_ + $SearchPath = "$Path\$Key" + Try { + $Values = (Get-ItemProperty $Path $Key -ErrorAction Stop).$Key + } + Catch [System.Management.Automation.PSArgumentException] { + $Values = "Registry Key Property missing" + } + Catch [System.Management.Automation.ItemNotFoundException] { + $Values = "Registry Key itself is missing" + } + + #Potential future enhancement, split values to compare each individual value. + #For now we'll join via | to get a single string value. + #$Values -split '[, ]' | %{} + $Objects += New-Object -TypeName PSObject -Property @{ + Type = "RegKey" + Set = "LsaSSP" + Path = $SearchPath + Value = $Values -join "|" + } + } +} + +#Image File Execution Options Injection +#https://attack.mitre.org/wiki/Technique/T1183 +#Debugger Values in the Registry under +#HKLM\Software\Microsoft\Windows NT\CurrentVersion\Image File Execution Options/ +#and HKLM\SOFTWARE\Wow6432Node\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\ where +$Key = "Debugger" +#$Paths = @('HKLM:Software\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\', +# 'HKLM:SOFTWARE\Wow6432Node\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\') +$Paths = @('HKLM:Software\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\') +# 'HKLM:SOFTWARE\Wow6432Node\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\') + +$Paths | %{ + $Path = $_ + $RegItems = Get-ChildItem $Path + $SearchItems = $RegItems | ?{ $_.property -eq $key } + $SearchItems | %{ + $Name = $_.Name + $SearchPath = "$Path\$($Name -replace '.*\\', '')" + Try { + $Value = (Get-ItemProperty $Path $Key -ErrorAction Stop).$Key + } + Catch [System.Management.Automation.PSArgumentException] { + $Value = "Registry Key Property missing" + } + Catch [System.Management.Automation.ItemNotFoundException] { + $Value = "Registry Key itself is missing" + } + catch { + #For some reason PS v2.0 doesn't seem to process the above catch statements + #and instead skips to this one??? I dunno. + $Value = "Unable to get registry information" + } + $Objects += New-Object -TypeName PSObject -Property @{ + Type = "RegKey" + Set = "IFEO" + Path = $SearchPath + Value = $Value + } + } +} + +#Port Monitors +#https://attack.mitre.org/wiki/Technique/T1013 +#HKLM\SYSTEM\CurrentControlSet\Control\Print\Monitors. + +$Key = "Driver" +$Paths = @('HKLM:SYSTEM\CurrentControlSet\Control\Print\Monitors\') +$Paths | %{ + $Path = $_ + $RegItems = Get-ChildItem $Path + $SearchItems = $RegItems | ?{ $_.property -eq $key } + $SearchItems | %{ + $Name = $_.Name + $SearchPath = "$Path\$($Name -replace '.*\\', '')" + Try { + $Value = (Get-ItemProperty $Path $Key -ErrorAction Stop).$Key + #the files must be located in the system32 dir + $hash = (Get-MyFileHash "$env:SystemRoot\System32\$Value").hash + $Objects += New-Object -TypeName PSObject -Property @{ + Type = "File" + Set = "PortMonitors" + Path = "$env:SystemRoot\System32\$Value" + Value = $hash + } + } + Catch [System.Management.Automation.PSArgumentException]{ + $Value = "Registry Key Property missing" + } + Catch [System.Management.Automation.ItemNotFoundException]{ + $Value = "Registry Key itself is missing" + } + catch { + #For some reason PS v2.0 doesn't seem to process the above catch statements + #and instead skips to this one??? I dunno. + $Value = "Unable to get registry information" + } + $Objects += New-Object -TypeName PSObject -Property @{ + Type = "RegKey" + Set = "PortMonitors" + Path = $SearchPath + Value = $Value + } + + } +} + +#Make the output consistent (select) and pretty (sort). +$Objects | select 'Type','Set','Path','Value' | sort 'Type','Set','Path' \ No newline at end of file diff --git a/Modules/ASEP/Get-SchedTasksAll.ps1 b/Modules/ASEP/Get-SchedTasksAll.ps1 new file mode 100644 index 00000000..b469bea1 --- /dev/null +++ b/Modules/ASEP/Get-SchedTasksAll.ps1 @@ -0,0 +1,97 @@ +<# +.SYNOPSIS +Get-SchedTasksAll.ps1 returns information about all Windows Scheduled Tasks and the associated binaries + +.NOTES +schtasks.exe output is not that good. It needs to be parsed to remove redundant headers. + +#> + +function Get-MyFileHash { + param + ( + [Parameter(Mandatory = $true)] + [String]$Path, + [String]$Algorithm + ) + if ($PSVersionTable.PSVersion.Major -ge 4) { + try { + return get-filehash -Path $Path -Algorithm $Algorithm -ErrorAction Stop + } + catch { + $return = New-Object -typename PSObject -Property @{ + Hash = "PermissionDenied" + } + return $return + } + } + try { + $file = [System.IO.File]::Open($Path, [System.IO.Filemode]::Open, [System.IO.FileAccess]::Read) + } + catch { + #Failed to open file for reading, nothing to do, just return + $return = New-Object -typename PSObject -Property @{ + Hash = "NoHashComputed" + } + return $return + } + try { + $md5 = new-object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider + $hash = ([System.BitConverter]::ToString($md5.ComputeHash($file))) -replace "-", "" + $return = New-Object -TypeName PSObject -Property @{ + Hash = $hash + } + } + finally { + $file.Dispose() + } + $return +} +# Run schtasks and convert csv to object +#schtasks returns duplicate header rows, so we'll ignore those +$tasks = (schtasks /query /FO CSV /v | ConvertFrom-Csv) | ? { $_.HostName -ne 'HostName' } + +#Parse tasks, find binaries, calculate hashes +#Ignore the "COM handler" as we can't extract useful information from those tasks +#foreach ($task in $tasks | ?{$_.'Task to Run' -ne 'COM handler') { +foreach ($task in $tasks) { + $BinaryHash = "" + try { + if ($task.'Task To Run' -match '(\w\:|%\w+%)\\([\w ]+\\)+[\w ]+(.exe|.com|.bat)') { + $BinaryPath = [System.Environment]::ExpandEnvironmentVariables($Matches[0]).ToLower() + if (Test-Path $BinaryPath -ErrorAction Stop) { + $BinaryHash = Get-MyFileHash -Path $BinaryPath -Algorithm md5 + $task | Add-Member -MemberType NoteProperty -Name BinaryHash -Value $BinaryHash.Hash + $task | Add-Member -MemberType NoteProperty -Name BinaryPath -Value $BinaryPath + } + else { + $task | Add-Member -MemberType NoteProperty -Name BinaryHash -Value "TestPath Failed" + } + #If path is rundll, extract dll name and hash + if ($task.'task to run' -match '\\rundll32.exe') { + $task.'task to run' -match '\w+\.dll' | Out-Null + $dllPath = "c:\Windows\System32\$($Matches[0])" + if (Test-Path $dllPath -ErrorAction Stop) { + $dllHash = Get-MyFileHash -Path $dllPath -Algorithm md5 + $task | Add-Member -MemberType NoteProperty -Name dllHash -Value $dllHash.Hash + $task | Add-Member -MemberType NoteProperty -Name dllPath -Value $dllPath + } + } + } + } + catch { + #Write-Error "Failed to access binary path. Likely permissions issue, or scheduled task references a binary that no longer exists" + #Write-Error $_.Exception.Message + } +} +#Specify the field order to ensure that individual computers all print out +#the fields in the exact same way so we can do analytics on them. +$fields = 'Next Run Time', 'Status', 'Logon Mode', 'Last Run Time', 'Last Result', 'Author' +$fields += 'Task To Run', 'Start In', 'Comment','Scheduled Task State', 'Run As User' +$fields += 'Schedule Type', 'Start Time', 'Start Date', 'End date', 'Days', 'Months' +$fields += 'Repeat: Every', 'Repeat: Until: Time', 'Repeat: Until: Duration', 'Repeat: Stop If Still Running' +$fields += 'BinaryPath', 'BinaryHash', 'dllPath', 'dllHash' + +$tasks | Select-Object $fields +#$tasks | select 'Author' -First 5 +#$tasks diff --git a/Modules/ASEP/Get-SvcAll.ps1 b/Modules/ASEP/Get-SvcAll.ps1 index 376ebd3d..e19f2675 100644 --- a/Modules/ASEP/Get-SvcAll.ps1 +++ b/Modules/ASEP/Get-SvcAll.ps1 @@ -1,9 +1,120 @@ <# .SYNOPSIS Get-SvcAll.ps1 returns information about all Windows services. + .NOTES The following line is required by Kansa.ps1, which uses it to determine how to handle the output from this script. -OUTPUT tsv + #> -Get-WmiObject win32_service | Select-Object Name, DisplayName, PathName, StartName, StartMode, State, TotalSessions, Description \ No newline at end of file + +#Powershell introduced the official Get-FileHash in Powershell version 4 +#If you have older versions, this function will compute md5 hashes +function Get-MyFileHash { + param + ( + [Parameter(Mandatory = $true)] + [String]$Path, + [String]$Algorithm + ) + if ($PSVersionTable.PSVersion.Major -ge 4) { + return get-filehash -Path $Path -Algorithm $Algorithm + } + try { + $file = [System.IO.File]::Open($Path, [System.IO.Filemode]::Open, [System.IO.FileAccess]::Read) + } + catch { + #Failed to open file for reading, nothing to do, just return + $return = New-Object -typename PSObject -Property @{ + Hash = "NoHashComputed" + } + return $return + } + try { + $md5 = new-object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider + $hash = ([System.BitConverter]::ToString($md5.ComputeHash($file))) -replace "-", "" + $return = New-Object -TypeName PSObject -Property @{ + Hash = $hash + } + } + finally { + $file.Dispose() + } + $return +} + +$Win32SvcObj = Get-WmiObject win32_service | Select-Object Name, DisplayName, Description, StartMode, Started, StartName, State, PathName, ProcessId, ServiceType + +$Services = @() +$Win32SvcObj | % { + try { + + #Set to "NULLSTRING" to reset the values, prevent previous loop values from bleeding over, but also to give LogParser something to parse if indeed they are blank. + $Name = $_.Name + #ToLower() to eliminate case-sensitive mis-matches later + $PathName = $_.PathName.toLower() + $ServiceDLL = "NULLSTRING" + $Path = "NULLSTRING" + $Service = "NULLSTRING" + $ServiceHash = "NULLSTRING" + $PathHash = "NULLSTRING" + if ($PathName -like "*\svchost.exe -k *") { + #Additional lookup for svchost processes to get the actual service DLL file path and hashes + ($path, $service) = $_.PathName.replace(' -k ', ',').split(',') + if (test-path "hklm:SYSTEM\CurrentControlSet\services\$Name\Parameters\") { + $ObjRegistryService = Get-Item -path "hklm:SYSTEM\CurrentControlSet\services\$Name\Parameters\" + $ServiceDLL = $ObjRegistryService.GetValue("ServiceDll") + #ToLower() to eliminate case-sensitive mis-matches later + $ServiceDLL = [Environment]::ExpandEnvironmentVariables($ServiceDLL).ToLower() + if (test-path $ServiceDLL) { + $ServiceHash = (Get-MyFileHash -Algorithm MD5 -Path $ServiceDLL).Hash + } + } + else { + #If the normal "Parameters" doesn't exist, sometimes the DLL is instead in the Descriptions field + #These values need a bit of parsing, so my preference is to use the Parameters above + if (Test-Path "hklm:SYSTEM\CurrentControlSet\services\$Name\") { + $ObjRegistryService = Get-Item -path "hklm:SYSTEM\CurrentControlSet\services\$Name" + $ServiceDLL = $ObjRegistryService.GetValue("Description") + #The data here needs cleaned up a bit + #ToLower() to eliminate case-sensitive mis-matches later + $ServiceDLL = $ServiceDLL -replace ",.*", "" -replace "@", "" + $ServiceDLL = [Environment]::ExpandEnvironmentVariables($ServiceDLL).ToLower() + if (test-path $ServiceDLL) { + $ServiceHash = (Get-MyFileHash -Algorithm MD5 -Path $ServiceDLL).Hash + } + } + } + } + $PathNameRegex = [regex]'(\".*?\")|([Cc]:\\.*?)(?:\s|$)' + #ToLower() to eliminate case-sensitive mis-matches later + $FilePath = ($PathNameRegex.match($pathname)).Captures[0].value.trim('"').ToLower() + if (test-path $FilePath) { + $PathHash = (Get-MyFileHash -Algorithm MD5 $FilePath).Hash + } + + $Services += New-Object -TypeName PSObject -Property @{ + Name = $_.Name + DescriptiveName = $_.DisplayName + Description = $_.Description + Mode = $_.StartMode + Started = $_.Started + StartedAs = $_.StartName + Status = $_.State + Path = $PathName + ServiceDLL = $ServiceDLL + PID = $_.ProcessID + Type = $_.ServiceType + PathMD5Sum = $PathHash + ServiceDLLMD5Sum = $ServiceHash + } + } + catch { + Write-Error "Error gathering information for Name:$Name Path:$PathName\n" + Write-Error $_.Exception.Message + } +} +#pipe select is necessary to ensure output in consistent column order across all files / computers +#not sure why powershell doesn't do thus but apparently hash arrays like listed above don't necessarily all spit info out the +#same order as specified in the source code. +$Services | select Name, DescriptiveName, Description, Mode, Started, StartedAs, Status, Path, ServiceDLL, PID, Type, PathMD5Sum, ServiceDLLMd5Sum From d46f7bd37b5b8f4979ce2da4bbebbaa641cbe82c Mon Sep 17 00:00:00 2001 From: Dave Crim Date: Mon, 30 Apr 2018 08:36:15 -0400 Subject: [PATCH 2/3] Commit Fix: Removed output directives, fixed spelling errors and some comments. --- ...> Get-PersistenceFilesAndRegistryKeys.ps1} | 42 +++---------------- Modules/ASEP/Get-SvcAll.ps1 | 5 --- 2 files changed, 5 insertions(+), 42 deletions(-) rename Modules/ASEP/{Get-PersistanceFilesAndRegistryKeys.ps1 => Get-PersistenceFilesAndRegistryKeys.ps1} (87%) diff --git a/Modules/ASEP/Get-PersistanceFilesAndRegistryKeys.ps1 b/Modules/ASEP/Get-PersistenceFilesAndRegistryKeys.ps1 similarity index 87% rename from Modules/ASEP/Get-PersistanceFilesAndRegistryKeys.ps1 rename to Modules/ASEP/Get-PersistenceFilesAndRegistryKeys.ps1 index 91db2dff..1116b434 100644 --- a/Modules/ASEP/Get-PersistanceFilesAndRegistryKeys.ps1 +++ b/Modules/ASEP/Get-PersistenceFilesAndRegistryKeys.ps1 @@ -1,14 +1,12 @@ <# .SYNOPSIS -Get-SvcAll.ps1 returns information about all Windows services. +Get-PersistenceFilesAndRegistryKeys.ps1 gathers information about various files and registry keys +derived from MITRE's Att&ck matrix data on Persistence mechanisms. They are broken down in to +sections which are included in the data returned so they can be broken up and analyzed +seperatly. -.NOTES -The following line is required by Kansa.ps1, which uses it to determine -how to handle the output from this script. -OUTPUT CSV #> - <# .SYNOPSIS Hash a file. @@ -65,38 +63,8 @@ function Get-MyFileHash { } $return } -<# - .SYNOPSIS - Retrieve a registry key value. - - .DESCRIPTION - Provided the hive, and path, retrieve a registry key value. - - NOTE: This function includes built-in error handling and shoud only ever return a string value indicating what happened. - I build this function this way so I didn't have to keep repeating error-handling in the script later. - This may not be the right or proper way to do error handling, but hey, it's my script. - - .PARAMETER hive - Specify which hive you want to query. - - .PARAMETER path - The path to the registry key. - - .PARAMETER key - The specific key withing the registry path that you want to query. - - .EXAMPLE - PS C:\> Get-RegistryKey -hive HKLM -path 'Value2' - - .NOTES - Additional information about the function. -#> -#Since this is a combined search, we need a good data structure to handle the results -#Type (file/regkey) -#set (list the set/persistence mechanism this is a part of, exe: "AppCertDLL" -#path (path to file or path to registy key ex: c:\Windows\System32\lsass.exe, or hklm:\SYSTEM\ControlSet\ -#value +#data object to hold information until we're ready to return the results $Objects = @() diff --git a/Modules/ASEP/Get-SvcAll.ps1 b/Modules/ASEP/Get-SvcAll.ps1 index e19f2675..b61ea940 100644 --- a/Modules/ASEP/Get-SvcAll.ps1 +++ b/Modules/ASEP/Get-SvcAll.ps1 @@ -1,11 +1,6 @@ <# .SYNOPSIS Get-SvcAll.ps1 returns information about all Windows services. - -.NOTES -The following line is required by Kansa.ps1, which uses it to determine -how to handle the output from this script. - #> #Powershell introduced the official Get-FileHash in Powershell version 4 From f6a300a2c92a74774e23d3b1be29a5dfa5e3e984 Mon Sep 17 00:00:00 2001 From: athegist <15118322+athegist@users.noreply.github.com> Date: Sat, 25 Jan 2020 13:58:01 -0600 Subject: [PATCH 3/3] Update and rename Get-PersistanceFilesAndRegistryKeysStack.ps1 to Get-PersistenceFilesAndRegistryKeysStack.ps1 --- ...Stack.ps1 => Get-PersistenceFilesAndRegistryKeysStack.ps1} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename Analysis/asep/{Get-PersistanceFilesAndRegistryKeysStack.ps1 => Get-PersistenceFilesAndRegistryKeysStack.ps1} (88%) diff --git a/Analysis/asep/Get-PersistanceFilesAndRegistryKeysStack.ps1 b/Analysis/asep/Get-PersistenceFilesAndRegistryKeysStack.ps1 similarity index 88% rename from Analysis/asep/Get-PersistanceFilesAndRegistryKeysStack.ps1 rename to Analysis/asep/Get-PersistenceFilesAndRegistryKeysStack.ps1 index db96c00e..11357a1e 100644 --- a/Analysis/asep/Get-PersistanceFilesAndRegistryKeysStack.ps1 +++ b/Analysis/asep/Get-PersistenceFilesAndRegistryKeysStack.ps1 @@ -1,6 +1,6 @@ <# .SYNOPSIS -Get-PersistanceFilesAndRegistryKeysStack.ps1 +Get-PersistenceFilesAndRegistryKeysStack.ps1 Requires logparser.exe in path Pulls stack rank of all Service Failures from acquired Service Failure data @@ -17,7 +17,7 @@ if (Get-Command logparser.exe) { COUNT(Type) as Quantity, Type, Set, Path, Value FROM - *PersistanceFilesAndRegistryKeys.csv + *PersistenceFilesAndRegistryKeys.csv GROUP BY Type, Set, Path, Value ORDER BY