造轮子记(附xshell7解密逆向分析)
0x00 前言
博客好长时间没更了,就把这段时间做的一点事情记录下吧。尝试第一次造轮子,写个xshell的密码读取工具(公司没有逼迫我qaq)。也挺好,有点压力也有动力,中间好多次想放弃。c语言实在是太太太太麻烦了。有了bug得扣内存去调试,解密出来乱码了也要读内存。头大。另外只是读取xshell6的密码,7的话用ida简单看了下,没找到加密函数,遂放弃。(我太菜了)
使用场景:
拿到主机权限后,上传该工具,一键读取xshell中存储的 host和账密。
1 | 读取并不难,密码不是明文传输,而是加密后的,解密的操作也浪费了很长时间。 |
0x01 加密分析
版本:
1 | xshell7: %userprofile%\Documents\NetSarang Computer\7\Xshell\Sessions |
1 | username+sid -> key |
1.密钥:
<5.1
Xshell 使用 MD5 算法来生成用于 RC4 密码的密钥,且 使用 16 字节长的 ASCII 字符串 MD5 摘要!X@s#h$e%l^l&
作为 RC4 密码密钥
1 | // Key = MD5("!X@s#h$e%l^l&"); |
5.1或5.2
XShell 使用 SHA-256 算法来生成密钥,密钥是当前操作系统帐户的 SID 字符串的 32 字节长的 SHA-256 摘要
1 | For example, if your current OS account's SID string is |
>5.2
这种情况与前一种(版本 5.1 或 5.2)类似,密钥是当前操作系统帐户名称(区分大小写)和当前操作系统帐户的 SID 字符串组合的 SHA-256 摘要。例如,如果您当前的操作系统帐户的名称和 SID 是(注意是当前登陆用户哈) Administrator S-1-5-21-917267712-1342860078-1792151419-512 密钥是字符串的 32 字节长 SHA-256 摘要”AdministratorS-1-5-21-917267712-1342860078-1792151419-512”
1 | For example, if your current OS account's name and SID are |
用户设置了主密码
密钥是用户设置了主密钥的 SHA-256摘要
1 | For example, if I set master password with "123123", the key that is used in RC4 cipher is |
2.计算原始密码的SHA-256摘要
此步骤将仅针对会话文件版本 >= 5.1 执行,且这个 32 字节长的数据将被视为校验和并附加到加密的密码中
1 | 如果原密码是 "This is a test", the SHA-256 digest would be: |
3.初始化密码
Xmanager 使用生成的密钥来初始化 RC4 密码。
4.加密密码
Xmanager 使用初始化的 RC4 密码加密原始密码
4.1 对于会话文件版本 < 5.1
由 XShell 加密
1
2
3
4unsigned char EncryptedPassword[] = {
0xff , 0xa2 , 0x9a , 0x4e , 0xb2 , 0xb0 , 0x9b , 0x47 ,
0x26 , 0x86 , 0xbd , 0x32 , 0x01 , 0x64
};
4.2 对于会话文件版本 == 5.1 OR 5.2
1 | unsigned char EncryptedPassword[] = { |
4.3 对于会话文件版本 > 5.2
1 | unsigned char EncryptedPassword[] = { |
4.4 对于用户设置了主密码的情况
1 | unsigned char EncryptedPassword[] = { |
5. 将校验和附加到加密密码。
此步骤将仅对 >= 5.1 的会话文件版本执行。
5.1 对于会话文件版本 == 5.1 或 5.2
1 | unsigned char FinalResult [ ] = |
5.2 对于会话文件版本 > 5.2
1 | unsigned char FinalResult [ ] = { |
5.3 对于用户设置了主密码的情况
1 | unsigned char finalresult [ ] = |
6.将最终结果转换为Base64格式
解密失败的原因: 先考虑版本问题,再考虑是否设置了userkey,再考虑是否使用其他方式登录验证,比如公钥,或者根本就没有保存。
0x02 代码实现
第一次造轮子,代码肯定很烂,如果有更好的实现可以交流
获取用户名和SID
这个微软有自带的api,可以读取当前用户的用户名和sid
1 | char* Getsid(); |
遍历目录
递归查找目录和后缀
1 | void find(char* path, char* name) |
宽字符转换
其中在读取txt文件的时候,可以直接读取里面的内容。
但我要读的是 xsh 后缀的文件,直接读取读出来是乱码。这里涉及到宽窄字符的问题,需要将宽字符转换为窄字符。
1 | char* become_char(WCHAR* source, int size) |
读取文件所有内容
1 | char* getfileall(char* fname, int MODEL) |
匹配字段
从待读取的字符串中匹配关键词
1 | char* extract(char* txt, char* name) |
RC4算法
1 | BYTE* RC4(char* pDate, int pData_len, BYTE* rc4_key, int rc4_key_len) |
SHA256
sha256.c
1 |
|
sha256.h
1 |
|
base64解码
1 | //base64解码 |
看下效果图
0x03 xshell7 本地密码算法逆向分析
2.23更新
首先看下xshell程序的组成,除了exe外,还有这么多的dll文件,而我们想要的是他的本地配置文件中明文密码的加密算法。但是这么多dll文件,我怎么知道加密算法在哪个dll文件中啊。
我比较笨,没有更好的方法,只能一个个看dll。先排除几个dll,比如 jsoncpp.dll
应该是存储数据的,nsactivate.dll
应该是激活程序的,nslicense.dll
应该和证书有关,nsprofile2.dll
配置文件,可能与加密算法有关,nsresource.dll
差不多是一些乱七八糟的资源文件……当然上面都是我个人的猜测。按照这个思路,这些dll放在后面考虑。
另外,根据 xshell5 和 xshell6 加密算法来看,加密算法离不开 sha256、RC4、md5、base64
这些函数,那我们就先过滤下这些字符串。
按照这个思路,运气很好。看来离目标不远了。
同时在该dll文件的导出表中发现了base64,狂喜
话不多说,直接梭
将 Password
push 入栈 ,同时还有 Public key
证明下确实是版本7 哈哈哈哈哈
也注意到里面定义的变量都是 wchar_t
型的,这也解释了为什么最初读文件时读的内容会乱码的问题,还要进行一步的宽字节转换。
获取当前域名
先进行变量初始化, 接着获取域名等变量,
fine,终于找到了!!! 前面说的都是屁话。正文开始
这里很明显,GetUserNameW
获取当前用户名,ConvertSidToStringSidW
获取当前用户的sid
而且做了版本的判断
我们先看 版本6的情况(>5.2),
a1 : 当前用户名
StringSid:当前用户的sid
在6的情况下,看到 返回的结果是 a1+StringSid 的拼接值 。
而在版本7,先进行了用户名的逆序,再进行用户名和sid的拼接,最终的返回值是 sid的逆序与用户名的拼接
1 | # 0range |
返回值作为 RC4 的密钥
其余和旧版本都不变。
总结
一个工具开发了一个多星期,期间几位师傅指导了我很多。体会到了改bug的快乐,也希望自己也能早日成为一名coder。明天就回学校了,下一阶段学习应该是免杀为主,但很多东西不能发,哎。希望也能多多输出文章吧。
Peace.