diff --git a/Manage-WindowsTelemetry.ps1 b/Manage-WindowsTelemetry.ps1 index 559f5a5..965e374 100644 --- a/Manage-WindowsTelemetry.ps1 +++ b/Manage-WindowsTelemetry.ps1 @@ -42,8 +42,9 @@ .\Manage-WindowsTelemetry.ps1 -UserMode # per-user items only, no admin .\Manage-WindowsTelemetry.ps1 -UserMode -DisableAll # harden your user profile .\Manage-WindowsTelemetry.ps1 -Report # print status and exit - .\Manage-WindowsTelemetry.ps1 -DisableAll # turn telemetry OFF (keeps [SEC] ON) + .\Manage-WindowsTelemetry.ps1 -DisableAll # turn telemetry OFF (keeps [SEC]/[NET] ON) .\Manage-WindowsTelemetry.ps1 -DisableAll -IncludeSecurity # also disable SmartScreen + .\Manage-WindowsTelemetry.ps1 -GoDark # MAX: disable everything incl [SEC]+[NET] .\Manage-WindowsTelemetry.ps1 -EnableAll # restore Windows default ON .\Manage-WindowsTelemetry.ps1 -Csv .\out.csv # export status and exit .\Manage-WindowsTelemetry.ps1 -Report -Csv .\status.csv @@ -58,6 +59,7 @@ param( [switch]$DisableAll, [switch]$EnableAll, [switch]$IncludeSecurity, # also disable [SEC] SmartScreen features in bulk actions + [switch]$GoDark, # maximum: disable EVERYTHING incl [SEC] and [NET] callbacks [switch]$UserMode, # only per-user (HKCU) items; no admin needed [string]$Csv ) @@ -127,7 +129,9 @@ function New-RegControl { [ValidateSet('On','Off')][string]$Default = 'On', [string]$RegType = 'DWord', [string]$Note = '', [switch]$RemoveOnEnable, - [switch]$Security # marks a control whose "Disabled" state REDUCES protection + [switch]$Security, # marks a control whose "Disabled" state REDUCES protection + [switch]$GoDark, # ambient MS callback; only disabled by -GoDark or individually + [switch]$Critical # breaks important functionality; per-item "confirm close" required ) [pscustomobject]@{ Type = 'Reg' @@ -142,6 +146,8 @@ function New-RegControl { RegType = $RegType RemoveOnEnable = [bool]$RemoveOnEnable Security = [bool]$Security + GoDark = [bool]$GoDark + Critical = [bool]$Critical AdminReq = ($Hive -eq 'HKLM') } } @@ -150,16 +156,19 @@ function New-ServiceControl { param( [string]$Name, [string]$ServiceName, [ValidateSet('Automatic','Manual')][string]$DefaultStartupType = 'Automatic', - [string]$Note = '' + [string]$Note = '', [string]$Category = 'Service', + [switch]$Critical ) [pscustomobject]@{ Type = 'Service' Name = $Name - Category = 'Service' + Category = $Category Note = $Note ServiceName = $ServiceName DefaultStartupType = $DefaultStartupType Security = $false + GoDark = $false + Critical = [bool]$Critical AdminReq = $true } } @@ -174,6 +183,8 @@ function New-TaskControl { Note = $Note Tasks = $Tasks Security = $false + GoDark = $false + Critical = $false AdminReq = $true } } @@ -501,6 +512,55 @@ function Get-Controls { -ValueName 'ServiceEnabled' -OnValue 1 -OffValue 0 -Default On -RemoveOnEnable -Security ` -Note 'SECURITY: Win11 password/phishing protection service') ) + # --- Defender cloud (MAPS) - SECURITY, off reduces AV (Tamper Protection may block) --- + [void]$c.Add( (New-RegControl -Name 'Defender Cloud (MAPS)' -Category 'Defender Cloud' ` + -Hive HKLM -Path 'SOFTWARE\Policies\Microsoft\Windows Defender\Spynet' ` + -ValueName 'SpynetReporting' -OnValue 2 -OffValue 0 -Default On -RemoveOnEnable -Security ` + -Note 'SECURITY: real-time cloud lookups. Tamper Protection may block changes.') ) + + [void]$c.Add( (New-RegControl -Name 'Defender Sample Submission' -Category 'Defender Cloud' ` + -Hive HKLM -Path 'SOFTWARE\Policies\Microsoft\Windows Defender\Spynet' ` + -ValueName 'SubmitSamplesConsent' -OnValue 1 -OffValue 2 -Default On -RemoveOnEnable -Security ` + -Note 'SECURITY/PRIVACY: sends files to Microsoft. Tamper Protection may block changes.') ) + + # --- Ambient Microsoft callbacks ([NET]) - functional connections, go-dark only --- + [void]$c.Add( (New-RegControl -Name 'NCSI Active Probe' -Category 'Connectivity' ` + -Hive HKLM -Path 'SYSTEM\CurrentControlSet\Services\NlaSvc\Parameters\Internet' ` + -ValueName 'EnableActiveProbing' -OnValue 1 -OffValue 0 -Default On -GoDark ` + -Note 'Probes msftconnecttest.com. Off breaks captive-portal / internet indicator.') ) + + [void]$c.Add( (New-RegControl -Name 'Root Certificate Auto-Update' -Category 'Connectivity' ` + -Hive HKLM -Path 'SOFTWARE\Policies\Microsoft\SystemCertificates\AuthRoot' ` + -ValueName 'DisableRootAutoUpdate' -OnValue 0 -OffValue 1 -Default On -RemoveOnEnable -GoDark ` + -Note 'Off stops new/rotated trusted-root CAs downloading - can break HTTPS over time.') ) + + [void]$c.Add( (New-RegControl -Name 'Store App Auto-Update' -Category 'Connectivity' ` + -Hive HKLM -Path 'SOFTWARE\Policies\Microsoft\WindowsStore' ` + -ValueName 'AutoDownload' -OnValue 4 -OffValue 2 -Default On -RemoveOnEnable -GoDark ` + -Note 'Off stops Store apps auto-updating (including their security fixes).') ) + + [void]$c.Add( (New-RegControl -Name 'Font Streaming' -Category 'Connectivity' ` + -Hive HKLM -Path 'SOFTWARE\Policies\Microsoft\Windows\System' ` + -ValueName 'EnableFontProviders' -OnValue 1 -OffValue 0 -Default On -RemoveOnEnable -GoDark ` + -Note 'Off stops on-demand font downloads from Microsoft.') ) + + [void]$c.Add( (New-RegControl -Name 'Windows Media DRM Online' -Category 'Connectivity' ` + -Hive HKLM -Path 'SOFTWARE\Policies\Microsoft\WMDRM' ` + -ValueName 'DisableOnline' -OnValue 0 -OffValue 1 -Default On -RemoveOnEnable -GoDark ` + -Note 'Off stops Media DRM contacting Microsoft for licenses/individualization.') ) + + # --- Critical ([CRIT]) - breaks important functionality; per-item confirm-close --- + # NOT touched by any bulk action (not even -GoDark). Local-config stand-ins for + # what a hardened env does with WSUS / a local NTP server / network isolation. + [void]$c.Add( (New-RegControl -Name 'Windows Update Auto' -Category 'Critical' ` + -Hive HKLM -Path 'SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU' ` + -ValueName 'NoAutoUpdate' -OnValue 0 -OffValue 1 -Default On -RemoveOnEnable -Critical ` + -Note 'BREAKS SECURITY PATCHING. Off = no automatic update check/download. Use WSUS instead.') ) + + [void]$c.Add( (New-ServiceControl -Name 'Windows Time Sync (W32Time)' -Category 'Critical' ` + -ServiceName 'W32Time' -DefaultStartupType Manual -Critical ` + -Note 'BREAKS time sync (Kerberos/TLS drift). Off = no NTP to time.windows.com. Use a local time server.') ) + # --- Services --- [void]$c.Add( (New-ServiceControl -Name 'Connected User Experiences' ` -ServiceName 'DiagTrack' -DefaultStartupType Automatic ` @@ -581,7 +641,7 @@ function Show-Status { Write-Host ('{0,-4}{1,-36}{2,-18}{3}' -f '---', '-------', '--------', '-----') -ForegroundColor DarkGray $i = 0 - $nEnabled = 0; $nDisabled = 0; $nAbsent = 0; $nSecOff = 0; $nSec = 0; $anyLock = $false + $nEnabled = 0; $nDisabled = 0; $nAbsent = 0; $nSecOff = 0; $nSec = 0; $nNet = 0; $nCrit = 0; $anyLock = $false foreach ($ctrl in $Controls) { $i++ $state = Get-ControlState -Ctrl $ctrl @@ -592,6 +652,10 @@ function Show-Status { if ($ctrl.Security) { $sec = ' [SEC]'; $nSec++ if ($state -like 'Disabled*') { $nSecOff++ } + } elseif ($ctrl.Critical) { + $sec = ' [CRIT]'; $nCrit++ + } elseif ($ctrl.GoDark) { + $sec = ' [NET]'; $nNet++ } $lock = '' if ($ctrl.AdminReq -and -not $Script:IsAdmin) { $lock = ' *'; $anyLock = $true } @@ -604,6 +668,12 @@ function Show-Status { if ($nSec -gt 0) { Write-Host ' [SEC] = anti-malware reputation check. Disabling REDUCES protection' -ForegroundColor DarkYellow } + if ($nNet -gt 0) { + Write-Host ' [NET] = ambient Microsoft callback. Disabling can break functionality' -ForegroundColor DarkYellow + } + if ($nCrit -gt 0) { + Write-Host ' [CRIT] = breaks patching/time/trust. Never bulk-disabled; needs per-item confirm (X)' -ForegroundColor DarkYellow + } if ($nSecOff -gt 0) { Write-Host (' WARNING: {0} security SmartScreen feature(s) are currently OFF' -f $nSecOff) -ForegroundColor Red } @@ -653,26 +723,48 @@ function Invoke-ControlAction { } } +function Invoke-ConfirmClose { + param($Ctrl) # per-item "confirm close" for [CRIT] items + Write-Host '' + Write-Host ('CONFIRM CLOSE: {0}' -f $Ctrl.Name) -ForegroundColor Red + if ($Ctrl.Note) { Write-Host (' {0}' -f $Ctrl.Note) -ForegroundColor DarkYellow } + Write-Host ' Local-config only - a hardened env would use WSUS / local NTP / network isolation.' -ForegroundColor DarkYellow + if ((Read-Host ('Close this? type CLOSE')) -ceq 'CLOSE') { + Invoke-ControlAction -Ctrl $Ctrl -Action Disable + } else { + Write-Host ' Skipped.' -ForegroundColor DarkGray + } +} + function Invoke-AllAction { param( $Controls, [ValidateSet('Enable','Disable')][string]$Action, - [switch]$WithSecurity # when disabling, also include [SEC] SmartScreen features + [switch]$WithSecurity, # when disabling, also include [SEC] SmartScreen/Defender features + [switch]$WithGoDark # when disabling, also include [NET] ambient callbacks ) $verb = if ($Action -eq 'Enable') { 'ENABLE (restore Windows default)' } else { 'DISABLE (harden)' } Write-Host '' Write-Host ("Applying {0} to ALL items..." -f $verb) -ForegroundColor Cyan - $skippedSec = 0 + $skippedSec = 0; $skippedNet = 0; $skippedCrit = 0 foreach ($ctrl in $Controls) { - # Never auto-disable SmartScreen reputation checks in bulk unless explicitly requested. - if ($Action -eq 'Disable' -and $ctrl.Security -and -not $WithSecurity) { - $skippedSec++ - continue + if ($Action -eq 'Disable') { + # [CRIT] items are NEVER bulk-disabled (not even by go-dark) - confirm-close only. + if ($ctrl.Critical) { $skippedCrit++; continue } + # Never auto-disable security or ambient-callback items in bulk unless asked. + if ($ctrl.Security -and -not $WithSecurity) { $skippedSec++; continue } + if ($ctrl.GoDark -and -not $WithGoDark) { $skippedNet++; continue } } Invoke-ControlAction -Ctrl $ctrl -Action $Action } if ($skippedSec -gt 0) { - Write-Host (' Kept {0} [SEC] SmartScreen feature(s) ON (use -IncludeSecurity / menu "S" to disable).' -f $skippedSec) -ForegroundColor DarkYellow + Write-Host (' Kept {0} [SEC] security feature(s) ON (use -IncludeSecurity / menu "S", or -GoDark).' -f $skippedSec) -ForegroundColor DarkYellow + } + if ($skippedNet -gt 0) { + Write-Host (' Kept {0} [NET] ambient callback(s) ON (use -GoDark / menu "G").' -f $skippedNet) -ForegroundColor DarkYellow + } + if ($skippedCrit -gt 0) { + Write-Host (' Kept {0} [CRIT] item(s) ON - close individually with confirm (menu "X").' -f $skippedCrit) -ForegroundColor DarkYellow } Write-Host '' } @@ -692,6 +784,8 @@ function Start-Menu { Write-Host ' D disable ALL shown (harden; keeps [SEC] SmartScreen ON)' Write-Host ' E enable ALL shown (restore Windows default)' Write-Host ' S disable ALL [SEC] SmartScreen features (reduces security)' + Write-Host ' G GO DARK - disable EVERYTHING incl [SEC] + [NET] (max privacy)' + Write-Host ' X close [CRIT] items (Update/Time) one-by-one with confirm' if ($Script:UserMode) { Write-Host ' m switch to FULL mode (also show system-wide items)' } else { @@ -737,6 +831,21 @@ function Start-Menu { } Read-Host 'Press Enter'; continue } + '^G$' { + Write-Host '' + Write-Host '============================ GO DARK ============================' -ForegroundColor Red + Write-Host 'Disables EVERYTHING shown: telemetry + [SEC] anti-malware reputation' -ForegroundColor Red + Write-Host '(SmartScreen, Defender cloud) + [NET] ambient callbacks (connectivity' -ForegroundColor Red + Write-Host 'probe, root-cert auto-update, Store updates, font/DRM). This REDUCES' -ForegroundColor Red + Write-Host 'SECURITY and can BREAK functionality. Windows Update, activation,' -ForegroundColor Red + Write-Host 'cert revocation and time sync are intentionally left working.' -ForegroundColor Red + Write-Host 'Reverse anytime with E (enable all).' -ForegroundColor DarkYellow + Write-Host '==================================================================' -ForegroundColor Red + if ((Read-Host 'Proceed? type GO-DARK') -ceq 'GO-DARK') { + Invoke-AllAction -Controls $view -Action Disable -WithSecurity -WithGoDark + } + Read-Host 'Press Enter'; continue + } '^[Cc]\s+(.+)$' { Export-StatusCsv -Controls $view -Path $Matches[1].Trim('"') Read-Host 'Press Enter'; continue @@ -749,8 +858,22 @@ function Start-Menu { } '^[Dd]\s+(\d+)$' { $n = [int]$Matches[1] - if ($n -ge 1 -and $n -le $view.Count) { Invoke-ControlAction -Ctrl $view[$n-1] -Action Disable } - else { Write-Host 'Out of range' -ForegroundColor Red } + if ($n -ge 1 -and $n -le $view.Count) { + $ctrl = $view[$n-1] + if ($ctrl.Critical) { Invoke-ConfirmClose -Ctrl $ctrl } else { Invoke-ControlAction -Ctrl $ctrl -Action Disable } + } else { Write-Host 'Out of range' -ForegroundColor Red } + Read-Host 'Press Enter'; continue + } + '^X$' { + $critList = @($view | Where-Object { $_.Critical }) + if ($critList.Count -eq 0) { + Write-Host 'No [CRIT] items shown.' -ForegroundColor DarkYellow + Read-Host 'Press Enter'; continue + } + Write-Host '' + Write-Host ('Closing {0} [CRIT] connection(s), one at a time with confirmation.' -f $critList.Count) -ForegroundColor Red + Write-Host 'These break patching / time sync / trust. Reverse with E (enable all).' -ForegroundColor DarkYellow + foreach ($ctrl in $critList) { Invoke-ConfirmClose -Ctrl $ctrl } Read-Host 'Press Enter'; continue } '^\d+$' { @@ -758,12 +881,13 @@ function Start-Menu { if ($n -ge 1 -and $n -le $view.Count) { $ctrl = $view[$n-1] $state = Get-ControlState -Ctrl $ctrl - if ($state -like 'Enabled*') { Invoke-ControlAction -Ctrl $ctrl -Action Disable } - else { Invoke-ControlAction -Ctrl $ctrl -Action Enable } + if ($state -like 'Enabled*') { + if ($ctrl.Critical) { Invoke-ConfirmClose -Ctrl $ctrl } else { Invoke-ControlAction -Ctrl $ctrl -Action Disable } + } else { Invoke-ControlAction -Ctrl $ctrl -Action Enable } } else { Write-Host 'Out of range' -ForegroundColor Red } Read-Host 'Press Enter'; continue } - default { Write-Host 'Unknown command (d/e need an item number; D/E/S/m act on the view)' -ForegroundColor Red; Start-Sleep -Milliseconds 600 } + default { Write-Host 'Unknown command (d/e need an item number; D/E/S/G/X/m act on the view)' -ForegroundColor Red; Start-Sleep -Milliseconds 600 } } } } @@ -781,6 +905,15 @@ if ($UserMode) { Write-Host ('USER MODE: showing {0} per-user item(s) only (no admin needed).' -f $active.Count) -ForegroundColor Magenta } +if ($GoDark) { + Write-Host '' + Write-Host 'GO DARK: disabling ALL telemetry + [SEC] security + [NET] ambient callbacks.' -ForegroundColor Red + Write-Host 'Reduces security and can break functionality. Reverse with -EnableAll.' -ForegroundColor DarkYellow + Invoke-AllAction -Controls $active -Action Disable -WithSecurity -WithGoDark + Show-Status -Controls $active + if ($Csv) { Export-StatusCsv -Controls $active -Path $Csv } + return +} if ($DisableAll) { Invoke-AllAction -Controls $active -Action Disable -WithSecurity:$IncludeSecurity Show-Status -Controls $active diff --git a/README.md b/README.md index 2679c4b..239f2f7 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,9 @@ services and scheduled tasks** from one place. # ... also disable the SmartScreen reputation checks (see [SEC] note below) .\Manage-WindowsTelemetry.ps1 -DisableAll -IncludeSecurity +# GO DARK - maximum privacy: everything incl [SEC] security + [NET] callbacks +.\Manage-WindowsTelemetry.ps1 -GoDark + # Restore Windows default behaviour: turn everything back ON .\Manage-WindowsTelemetry.ps1 -EnableAll @@ -154,8 +157,57 @@ The header shows `MODE: USER` or `MODE: FULL`. In the interactive menu press | `` | Toggle item *n* (Enabled <-> Disabled) | | `e ` / `d ` | Enable / disable item *n* | | `E` / `D` (uppercase) | Enable / disable **ALL** shown items (asks for `YES` confirmation; `D` keeps `[SEC]` items ON) | -| `S` | Disable the `[SEC]` SmartScreen features (requires typing `DISABLE-SECURITY`) | +| `S` | Disable the `[SEC]` SmartScreen/Defender-cloud features (requires typing `DISABLE-SECURITY`) | +| `G` | **GO DARK** - disable everything incl `[SEC]` + `[NET]` (requires typing `GO-DARK`) | +| `X` | Close `[CRIT]` items (Windows Update / Time) one-by-one, each with a `CLOSE` confirm | | `m` | Toggle between USER mode (per-user only) and FULL mode (all items) | + +## Go dark (`-GoDark` / menu `G`) - maximum privacy + +Ordinary `-DisableAll` deliberately leaves two classes of item **on**, because +turning them off has real costs: + +- **`[SEC]`** - anti-malware reputation checks: Windows SmartScreen (apps/files, + Store, phishing) and Defender cloud (MAPS + sample submission). Off = no + malicious-URL/app warnings and weaker AV. +- **`[NET]`** - ambient Microsoft callbacks: NCSI connectivity probe, root-cert + auto-update, Store app auto-update, font streaming, Media DRM online. Off can + break captive-portal detection, new-CA trust, app updates and some fonts/DRM. + +**Go dark disables all of it** - all telemetry **plus** every `[SEC]` and +`[NET]` item - for the smallest practical Microsoft footprint. It requires an +explicit switch/confirmation (`GO-DARK`) and is fully reversible with +`-EnableAll` (menu `E`). + +## `[CRIT]` - the last-mile connections (confirm-close only) + +Even go-dark won't touch these, because disabling them breaks core function. +They exist as **local-config stand-ins** for what a hardened environment does +properly (WSUS, a local NTP server, network isolation). Each is **never** +bulk-disabled - not by `-DisableAll` and not by `-GoDark` - and can only be +closed **individually, with a per-item `CLOSE` confirmation**: + +| `[CRIT]` item | Key/service | Cost of closing | Proper alternative | +|---|---|---|---| +| **Windows Update Auto** | `...\WindowsUpdate\AU\NoAutoUpdate=1` | No automatic security patching | WSUS / managed updates | +| **Windows Time Sync** | `W32Time` service disabled | Clock drift breaks Kerberos/TLS | Internal NTP time server | + +Close them from the menu with **`X`** (walks each with a confirm) or by +toggling the item's number (a `[CRIT]` disable always prompts `CLOSE`). Reverse +with `-EnableAll` / menu `E`, which restores auto-update and sets `W32Time` +back to its default (Manual) start. + +**Activation** and **certificate revocation (OCSP/CRL)** are *not* included: +there is no clean single-key local toggle for them (disabling activation +de-activates Windows; blanket revocation-off is dangerous and messy). In a +locked-down build those are handled by **network isolation / firewall**, which +is the right layer for them - a point you already make with WSUS + isolation + +a time server. + +Some `[SEC]` Defender items may be blocked by **Tamper Protection** (they'll +report a failure rather than silently not applying) - turn it off in Windows +Security first if you truly need them off. For the network callbacks that a +registry setting cannot stop, pair go-dark with host-firewall rules. | `r` | Refresh the view | | `c ` | Export status to CSV | | `q` | Quit | diff --git a/View-WindowsIdentifiers.ps1 b/View-WindowsIdentifiers.ps1 index 508c830..1f94ca2 100644 --- a/View-WindowsIdentifiers.ps1 +++ b/View-WindowsIdentifiers.ps1 @@ -125,11 +125,56 @@ function Get-DsRegStatus { # identifier baked into the TPM at manufacture. Needs admin. Uses the built-in # Get-TpmEndorsementKeyInfo cmdlet; returns the EKpub hash and (if RSA) the # modulus so the actual public key can be recorded. +# Minimal ASN.1/DER walker: collects every INTEGER value in the blob, descending +# into SEQUENCEs and BIT STRINGs. Used to pull the RSA modulus (the longest +# INTEGER) out of the EKpub without relying on .NET Core-only import APIs. +function Invoke-Asn1Walk { + param([byte[]]$d, [int]$s, [int]$e, [int]$depth = 0) + if ($depth -gt 24) { return } + $i = $s + while ($i -lt $e) { + $tag = $d[$i]; $i++ + if ($i -ge $e) { break } + $b = $d[$i]; $i++ + if ($b -lt 0x80) { $len = $b } else { + $n = $b -band 0x7f + if ($n -eq 0 -or ($i + $n) -gt $e) { break } + $len = 0; for ($k = 0; $k -lt $n; $k++) { $len = ($len * 256) + $d[$i]; $i++ } + } + if (($i + $len) -gt $e) { break } + if ($tag -eq 0x02) { [void]$Script:Asn1Ints.Add([byte[]]($d[$i..($i + $len - 1)])) } + elseif ($tag -eq 0x03) { if ($len -gt 1) { Invoke-Asn1Walk $d ($i + 1) ($i + $len) ($depth + 1) } } + elseif (($tag -band 0x20) -ne 0) { Invoke-Asn1Walk $d $i ($i + $len) ($depth + 1) } + $i += $len + } +} + +function Get-RsaModulus { + param([byte[]]$Der) + $Script:Asn1Ints = New-Object System.Collections.ArrayList + try { Invoke-Asn1Walk -d $Der -s 0 -e $Der.Length } catch { } + $best = $null + foreach ($b in $Script:Asn1Ints) { if ($null -eq $best -or $b.Length -gt $best.Length) { $best = $b } } + if ($null -eq $best -or $best.Length -lt 128) { return $null } # <1024-bit => not an RSA modulus (e.g. ECC EK) + if ($best.Length -gt 1 -and $best[0] -eq 0) { $best = $best[1..($best.Length - 1)] } # strip DER sign byte + return [byte[]]$best +} + +function Get-Sha { + param([byte[]]$Bytes, [ValidateSet('SHA1','SHA256')][string]$Algo = 'SHA256') + $h = $null + try { + $h = [System.Security.Cryptography.HashAlgorithm]::Create($Algo) + $hb = $h.ComputeHash($Bytes) + return (-join ($hb | ForEach-Object { $_.ToString('x2') })) + } finally { if ($h) { $h.Dispose() } } +} + function Get-TpmEkPub { # Windows exposes PublicKey as AsnEncodedData (DER SubjectPublicKeyInfo) and # often leaves PublicKeyHash empty - so we base64 the DER (the actual EKpub) # and compute the SHA-256 ourselves as a stable fingerprint. - $info = [pscustomobject]@{ Available = $false; Present = $false; WinHash = $null; Sha256 = $null; DerB64 = $null } + $info = [pscustomobject]@{ Available = $false; Present = $false; WinHash = $null; Sha256 = $null; Sha1 = $null; ModSha256 = $null; DerB64 = $null } if (-not (Get-Command Get-TpmEndorsementKeyInfo -ErrorAction SilentlyContinue)) { return $info } $info.Available = $true try { @@ -142,12 +187,10 @@ function Get-TpmEkPub { if ($raw -and $raw.Length -gt 0) { $bytes = [byte[]]$raw $info.DerB64 = [Convert]::ToBase64String($bytes) - $sha = $null - try { - $sha = [System.Security.Cryptography.SHA256]::Create() - $hb = $sha.ComputeHash($bytes) - $info.Sha256 = -join ($hb | ForEach-Object { $_.ToString('x2') }) - } finally { if ($sha) { $sha.Dispose() } } + $info.Sha256 = Get-Sha -Bytes $bytes -Algo SHA256 # hash of the DER SubjectPublicKeyInfo + $info.Sha1 = Get-Sha -Bytes $bytes -Algo SHA1 + $mod = Get-RsaModulus -Der $bytes # raw RSA modulus (null for ECC EKs) + if ($mod) { $info.ModSha256 = Get-Sha -Bytes $mod -Algo SHA256 } } } catch { } return $info @@ -188,18 +231,28 @@ function Get-DeviceIdentityAsSystem { $taskName = "WID_DeviceExtract_$PID" $payload = @" `$ErrorActionPreference='SilentlyContinue' -`$out=@() -`$base='HKCU:\SOFTWARE\Microsoft\IdentityCRL\UserExtendedProperties' -if(Test-Path `$base){ - foreach(`$k in (Get-ChildItem `$base)){ - `$cid=(Get-ItemProperty -LiteralPath `$k.PSPath).cid - if(`$cid){ - `$puid=`$null; try{`$puid=([Convert]::ToUInt64(`$cid,16)).ToString()}catch{} - `$out+=[pscustomobject]@{Account=(Split-Path `$k.Name -Leaf);Cid=`$cid;Puid=`$puid} +`$found=@{} +`$roots=@('HKCU:\SOFTWARE\Microsoft\IdentityCRL','HKLM:\SOFTWARE\Microsoft\IdentityStore\Cache\S-1-5-18') +foreach(`$root in `$roots){ + if(Test-Path `$root){ + `$keys=@(Get-Item `$root -ErrorAction SilentlyContinue) + @(Get-ChildItem `$root -Recurse -ErrorAction SilentlyContinue) + foreach(`$k in `$keys){ + `$props=Get-ItemProperty -LiteralPath `$k.PSPath -ErrorAction SilentlyContinue + if(`$props){ + foreach(`$vn in @('cid','CID')){ + if(`$props.PSObject.Properties.Match(`$vn).Count -gt 0){ + `$cid="`$(`$props.`$vn)" + if(`$cid -and -not `$found.ContainsKey(`$cid)){ + `$puid=`$null; try{`$puid=([Convert]::ToUInt64(`$cid,16)).ToString()}catch{} + `$found[`$cid]=[pscustomobject]@{Account=(Split-Path `$k.Name -Leaf);Cid=`$cid;Puid=`$puid;KeyPath="`$(`$k.Name)"} + } + } + } + } } } } -`$out | ConvertTo-Json -Depth 3 | Out-File -FilePath '$tmp' -Encoding ASCII +@(`$found.Values) | ConvertTo-Json -Depth 4 | Out-File -FilePath '$tmp' -Encoding ASCII "@ try { Set-Content -LiteralPath $ps1 -Value $payload -Encoding ASCII @@ -399,6 +452,23 @@ function Get-Identifiers { [void]$ids.Add( (New-Id 'Activation' 'Installed product key (decoded)' $decoded -Sensitive -Source 'CurrentVersion\DigitalProductId' ` -Explain 'Full key recovered from the DigitalProductId blob (classic base-24 decode). Retail/OEM installs decode to a real key; volume (MAK/KMS) installs do not store a recoverable key.') ) + # Consolidated "full product key": pick the best available source, or say why none exists. + $keyPattern = '^[A-Z0-9]{5}(-[A-Z0-9]{5}){4}$' + $fullKey = $null; $fullSrc = $null + if ($oemKey -and "$oemKey" -match $keyPattern) { $fullKey = "$oemKey"; $fullSrc = 'OEM firmware (OA3xOriginalProductKey)' } + elseif ($decoded -and "$decoded" -match $keyPattern) { $fullKey = "$decoded"; $fullSrc = 'DigitalProductId decode' } + if ($fullKey) { + [void]$ids.Add( (New-Id 'Activation' 'Full product key' $fullKey -Sensitive -Source $fullSrc ` + -Explain ('The full 25-character product key for this install, recovered from ' + $fullSrc + '. Masked unless -Reveal.')) ) + } else { + $reason = + if ("$chan" -match 'Volume|MAK|KMS') { '(not recoverable - Volume/MAK/KMS: only the last 5 chars are stored on the device)' } + elseif (-not $Script:IsAdmin) { '(not recoverable unelevated - run as admin with -Reveal; may exist in OEM firmware)' } + else { '(not recoverable on this install)' } + [void]$ids.Add( (New-Id 'Activation' 'Full product key' $reason -Source 'best-effort' ` + -Explain 'The complete 25-character key. It is only recoverable for retail/OEM installs (via OEM firmware OA3 key or DigitalProductId decode). Volume MAK/KMS keys are NOT stored on the device by design - Windows keeps only the last 5 characters - so no tool can display them.') ) + } + # ---- Advertising / user ---- [void]$ids.Add( (New-Id 'User' 'User name' ('{0}\{1}' -f $env:USERDOMAIN, $env:USERNAME) -Source 'env' ` -Explain 'Domain (or machine) and username of the current account.') ) @@ -540,12 +610,22 @@ function Get-Identifiers { [void]$ids.Add( (New-Id 'Hardware' 'TPM EKpub hash (Windows)' $ek.WinHash -Sensitive -Source 'Get-TpmEndorsementKeyInfo.PublicKeyHash' -Explain $ekExplain) ) } if ($ek.Sha256) { - [void]$ids.Add( (New-Id 'Hardware' 'TPM EKpub SHA-256' $ek.Sha256 -Sensitive -Source 'SHA-256 of PublicKey.RawData' ` - -Explain ($ekExplain + ' (SHA-256 fingerprint of the EKpub, computed here since Windows left PublicKeyHash empty.)')) ) + [void]$ids.Add( (New-Id 'Hardware' 'TPM EKpub SHA-256 (SPKI)' $ek.Sha256 -Sensitive -Source 'SHA-256 of PublicKey.RawData (DER SPKI)' ` + -Explain ($ekExplain + ' (SHA-256 over the DER SubjectPublicKeyInfo; computed here since Windows left PublicKeyHash empty.)')) ) + } + if ($ek.Sha1) { + [void]$ids.Add( (New-Id 'Hardware' 'TPM EKpub SHA-1 (SPKI)' $ek.Sha1 -Sensitive -Source 'SHA-1 of PublicKey.RawData (DER SPKI)' ` + -Explain 'SHA-1 fingerprint of the EKpub DER SubjectPublicKeyInfo. Some tools/attestation flows key off SHA-1 rather than SHA-256.') ) + } + if ($ek.ModSha256) { + [void]$ids.Add( (New-Id 'Hardware' 'TPM EKpub modulus SHA-256' $ek.ModSha256 -Sensitive -Source 'SHA-256 of raw RSA modulus' ` + -Explain 'SHA-256 over the RAW RSA modulus (public key value only, DER wrapper stripped) - the "raw-modulus" fingerprint some Microsoft/attestation formats use, as opposed to hashing the whole SubjectPublicKeyInfo.') ) } if ($ek.DerB64) { [void]$ids.Add( (New-Id 'Hardware' 'TPM EKpub (DER, b64)' $ek.DerB64 -Sensitive -Source 'Get-TpmEndorsementKeyInfo.PublicKey.RawData' ` - -Explain 'The actual EKpub public key: the DER-encoded SubjectPublicKeyInfo, base64. This IS the endorsement public key (not just a hash). Use -Reveal to record it.') ) + -Explain 'The actual EKpub public key: the DER-encoded SubjectPublicKeyInfo, base64. This IS the endorsement public key (not just a hash) - from it you can derive any required format. Use -Reveal to record it.') ) + [void]$ids.Add( (New-Id 'Hardware' 'TPM EKpub Name (TPM2B_NAME)' '(not derivable from this cmdlet)' -Source 'n/a' ` + -Explain 'The TPM "Name" (TPM2B_NAME = alg id + hash of the TPMT_PUBLIC area) is NOT computable from the DER SubjectPublicKeyInfo this cmdlet returns - it needs the raw TPMT_PUBLIC blob (via tpm2-tools or the NCrypt/PCPKSP provider). Shown for completeness; the values above are what is available here.') ) } } elseif ($ek.Present) { [void]$ids.Add( (New-Id 'Hardware' 'TPM EKpub' '(EK present but public key not readable)' -Source 'Get-TpmEndorsementKeyInfo' -Explain $ekExplain) )