Skip to content

Instantly share code, notes, and snippets.

@daaximus
Last active January 17, 2025 21:26
Show Gist options
  • Save daaximus/c0ee871f6a02cd3f657b34d964ddc400 to your computer and use it in GitHub Desktop.
Save daaximus/c0ee871f6a02cd3f657b34d964ddc400 to your computer and use it in GitHub Desktop.
# probably exists in a better form; but script is useful for caching OS modules based on major OS version/build and file
# hash. intended to make life easier, ymmv.
#
# .\symcache.ps1 -src "C:\Windows\System32\drivers" -dst "X:\Windows\drivers"
# ^^ This will copy and organize the bins in the subdirectory and recurse through all subdirectories, and then download
# the symbols if they are available.
#
# - daax
param(
[Parameter(Mandatory=$true)]
[string]$src,
[Parameter(Mandatory=$true)]
[string]$dst
)
function Get-Winvers {
$os_info = Get-WmiObject -Class Win32_OperatingSystem
$version = $os_info.Version
$build = $os_info.BuildNumber
$ubr = Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" -Name UBR | Select-Object -ExpandProperty UBR
return "${version}_${ubr}"
}
function Get-FileHash256($filepath) {
$hash = Get-FileHash -Path $filepath -Algorithm SHA256
return $hash.Hash.Substring(0, 16)
}
function Get-FileMetadata($filepath) {
$item = Get-Item $filepath
$acl = Get-Acl $filepath
try {
$ver_info = [System.Diagnostics.FileVersionInfo]::GetVersionInfo($filepath)
$file_ver = @{
FileVersion = $ver_info.FileVersion
ProductVersion = $ver_info.ProductVersion
CompanyName = $ver_info.CompanyName
FileDescription = $ver_info.FileDescription
ProductName = $ver_info.ProductName
OriginalFilename = $ver_info.OriginalFilename
InternalName = $ver_info.InternalName
LegalCopyright = $ver_info.LegalCopyright
}
}
catch {
$file_ver = $null
Write-Log "Warning: Could not get version info for $filepath" -Color Yellow
}
try {
$signature = Get-AuthenticodeSignature $filepath
$signer_cert = $null
if ($signature.SignerCertificate) {
$signer_cert = @{
Thumbprint = $signature.SignerCertificate.Thumbprint
Subject = $signature.SignerCertificate.Subject
Issuer = $signature.SignerCertificate.Issuer
NotBefore = $signature.SignerCertificate.NotBefore.ToString("o")
NotAfter = $signature.SignerCertificate.NotAfter.ToString("o")
}
}
$timestamp_cert = $null
if ($signature.TimeStamperCertificate) {
$timestamp_cert = @{
Thumbprint = $signature.TimeStamperCertificate.Thumbprint
Subject = $signature.TimeStamperCertificate.Subject
Issuer = $signature.TimeStamperCertificate.Issuer
}
}
$sig_info = @{
SignatureStatus = $signature.Status.ToString()
SignerCertificate = $signer_cert
TimeStamperCertificate = $timestamp_cert
}
}
catch {
$sig_info = $null
Write-Log "Warning: Could not get signature info for $filepath" -Color Yellow
}
try {
$stream = [System.IO.File]::OpenRead($filepath)
$reader = New-Object System.IO.BinaryReader($stream)
$stream.Seek(0, [System.IO.SeekOrigin]::Begin) | Out-Null
$dos_hdr = $reader.ReadBytes(64)
if ([System.Text.Encoding]::ASCII.GetString($dos_hdr, 0, 2) -eq "MZ") {
$stream.Seek(60, [System.IO.SeekOrigin]::Begin) | Out-Null
$offs = $reader.ReadInt32()
$stream.Seek($offs, [System.IO.SeekOrigin]::Begin) | Out-Null
$pe_hdr = @{
Signature = [System.Text.Encoding]::ASCII.GetString($reader.ReadBytes(4))
Machine = "0x" + ($reader.ReadInt16().ToString("X4"))
NumberOfSections = $reader.ReadInt16()
TimeDateStamp = [DateTimeOffset]::FromUnixTimeSeconds($reader.ReadInt32()).ToString("o")
PointerToSymbolTable = "0x" + ($reader.ReadInt32().ToString("X8"))
NumberOfSymbols = $reader.ReadInt32()
SizeOfOptionalHeader = $reader.ReadInt16()
Characteristics = "0x" + ($reader.ReadInt16().ToString("X4"))
}
}
else {
$pe_hdr = $null
}
$reader.Dispose()
$stream.Dispose()
}
catch {
$pe_hdr = $null
Write-Log "Warning: Could not read PE header for $filepath" -Color Yellow
}
$metadata = @{
OriginalPath = $item.FullName
CollectionDate = (Get-Date).ToString("o")
FileSize = $item.Length
LastWriteTime = $item.LastWriteTime.ToString("o")
CreationTime = $item.CreationTime.ToString("o")
LastAccessTime = $item.LastAccessTime.ToString("o")
Hash = Get-FileHash256 $filepath
Owner = $acl.Owner
Access = @($acl.Access | ForEach-Object {
@{
IdentityReference = $_.IdentityReference.ToString()
AccessControlType = $_.AccessControlType.ToString()
FileSystemRights = $_.FileSystemRights.ToString()
IsInherited = $_.IsInherited
}
})
VersionInfo = $file_ver
SignatureInfo = $sig_info
PEHeader = $pe_hdr
}
return $metadata
}
function Write-Log {
param($msg, [System.ConsoleColor]$Color = 'White')
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$logmsg = "[$timestamp] $msg"
Write-Host $logmsg -ForegroundColor $Color
Add-Content -Path $logfile -Value $logmsg
}
if (-not (Get-Command "symchk.exe" -ErrorAction SilentlyContinue)) {
Write-Host "error: symchk.exe not found in PATH." -ForegroundColor Red
exit 1
}
$winvers = Get-Winvers
$logfile = Join-Path $dst "collector.log"
$ErrorActionPreference = "Stop"
$new_files = @()
if (-not (Test-Path $dst)) {
New-Item -ItemType Directory -Path $dst -Force | Out-Null
}
Set-Content -Path $logfile -Value "Starting collection @ $(Get-Date)"
Write-Log "Starting binary collection from $src" -Color Cyan
Write-Log "Winver: $winvers" -Color Cyan
Write-Log "Dest: $dst" -Color Cyan
Write-Log ">>> Copying bins..." -Color Green
$files = Get-ChildItem -Path $src -Recurse -Include *.sys, *.dll, *.exe, *.efi -ErrorAction SilentlyContinue |
Where-Object { -not $_.PSIsContainer }
$fcnt = $files.Count
$curr = 0
$new_copied = 0
foreach ($file in $files) {
$curr++
$percent = [math]::Round(($curr / $fcnt) * 100, 1)
try {
$mname = [System.IO.Path]::GetFileNameWithoutExtension($file.Name)
$filehash = Get-FileHash256 $file.FullName
$mdir = Join-Path $dst "$mname\$winvers\$filehash"
$dst_full = Join-Path $mdir $file.Name
$meta_file = Join-Path $mdir "metadata.json"
if (Test-Path $dst_full) {
Write-Log "Skipping ($curr/$fcnt): $($file.Name) - Identical hash in $winvers" -Color Yellow
continue
}
if (-not (Test-Path $mdir)) {
New-Item -ItemType Directory -Path $mdir -Force | Out-Null
}
Copy-Item -Path $file.FullName -Destination $dst_full -Force
$new_files += $dst_full
$metadata = Get-FileMetadata $file.FullName
$metadata | ConvertTo-Json -Depth 10 | Set-Content $meta_file
$new_copied++
Write-Progress -Activity "Copying Files" -Status "$mname ($percent%)" -PercentComplete $percent
Write-Log "Copied ($curr/$fcnt): $($file.Name) [Hash: $filehash]"
}
catch {
Write-Log "Error copying $($file.Name): $_" -Color Red
}
}
Write-Progress -Activity "Copying Files" -Completed
if ($new_copied -eq 0) {
Write-Log "No new files to copy for version $winvers" -Color Yellow
Write-Log "Operation completed - no changes." -Color Cyan
exit 0
}
Write-Log ">>> Complete: $new_copied new binaries copied" -Color Green
Write-Log ">>> Downloading symbols..." -Color Green
$copied_files = $new_files
$fcnt = $copied_files.Count
$curr = 0
$job_cnt = 15
foreach ($file in $copied_files) {
$curr++
$percent = [math]::Round(($curr / $fcnt) * 100, 1)
while ((Get-Job -State Running).Count -ge $job_cnt) {
Start-Sleep -Seconds 1
Get-Job | Where-Object { $_.State -eq "Completed" } | ForEach-Object {
Receive-Job -Job $_ | Out-Null
Remove-Job -Job $_
}
}
$mdir = Split-Path $file
$symdir = Join-Path $mdir "symbols"
if (Test-Path $symdir) {
Write-Log "Skipping symbols for $($file) - Already exist for this hash" -Color Yellow
continue
}
$symsrv = "srv*$symdir*https://msdl.microsoft.com/download/symbols"
Start-Job -ScriptBlock {
param ($fpath, $sympath)
try {
& "symchk.exe" $fpath /s $sympath 2>&1
return "Successfully processed symbols for: $fpath"
}
catch {
return "Error processing symbols for $fpath : $_"
}
} -ArgumentList $file, $symsrv | Out-Null
Write-Progress -Activity "Processing Symbols" -Status "Processing $($file) ($percent%)" -PercentComplete $percent
}
if ($copied_files.Count -gt 0) {
Write-Progress -Activity "Processing Symbols" -Status "Waiting for remaining jobs..." -PercentComplete 100
Get-Job | Wait-Job | ForEach-Object {
$result = Receive-Job -Job $_
Write-Log $result
Remove-Job -Job $_
}
Write-Log ">>> Complete: Symbol download completed" -Color Green
}
else {
Write-Log ">>> Skipped: No new files for symbols" -Color Yellow
}
Write-Log "All operations completed." -Color Cyan
Write-Log "Log @ $logfile" -Color Cyan
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment