1+ # Get the SuperFetch info from
2+ # HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Superfetch\PfAp
3+ #
4+ # Since Value namess might not be the same in different machine:
5+ $key = " Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Superfetch\PfAp"
6+ $KeyValues = Get-Item - Path $key - ErrorAction Stop
7+ $keyProperties = Get-ItemProperty - Path $key - ErrorAction Stop
8+ $UserTimeValue = $KeyValues.Property.Where {$_.startswith (' UserTime' ) }
9+ $UserTime = $keyProperties .$UserTimeValue
10+ $FetchValue = $KeyValues.Property.Where {$_.startswith (' ApFetch' ) }
11+ $Fetch = $keyProperties .$FetchValue
12+ $LaunchValue = $KeyValues.Property.Where {$_.startswith (' ApLaunch' ) }
13+ $Launch = $keyProperties .$LaunchValue
14+ # - ApFetch_%SIDHashed Compressed buffer. (not seen anything of interest here yet)
15+ # - ApLaunch_%SIDHashed Compressed buffer with a fixed size contains Win Universal Windows Platform (UWP) Apps. This buffer is updated periodically.
16+ # - UserTime_%ID Compressed buffer related to the context for a given user. This buffer is updated periodically.
17+ # More info at: https://papers.vx-underground.org/papers/Windows/Analysis%20and%20Internals/Superfetch%20-%20Unknown%20Spy.pdf
18+ #
19+ # XpressStream decompress C# code sourced from:
20+ # https://github.com/EricZimmerman/Prefetch/blob/master/Prefetch/XpressStream/Xpress2.cs
21+ $xpress = @"
22+ using System;
23+ using System.Runtime.InteropServices;
24+
25+ namespace Prefetch.XpressStream
26+ {
27+ public class Xpress2
28+ {
29+ // const ushort COMPRESSION_FORMAT_LZNT1 = 2;
30+ // const ushort COMPRESSION_FORMAT_XPRESS = 3;
31+ private const ushort CompressionFormatXpressHuff = 4;
32+
33+ [DllImport("ntdll.dll")]
34+ private static extern uint RtlGetCompressionWorkSpaceSize(ushort compressionFormat,
35+ ref ulong compressBufferWorkSpaceSize, ref ulong compressFragmentWorkSpaceSize);
36+
37+ [DllImport("ntdll.dll")]
38+ private static extern uint RtlDecompressBufferEx(ushort compressionFormat, byte[] uncompressedBuffer,
39+ int uncompressedBufferSize, byte[] compressedBuffer, int compressedBufferSize, ref int finalUncompressedSize,
40+ byte[] workSpace);
41+
42+ public static byte[] Decompress(byte[] buffer, ulong decompressedSize)
43+ {
44+ // our uncompressed data will go here
45+ var outBuf = new byte[decompressedSize];
46+ ulong compressBufferWorkSpaceSize = 0;
47+ ulong compressFragmentWorkSpaceSize = 0;
48+
49+ //get the size of what our workspace needs to be
50+ var ret = RtlGetCompressionWorkSpaceSize(CompressionFormatXpressHuff, ref compressBufferWorkSpaceSize,
51+ ref compressFragmentWorkSpaceSize);
52+ if (ret != 0)
53+ {
54+ return null;
55+ }
56+
57+ var workSpace = new byte[compressFragmentWorkSpaceSize];
58+ var dstSize = 0;
59+
60+ ret = RtlDecompressBufferEx(CompressionFormatXpressHuff, outBuf, outBuf.Length, buffer, buffer.Length,
61+ ref dstSize, workSpace);
62+ //if (ret == 0)
63+ // {
64+ return outBuf;
65+ // }
66+
67+ //return null;
68+ }
69+ }
70+ }
71+ "@
72+ $null = Add-Type - TypeDefinition $xpress
73+ cls
74+
75+ # Function to Process the ApLaunch decompressed data from a Byte Array
76+ function Get-DecompressedInfo {
77+ param
78+ (
79+ [Parameter (Mandatory = $true )]
80+ [Byte []]$decompressed
81+ )
82+
83+ $dBversion = [System.BitConverter ]::ToUint32($decompressed [0 .. 3 ], 0 )
84+ $numberofentries = [System.BitConverter ]::ToUint32($decompressed [4 .. 7 ], 0 )
85+
86+ Write-Host " ------------------------------------"
87+ write-host " dB Version: $ ( $dBversion ) "
88+ write-host " Number of Entries: $ ( $numberofentries ) "
89+ Write-Host " (Header size: 8 - Entry length: 352)"
90+ Write-Host " ------------------------------------"
91+ $start = 8
92+ $offset = $start
93+
94+ $entries = @ (for ($x = 0 ;$x -lt $numberofentries ;$x ++ ){
95+
96+ $Application = [System.Text.Encoding ]::Unicode.GetString($decompressed [($offset ).. ($offset + 263 )]).Replace(' ' , ' ' )
97+ $tsd = [System.BitConverter ]::ToUint64($decompressed [($offset + 264 ).. ($offset + 271 )], 0 )
98+ $timestamp = [datetime ]::FromFileTimeUtc($tsd ).ToString(" dd/MM/yyyy HH:mm:ss.fffffff" )
99+ $Hash = [System.BitConverter ]::ToString($decompressed [($offset + 340 ).. ($offset + 340 + 3 )]).Replace(" -" , " " )
100+ $flag = [System.BitConverter ]::ToUint32($decompressed [($offset + 344 ).. ($offset + 344 + 3 )], 0 )
101+
102+ [pscustomobject ]@ {
103+ ' Offset' = $offset.ToString (' D6' )
104+ ' Application' = $Application
105+ ' Timestamp' = $timestamp
106+ ' Hash' = $Hash
107+ ' Flag' = [System.Boolean ]$flag
108+ }
109+ $offset = $offset + 352
110+ })
111+ $entries
112+ $decompressed = $null
113+
114+ } # End Get-Launch
115+
116+
117+ if ($UserTime ){
118+
119+ $value = [System.BitConverter ]::ToString($UserTime [0 .. 7 ]).Replace(" -" , " " )
120+ try {
121+ $tm0 = [System.BitConverter ]::ToUint64($UserTime [8 .. 15 ], 0 )
122+ $timestamp0 = [datetime ]::FromFileTimeUtc($tm0 ).ToString(" dd/MM/yyyy HH:mm:ss.fffffff" )
123+ }
124+ catch {$timestamp0 = ' --' }
125+ try {
126+ $tm1 = [System.BitConverter ]::ToUint64($UserTime [16 .. 23 ], 0 )
127+ $timestamp1 = [datetime ]::FromFileTimeUtc($tm1 ).ToString(" dd/MM/yyyy HH:mm:ss.fffffff" )
128+ }
129+ catch {$timestamp1 = ' --' }
130+
131+ if ($Fetch ){
132+
133+ $FetchSize = $Fetch.Length
134+
135+ # File Signature
136+ $FetchSignature = [System.BitConverter ]::ToString($Fetch [0 .. 3 ]).Replace(" -" , " " )
137+ # Total uncompressed data size
138+ $FetchTotalUncompressedSize = [Bitconverter ]::ToUInt32($Fetch [4 .. 7 ], 0 )
139+ # Data
140+ $fetchuncompressed = if ($FetchTotalUncompressedSize -ge 8 ){[Prefetch.XpressStream.Xpress2 ]::Decompress($Fetch [8 .. ($FetchSize -8 -1 )], $FetchTotalUncompressedSize ) }
141+ }
142+
143+ if (!! $Launch ){
144+ $LaunchSize = $Launch.Length
145+
146+ # File Signature
147+ $LaunchSignature = [System.BitConverter ]::ToString($Launch [0 .. 3 ]).Replace(" -" , " " )
148+ # Total uncompressed data size
149+ $LaunchTotalUncompressedSize = [Bitconverter ]::ToUInt32($Launch [4 .. 7 ], 0 )
150+ # Data
151+ $LCompressed = $Launch [12 .. (($Launch.Length ) - 13 )]
152+ $LaunchUncompressed = if ($LaunchTotalUncompressedSize -ge 8 ){[Prefetch.XpressStream.Xpress2 ]::Decompress($LCompressed , $LaunchTotalUncompressedSize ) }
153+ $LaunchedApps = try { Get-DecompressedInfo - decompressed $Launchuncompressed }
154+ catch { $null }
155+
156+ # Export uncompressed Fetch data
157+ $exportedfile = " $ ( $env: TEMP ) \decompressed_$ ( $LaunchValue ) .hex"
158+ $OutputFileStream = [IO.File ]::OpenWrite($exportedfile )
159+ $OutputFileStream.Write ($LaunchUncompressed , 0 , $LaunchTotalUncompressedSize )
160+ $OutputFileStream.Dispose ()
161+ Write-Host " The decompressed contents of $ ( $LaunchValue ) are saved to $ ( $exportedfile ) " -f White
162+ }
163+
164+
165+ [pscustomobject ]@ {
166+ " $ ( $UserTimeValue ) Value" = $value
167+ " $ ( $UserTimeValue ) TimeStamp 1" = $timestamp0
168+ " $ ( $UserTimeValue ) TimeStamp 2" = $timestamp1
169+ " $ ( $FetchValue ) Signature" = $FetchSignature
170+ " $ ( $FetchValue ) Total Compressed Size" = $FetchSize
171+ " $ ( $FetchValue ) Total Uncompressed Size" = $FetchTotalUncompressedSize
172+ " $ ( $FetchValue ) Data" = $fetchuncompressedhex
173+ " $ ( $LaunchValue ) Signature" = $LaunchSignature
174+ " $ ( $LaunchValue ) Total Compressed Size" = $LaunchSize
175+ " $ ( $LaunchValue ) Total Uncompressed Size" = $LaunchTotalUncompressedSize
176+ }
177+
178+ $LaunchedApps | Out-GridView - PassThru
179+
180+ } # endif keys
0 commit comments