Skip to content

Commit

Permalink
Merge pull request #27 from PowerShellMafia/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
mattifestation authored Jun 8, 2017
2 parents 04fcb71 + b1f17c0 commit 35d4531
Show file tree
Hide file tree
Showing 8 changed files with 782 additions and 9 deletions.
353 changes: 353 additions & 0 deletions CimSweep/ArtifactRetrieval/AppCompatCache.ps1
Original file line number Diff line number Diff line change
@@ -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
5 changes: 4 additions & 1 deletion CimSweep/ArtifactRetrieval/AppCompatDatabases.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand Down
Loading

0 comments on commit 35d4531

Please sign in to comment.