Skip to content

Commit b92d73c

Browse files
authored
Get the SuperFetch info from "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Superfetch\PfAp"
- ApFetch_%SIDHashed Compressed buffer. - ApLaunch_%SIDHashed Compressed buffer with a fixed size contains Win Universal Windows Platform (UWP) Apps. This buffer is updated periodically. - UserTime_%ID Compressed buffer related to the context for a given user. This buffer is updated periodically. More info at: https://papers.vx-underground.org/papers/Windows/Analysis%20and%20Internals/Superfetch%20-%20Unknown%20Spy.pdf
1 parent 646441d commit b92d73c

1 file changed

Lines changed: 180 additions & 0 deletions

File tree

Local_PfAp.ps1

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
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

Comments
 (0)