Skip to content

Instantly share code, notes, and snippets.

@zaneGittins
Last active June 30, 2021 13:43
Show Gist options
  • Save zaneGittins/67552ab273db0fe86277deabb6f0b4de to your computer and use it in GitHub Desktop.
Save zaneGittins/67552ab273db0fe86277deabb6f0b4de to your computer and use it in GitHub Desktop.
Prefetch
#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