<# .SYNOPSIS Offline (WinPE / WinRE) neutralization of Sophos Endpoint tamper protection so that SophosZap can complete removal after a single reboot. .DESCRIPTION Inherited-MSP Sophos installs with NO Sophos Central access cannot be removed from inside Windows: tamper protection is enforced by a boot-start kernel driver (SophosED.sys / SophosEL.sys), and SophosZap refuses to run while the registry flag SEDEnabled = 1. Run this from a PowerShell prompt in WinPE / WinRE (NOT normal Windows), pointed at the OFFLINE Windows volume. It performs every edit needed so that after ONE reboot, SophosZap --confirm runs cleanly: 1. Renames Sophos*.sys driver files -> .old (cannot load at boot) 2. Sets the "Sophos Endpoint Defense" service Start = 4 (Disabled) 3. Clears the tamper flags SEDEnabled = 0 and IgnoreSAV = 0 It asks for the Windows drive letter, proves the volume is really Windows (not the ~600 MB recovery partition), shows you the current values before changing anything, and confirms at every destructive step. .NOTES Origin : Built from the Lone Star Electrical LS-1 removal, 2026-06-02. Run from : WinPE / WinRE -> Command Prompt -> powershell (or a PE with PS). Requires : the target Windows volume must be UNLOCKED. If BitLocker is on, System32\config\SYSTEM is unreadable -- unlock with the recovery key first (manage-bde -unlock X: -RecoveryPassword ), or confirm BitLocker OFF from normal Windows before booting to PE. AFTER this script: a. Remove the PE USB. b. Reboot into normal Windows. c. Run: SophosZap.exe --confirm (pass 1 -- bulk removal) d. Reboot when it says "reboot and re-execute". e. Run: SophosZap.exe --confirm (pass 2 -- finishes the job) f. Verify: no Sophos services, drivers, folders, or Add/Remove entries; Windows Defender real-time protection ON. #> [CmdletBinding()] param() $ErrorActionPreference = 'Stop' $HiveMount = 'HKLM\OFFSYS' # temporary mount point for the offline SYSTEM hive function Write-Head([string]$t) { Write-Host ""; Write-Host "==== $t ====" -ForegroundColor Cyan } function Write-Ok ([string]$t) { Write-Host " [OK] $t" -ForegroundColor Green } function Write-Warn([string]$t) { Write-Host " [WARN] $t" -ForegroundColor Yellow } function Write-Err ([string]$t) { Write-Host " [ERROR] $t" -ForegroundColor Red } function Confirm-Step([string]$Message) { $ans = Read-Host "$Message [y/N]" return ($ans.Trim() -match '^(y|yes)$') } Write-Host @" ============================================================ Sophos Offline Removal (PE) - tamper-protection neutralizer ============================================================ This edits an OFFLINE Windows volume. Make sure you are in WinPE/WinRE, NOT the live Windows you want to clean. "@ -ForegroundColor White try { # --------------------------------------------------------------------------- # 1. Identify and validate the Windows drive letter # --------------------------------------------------------------------------- Write-Head "Step 1 - Identify the offline Windows volume" Write-Host "Volumes visible in this PE session:" Get-Volume -ErrorAction SilentlyContinue | Where-Object DriveLetter | Select-Object DriveLetter, FileSystemLabel, @{n='Size(GB)';e={[math]::Round($_.Size/1GB,1)}}, @{n='Free(GB)';e={[math]::Round($_.SizeRemaining/1GB,1)}} | Format-Table -AutoSize | Out-String | Write-Host $drive = $null do { $entry = (Read-Host "Enter the Windows drive letter as shown HERE in PE (e.g. C, D, E)").Trim().TrimEnd(':') if ($entry -notmatch '^[A-Za-z]$') { Write-Warn "Enter a single letter."; continue } $win = "${entry}:\Windows" $hive = "${entry}:\Windows\System32\config\SYSTEM" if (-not (Test-Path $win)) { Write-Warn "$win not found -- that is not the Windows volume."; continue } if (-not (Test-Path $hive)) { Write-Warn "$hive not found -- volume locked by BitLocker? Unlock it first."; continue } $drive = $entry.ToUpper() } while (-not $drive) # Prove it is the real OS volume, not the recovery partition Write-Host "" Write-Host "Evidence that ${drive}: is the real Windows volume:" foreach ($p in 'Windows','Windows\System32','Windows\System32\config','Users','Program Files') { $present = Test-Path "${drive}:\$p" "{0,-28} {1}" -f $p, $(if ($present) {'present'} else {'MISSING'}) | Write-Host } Write-Host "" if (-not (Confirm-Step "Is ${drive}: definitely the Windows install you want to clean?")) { Write-Err "Aborted by user. No changes made."; return } $driversDir = "${drive}:\Windows\System32\drivers" $systemHive = "${drive}:\Windows\System32\config\SYSTEM" # --------------------------------------------------------------------------- # 2. Find Sophos kernel driver files # --------------------------------------------------------------------------- Write-Head "Step 2 - Sophos driver files on disk" $sophosDrivers = @(Get-ChildItem $driversDir -Filter 'Sophos*.sys' -ErrorAction SilentlyContinue) if ($sophosDrivers.Count -eq 0) { Write-Warn "No Sophos*.sys driver files found (already removed, or different names)." } else { $sophosDrivers | Select-Object Name, Length, LastWriteTime | Format-Table -AutoSize | Out-String | Write-Host } # Note: *.man files are ETW manifests, not drivers -- SophosZap removes them. Ignore here. # --------------------------------------------------------------------------- # 3. Load the offline SYSTEM hive and resolve the active ControlSet # --------------------------------------------------------------------------- Write-Head "Step 3 - Load the offline registry hive" # Clean up a stale mount from a previous aborted run, if any. reg unload $HiveMount 2>$null | Out-Null $loaded = $false try { & reg load $HiveMount $systemHive | Out-Null if ($LASTEXITCODE -ne 0) { throw "reg load failed (exit $LASTEXITCODE). Is the hive in use / volume locked?" } $loaded = $true Write-Ok "Loaded $systemHive as $HiveMount" # Offline hives have ControlSet001/002 + Select\Current -- NOT CurrentControlSet. $controlSet = 'ControlSet001' $sel = & reg query "$HiveMount\Select" /v Current 2>$null if ($sel -match 'Current\s+REG_DWORD\s+0x([0-9a-fA-F]+)') { $controlSet = "ControlSet{0:D3}" -f [Convert]::ToInt32($matches[1], 16) } Write-Ok "Active control set: $controlSet" $svcKey = "$HiveMount\$controlSet\Services\Sophos Endpoint Defense" $tpKey = "$svcKey\TamperProtection\Config" # ----------------------------------------------------------------------- # 4. Show current values BEFORE changing anything # ----------------------------------------------------------------------- Write-Head "Step 4 - Current Sophos tamper state (offline hive)" $svcExists = $false & reg query $svcKey 2>$null | Out-Null if ($LASTEXITCODE -eq 0) { $svcExists = $true Write-Host "Service 'Sophos Endpoint Defense' -> Start:" & reg query $svcKey /v Start 2>$null | Where-Object { $_ -match 'Start' } | Write-Host Write-Host "TamperProtection flags:" & reg query $tpKey /v SEDEnabled 2>$null | Where-Object { $_ -match 'SEDEnabled' } | Write-Host & reg query $tpKey /v IgnoreSAV 2>$null | Where-Object { $_ -match 'IgnoreSAV' } | Write-Host } else { Write-Warn "Service key 'Sophos Endpoint Defense' not found under $controlSet (already removed?)." } Write-Host "" Write-Host "Planned changes:" -ForegroundColor White Write-Host " - rename $($sophosDrivers.Count) Sophos*.sys driver file(s) to .old" Write-Host " - set service 'Sophos Endpoint Defense' Start = 4 (Disabled)" Write-Host " - set SEDEnabled = 0 and IgnoreSAV = 0" Write-Host "" if (-not (Confirm-Step "Apply these changes to ${drive}: now?")) { Write-Err "Aborted by user before changes. Unloading hive, no edits made." return } # ----------------------------------------------------------------------- # 5. Apply registry edits (hive still loaded) # ----------------------------------------------------------------------- Write-Head "Step 5 - Apply registry edits" if ($svcExists) { & reg add $svcKey /v Start /t REG_DWORD /d 4 /f | Out-Null if ($LASTEXITCODE -eq 0) { Write-Ok "Service Start set to 4 (Disabled)" } else { Write-Err "Failed to set Start" } & reg add $tpKey /v SEDEnabled /t REG_DWORD /d 0 /f | Out-Null if ($LASTEXITCODE -eq 0) { Write-Ok "SEDEnabled set to 0" } else { Write-Warn "Could not set SEDEnabled (key may not exist on this version)" } & reg add $tpKey /v IgnoreSAV /t REG_DWORD /d 0 /f | Out-Null if ($LASTEXITCODE -eq 0) { Write-Ok "IgnoreSAV set to 0" } else { Write-Warn "Could not set IgnoreSAV" } Write-Host "" Write-Host "Read-back after edit:" & reg query $svcKey /v Start 2>$null | Where-Object { $_ -match 'Start' } | Write-Host & reg query $tpKey /v SEDEnabled 2>$null | Where-Object { $_ -match 'SEDEnabled' } | Write-Host } else { Write-Warn "No SED service key to edit -- skipping registry changes." } } finally { if ($loaded) { [gc]::Collect(); Start-Sleep -Milliseconds 300 & reg unload $HiveMount 2>$null | Out-Null if ($LASTEXITCODE -eq 0) { Write-Ok "Unloaded offline hive ($HiveMount)" } else { Write-Warn "reg unload reported a non-zero exit -- if it stayed mounted, close regedit/handles and run: reg unload $HiveMount" } } } # --------------------------------------------------------------------------- # 6. Rename the driver files (after the hive is unloaded) # --------------------------------------------------------------------------- Write-Head "Step 6 - Rename Sophos driver files" if ($sophosDrivers.Count -gt 0) { if (Confirm-Step "Rename $($sophosDrivers.Count) Sophos*.sys file(s) to .old so they cannot load?") { foreach ($f in $sophosDrivers) { $target = "$($f.FullName).old" try { if (Test-Path $target) { Remove-Item $target -Force } Rename-Item -LiteralPath $f.FullName -NewName "$($f.Name).old" -Force Write-Ok "Renamed $($f.Name) -> $($f.Name).old" } catch { Write-Err "Could not rename $($f.Name): $($_.Exception.Message)" } } } else { Write-Warn "Skipped driver rename (service Start=4 alone should still stop it loading)." } } else { Write-Host " (nothing to rename)" } # --------------------------------------------------------------------------- # 7. Next steps # --------------------------------------------------------------------------- Write-Head "DONE - offline edits complete" Write-Host @" Next, in NORMAL Windows (not PE): 1. Remove the PE USB so the box boots to Windows. 2. Reboot into Windows. 3. Run: SophosZap.exe --confirm (pass 1) 4. Reboot when it reports 'reboot and re-execute'. 5. Run: SophosZap.exe --confirm (pass 2) 6. Verify clean: Get-Service *sophos* -> nothing dir C:\Windows\System32\drivers\Sophos* -> nothing (or only *.old) 'C:\Program Files\Sophos','C:\ProgramData\Sophos' -> gone Get-MpComputerStatus -> RealTimeProtectionEnabled = True If SophosZap still says 'tamper protection on', the SEDEnabled flag did not clear -- re-check HKLM\SYSTEM\CurrentControlSet\services\Sophos Endpoint Defense\ TamperProtection\Config\SEDEnabled in live Windows and set it to 0. "@ -ForegroundColor White } catch { Write-Host "" Write-Err "Script stopped on an error:" Write-Host " $($_.Exception.Message)" -ForegroundColor Red if ($_.InvocationInfo) { Write-Host " at line $($_.InvocationInfo.ScriptLineNumber): $($_.InvocationInfo.Line.Trim())" -ForegroundColor DarkGray } # Best-effort: make sure we never leave the offline hive mounted after a crash. reg unload $HiveMount 2>$null | Out-Null } finally { Write-Host "" [void](Read-Host "Press Enter to close this window") }