Last active
January 17, 2025 21:26
-
-
Save daaximus/c0ee871f6a02cd3f657b34d964ddc400 to your computer and use it in GitHub Desktop.
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
# 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