# NahuerDebloat bootstrap # Usage, from an elevated PowerShell: irm https://debloat.nahuer.com | iex # # Downloads the one release pinned below into memory, checks its SHA256 and only then writes it to # %ProgramData%\NahuerDebloat\app\- and runs it with the 64-bit Windows PowerShell 5.1. # A download that does not match the pinned hash is never written and nothing runs; there is no # fallback. tools/build_release.py fills in the three pinned values. # # This text is ASCII so any PowerShell host reads it the same way; accented letters are [char] codes. # The whole body is one script block: a truncated download fails to parse and runs nothing. # It uses return, never exit: under iex, exit would close the technician's console. & { Set-StrictMode -Version 2.0 $ErrorActionPreference = 'Stop' $ProgressPreference = 'SilentlyContinue' $version = '0.1.0' $sha256 = '093ff8e4c99749918426b6ae2ca1922f0fc018396a0833fa9e9902fd7749442e' $baseUrl = 'https://debloat.nahuer.com' $fileName = "nahuerdebloat-$version.zip" $releaseUrl = "$baseUrl/releases/$fileName" $aa = [char]0x00E1 $ea = [char]0x00E9 $oa = [char]0x00F3 $iq = [char]0x00BF if ([Environment]::OSVersion.Platform -ne 'Win32NT') { Write-Host 'NahuerDebloat funciona solo en Windows.' -ForegroundColor Red return } # The machine may still default to TLS 1.0; add 1.2 to whatever is already enabled. [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 # 64-bit Windows PowerShell 5.1: a 32-bit process sees it only through Sysnative. $systemDir = 'System32' if ([Environment]::Is64BitOperatingSystem -and -not [Environment]::Is64BitProcess) { $systemDir = 'Sysnative' } $powershell = Join-Path $env:SystemRoot "$systemDir\WindowsPowerShell\v1.0\powershell.exe" $principal = New-Object Security.Principal.WindowsPrincipal ([Security.Principal.WindowsIdentity]::GetCurrent()) if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { Write-Host 'NahuerDebloat necesita PowerShell como administrador.' -ForegroundColor Yellow $answer = Read-Host "${iq}Abrimos una ventana de PowerShell como administrador? (s/n)" if ($answer -match '^\s*s') { # The elevation service starts a 64-bit process, which has no Sysnative folder. $elevatedPowershell = Join-Path $env:SystemRoot 'System32\WindowsPowerShell\v1.0\powershell.exe' # -NoExit keeps the elevated window open so the result can be read. $arguments = '-NoExit -NoProfile -ExecutionPolicy Bypass -Command "irm {0} | iex"' -f $baseUrl Start-Process -FilePath $elevatedPowershell -Verb RunAs -ArgumentList $arguments } return } # The release stays in memory until its hash matches: there is no file to swap between check and use. Write-Host "Descargando NahuerDebloat $version desde $releaseUrl ..." $bytes = (New-Object Net.WebClient).DownloadData($releaseUrl) $hasher = [Security.Cryptography.SHA256]::Create() $actual = -join ($hasher.ComputeHash($bytes) | ForEach-Object { $_.ToString('x2') }) $hasher.Dispose() if ($actual -ine $sha256) { Write-Host "Error: el archivo descargado no coincide con la versi${oa}n publicada (SHA256)." -ForegroundColor Red Write-Host "No se guard$oa ni se ejecut$oa nada. Volv$ea a intentar m${aa}s tarde o us$aa el USB." -ForegroundColor Red return } # Only Administrators and SYSTEM may write here, so nobody else can change the files that run as admin. # SIDs, not names: Administrators, SYSTEM and Users have localized names. $administrators = New-Object Security.Principal.SecurityIdentifier 'S-1-5-32-544' $system = New-Object Security.Principal.SecurityIdentifier 'S-1-5-18' $users = New-Object Security.Principal.SecurityIdentifier 'S-1-5-32-545' $trustedSids = @('S-1-5-32-544', 'S-1-5-18') $inherit = [Security.AccessControl.InheritanceFlags]'ContainerInherit, ObjectInherit' $noPropagation = [Security.AccessControl.PropagationFlags]::None $allow = [Security.AccessControl.AccessControlType]::Allow $security = New-Object Security.AccessControl.DirectorySecurity $security.SetAccessRuleProtection($true, $false) $security.SetOwner($administrators) foreach ($rule in @( (New-Object Security.AccessControl.FileSystemAccessRule $administrators, 'FullControl', $inherit, $noPropagation, $allow), (New-Object Security.AccessControl.FileSystemAccessRule $system, 'FullControl', $inherit, $noPropagation, $allow), (New-Object Security.AccessControl.FileSystemAccessRule $users, 'ReadAndExecute', $inherit, $noPropagation, $allow))) { $security.AddAccessRule($rule) } # Rights that let someone change a folder or what is inside it, plus GENERIC_ALL and GENERIC_WRITE. $writeRights = [long][Security.AccessControl.FileSystemRights]'WriteData, AppendData, WriteExtendedAttributes, DeleteSubdirectoriesAndFiles, WriteAttributes, Delete, ChangePermissions, TakeOwnership' $writeRights = $writeRights -bor 0x10000000 -bor 0x40000000 # A folder made by someone else (before this bootstrap ran) is never reused or repaired: it is refused. function Test-NdTrustedFolder([string]$Path) { $item = Get-Item -LiteralPath $Path -Force if (-not $item.PSIsContainer -or ($item.Attributes -band [IO.FileAttributes]::ReparsePoint)) { return $false } $acl = Get-Acl -LiteralPath $Path if ($trustedSids -notcontains $acl.GetOwner([Security.Principal.SecurityIdentifier]).Value) { return $false } if (-not $acl.AreAccessRulesProtected) { return $false } foreach ($rule in $acl.GetAccessRules($true, $true, [Security.Principal.SecurityIdentifier])) { if ($rule.AccessControlType -ne $allow -or $trustedSids -contains $rule.IdentityReference.Value) { continue } if (([long]$rule.FileSystemRights -band $writeRights) -ne 0) { return $false } } return $true } function Add-NdTrustedFolder([string]$Path) { # One step: the folder never exists with the inherited ProgramData permissions. PowerShell 7 has # this overload as an extension method instead. if ($PSVersionTable.PSEdition -eq 'Core') { ('System.IO.FileSystemAclExtensions' -as [type])::CreateDirectory($security, $Path) | Out-Null } else { [IO.Directory]::CreateDirectory($Path, $security) | Out-Null } # CreateDirectory silently accepts a folder someone else created a moment earlier, without applying # our permissions; check the result instead of trusting it. return (Test-NdTrustedFolder $Path) } $root = Join-Path ([Environment]::GetFolderPath('CommonApplicationData')) 'NahuerDebloat' $notSafe = "Error: no se pudo crear una carpeta segura en {0}. Renombr${aa} $root y volv$ea a ejecutar el comando. No se ejecut$oa nada." if (Test-Path -LiteralPath $root) { if (-not (Test-NdTrustedFolder $root)) { Write-Host "Error: la carpeta $root ya existe y no es segura (no es de Administradores o la pueden modificar otros usuarios)." -ForegroundColor Red Write-Host "Renombr${aa}la (por ejemplo a NahuerDebloat.old) y volv$ea a ejecutar el comando. No se ejecut$oa nada." -ForegroundColor Red return } } elseif (-not (Add-NdTrustedFolder $root)) { Write-Host ($notSafe -f $root) -ForegroundColor Red return } $appRoot = Join-Path $root 'app' if (-not (Test-Path -LiteralPath $appRoot)) { if (-not (Add-NdTrustedFolder $appRoot)) { Write-Host ($notSafe -f $appRoot) -ForegroundColor Red return } } elseif (-not (Test-NdTrustedFolder $appRoot)) { Write-Host "Error: la carpeta $appRoot no es segura. Renombr${aa} $root y volv$ea a ejecutar el comando. No se ejecut$oa nada." -ForegroundColor Red return } # Older versions are not needed any more; only administrators could have written them, so deleting is safe. # Directory.Delete removes links without following them. foreach ($old in @(Get-ChildItem -LiteralPath $appRoot -Directory -Force -ErrorAction SilentlyContinue)) { try { [IO.Directory]::Delete($old.FullName, $true) } catch { Write-Host "No se pudo borrar la versi${oa}n anterior $($old.FullName); se deja como est${aa}." } } # A fresh folder nobody could have prepared in advance. $app = Join-Path $appRoot "$version-$([Guid]::NewGuid().ToString('N').Substring(0, 8))" if (-not (Add-NdTrustedFolder $app)) { Write-Host ($notSafe -f $app) -ForegroundColor Red return } $prefix = [IO.Path]::GetFullPath($app).TrimEnd('\') + '\' Add-Type -AssemblyName System.IO.Compression $stream = New-Object IO.MemoryStream (, $bytes) $archive = New-Object IO.Compression.ZipArchive $stream, ([IO.Compression.ZipArchiveMode]::Read) $extracted = $false try { foreach ($item in $archive.Entries) { $target = [IO.Path]::GetFullPath((Join-Path $app $item.FullName)) # Zip-slip: an entry like ..\..\x must not land outside the fresh folder. if (-not $target.StartsWith($prefix, [StringComparison]::OrdinalIgnoreCase)) { throw "entrada fuera de la carpeta: $($item.FullName)" } if ($item.FullName.EndsWith('/') -or $item.FullName.EndsWith('\')) { [IO.Directory]::CreateDirectory($target) | Out-Null continue } [IO.Directory]::CreateDirectory([IO.Path]::GetDirectoryName($target)) | Out-Null $source = $item.Open() try { $file = [IO.File]::Open($target, [IO.FileMode]::CreateNew, [IO.FileAccess]::Write, [IO.FileShare]::None) try { $source.CopyTo($file) } finally { $file.Dispose() } } finally { $source.Dispose() } } $extracted = $true } catch { Write-Host "Error: no se pudo extraer la versi${oa}n descargada ($($_.Exception.Message)). No se ejecut$oa nada." -ForegroundColor Red } finally { $archive.Dispose() $stream.Dispose() } if (-not $extracted) { [IO.Directory]::Delete($app, $true) return } $entry = Join-Path $app 'NahuerDebloat.ps1' & $powershell -NoProfile -ExecutionPolicy Bypass -File $entry $code = $LASTEXITCODE Write-Host "NahuerDebloat termin$oa con c${oa}digo $code. Archivos en $app" }