Last active
June 30, 2021 13:43
-
-
Save zaneGittins/67552ab273db0fe86277deabb6f0b4de to your computer and use it in GitHub Desktop.
Prefetch
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#Requires -Version 5.0 | |
<# | |
.SYNOPSIS | |
Parses Windows Prefetch Files | |
.PARAMETER FileNames | |
Names of files to search for. If matches logged to critical. | |
.PARAMETER CSV | |
Output to CSV format. | |
.NOTES | |
Author: Zane Gittins | |
Credits: Eric Zimmerman - C# Code to decompress Windows 10 prefetch file. | |
#> | |
param ( | |
[Parameter(Mandatory=$false, Position=1)][AllowNull()][array]$FileNames, | |
[Parameter(Mandatory=$false, Position=2)][AllowNull()][switch]$CSV | |
) | |
# Return arrays. | |
$global:Informational = @() | |
$global:Critical = @() | |
class PrefetchHeader { | |
[int]$Version | |
[int]$Signature | |
[int]$FileSize | |
[string]$Filename | |
[string]$Hash | |
[string]ToString() { | |
$ToReturn += "Version: " + $this.Version.ToString() + "`n" | |
$ToReturn += "Signature: " + $this.Signature.ToString() + "`n" | |
$ToReturn += "Size: " + $this.FileSize.ToString() + "`n" | |
$ToReturn += "Filename: " + $this.Filename + "`n" | |
$ToReturn += "`n" | |
Return $ToReturn | |
} | |
[string]ToCSV() { | |
$ToReturn += $this.Version.ToString() + "," | |
$ToReturn += $this.Signature.ToString() + "," | |
$ToReturn += $this.FileSize.ToString() + "," | |
$ToReturn += $this.Filename + "," | |
Return $ToReturn | |
} | |
} | |
class PrefetchFile { | |
$Header | |
[array]$Runtimes | |
[int]$RunCount | |
[string]ToString() { | |
$ToReturn += "`n------------ PREFETCH-DATA ------------`n" | |
$ToReturn += $this.Header.ToString() | |
$ToReturn += "Run Times: `n" | |
$Counter = 1 | |
foreach($Time in $this.Runtimes) { | |
$ToReturn += "`t" + $Counter.ToString() + " " + $Time.ToString() + "`n" | |
$Counter += 1 | |
} | |
$ToReturn += "Run Count: " + $this.RunCount.ToString() + "`n" | |
$ToReturn += "---------- END-PREFETCH-DATA ----------`n" | |
Return $ToReturn | |
} | |
[string]ToCSV() { | |
$ToReturn += $this.Header.ToCSV() | |
$ToReturn += $this.Runtimes[0].ToString() | |
Return $ToReturn | |
} | |
} | |
function Compare-PrefetchHeader { | |
[CmdletBinding()] | |
param( | |
[Parameter(Mandatory=$true)]$FileHex | |
) | |
$Byte1 = $FileHex.Bytes[0] | |
$Byte2 = $FileHex.Bytes[1] | |
$Byte3 = $FileHex.Bytes[2] | |
$Byte4 = $FileHex.Bytes[3] | |
if(($Byte1 -eq 0x4d) -and ($Byte2 -eq 0x41) -and ($Byte3 -eq 0x4d) -and ($Byte4 -eq 0x04)) { | |
return $true | |
} | |
else { | |
return $false | |
} | |
} | |
function Get-PrefetchFilePaths { | |
[CmdletBinding()] | |
param( | |
[Parameter(Mandatory=$true)][string]$Location | |
) | |
[array]$FileLocations = @() | |
$PrefetchFiles = Get-ChildItem $Location | |
foreach($PrefetchFile in $PrefetchFiles) { | |
try { | |
$FileData = Get-Content $PrefetchFile.FullName | |
$FileData = $FileData | Format-Hex | |
if((Compare-PrefetchHeader $FileData) -eq $true) { | |
$FileLocations += $PrefetchFile.FullName | |
} | |
} | |
catch { | |
Continue | |
} | |
} | |
Return $FileLocations | |
} | |
function Get-BigEndian { | |
[CmdletBinding()] | |
param( | |
[Parameter(Mandatory=$true)][array]$LittleEndian | |
) | |
$ArraySize = $LittleEndian.Length | |
$CounterLittle = $ArraySize - 1 | |
[array]$BigEndian | |
while ($CounterLittle -gt 0) { | |
$BigEndian += ([byte] $LittleEndian[$CounterLittle-1]) | |
$BigEndian += ([byte] $LittleEndian[$CounterLittle]) | |
$CounterLittle -= 2 | |
} | |
Return $BigEndian | |
} | |
function Get-HexToString { | |
param( | |
[Parameter(Mandatory=$true)]$Hex | |
) | |
$Converted = "" | |
foreach($Byte in ($Hex)) { | |
$Converted += [char][byte]$Byte | |
} | |
Return $Converted | |
} | |
function Get-PrefetchData { | |
[CmdletBinding()] | |
param( | |
[Parameter(Mandatory=$true)][string]$PrefetchFilePath | |
) | |
$InputBuffer = [System.IO.File]::ReadAllBytes($PrefetchFilePath) | |
[int32]$Size = [bitconverter]::ToUInt32($InputBuffer, 4); | |
$MethodDefinition = @' | |
// Author: Eric Zimmerman (https://github.com/EricZimmerman) (MIT LICENSE) | |
// Minor Modifications: Zane Gittins | |
using System; | |
using System.Runtime.InteropServices; | |
namespace Prefetch.XpressStream | |
{ | |
public class Xpress2 | |
{ | |
private const ushort CompressionFormatXpressHuff = 4; | |
[DllImport("ntdll.dll", CharSet = CharSet.Auto)] | |
private static extern uint RtlGetCompressionWorkSpaceSize(ushort compressionFormat, | |
ref ulong compressBufferWorkSpaceSize, ref ulong compressFragmentWorkSpaceSize); | |
[DllImport("ntdll.dll", CharSet = CharSet.Auto)] | |
private static extern uint RtlDecompressBufferEx(ushort compressionFormat, byte[] uncompressedBuffer, | |
int uncompressedBufferSize, byte[] compressedBuffer, int compressedBufferSize, ref int finalUncompressedSize, | |
byte[] workSpace); | |
public static byte[] Decompress(byte[] buffer, ulong decompressedSize) | |
{ | |
var outBuf = new byte[decompressedSize]; | |
ulong compressBufferWorkSpaceSize = 0; | |
ulong compressFragmentWorkSpaceSize = 0; | |
var ret = RtlGetCompressionWorkSpaceSize(CompressionFormatXpressHuff, ref compressBufferWorkSpaceSize, | |
ref compressFragmentWorkSpaceSize); | |
if (ret != 0) | |
{ | |
Console.WriteLine(ret); | |
return null; | |
} | |
var workSpace = new byte[compressFragmentWorkSpaceSize]; | |
var dstSize = 0; | |
ret = RtlDecompressBufferEx(CompressionFormatXpressHuff, outBuf, outBuf.Length, buffer, buffer.Length, | |
ref dstSize, workSpace); | |
if (ret == 0) | |
{ | |
return outBuf; | |
} | |
else | |
{ | |
Console.WriteLine(ret); | |
return null; | |
} | |
} | |
} | |
} | |
'@ | |
Add-Type -TypeDefinition $MethodDefinition | |
$InputBuffer = $InputBuffer[8..$InputBuffer.Length] | |
$DecompressedData = [Prefetch.XpressStream.Xpress2]::Decompress($InputBuffer, $Size) | |
$PrefetchVersion = $DecompressedData[0..3] | |
$Signature = $DecompressedData[4..7] | |
$FileSize = $DecompressedData[12..15] | |
$ExectuableName = $DecompressedData[16..59] | |
$PrefetchHash = $DecompressedData[76..79] | |
# Parse Header Information | |
$PrefetchHeader = [PrefetchHeader]::new() | |
$PrefetchHeader.Version = [bitconverter]::ToInt32($PrefetchVersion, 0) | |
$PrefetchHeader.Signature = [bitconverter]::ToInt32($Signature, 0) | |
$PrefetchHeader.FileSize = [bitconverter]::ToInt32($FileSize, 0) | |
$PrefetchHeader.Filename = ((Get-HexToString $ExectuableName) -replace "\W","") | |
$PrefetchHeader.Hash = (Get-BigEndian $PrefetchHash).ToString() | |
$PrefetchFile = [PrefetchFile]::new() | |
$PrefetchFile.Header = $PrefetchHeader | |
# Parse Prefetch File. | |
switch($PrefetchHeader.Version) { | |
# Windows XP, 2003 | |
17 { | |
break | |
} | |
# Windows Vista, 7 | |
23 { | |
break | |
} | |
# Windows 8.1 | |
26 { | |
break | |
} | |
# Windows 10 | |
30 { | |
$Filetime = $DecompressedData[128..191] | |
$Counter = 0 | |
[array]$RunTimeArray = @() | |
while($Counter -lt 8) { | |
[int64]$FileTimeConverted = [int64][bitconverter]::ToInt64($Filetime, ($Counter * 8)) | |
$NewRunTime = ([System.DateTimeOffset]::FromFileTime(($FileTimeConverted))) | |
$RunTimeArray += $NewRunTime | |
$Counter += 1 | |
} | |
$PrefetchFile.Runtimes = $RunTimeArray | |
$PrefetchRunCount = [bitconverter]::ToInt32($DecompressedData[208..211], 0) | |
$PrefetchFile.RunCount = $PrefetchRunCount | |
break | |
} | |
} | |
Return $PrefetchFile | |
} | |
function Get-Hashtable { | |
[CmdletBinding()] | |
param( | |
[Parameter(Mandatory=$true)][array]$Items | |
) | |
[hashtable]$Hashtable = @{} | |
foreach($Item in $Items) { | |
if($Item) { | |
$Hashtable[$Item.ToString().ToUpper()] = "present" | |
} | |
} | |
Return $Hashtable | |
} | |
$FileNameHashtable = $null | |
if($PSBoundParameters.ContainsKey('FileNames') -eq $true -and $FileNames) { | |
[hashtable] $FileNameHashtable = Get-Hashtable $FileNames | |
} | |
$PrefetchFilePaths = Get-PrefetchFilePaths "C:\Windows\Prefetch" -ErrorAction 'silentlycontinue' | |
foreach($PrefetchFilePath in $PrefetchFilePaths) { | |
try { | |
$PrefetchData = Get-PrefetchData $PrefetchFilePath -ErrorAction 'silentlycontinue' | |
if($FileNameHashtable -eq $true -and $FileNameHashtable[$PrefetchData.Header.Filename]) { | |
$global:Critical += $PrefetchData | |
} | |
else { | |
$global:Informational += $PrefetchData | |
} | |
} | |
catch { | |
Continue | |
} | |
} | |
if($CSV -eq $true) { | |
$Output = "Version,Signature,FileSize,FileName,LastRunTime,Critical`n" | |
} | |
if($global:Informational.Length -ge 1) { | |
foreach($Element in $global:Informational) { | |
if($CSV -eq $true) { $Output += $Element.ToCSV() + "`n" } | |
else { $Output += ($Element.ToString() + "`n" )} | |
} | |
} | |
if($global:Critical.Length -ge 1) { | |
foreach($Element in $global:Informational) { | |
if($CSV -eq $true) { $Output += $Element.ToCSV() + ",true" + "`n"} | |
else { $Output += ($Element.ToString() + "`n" )} | |
} | |
} | |
return $Output |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment