🆕📢
之前的帖子 Xshell、Xftp(7.0 & 8.0)免费版去除授权弹窗和强制更新-新手实战 自动关闭了,后续的更新和交流就在这个新帖子哈~
Xshell、Xftp 家庭/学校免费版(free-for-home-school) 仅用于非商业用途,如需商用,请购买许可证。
通过不断地调试追码和猜测验证“定向爆破”,暂不涉及复杂的授权许可算法分析,作为个人兴趣业余玩家,能解决实际需求问题已经够用了。
更新记录
20260508-更新通用修补+低版本修补
20260315-去除评估版到期限制
版本说明
以 7.0 最后一个版本为例:
坛友发布的资源帖子中有官方旧版本,安装后会提示下载更新到最新版,下载完成后,用 Everything 搜“xshell exe”、“xftp exe”,按修改时间降序排列就能找到最后版本的安装包了。
安装包签名验证:选中安装包-右键-属性-数字签名-看一下签名者姓名-选中签名-详细信息-此数字签名正常。
在非官网渠道下载的安装包,一定要检查数字签名是否正常,最好跟官网能下载到的版本对比一下证书信息,查看证书,确认颁发给哪个公司组织、颁发者是否跟官网安装包一致!
个人版在官网免费授权页面直接下载
https://www.xshell.com/zh/free-for-home-school/
名称: Xshell-7.0.0170p.exe
大小: 49222760 字节 : 46 MiB
SHA1: 852416874cce8462c675711443c06b1249c78c34
名称: Xftp-7.0.0167p.exe
大小: 40753880 字节 : 38 MiB
SHA1: c9d1bda7b2ccbe5270716cc4816d714b08d25c67
- 版本号带p,授权许可为:Personal 个人版,安装过程窗口显示为个人版,安装完成启动程序后,帮助-关于 Xshell,能看到许可状态为个人版
- 联网安装时,可能会限制低版本安装,提示下载安装新版本,可以尝试断网安装
- 启动程序版本过低时,提示需要下载新版本才能运行,版本终止支持时,提示需要下载安装新的大版本
- 版本正常时,需要联网登录账号授权,注册一个临时邮箱即可通过授权验证,后续可能会限制临时邮箱账号
- 半年后可能会要求强制更新版本,更新后可能需要重新登录账号授权验证,使用之前的邮箱账号登录即可通过授权许可验证
- 不想注册账号登录授权,或者在局域网内,可以使用脚本修补去除授权验证和强制更新等各种限制
30天评估试用版需要填写邮箱获取下载链接
https://www.xshell.com/zh/xshell-download/
名称: Xshell-7.0.0170.exe
大小: 49225168 字节 : 46 MiB
SHA1: 1b1ef6efadaa2d0f4366aa6fbe07be7f6163a3e7
名称: Xftp-7.0.0167.exe
大小: 40917640 字节 : 39 MiB
SHA1: 4b5abcef74609a9b8fb0b19ce35134fc97a41c5a
- 版本号不带p,授权许可为:Evaluation 评估版,安装过程窗口显示为评估版,安装完成启动程序后,帮助-关于 Xshell,能看到许可状态为评估版
- 无论网络环境(联网、断网、局域网),都能直接安装,不限制低版本安装,不提示下载新版本
- 启动时不会要求强制更新,没有登录授权弹窗,30天后评估试用到期,不能继续免费试用,修补去除评估到期验证后可继续试用,功能应该是与个人版一致的(没有深度使用验证)
个人版与评估版都是官方签名版本,应用升级只能升级到各自对应的版本
两个版本二进制比对差异比较大,并不是只有一个标志位这么简单
评估版看起来限制更少,但是需要填写邮箱获取下载链接,修补脚本对个人版和评估版都适用
20260315
之前的帖子一直用的是 Personal 个人版进行修补,直到前些天有坛友发消息截图说用不了,验证许久之后才发现还有一个 30天评估版本 !!Evaluation 评估版限制相对要少一些,只有一个月的评估试用期,到期后会弹窗提示“评估期已过”,关闭弹窗就退出程序。
在个人版的基础上,修补去除评估版到期限制。仅验证了修改系统日期到 30+1 天,以及三年后,评估版主程序能正常打开运行,不再弹出“评估期已过”,没有验证局域网SSH是否正常可用,大概率是正常的,如果有问题,可以及时反馈。
Xshell 30天评估试用到期
验证操作步骤(新手实战请参照之前的帖子)
- 安装 Xshell、Xftp 评估版(版本号后面没有p),完成后运行程序,此时没有登录授权弹窗,也没有评估到期弹窗
- 关闭程序,修改系统日期到 30+1 天,注意先关闭系统自动校时
- x32dbg 打开安装目录下的主程序 Xshell.exe,命令框输入
bp CreateWindowExW打上弹窗断点
- 一直按 F9 运行调试,直到出现“评估期已过”的弹窗,可能会遇到调试卡住的情况,到【线程】栏,选中主线程,右键-恢复线程,继续 F9 调试。在【调用堆栈】栏找到用户模块下最近一次调用弹窗函数的入口地址,选择一个通过方法名称能看懂含义的方法,本次找到的是
nslicense.NSLICENSE_CheckLicense+4C0
- 双击入口地址跳转到代码位置,上方的 call 则是处理弹窗的方法,在 call 这一行 F2 打个断点
- 重新开始调试,F9 运行到 call 断点(出现卡住就恢复主线程运行),F8 单步调试,call 执行后,立马出现弹窗
- 猜测并尝试修改,在流程图分支处,修改不走评估到期分支,然后 F9 放行,运气不错,已经绕过了离线许可验证
Xshell 评估期已过-调试追码
Xshell 评估期已过-修补
20260508
特征码改用更为“通杀”的修补方式
添加 7.0 低版本【限制标签】和【退出弹窗】修补
最新脚本
理论上支持全版本 7.0.x、8.0.x 家庭/学校免费版和30天评估版
7.0 低版本有标签限制、退出弹窗,将脚本中 $rmTabLimit、$rmExitDialog 设置 true 即可修补去除
cmd 执行 notepad,打开记事本空白文档,复制脚本内容粘贴进去
Ctrl + S 保存,设置文件后缀名为.ps1,编码格式指定为ANSI
在脚本文件上右键-使用 PowerShell 运行,允许本次执行
# 默认情况下,Windows PowerShell 的执行策略为 Restricted,不允许运行任何脚本。
# 按住 Shift,右键-在此处打开 PowerShell 窗口
# 查看当前执行策略:Get-ExecutionPolicy
# 修改执行策略(当前用户有效):Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
# 确认执行策略为 RemoteSigned,选中这个脚本文件,右键-使用 PowerShell 运行
# PowerShell 5.1 版本对 UTF-8 中文支持不友好,中文解析报错,导致脚本不能正常执行,所以脚本编码使用的是 ANSI
# 查看版本:Get-Host | Select-Object Version
# 脚本自动修补只对【家庭/学校免费版】、【30天评估版】有效,其他专业收费版无效
# https://www.xshell.com/zh/free-for-home-school/
# 教程出处:https://www.52pojie.cn/thread-2096700-1-1.html
function Repair-BinaryFile {
param(
[string]$FilePath,
[string]$SearchHex,
[string]$ReplaceHex,
[switch]$ReplaceAll
)
# 验证文件存在
if (-not (Test-Path $FilePath)) {
#Write-Error "文件不存在: $FilePath"
Write-Host "文件不存在: $FilePath" -ForegroundColor Cyan
return
}
Write-Host "`n正在修补文件: $FilePath" -ForegroundColor Green
# 创建备份
$backupPath = Create-Backup -FilePath $FilePath
# 转换十六进制为字节数组
$SearchHex = $SearchHex -replace '\s',''
$ReplaceHex = $ReplaceHex -replace '\s',''
if ($SearchHex.Length -ne $ReplaceHex.Length) {
throw "搜索和替换的十六进制字符串长度必须相同"
}
if ($SearchHex.Length % 2 -ne 0) {
throw "十六进制字符串长度必须是偶数"
}
$searchBytes = for ($i = 0; $i -lt $SearchHex.Length; $i += 2) {
[Convert]::ToByte($SearchHex.Substring($i, 2), 16)
}
$replaceBytes = for ($i = 0; $i -lt $ReplaceHex.Length; $i += 2) {
[Convert]::ToByte($ReplaceHex.Substring($i, 2), 16)
}
Write-Host "开始搜索特征码..." -ForegroundColor Yellow
Write-Host "搜索: $SearchHex" -ForegroundColor Cyan
Write-Host "替换: $ReplaceHex" -ForegroundColor Cyan
Write-Host "模式: $(if ($ReplaceAll) { '替换所有匹配项' } else { '替换第一个匹配项' })" -ForegroundColor Cyan
# 执行直接替换
$matchesFound = SearchAndReplaceBinary -FilePath $FilePath -SearchBytes $searchBytes -ReplaceBytes $replaceBytes -ReplaceAll:$ReplaceAll
if ($matchesFound -gt 0) {
Write-Host "修补完成! 共替换了 $matchesFound 个匹配项" -ForegroundColor Green
#Write-Host "原文件备份: $backupPath" -ForegroundColor Cyan
} else {
Write-Host "未找到匹配的特征码" -ForegroundColor Yellow
<#
if (Test-Path $backupPath) {
Remove-Item $backupPath -Force
Write-Host "原文件备份已删除: $backupPath" -ForegroundColor Cyan
}
#>
}
}
function SearchAndReplaceBinary {
param(
[string]$FilePath,
[byte[]]$SearchBytes,
[byte[]]$ReplaceBytes,
[bool]$ReplaceAll
)
$searchLength = $SearchBytes.Length
$matchesFound = 0
# 以读写方式打开文件
$stream = [System.IO.File]::Open($FilePath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::ReadWrite)
try {
$buffer = New-Object byte[] (64KB)
$fileLength = $stream.Length
$position = 0
while ($position -lt $fileLength) {
# 计算要读取的字节数
$bytesToRead = [Math]::Min($buffer.Length, $fileLength - $position)
# 读取数据到缓冲区
$bytesRead = $stream.Read($buffer, 0, $bytesToRead)
if ($bytesRead -eq 0) { break }
# 在缓冲区中搜索
for ($i = 0; $i -le $bytesRead - $searchLength; $i++) {
$isMatch = $true
for ($j = 0; $j -lt $searchLength; $j++) {
if ($buffer[$i + $j] -ne $SearchBytes[$j]) {
$isMatch = $false
break
}
}
if ($isMatch) {
# 计算文件中的绝对位置
$matchPosition = $position + $i
# 定位并写入替换数据
$stream.Seek($matchPosition, [System.IO.SeekOrigin]::Begin) | Out-Null
$stream.Write($ReplaceBytes, 0, $ReplaceBytes.Length)
Write-Host "在位置 0x$($matchPosition.ToString('X8')) 找到匹配并替换" -ForegroundColor Green
$matchesFound++
# 恢复读取位置
$stream.Seek($position + $bytesRead, [System.IO.SeekOrigin]::Begin) | Out-Null
if (-not $ReplaceAll) {
return $matchesFound
}
}
}
$position += $bytesRead
# 显示进度
$percentComplete = [Math]::Round(($position / $fileLength) * 100, 2)
Write-Progress -Activity "处理文件" -Status "已扫描 $position/$fileLength 字节" -PercentComplete $percentComplete
}
Write-Progress -Activity "处理文件" -Completed
}
finally {
$stream.Close()
}
return $matchesFound
}
function Create-Backup() {
param(
[string]$FilePath
)
#$backupPath = "$FilePath.backup_$(Get-Date -Format 'yyyyMMdd_HHmmss')"
$backupPath = Get-Backup-Path -FilePath $FilePath
if (Test-Path $backupPath) {
Write-Host "原文件备份已存在: $backupPath" -ForegroundColor Cyan
return $backupPath
}
Write-Host "正在创建备份..." -ForegroundColor Yellow
Copy-Item $FilePath $backupPath -Force
Write-Host "备份已创建: $backupPath" -ForegroundColor Green
return $backupPath
}
function Get-Backup-Path() {
param(
[string]$FilePath
)
return $FilePath + "0000";
}
function Get-Install-Path-Parent() {
#$xshellExe = Get-Command -Name "Xshell" | Select-Object -ExpandProperty Source
$xshellExe = (Get-Command -Name "Xshell").Source # D:\Program Files (x86)\NetSarang\Xshell 8\Xshell.exe
if ($xshellExe) {
$installDir = Split-Path -Path $xshellExe -Parent # D:\Program Files (x86)\NetSarang\Xshell 8
$parentDir = Split-Path -Path $installDir -Parent # D:\Program Files (x86)\NetSarang
} else {
$xshellExe = $pwd
#Write-Host "脚本所在路径:$pwd" -ForegroundColor Gray
if ($xshellExe) {
$installDir = Split-Path -Path $xshellExe # D:\Program Files (x86)\NetSarang
#Write-Host "installDir:$installDir" -ForegroundColor Gray
$parentDir = $installDir
}
}
if (-not ($parentDir -match "NetSarang")) {
throw "请确认系统环境变量 Path 中存在程序安装路径,或者将脚本文件放在 Xshell 的安装目录下,并确保安装目录上级为 NetSarang"
}
# 最终返回程序安装父级目录:D:\Program Files (x86)\NetSarang\
return $parentDir + "\"
}
function Get-FileVersion {
param(
[string]$FilePath
)
if (Test-Path $FilePath) {
# 获取安装目录下主程序文件版本(8,0,0,21),用于判断处理逻辑
return (Get-Command $FilePath).FileVersionInfo.FileVersion
}
return ""
}
function Rename-File {
param(
[string]$FilePath
)
if ((Test-Path $FilePath) -and !(Test-Path ($FilePath + "0000"))) {
$fName = (Get-Item $FilePath).Name
#$bName = (Get-Item $FilePath).BaseName
#$ext = Get-ChildItem $FilePath | Select-Object Extension
$rName = $fName + "0000"
Rename-Item -Path $FilePath -NewName $rName
Write-Host "$FilePath 文件已重命名为: $rName" -ForegroundColor Green
}
#return $FilePath
}
function Show-ExitMessage {
Write-Host "脚本执行完成,按任意键退出..." -ForegroundColor Gray
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
}
# 设置错误处理,报错后不再往下执行
#$ErrorActionPreference = "Stop"
# 修补程序
$xshell7 = $true
$xftp7 = $true
$xshell8 = $true
$xftp8 = $true
# 去除4个标签限制,家庭/学校免费版安装包版本在 build [7.0.x, 7.0.0099),限制一个窗口最多只能打开4个标签
$rmTabLimit = $false
# 去除退出程序时弹窗,家庭/学校免费版安装包版本在 build [7.0.0099, 7.0.0134),退出程序时会弹窗提示当前是家庭/学校免费版
$rmExitDialog = $false
# 主程序入口【需要放在函数定义之后】
try {
Write-Host "`n【使用安装包升级版本后,需要手动将安装目录下的备份文件删除(后缀0000结尾的文件),再执行修补脚本】`n" -ForegroundColor Yellow
$installPath = Get-Install-Path-Parent
Write-Host "程序安装父级目录:$installPath" -ForegroundColor Gray
# Xshell 7.0.x
if ($xshell7) {
$fPath = $installPath + "Xshell 7\"
$fName = "nsssh3.dll"
# 去除授权登录弹窗-校验输入内容 NSSSH_CheckSubmitTokenIsValid
Repair-BinaryFile -FilePath ($fPath + $fName) -SearchHex "53 56 E8 A9 F9 FF FF 83 C4 08 8D 4D D8" -ReplaceHex "B8 01 00 00 00 59 5F 5E 5B 8B E5 5D C3"
$fName = "nslicense.dll"
# 绕过应用版本及授权许可检查/去除强制更新弹窗 NSLICENSE_CheckLicense
Repair-BinaryFile -FilePath ($fPath + $fName) -SearchHex "55 8B EC 81 EC CC 0D 00 00" -ReplaceHex "B8 05 00 00 00 C3 90 90 90"
# 去除首次启动时提示免费版;去除[7.0, 7.0.0090p)最多4个标签限制;去除[7.0.0099p, 7.0.0134p)在程序退出时弹出一个免费许可弹窗
if ($rmTabLimit -or $rmExitDialog) { # TODO 需要手动将标识变量赋值为$true
#Write-Host "7.0 旧版本修补:rmTabLimit=$rmTabLimit, rmExitDialog=$rmExitDialog" -ForegroundColor Gray
Repair-BinaryFile -FilePath ($fPath + $fName) -SearchHex "55 8B EC 81 EC 38 04 00 00" -ReplaceHex "B8 05 00 00 00 C3 90 90 90"
}
$fName = "nsutil2.dll"
# 去除授权登录弹窗-校验返回内容 NsIsLastCheckInReturnPassed
Repair-BinaryFile -FilePath ($fPath + $fName) -SearchHex "57 56 E8 3E FE FF FF 83 C4 08" -ReplaceHex "33 C0 59 5F 5E 5B 8B E5 5D C3"
# 重命名 LiveUpdate.exe,让检查更新功能失效
$fName = "LiveUpdate.exe"
Rename-File -FilePath ($fPath + $fName)
}
# Xftp 7.0.x
if ($xftp7) {
$fPath = $installPath + "Xftp 7\"
$fName = "nsssh3.dll"
# 去除授权登录弹窗-校验输入内容 NSSSH_CheckSubmitTokenIsValid
Repair-BinaryFile -FilePath ($fPath + $fName) -SearchHex "53 56 E8 A9 F9 FF FF 83 C4 08 8D 4D D8" -ReplaceHex "B8 01 00 00 00 59 5F 5E 5B 8B E5 5D C3"
$fName = "nslicense.dll"
# 绕过应用版本及授权许可检查/去除强制更新弹窗 NSLICENSE_CheckLicense
Repair-BinaryFile -FilePath ($fPath + $fName) -SearchHex "55 8B EC 81 EC CC 0D 00 00" -ReplaceHex "B8 05 00 00 00 C3 90 90 90"
# 去除首次启动时提示免费版;去除[7.0, 7.0.0090p)最多4个标签限制;去除[7.0.0099p, 7.0.0134p)在程序退出时弹出一个免费许可弹窗
if ($rmTabLimit -or $rmExitDialog) { # TODO 需要手动将标识变量赋值为$true
Repair-BinaryFile -FilePath ($fPath + $fName) -SearchHex "55 8B EC 81 EC 38 04 00 00" -ReplaceHex "B8 05 00 00 00 C3 90 90 90"
}
$fName = "nsutil2.dll"
# 去除授权登录弹窗-校验返回内容 NsIsLastCheckInReturnPassed
Repair-BinaryFile -FilePath ($fPath + $fName) -SearchHex "57 56 E8 3E FE FF FF 83 C4 08" -ReplaceHex "33 C0 59 5F 5E 5B 8B E5 5D C3"
# 重命名 LiveUpdate.exe,让检查更新功能失效
$fName = "LiveUpdate.exe"
Rename-File -FilePath ($fPath + $fName)
}
# Xshell 8.0.x
if ($xshell8) {
$fPath = $installPath + "Xshell 8\"
$fName = "nsssh3.dll"
# 去除授权登录弹窗-校验输入内容 NSSSH_CheckSubmitTokenIsValid
Repair-BinaryFile -FilePath ($fPath + $fName) -SearchHex "53 56 E8 A9 F9 FF FF 83 C4 08 8D 4D D8" -ReplaceHex "B8 01 00 00 00 59 5F 5E 5B 8B E5 5D C3"
$fName = "nslicense.dll"
# 绕过应用版本及授权许可检查/去除强制更新弹窗 NSLICENSE_CheckLicense
Repair-BinaryFile -FilePath ($fPath + $fName) -SearchHex "55 8B EC 81 EC CC 0D 00 00" -ReplaceHex "B8 05 00 00 00 C3 90 90 90"
$fName = "nsutil2.dll"
# 去除授权登录弹窗-校验返回内容 NsIsLastCheckInReturnPassed
Repair-BinaryFile -FilePath ($fPath + $fName) -SearchHex "57 56 E8 3E FE FF FF 83 C4 08" -ReplaceHex "33 C0 59 5F 5E 5B 8B E5 5D C3"
# 去除启动时检查更新及系统通知 NsIsUpdateIntervalPassed
Repair-BinaryFile -FilePath ($fPath + $fName) -SearchHex "55 8B EC FF 75 10 8B 55 0C" -ReplaceHex "B8 00 00 00 00 C3 90 90 90"
# 重命名 LiveUpdate.exe,让检查更新功能失效
$fName = "LiveUpdate.exe"
Rename-File -FilePath ($fPath + $fName)
}
# Xftp 8.0.x
if ($xftp8) {
$fPath = $installPath + "Xftp 8\"
$fName = "nsssh3.dll"
# 去除授权登录弹窗-校验输入内容 NSSSH_CheckSubmitTokenIsValid
Repair-BinaryFile -FilePath ($fPath + $fName) -SearchHex "53 56 E8 A9 F9 FF FF 83 C4 08 8D 4D D8" -ReplaceHex "B8 01 00 00 00 59 5F 5E 5B 8B E5 5D C3"
$fName = "nslicense.dll"
# 绕过应用版本及授权许可检查/去除强制更新弹窗 NSLICENSE_CheckLicense
Repair-BinaryFile -FilePath ($fPath + $fName) -SearchHex "55 8B EC 81 EC CC 0D 00 00" -ReplaceHex "B8 05 00 00 00 C3 90 90 90"
$fName = "nsutil2.dll"
# 去除授权登录弹窗-校验返回内容 NsIsLastCheckInReturnPassed
Repair-BinaryFile -FilePath ($fPath + $fName) -SearchHex "57 56 E8 3E FE FF FF 83 C4 08" -ReplaceHex "33 C0 59 5F 5E 5B 8B E5 5D C3"
# 去除启动时检查更新及系统通知 NsIsUpdateIntervalPassed
Repair-BinaryFile -FilePath ($fPath + $fName) -SearchHex "55 8B EC FF 75 10 8B 55 0C" -ReplaceHex "B8 00 00 00 00 C3 90 90 90"
# 重命名 LiveUpdate.exe,让检查更新功能失效
$fName = "LiveUpdate.exe"
Rename-File -FilePath ($fPath + $fName)
}
} catch {
Write-Host "错误: $($_.Exception.Message)" -ForegroundColor Red
Write-Host "堆栈跟踪: $($_.Exception.StackTrace)" -ForegroundColor Red
} finally {
Show-ExitMessage
}
附录