diff --git a/CimSweep/ArtifactRetrieval/AppCompatCache.ps1 b/CimSweep/ArtifactRetrieval/AppCompatCache.ps1 new file mode 100644 index 0000000..b189aa5 --- /dev/null +++ b/CimSweep/ArtifactRetrieval/AppCompatCache.ps1 @@ -0,0 +1,353 @@ +function Get-CSAppCompatCache { +<# +.SYNOPSIS + +Retrieves and parses entries from the AppCompatCache based on OS version. + +Author: Jesse Davis (@secabstraction) +License: BSD 3-Clause + +.DESCRIPTION + +Get-CSAppCompatCache parses entries from the Application Compatibility Cache stored in the registry. + +.PARAMETER CimSession + +Specifies the CIM session to use for this cmdlet. Enter a variable that contains the CIM session or a command that creates or gets the CIM session, such as the New-CimSession or Get-CimSession cmdlets. For more information, see about_CimSessions. + +.EXAMPLE + +Get-CSAppCompatCache + +.EXAMPLE + +Get-CSAppCompatCache -CimSession $CimSession + +.OUTPUTS + +CimSweep.AppCompatCacheEntry + +Outputs objects consisting of the application's file path and that file's last modified time. Note: the LastModified property is a UTC datetime string in Round-trip format. + +#> + + [CmdletBinding()] + [OutputType('CimSweep.AppCompatCacheEntry')] + param ( + [Alias('Session')] + [ValidateNotNullOrEmpty()] + [Microsoft.Management.Infrastructure.CimSession[]] + $CimSession + ) + + begin { + # If a CIM session is not provided, trick the function into thinking there is one. + if (-not $PSBoundParameters['CimSession']) { + $CimSession = '' + $CIMSessionCount = 1 + } else { + $CIMSessionCount = $CimSession.Count + } + + $CurrentCIMSession = 0 + } + + process { + foreach ($Session in $CimSession) { + $ComputerName = $Session.ComputerName + if (-not $Session.ComputerName) { $ComputerName = 'localhost' } + + # Display a progress activity for each CIM session + Write-Progress -Id 1 -Activity 'CimSweep - AppCompatCache sweep' -Status "($($CurrentCIMSession+1)/$($CIMSessionCount)) Current computer: $ComputerName" -PercentComplete (($CurrentCIMSession / $CIMSessionCount) * 100) + $CurrentCIMSession++ + + $CommonArgs = @{} + + if ($Session.Id) { $CommonArgs['CimSession'] = $Session } + + $OS = Get-CimInstance -ClassName Win32_OperatingSystem @CommonArgs + + if ($OS.Version -like "5.1*") { + $Parameters = @{ + Hive = 'HKLM' + SubKey = 'SYSTEM\CurrentControlSet\Control\Session Manager\AppCompatibility' + } + } + else { + $Parameters = @{ + Hive = 'HKLM' + SubKey = 'SYSTEM\CurrentControlSet\Control\Session Manager\AppCompatCache' + ValueName = 'AppCompatCache' + } + } + + $AppCompatCacheValue = Get-CSRegistryValue @Parameters @CommonArgs + ConvertFrom-ByteArray -CacheValue $AppCompatCacheValue -OSVersion $OS.Version -OSArchitecture $OS.OSArchitecture + } + } + end {} +} + + +function ConvertFrom-ByteArray { +<# +.SYNOPSIS + +Converts bytes from the AppCompatCache registry key into objects. + +Author: Jesse Davis (@secabstraction) +License: BSD 3-Clause + +Thanks to @ericrzimmerman for these test files https://github.com/EricZimmerman/AppCompatCacheParser/tree/master/AppCompatCacheParserTest/TestFiles + +.DESCRIPTION + +ConvertFrom-ByteArray converts bytes from the AppCompatCache registry key into objects. + +.PARAMETER CacheValue + +Byte array from the AppCompatCache registry key. + +.PARAMETER OSVersion + +Specifies the operating system version from which the AppCompatCache bytes were retrieved. + +.PARAMETER OSArchitecture + +Specifies the bitness of the operating system from which the AppCompatCache bytes were retrieved. + +.EXAMPLE + +ConvertFrom-ByteArray -CacheBytes $AppCompatCacheKeyBytes -OSVersion 6.1 -OSArchitecture 32-bit +#> + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [Object] + $CacheValue, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string] + $OSVersion, + + [Parameter()] + [string] + $OSArchitecture + ) + + $BinaryReader = New-Object IO.BinaryReader (New-Object IO.MemoryStream (,$CacheValue.ValueContent)) + + $ASCIIEncoding = [Text.Encoding]::ASCII + $UnicodeEncoding = [Text.Encoding]::Unicode + + switch ($OSVersion) { + + { $_ -like '10.*' } { # Windows 10 + + $null = $BinaryReader.BaseStream.Seek(48, [IO.SeekOrigin]::Begin) + + # check for magic + if ($ASCIIEncoding.GetString($BinaryReader.ReadBytes(4)) -ne '10ts') { + $null = $BinaryReader.BaseStream.Seek(52, [IO.SeekOrigin]::Begin) # offset shifted in creators update + if ($ASCIIEncoding.GetString($BinaryReader.ReadBytes(4)) -ne '10ts') { throw 'Not Windows 10' } + } + + do { # parse entries + $null = $BinaryReader.BaseStream.Seek(8, [IO.SeekOrigin]::Current) # padding between entries + + $Path = $UnicodeEncoding.GetString($BinaryReader.ReadBytes($BinaryReader.ReadUInt16())) + $LastModifiedTime = [DateTimeOffset]::FromFileTime($BinaryReader.ReadInt64()).DateTime + + $null = $BinaryReader.ReadBytes($BinaryReader.ReadInt32()) # skip some bytes + + $ObjectProperties = [ordered] @{ + PSTypeName = 'CimSweep.AppCompatCacheEntry' + Path = $Path + LastModifiedTime = $LastModifiedTime.ToUniversalTime().ToString('o') + } + + if ($CacheValue.PSComputerName) { $ObjectProperties['PSComputerName'] = $CacheValue.PSComputerName } + [PSCustomObject]$ObjectProperties + + } until ($ASCIIEncoding.GetString($BinaryReader.ReadBytes(4)) -ne '10ts') + } + + { $_ -like '6.3*' } { # Windows 8.1 / Server 2012 R2 + + $null = $BinaryReader.BaseStream.Seek(128, [IO.SeekOrigin]::Begin) + + # check for magic + if ($ASCIIEncoding.GetString($BinaryReader.ReadBytes(4)) -ne '10ts') { throw 'Not windows 8.1/2012r2' } + + do { # parse entries + $null = $BinaryReader.BaseStream.Seek(8, [IO.SeekOrigin]::Current) # padding & datasize + + $Path = $UnicodeEncoding.GetString($BinaryReader.ReadBytes($BinaryReader.ReadUInt16())) + + $null = $BinaryReader.ReadBytes(10) # skip insertion/shim flags & padding + + $LastModifiedTime = [DateTimeOffset]::FromFileTime($BinaryReader.ReadInt64()).DateTime + + $null = $BinaryReader.ReadBytes($BinaryReader.ReadInt32()) # skip some bytes + + $ObjectProperties = [ordered] @{ + PSTypeName = 'CimSweep.AppCompatCacheEntry' + Path = $Path + LastModifiedTime = $LastModifiedTime.ToUniversalTime().ToString('o') + } + + if ($CacheValue.PSComputerName) { $ObjectProperties['PSComputerName'] = $CacheValue.PSComputerName } + [PSCustomObject]$ObjectProperties + + } until ($ASCIIEncoding.GetString($BinaryReader.ReadBytes(4)) -ne '10ts') + } + + { $_ -like '6.2*' } { # Windows 8.0 / Server 2012 + + # check for magic + $null = $BinaryReader.BaseStream.Seek(128, [IO.SeekOrigin]::Begin) + if ($ASCIIEncoding.GetString($BinaryReader.ReadBytes(4)) -ne '00ts') { throw 'Not Windows 8/2012' } + + do { # parse entries + $null = $BinaryReader.BaseStream.Seek(8, [IO.SeekOrigin]::Current) # padding & datasize + + $Path = $UnicodeEncoding.GetString($BinaryReader.ReadBytes($BinaryReader.ReadUInt16())) + + $null = $BinaryReader.BaseStream.Seek(10, [IO.SeekOrigin]::Current) # skip insertion/shim flags & padding + + $LastModifiedTime = [DateTimeOffset]::FromFileTime($BinaryReader.ReadInt64()).DateTime + + $null = $BinaryReader.ReadBytes($BinaryReader.ReadInt32()) # skip some bytes + + $ObjectProperties = [ordered] @{ + PSTypeName = 'CimSweep.AppCompatCacheEntry' + Path = $Path + LastModifiedTime = $LastModifiedTime.ToUniversalTime().ToString('o') + } + + if ($CacheValue.PSComputerName) { $ObjectProperties['PSComputerName'] = $CacheValue.PSComputerName } + [PSCustomObject]$ObjectProperties + + } until ($ASCIIEncoding.GetString($BinaryReader.ReadBytes(4)) -ne '00ts') + } + + { $_ -like '6.1*' } { # Windows 7 / Server 2008 R2 + + # check for magic + if ([BitConverter]::ToString($BinaryReader.ReadBytes(4)[3..0]) -ne 'BA-DC-0F-EE') { throw 'Not Windows 7/2008R2'} + + $NumberOfEntries = $BinaryReader.ReadInt32() + + $null = $BinaryReader.BaseStream.Seek(128, [IO.SeekOrigin]::Begin) # skip padding + + if ($OSArchitecture -eq '32-bit') { + + do { + $EntryPosition++ + + $PathSize = $BinaryReader.ReadUInt16() + + $null = $BinaryReader.ReadUInt16() # MaxPathSize + + $PathOffset = $BinaryReader.ReadInt32() + + $LastModifiedTime = [DateTimeOffset]::FromFileTime($BinaryReader.ReadInt64()).DateTime + + $null = $BinaryReader.BaseStream.Seek(16, [IO.SeekOrigin]::Current) + + $Position = $BinaryReader.BaseStream.Position + + $null = $BinaryReader.BaseStream.Seek($PathOffset, [IO.SeekOrigin]::Begin) + + $Path = $UnicodeEncoding.GetString($BinaryReader.ReadBytes($PathSize)) + + $null = $BinaryReader.BaseStream.Seek($Position, [IO.SeekOrigin]::Begin) + + $ObjectProperties = [ordered] @{ + PSTypeName = 'CimSweep.AppCompatCacheEntry' + Path = $Path + LastModifiedTime = $LastModifiedTime.ToUniversalTime().ToString('o') + } + + if ($CacheValue.PSComputerName) { $ObjectProperties['PSComputerName'] = $CacheValue.PSComputerName } + [PSCustomObject]$ObjectProperties + + } until ($EntryPosition -eq $NumberOfEntries) + } + + else { # 64-bit + + do { + $EntryPosition++ + + $PathSize = $BinaryReader.ReadUInt16() + + # Padding + $null = $BinaryReader.BaseStream.Seek(6, [IO.SeekOrigin]::Current) + + $PathOffset = $BinaryReader.ReadInt64() + $LastModifiedTime = [DateTimeOffset]::FromFileTime($BinaryReader.ReadInt64()).DateTime + + $null = $BinaryReader.BaseStream.Seek(24, [IO.SeekOrigin]::Current) + + $Position = $BinaryReader.BaseStream.Position + + $null = $BinaryReader.BaseStream.Seek($PathOffset, [IO.SeekOrigin]::Begin) + + $Path = $UnicodeEncoding.GetString($BinaryReader.ReadBytes($PathSize)) + + $null = $BinaryReader.BaseStream.Seek($Position, [IO.SeekOrigin]::Begin) + + $ObjectProperties = [ordered] @{ + PSTypeName = 'CimSweep.AppCompatCacheEntry' + Path = $Path + LastModifiedTime = $LastModifiedTime.ToUniversalTime().ToString('o') + } + + if ($CacheValue.PSComputerName) { $ObjectProperties['PSComputerName'] = $CacheValue.PSComputerName } + [PSCustomObject]$ObjectProperties + + } until ($EntryPosition -eq $NumberOfEntries) + } + } + + { $_ -like '6.0*' } { <# Windows Vista / Server 2008 #> } + + { $_ -like '5.2*' } { <# Windows XP Pro 64-bit / Server 2003 (R2) #> } + + { $_ -like '5.1*' } { # Windows XP 32-bit + + # check for magic + if ([BitConverter]::ToString($BinaryReader.ReadBytes(4)[3..0]) -ne 'DE-AD-BE-EF') { throw 'Not Windows XP 32-bit'} + + $NumberOfEntries = $BinaryReader.ReadInt32() # this is always 96, even if there aren't 96 entries + + $null = $BinaryReader.BaseStream.Seek(400, [IO.SeekOrigin]::Begin) # skip padding + + do { # parse entries + $EntryPosition++ + $Path = $UnicodeEncoding.GetString($BinaryReader.ReadBytes(528)).TrimEnd("`0") # 528 == MAX_PATH + 4 unicode chars + $LastModifiedTime = [DateTimeOffset]::FromFileTime($BinaryReader.ReadInt64()).DateTime + + if (($LastModifiedTime.Year -eq 1600) -and !$Path) { break } # empty entries == end + + $null = $BinaryReader.BaseStream.Seek(16, [IO.SeekOrigin]::Current) # skip some bytes + + $ObjectProperties = [ordered] @{ + PSTypeName = 'CimSweep.AppCompatCacheEntry' + Path = $Path + LastModifiedTime = $LastModifiedTime.ToUniversalTime().ToString('o') + } + + if ($CacheValue.PSComputerName) { $ObjectProperties['PSComputerName'] = $CacheValue.PSComputerName } + [PSCustomObject]$ObjectProperties + + } until ($EntryPosition -eq $NumberOfEntries) + } + } + $BinaryReader.BaseStream.Dispose() + $BinaryReader.Dispose() +} + +Export-ModuleMember -Function Get-CSAppCompatCache \ No newline at end of file diff --git a/CimSweep/ArtifactRetrieval/AppCompatDatabases.ps1 b/CimSweep/ArtifactRetrieval/AppCompatDatabases.ps1 index bfa0e31..d4bfe79 100644 --- a/CimSweep/ArtifactRetrieval/AppCompatDatabases.ps1 +++ b/CimSweep/ArtifactRetrieval/AppCompatDatabases.ps1 @@ -67,8 +67,11 @@ Outputs objects representing the relevant information regarding installed applic Get-CSRegistryValue -ValueNameOnly | Group-Object -Property ValueName -AsHashTable $InstalledSdb = Get-CSRegistryKey -Hive HKLM -SubKey 'SOFTWARE\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\InstalledSdb' @CommonArgs + if (-not $InstalledSdb) { continue } + if ($InstalledSdb.GetType() -isnot [System.Array]) { $InstalledSdb = @($InstalledSdb)} + $CurrentSdb = 0 - + foreach ($Database in $InstalledSdb) { $GUID = $Database.SubKey.Split('\')[-1] diff --git a/CimSweep/ArtifactRetrieval/NetworkProfiles.ps1 b/CimSweep/ArtifactRetrieval/NetworkProfiles.ps1 new file mode 100644 index 0000000..b361550 --- /dev/null +++ b/CimSweep/ArtifactRetrieval/NetworkProfiles.ps1 @@ -0,0 +1,150 @@ +function Get-CSNetworkProfile { +<# +.SYNOPSIS + +Retrieves network profile information. + +Author: Jesse Davis (@secabstraction) +License: BSD 3-Clause + +.DESCRIPTION + +Get-CSNetworkProfile retrieves and parses network profile information stored in the registry. + +.PARAMETER CimSession + +Specifies the CIM session to use for this cmdlet. Enter a variable that contains the CIM session or a command that creates or gets the CIM session, such as the New-CimSession or Get-CimSession cmdlets. For more information, see about_CimSessions. + +.EXAMPLE + +Get-CSNetworkProfile + +.EXAMPLE + +Get-CSNetworkProfile -CimSession $CimSession + +.OUTPUTS + +CimSweep.NetworkProfile + +Outputs objects consisting of relevant network profile information. Note: the timestamps of this object are a UTC datetime string in Round-trip format. + +#> + + [CmdletBinding()] + [OutputType('CimSweep.NetworkProfile')] + param ( + [Alias('Session')] + [ValidateNotNullOrEmpty()] + [Microsoft.Management.Infrastructure.CimSession[]] + $CimSession + ) + + begin { + # If a CIM session is not provided, trick the function into thinking there is one. + if (-not $PSBoundParameters['CimSession']) { + $CimSession = '' + $CIMSessionCount = 1 + } else { + $CIMSessionCount = $CimSession.Count + } + + $CurrentCIMSession = 0 + } + + process { + foreach ($Session in $CimSession) { + $ComputerName = $Session.ComputerName + if (-not $Session.ComputerName) { $ComputerName = 'localhost' } + + # Display a progress activity for each CIM session + Write-Progress -Id 1 -Activity 'CimSweep - Network Profile sweep' -Status "($($CurrentCIMSession+1)/$($CIMSessionCount)) Current computer: $ComputerName" -PercentComplete (($CurrentCIMSession / $CIMSessionCount) * 100) + $CurrentCIMSession++ + + $CommonArgs = @{} + + if ($Session.Id) { $CommonArgs['CimSession'] = $Session } + + $Parameters = @{ + Hive = 'HKLM' + SubKey = 'SYSTEM\CurrentControlSet\Control\TimeZoneInformation' + ValueName = 'TimeZoneKeyName' + ValueType = 'REG_SZ' + } + + $TimeZoneName = Get-CSRegistryValue @Parameters @CommonArgs + + # TimeZoneKeyName doesn't exist on XP, but CimSweep still returns an object as though it did + # NetworkList also doesn't exist on XP, so might as well bail now. + + try { $TimeZoneInfo = [TimeZoneInfo]::FindSystemTimeZoneById($TimeZoneName.ValueContent) } + catch { break } + + $Parameters = @{ + Hive = 'HKLM' + SubKey = 'SOFTWARE\Microsoft\Windows NT\CurrentVersion\NetworkList\Profiles' + } + + Get-CSRegistryKey @Parameters @CommonArgs | ForEach-Object { + + $ObjectProperties = [ordered] @{ PSTypeName = 'CimSweep.NetworkProfile' } + + Get-CSRegistryValue -Hive $_.Hive -SubKey $_.SubKey @CommonArgs | ForEach-Object { + + $ValueName = $_.ValueName + $ValueContent = $_.ValueContent + + switch ($ValueName) { + + { $_ -like "Date*" } { + $BinaryReader = New-Object IO.BinaryReader (New-Object IO.MemoryStream (,$ValueContent)) + + $Year = $BinaryReader.ReadInt16() + $Month = $BinaryReader.ReadInt16() + $null = $BinaryReader.ReadInt16() # skip week day + $Day = $BinaryReader.ReadInt16() + $Hour = $BinaryReader.ReadInt16() + $Minute = $BinaryReader.ReadInt16() + $Second = $BinaryReader.ReadInt16() + $Millisecond = $BinaryReader.ReadInt16() + + $BinaryReader.Dispose() + + # dates are stored in local timezone + $DateTime = New-Object datetime -ArgumentList @($Year, $Month, $Day, $Hour, $Minute, $Second, $Millisecond, 'Unspecified') + $CorrectedTime = [TimeZoneInfo]::ConvertTimeToUtc($DateTime, $TimeZoneInfo) + + $ObjectProperties[$ValueName] = $CorrectedTime.ToString('o') + } + + 'NameType' { + $ObjectProperties['Type'] = switch ($ValueContent) { + 6 { 'Wired' } + 23 { 'VPN' } + 71 { 'Wireless' } + default { $ValueContent } + } + } + + 'Category' { + $ObjectProperties['Category'] = switch ($ValueContent) { + 0 { 'Public' } + 1 { 'Private' } + 2 { 'Domain' } + } + } + + 'Managed' { $ObjectProperties['Managed'] = [bool]$ValueContent } + + default { $ObjectProperties[$ValueName] = $ValueContent } + } + } + if ($_.PSComputerName) { $ObjectProperties['PSComputerName'] = $_.PSComputerName } + [PSCustomObject]$ObjectProperties + } + } + } + end {} +} + +Export-ModuleMember -Function Get-CSNetworkProfile \ No newline at end of file diff --git a/CimSweep/ArtifactRetrieval/UserAssist.ps1 b/CimSweep/ArtifactRetrieval/UserAssist.ps1 new file mode 100644 index 0000000..603bff1 --- /dev/null +++ b/CimSweep/ArtifactRetrieval/UserAssist.ps1 @@ -0,0 +1,118 @@ +function Get-CSUserAssist { +<# +.SYNOPSIS + +Retrieves and parses user assist entries. + +Author: Jesse Davis (@secabstraction) +License: BSD 3-Clause + +.DESCRIPTION + +Get-CSUserAssist retrieves and parses user assist entry information stored in the registry. + +.PARAMETER CimSession + +Specifies the CIM session to use for this cmdlet. Enter a variable that contains the CIM session or a command that creates or gets the CIM session, such as the New-CimSession or Get-CimSession cmdlets. For more information, see about_CimSessions. + +.EXAMPLE + +Get-CSUserAssist + +.EXAMPLE + +Get-CSUserAssist -CimSession $CimSession + +.OUTPUTS + +CimSweep.UserAssistEntry + +Outputs objects consisting of relevant user assist information. Note: the LastExecutedTime of this object is a UTC datetime string in Round-trip format. + +#> + + [CmdletBinding()] + [OutputType('CimSweep.UserAssistEntry')] + param ( + [Alias('Session')] + [ValidateNotNullOrEmpty()] + [Microsoft.Management.Infrastructure.CimSession[]] + $CimSession + ) + + begin { + # If a CIM session is not provided, trick the function into thinking there is one. + if (-not $PSBoundParameters['CimSession']) { + $CimSession = '' + $CIMSessionCount = 1 + } else { + $CIMSessionCount = $CimSession.Count + } + + $CurrentCIMSession = 0 + } + + process { + foreach ($Session in $CimSession) { + $ComputerName = $Session.ComputerName + if (-not $Session.ComputerName) { $ComputerName = 'localhost' } + + # Display a progress activity for each CIM session + Write-Progress -Id 1 -Activity 'CimSweep - UserAssist sweep' -Status "($($CurrentCIMSession+1)/$($CIMSessionCount)) Current computer: $ComputerName" -PercentComplete (($CurrentCIMSession / $CIMSessionCount) * 100) + $CurrentCIMSession++ + + $CommonArgs = @{} + + if ($Session.Id) { $CommonArgs['CimSession'] = $Session } + + $UserSids = Get-HKUSID @CommonArgs + + foreach ($Sid in $UserSids) { + + $Parameters = @{ + Hive = 'HKU' + SubKey = "$Sid\Software\Microsoft\Windows\CurrentVersion\Explorer\UserAssist" + Recurse = $true + } + + Get-CSRegistryKey @Parameters @CommonArgs | Where-Object { $_.SubKey -like "*Count" } | Get-CSRegistryValue @CommonArgs | ForEach-Object { + + # Decrypt Rot13 from https://github.com/StackCrash/PoshCiphers + # truncated && streamlined algorithm a little + + $PlainCharList = New-Object Collections.Generic.List[char] + foreach ($CipherChar in $_.ValueName.ToCharArray()) { + + switch ($CipherChar) { + { $_ -ge 65 -and $_ -le 90 } { $PlainCharList.Add((((($_ - 65 - 13) % 26 + 26) % 26) + 65)) } # Uppercase characters + { $_ -ge 97 -and $_ -le 122 } { $PlainCharList.Add((((($_ - 97 - 13) % 26 + 26) % 26) + 97)) } # Lowercase characters + default { $PlainCharList.Add($CipherChar) } # Pass through symbols and numbers + } + } + + $ValueContent = $_.ValueContent + + # Parse LastExecutedTime from binary data + $FileTime = switch ($ValueContent.Count) { + 8 { [datetime]::FromFileTime(0) } + 16 { [datetime]::FromFileTime([BitConverter]::ToInt64($ValueContent[8..15],0)) } + default { [datetime]::FromFileTime([BitConverter]::ToInt64($ValueContent[60..67],0)) } + } + + $ObjectProperties = [ordered] @{ + PSTypeName = 'CimSweep.UserAssistEntry' + Name = -join $PlainCharList + UserSid = $Sid + LastExecutedTime = $FileTime.ToUniversalTime().ToString('o') + } + + if ($_.PSComputerName) { $ObjectProperties['PSComputerName'] = $_.PSComputerName } + [PSCustomObject]$ObjectProperties + } + } + } + } + end {} +} + +Export-ModuleMember -Function Get-CSUserAssist \ No newline at end of file diff --git a/CimSweep/Auditing/ACLAudits.ps1 b/CimSweep/Auditing/ACLAudits.ps1 index 71c68f6..2fa37c1 100644 --- a/CimSweep/Auditing/ACLAudits.ps1 +++ b/CimSweep/Auditing/ACLAudits.ps1 @@ -1,4 +1,4 @@ -function Get-CSVulnerableServicePermission { +function Get-CSServicePermission { <# .SYNOPSIS @@ -9,7 +9,7 @@ License: BSD 3-Clause .DESCRIPTION -Get-CSVulnerableServicePermission is used to perform service ACL audits at scale. For each computer, it iterates through the service and associated file permissions and groups potentially vulnerable access rights granted to each user. This can be used to quickly identify if members of lower privileged groups can elevate privileges via service misconfigurations. +Get-CSServicePermission is used to perform service ACL audits at scale. For each computer, it iterates through the service and associated file permissions and groups potentially vulnerable access rights granted to each user. This can be used to quickly identify if members of lower privileged groups can elevate privileges via service misconfigurations. .PARAMETER IncludeDrivers @@ -21,7 +21,7 @@ Specifies the CIM session to use for this cmdlet. Enter a variable that contains .EXAMPLE -Get-CSVulnerableServicePermission +Get-CSServicePermission Returns a list of groups and the services to which they are granted access to perform potentially vulnerable actions. @@ -208,4 +208,141 @@ Service ACL sweep across a large amount of hosts will take a long time. } } -Export-ModuleMember -Function Get-CSVulnerableServicePermission \ No newline at end of file +function Get-CSEventLogPermission { +<# +.SYNOPSIS + +List event log permissions granted for each defined group. + +Author: Matthew Graeber (@mattifestation) +License: BSD 3-Clause + +.DESCRIPTION + +Get-CSEventLogPermission is used to perform event log ACL audits at scale. For each computer, it iterates through each event log security descriptor and groups potentially vulnerable access rights granted to each group. Event log security descriptors are stored in HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WINEVT\Channels. + +.PARAMETER CimSession + +Specifies the CIM session to use for this cmdlet. Enter a variable that contains the CIM session or a command that creates or gets the CIM session, such as the New-CimSession or Get-CimSession cmdlets. For more information, see about_CimSessions. + +.EXAMPLE + +Get-CSEventLogPermission + +.OUTPUTS + +CimSweep.EventLogACLAudit + +Outputs objects representing each group granted event log access rights. + +.NOTES + +Event log ACL sweep across a large amount of hosts will take a long time. +#> + + [CmdletBinding()] + [OutputType('CimSweep.EventLogACLAudit')] + param ( + [Alias('Session')] + [ValidateNotNullOrEmpty()] + [Microsoft.Management.Infrastructure.CimSession[]] + $CimSession + ) + + BEGIN { + if (-not $PSBoundParameters['CimSession']) { + $CimSession = '' + $CIMSessionCount = 1 + } else { + $CIMSessionCount = $CimSession.Count + } + + $CurrentCIMSession = 0 + } + + PROCESS { + foreach ($Session in $CimSession) { + $ComputerName = $Session.ComputerName + if (-not $Session.ComputerName) { $ComputerName = 'localhost' } + + # Display a progress activity for each CIM session + Write-Progress -Id 1 -Activity 'CimSweep - Event log ACL sweep' -Status "($($CurrentCIMSession+1)/$($CIMSessionCount)) Current computer: $ComputerName" -PercentComplete (($CurrentCIMSession / $CIMSessionCount) * 100) + $CurrentCIMSession++ + + $CommonArgs = @{} + + if ($Session.Id) { $CommonArgs['CimSession'] = $Session } + + $UserGrouping = @{} + + $EventChannels = Get-CSRegistryKey -Hive HKLM -SubKey 'SOFTWARE\Microsoft\Windows\CurrentVersion\WINEVT\Channels' @CommonArgs + + $ChannelCount = $EventChannels.Count + $CurrentChannelCount = 0 + + $EventChannels | Get-CSRegistryValue -ValueName ChannelAccess @CommonArgs | ForEach-Object { + $ChannelName = $_.Subkey.Split('\')[-1] + + Write-Progress -Id 2 -ParentId 1 -Activity "Current event channel:" -Status $ChannelName -PercentComplete (($CurrentChannelCount / $ChannelCount) * 100) + + $CurrentChannelCount++ + + $AccessString = $_.ValueContent + $SecurityDescriptor = ConvertFrom-SddlString -Sddl $AccessString + + foreach ($ACE in $SecurityDescriptor.RawDescriptor.DiscretionaryAcl) { + if ($ACE.AceQualifier -eq [Security.AccessControl.AceQualifier]::AccessAllowed) { + $Account = $null + + $SID = [Security.Principal.SecurityIdentifier] $ACE.SecurityIdentifier + + try { $Account = $SID.Translate([Security.Principal.NTAccount]).ToString() } catch { + $Account = $ACE.SecurityIdentifier + } + + if (-not $UserGrouping.ContainsKey($Account)) { + $Permissions = [PSCustomObject] @{ + EventCanRead = (New-Object 'Collections.ObjectModel.Collection`1[System.String]') + EventCanWrite = (New-Object 'Collections.ObjectModel.Collection`1[System.String]') + EventCanClear = (New-Object 'Collections.ObjectModel.Collection`1[System.String]') + EventCanWriteDAC = (New-Object 'Collections.ObjectModel.Collection`1[System.String]') + EventCanWriteOwner = (New-Object 'Collections.ObjectModel.Collection`1[System.String]') + } + } else { + $Permissions = $UserGrouping[$Account] + } + + $UserGrouping[$Account] = $Permissions + + if (($ACE.AccessMask -band 1) -eq 1) { $UserGrouping[$Account].EventCanRead.Add($ChannelName) } + if (($ACE.AccessMask -band 2) -eq 2) { $UserGrouping[$Account].EventCanWrite.Add($ChannelName) } + if (($ACE.AccessMask -band 4) -eq 4) { $UserGrouping[$Account].EventCanClear.Add($ChannelName) } + if (($ACE.AccessMask -band 0x00040000) -eq 0x00040000) { $UserGrouping[$Account].EventCanWriteDAC.Add($ChannelName) } + if (($ACE.AccessMask -band 0x00080000) -eq 0x00080000) { $UserGrouping[$Account].EventCanWriteOwner.Add($ChannelName) } + } + } + } + + foreach ($Group in $UserGrouping.Keys) { + $Permissions = $UserGrouping[$Group] + + $ObjectProperties = [Ordered] @{ + PSTypeName = 'CimSweep.EventLogACLAudit' + GroupName = $Group + EventCanRead = $Permissions.EventCanRead + EventCanWrite = $Permissions.EventCanWrite + EventCanClear = $Permissions.EventCanClear + EventCanWriteDAC = $Permissions.EventCanWriteDAC + EventCanWriteOwner = $Permissions.EventCanWriteOwner + } + + if ($Session.ComputerName) { $ObjectProperties['PSComputerName'] = $Session.ComputerName } + + [PSCustomObject] $ObjectProperties + } + } + } +} + +Export-ModuleMember -Function 'Get-CSServicePermission', + 'Get-CSEventLogPermission' \ No newline at end of file diff --git a/CimSweep/CimSweep.cat b/CimSweep/CimSweep.cat deleted file mode 100644 index 84b5ae9..0000000 Binary files a/CimSweep/CimSweep.cat and /dev/null differ diff --git a/CimSweep/CimSweep.psd1 b/CimSweep/CimSweep.psd1 index d057c61..cfc0b75 100644 --- a/CimSweep/CimSweep.psd1 +++ b/CimSweep/CimSweep.psd1 @@ -1,7 +1,7 @@ @{ RootModule = 'CimSweep.psm1' -ModuleVersion = '0.6.0.0' +ModuleVersion = '0.6.1.1' GUID = 'f347ef1c-d752-4d07-bf68-3197c0aa661a' @@ -33,10 +33,14 @@ FunctionsToExport = @( 'Get-CSTypedURL', 'Get-CSWmiPersistence', 'Get-CSWmiNamespace', - 'Get-CSVulnerableServicePermission', + 'Get-CSServicePermission', + 'Get-CSEventLogPermission', 'Get-CSAVInfo', 'Get-CSProxyConfig', + 'Get-CSNetworkProfile', 'Get-CSInstalledAppCompatShimDatabase', + 'Get-CSAppCompatCache', + 'Get-CSUserAssist', 'Get-CSBitlockerKeyProtector', 'Get-CSDeviceGuardStatus' ) @@ -51,6 +55,14 @@ PrivateData = @{ ProjectUri = 'https://github.com/PowerShellMafia/CimSweep' ReleaseNotes = @' +0.6.1 +----- +Enhancements: +* Added Get-CSEventLogPermission +* Added the following from @secabstraction: Get-CSAppCompatCache, Get-CSNetworkProfile, Get-CSUserAssist +* Bugfix: Removed .ForEach PSv4 language dependency +* Renamed Get-CSVulnerableServicePermission to Get-CSServicePermission + 0.6.0 ----- Enhancements: @@ -95,7 +107,7 @@ Removed: 0.4.1 ----- -* Bigfix: Forgot to rename Set-DefaultDisplayProperty in Get-CSRegistryAutoStart. +* Bugfix: Forgot to rename Set-DefaultDisplayProperty in Get-CSRegistryAutoStart. * Enhancement: Addressed PSScriptAnalyzer warnings 0.4.0 diff --git a/CimSweep/Core/CoreFunctions.ps1 b/CimSweep/Core/CoreFunctions.ps1 index 63460bf..55e8ba8 100644 --- a/CimSweep/Core/CoreFunctions.ps1 +++ b/CimSweep/Core/CoreFunctions.ps1 @@ -469,7 +469,7 @@ Outputs a list of objects representing registry value names, their respective ty # Only progress if EnumValues returns actual value and type data if ($Result.Types.Length) { - $Types = $Result.Types.ForEach({$Type[$_]}) + $Types = foreach ($Value in $Result.Types) { $Type[$Value] } $ValueNames = $Result.sNames