文章

Xshell 密码解密

所需信息

  1. Xshell 密文密码
  2. Xshell 版本信息(不同版本的密钥有些差异)
  3. 操作系统用户名(不包含计算机名)
  4. 用户 SID

密文密码

密文密码存在于 .xsh 后缀的 Xshell 配置文件中。

不同版本的 Xshell 在 Windows 上的配置文件(.xsh)所在目录不同,并且电脑上可以同时存在多个不同版本的 Xshell:

版本目录路径
Xshell 5%userprofile%\Documents\NetSarang\Xshell\Sessions
Xshell 6%userprofile%\Documents\NetSarang Computer\6\Xshell\Sessions
Xshell 7%userprofile%\Documents\NetSarang Computer\7\Xshell\Sessions

注:由于部分版本(Xshell5)的 UserDataPath 路径在安装时可以指定,会导致 *.xsh 文件位置产生变化。建议使用读取注册表的方式获取路径,比较准确(注册表位置没有改变)。

Xshell 用户数据路径(UserDataPath)相关的注册表位置:

注册表路径描述示例值
HKEY_CURRENT_USER\Software\NetSarang\Common\子目录名为 NetSarang 产品的大版本号信息如:7
HKEY_CURRENT_USER\Software\NetSarang\Common\大版本号\UserData\键 UserDataPath 的值为所有 NetSarang 产品的用户数据目录如:C:\Users\username\Documents\NetSarang Computer\7

配置文件就在 Windows 文件目录 UserDataPath键值\产品名(Xshell)\Sessions 下,每个 .xsh 文件就是一台主机的连接配置。

使用如下命令可以快速查看并过滤出 .xsh 配置文件中的关键信息:

1
type *.xsh | findstr /c:"Host=" /c:"Port=" /c:"UserName=" /c:"Password=" /c:"Version="

用户名和 SID

使用 WMIC 命令获取用户名和 SID:

1
2
3
PS C:\Users\hp\Desktop> WMIC useraccount get name,sid
Name                SID
hp                  S-1-5-21-2029295174-2268103727-2717561949-1000

用户名和 SID 的注册表位置:

注册表路径描述示例值
HKEY_LOCAL_MACHINE\SAM\SAM\Domains\Builtin\Aliases\Members\本地帐号的所有 SID 列表如:S-1-5-21-2029295174-2268103727-2717561949-1000
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\SID 值键 ProfileImagePath 的值为 SID 所关联的用户名如:C:\Users\hp

使用 Java 代码(jna-platform 包)获取操作系统用户名和 SID:

1
2
3
4
5
6
7
8
9
10
11
Netapi32Util.User[] users = Netapi32Util.getUsers();
System.out.println("用户数量: " + users.length);
System.out.println();
for (Netapi32Util.User user : users) {
    System.out.println("账户名称:" + user.name);
    Advapi32Util.Account account = Advapi32Util
            .getAccountByName(user.name);
    System.out.println("账户全名:" + account.fqn);
    System.out.println("账户SID:" + account.sidString);
    System.out.println();
}

加密

任何版本的 Xshell 密码均使用 RC4 加密算法,但不同版本的密钥有些变化。

版本 < 5.1 生成密码方式:使用固定密钥,对明文进行 RC4 加密,再进行 base64 编码。

版本 >= 5.1 生成密码的方式为:"明文进行 RC4 加密(密码)+ 明文的 sha256 摘要(校验和)" 再进行 Base64 编码。

版本 < 5.1

Xshell 5.1 以前使用固定字符串 !X@s#c$e%l^l& 的 MD5 值作为密钥

1
2
3
4
5
6
from hashlib import *
from Crypto.Cipher import ARC4
from base64 import *

cipher = ARC4.new(md5(b'!X@s#h$e%l^l&').digest())
print(b64encode(cipher.encrypt(b'hello')).decode())

版本 = 5.1 or 5.2

直接使用 SID 的 32 个字节的 sha256 摘要作为密钥

版本 > 5.2

使用 "用户名" + "SID" 的 sha256 摘要作为密钥

1
2
3
4
5
6
7
8
// Windows 用户名
String winUserName = "hp";
// Windows SID
String winSID = "S-1-5-21-2029295174-2268103727-2717561949-1000";
// XShell 密文密码 (明文为 test@!@#)
String encryptedPasswd = "MtrGl/ZA5JU/PqCczXcPOXyCXJ0lwQZaHeRKIzPV7P6xvfaw2sUscA==";
// 创建密钥 Key (密钥为 "操作系统帐户名称 + SID" 的 sha256 摘要数组)
byte[] sha256hex = org.apache.commons.codec.digest.DigestUtils.sha256(winUserName + winSID);

版本 7

使用 "倒序的SID + 用户名" 的 sha256 摘要数组作为密钥

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Windows 用户名
String winUserName = "hp";
// Windows SID
String winSID = "S-1-5-21-2029295174-2268103727-2717561949-1000";
// 明文密码
String plaintextPasswd = "test@!@#";
// 创建密钥 Key (密钥为 “倒序的SID + 操作系统帐户名称” 的 sha256 摘要数组)
byte[] sha256hex = DigestUtils.sha256(StringUtils.reverse(winSID) + winUserName);
Key key = new SecretKeySpec(sha256hex, "RC4");  // 指定该数组为密钥
// 创建 RC4加密器
Cipher cipher = Cipher.getInstance("RC4");
// 初始化加密器,指定加密密钥 Key
cipher.init(Cipher.ENCRYPT_MODE, key);
// 对数据进行加密
byte[] cipherBytes = cipher.doFinal(plaintextPasswd.getBytes());

// 在加密后的字节数组后面添加明文密码的sha256摘要(校验和),再进行 base64 编码
String encryptedPasswd = new String(org.apache.commons.codec.binary.Base64.encodeBase64(ArrayUtils.addAll(cipherBytes, DigestUtils.sha256(plaintextPasswd))));
System.out.println(encryptedPasswd);
// 密文密码 6WgMEKQvz4Y/PqCczXcPOXyCXJ0lwQZaHeRKIzPV7P6xvfaw2sUscA==

解密

需要注意的是,在 XShell >= 5.1 明文密码的 sha256 摘要(32 个字节)将被视为校验和,附加到加密的密码后,所以在解密时需要将其从末尾去除。

XShell 7 解密过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Windows 用户名
String winUserName = "hp";
// Windows SID
String winSID = "S-1-5-21-2029295174-2268103727-2717561949-1000";
// XShell 密文密码
String encryptedPasswd = "6WgMEKQvz4Y/PqCczXcPOXyCXJ0lwQZaHeRKIzPV7P6xvfaw2sUscA==";

// base64 解码后的字节数组
byte[] originalBytes = org.apache.commons.codec.binary.Base64.decodeBase64(encryptedPasswd);

// 从密文数组中去除校验和 (校验和,是 32 个字节长的 SHA-256 摘要,有的脚本里写的减 0x20 这是 32 的 16 进制值)
byte[] newBytes = new byte[originalBytes.length - 32];
System.arraycopy(originalBytes, 0, newBytes, 0, originalBytes.length - 32);

// 创建密钥 Key (密钥为 "倒序的SID + 操作系统帐户名称" 的 sha256 摘要数组)
byte[] sha256hex = org.apache.commons.codec.digest.DigestUtils.sha256(StringUtils.reverse(winSID) + winUserName);
Key key = new SecretKeySpec(sha256hex, "RC4");

// 创建RC4解密器
Cipher cipher = Cipher.getInstance("RC4");
// 初始化解密器,并指定 Key
cipher.init(Cipher.DECRYPT_MODE, key);

// 解密后的密码
String decryptedPasswd = new String(cipher.doFinal(newBytes));  // 解密后结果为字节数组,使用 new String() 将其转化为字符串
System.out.println(decryptedPasswd);
// 明文密码为 test@!@#
本文由作者按照 CC BY 4.0 进行授权

© h0ny. 保留部分权利。

本站由 Jekyll 生成,采用 Chirpy 主题。