#requires -Version 5.1 <# Verlfication VPN: install a verified sing-box client and connect using Hysteria2. Run in an elevated 64-bit PowerShell window on Windows 10/11. The VPN runs in this window. Press Ctrl+C to disconnect. No startup task, background service, or persistent execution-policy change is created. Use -InstallOnly when running a saved copy to install and validate without connecting. #> [CmdletBinding()] param([switch]$InstallOnly) & { param([bool]$OnlyInstall) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' $BaseUrl = 'https://verlfication.online' $ServerIp = '151.158.134.192' $Domain = 'verlfication.online' $Version = '1.14.0' if ([Environment]::OSVersion.Platform -ne 'Win32NT' -or -not [Environment]::Is64BitProcess) { throw 'Use 64-bit PowerShell on Windows 10/11.' } $identity = [Security.Principal.WindowsIdentity]::GetCurrent() $principal = New-Object Security.Principal.WindowsPrincipal($identity) if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { throw 'Open PowerShell with Run as administrator, then run this script again.' } [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 $architecture = $env:PROCESSOR_ARCHITECTURE.ToUpperInvariant() $releases = @{ AMD64 = @{ Arch = 'amd64' ZipHash = '3ffb56267da14e287be48bd10cf7e6505260125bad940b75101fbb4d5d58e5d6' ExeHash = 'aad0ede010eafa7b277e520464f3a66fde820103d737eff739f40f3cc9451dcc' DllHash = 'eee741046f0a3975124bae349aeac237aa306f3cc4de59ff5de070e74dbfdaeb' } ARM64 = @{ Arch = 'arm64' ZipHash = 'f58dff882b2feb022da8de41943804b38681ecab5e1f490f23602fc37e9d5dd4' ExeHash = '0ce5fbdad0c28fed8ee92f5e011db5a6eca9ec3a4c310f9a03d92f9162977d6e' DllHash = 'fd6a6a85a16044ecffe71abe6691deda8be244212b840f66e0e0b2e6508b0172' } } if (-not $releases.ContainsKey($architecture)) { throw 'Unsupported Windows architecture.' } $release = $releases[$architecture] $root = Join-Path ([Environment]::GetFolderPath('ProgramFiles')) 'VerlficationVPN' $exe = Join-Path $root 'sing-box.exe' $configFile = Join-Path $root 'config.json' $mutex = New-Object Threading.Mutex($false, 'Global\VerlficationVPN') $ownsMutex = $false $stage = $null function Test-VerifiedFile([string]$Path, [string]$Hash) { if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) { return $false } if ((Get-Item -LiteralPath $Path).Attributes -band [IO.FileAttributes]::ReparsePoint) { throw "Unexpected reparse point: $Path" } return (Get-FileHash -LiteralPath $Path -Algorithm SHA256).Hash -eq $Hash } try { try { $ownsMutex = $mutex.WaitOne(0) } catch [Threading.AbandonedMutexException] { $ownsMutex = $true } if (-not $ownsMutex) { throw 'This VPN is already running in another PowerShell window.' } if (Test-Path -LiteralPath $root) { $rootItem = Get-Item -LiteralPath $root if (-not $rootItem.PSIsContainer -or ($rootItem.Attributes -band [IO.FileAttributes]::ReparsePoint)) { throw 'The installation path must be a regular directory.' } } else { New-Item -ItemType Directory -Path $root | Out-Null } $acl = New-Object Security.AccessControl.DirectorySecurity $acl.SetAccessRuleProtection($true, $false) foreach ($sid in @('S-1-5-18', 'S-1-5-32-544')) { $account = New-Object Security.Principal.SecurityIdentifier($sid) $rule = New-Object Security.AccessControl.FileSystemAccessRule($account, 'FullControl', 'ContainerInherit,ObjectInherit', 'None', 'Allow') $acl.AddAccessRule($rule) } Set-Acl -LiteralPath $root -AclObject $acl $running = @(Get-CimInstance Win32_Process -Filter "Name='sing-box.exe'" | Where-Object { $_.ExecutablePath -eq $exe }) if ($running.Count) { throw 'This VPN client is already running. Stop it in its original window first.' } $validExe = Test-VerifiedFile $exe $release.ExeHash $validDll = Test-VerifiedFile (Join-Path $root 'libcronet.dll') $release.DllHash if (-not ($validExe -and $validDll)) { Write-Host "Installing sing-box $Version ($($release.Arch)) from the official release..." $stage = Join-Path $root ('stage-' + [Guid]::NewGuid().ToString('N')) New-Item -ItemType Directory -Path $stage | Out-Null $zip = Join-Path $stage 'client.zip' $url = "https://github.com/SagerNet/sing-box/releases/download/v$Version/sing-box-$Version-windows-$($release.Arch).zip" $web = New-Object Net.WebClient try { $web.DownloadFile($url, $zip) } finally { $web.Dispose() } if (-not (Test-VerifiedFile $zip $release.ZipHash)) { throw 'The downloaded archive failed SHA-256 verification.' } Expand-Archive -LiteralPath $zip -DestinationPath $stage $source = Join-Path $stage "sing-box-$Version-windows-$($release.Arch)" if (-not (Test-VerifiedFile (Join-Path $source 'sing-box.exe') $release.ExeHash)) { throw 'Client verification failed.' } if (-not (Test-VerifiedFile (Join-Path $source 'libcronet.dll') $release.DllHash)) { throw 'Library verification failed.' } foreach ($name in @('sing-box.exe', 'libcronet.dll', 'LICENSE')) { Copy-Item -LiteralPath (Join-Path $source $name) -Destination (Join-Path $root $name) -Force } } Write-Host 'Requesting the profile for your current public IP...' $web = New-Object Net.WebClient $web.Headers['Content-Type'] = 'application/json' try { $profile = $web.UploadString("$BaseUrl/api/enroll", 'POST', '{}') | ConvertFrom-Json } finally { $web.Dispose() } if ($profile.server -ne $ServerIp -or $profile.server_name -ne $Domain -or $profile.port -ne 443 -or $profile.profile_id -notmatch '^[a-f0-9]{32}$' -or $profile.auth -notmatch '^[a-f0-9]{32}:[A-Za-z0-9_-]{43}$' -or $profile.obfs_password -notmatch '^[a-f0-9]{64}$') { throw 'The server returned an unexpected profile.' } $config = @{ log = @{ level = 'info'; timestamp = $true } dns = @{ servers = @(@{ type = 'https'; tag = 'vpn-dns'; server = '1.1.1.1'; server_port = 443 path = '/dns-query'; detour = 'vpn' tls = @{ enabled = $true; server_name = 'cloudflare-dns.com' } }) final = 'vpn-dns'; strategy = 'ipv4_only' } inbounds = @(@{ type = 'tun'; tag = 'tun-in'; interface_name = 'VerlficationVPN' address = @('172.19.0.1/30', 'fdfe:dcba:9876::1/126') mtu = 1400; auto_route = $true; strict_route = $true dns_mode = 'native'; stack = 'mixed' route_exclude_address = @("$ServerIp/32") }) outbounds = @(@{ type = 'hysteria2'; tag = 'vpn'; server = $ServerIp; server_port = 443 password = $profile.auth obfs = @{ type = 'salamander'; password = $profile.obfs_password } tls = @{ enabled = $true; server_name = $Domain } }) route = @{ auto_detect_interface = $true; final = 'vpn' rules = @(@{ port = 53; action = 'hijack-dns' }) } } if ((Test-Path -LiteralPath $configFile) -and ((Get-Item -LiteralPath $configFile).Attributes -band [IO.FileAttributes]::ReparsePoint)) { throw 'Unexpected configuration reparse point.' } [IO.File]::WriteAllText($configFile, ($config | ConvertTo-Json -Depth 16), (New-Object Text.UTF8Encoding($false))) & $exe check -c $configFile if ($LASTEXITCODE -ne 0) { throw 'The VPN configuration did not pass validation.' } Write-Host "Profile: $($profile.profile_id) Public IP: $($profile.source_ip)" if ($OnlyInstall) { Write-Host "Installed and validated. Configuration: $configFile" return } Write-Host 'Connecting all Windows IP traffic through Hysteria2. HTTPS access to the VPN server stays direct.' Write-Host 'Keep this window open. Press Ctrl+C to disconnect and restore normal routing.' & $exe run -c $configFile if ($LASTEXITCODE -ne 0) { Write-Warning "The VPN client exited with code $LASTEXITCODE." } } finally { if ($stage -and (Test-Path -LiteralPath $stage)) { $resolvedStage = [IO.Path]::GetFullPath($stage) $resolvedRoot = [IO.Path]::GetFullPath($root).TrimEnd('\') + '\' if (-not $resolvedStage.StartsWith($resolvedRoot, [StringComparison]::OrdinalIgnoreCase)) { throw 'Refusing to clean a staging path outside the installation directory.' } Remove-Item -LiteralPath $resolvedStage -Recurse -Force } if ($ownsMutex) { $mutex.ReleaseMutex() } $mutex.Dispose() } } ([bool]$InstallOnly)