我是靠谱客的博主 欣喜铃铛,这篇文章主要介绍一点一滴解读网狐的加解密,现在分享给大家,希望可以做个参考。

先贴出来源码

复制代码
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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
#ifndef PACKET_HEAD_FILE #define PACKET_HEAD_FILE #pragma pack(1) // //端口定义 #define MAX_CONTENT 512 //并发容量 #define PORT_AUTO_SELECT INVALID_WORD //自动端口 #define PORT_LOGON 8300 //登陆端口 #define PORT_CENTER 8310 //协调端口 #define PORT_MANAGER 8320 //管理端口 // //网络定义 //数据类型 #define DK_MAPPED 0x01 //映射类型 #define DK_ENCRYPT 0x02 //加密类型 #define DK_COMPRESS 0x04 //压缩类型 //长度定义 #define SOCKET_TCP_VER 0x66 //网络版本 #define SOCKET_TCP_BUFFER 16384 //网络缓冲 #define SOCKET_TCP_PACKET (SOCKET_TCP_BUFFER-sizeof(TCP_Head))//网络缓冲 //长度定义 #define SOCKET_UDP_BUFFER 16384 //网络缓冲 #define SOCKET_UDP_PACKET (SOCKET_UDP_BUFFER-sizeof(UDP_Head))//网络缓冲 // //结构定义 //网络内核 struct TCP_Info { BYTE cbVersion; //版本标识 BYTE cbCheckCode; //效验字段 WORD wPacketSize; //数据大小 }; //网络命令 struct TCP_Command { WORD wMainCmdID; //主命令码 WORD wSubCmdID; //子命令码 }; //网络包头 struct TCP_Head { TCP_Info TCPInfo; //基础结构 TCP_Command CommandInfo; //命令信息 }; //网络缓冲 struct TCP_Buffer { TCP_Head Head; //数据包头 BYTE cbBuffer[SOCKET_TCP_PACKET]; //数据缓冲 }; // //网络内核 struct UDP_Info { BYTE cbDataKind; //数据类型 BYTE cbCheckCode; //效验字段 WORD wPacketSize; //数据大小 WORD wPacketIndex; //数据序列 WORD wConnectIndex; //连接索引 }; //网络命令 struct UDP_Command { WORD wMainCmdID; //主命令码 WORD wSubCmdID; //子命令码 }; //网络包头 struct UDP_Head { UDP_Info UDPInfo; //基础结构 UDP_Command CommandInfo; //命令信息 }; //网络缓冲 struct UDP_Buffer { UDP_Head Head; //数据包头 BYTE cbBuffer[SOCKET_UDP_PACKET]; //数据缓冲 }; // //内核命令 #define MDM_KN_COMMAND 0 //内核命令 #define SUB_KN_DETECT_SOCKET 1 //检测命令 // //传输数据 #define IPC_VER 1 //版本标识 #define IPC_PACKET (10240-sizeof(IPC_Head)) //最大包长 #define IPC_BUFFER (sizeof(IPC_Head)+IPC_PACKET) //缓冲长度 //数据包头 struct IPC_Head { WORD wVersion; //版本标识 WORD wPacketSize; //数据大小 WORD wMainCmdID; //主命令码 WORD wSubCmdID; //子命令码 }; //IPC 包结构 struct IPC_Buffer { IPC_Head Head; //数据包头 BYTE cbBuffer[IPC_PACKET]; //数据缓冲 }; // //数据定义 //加密密钥 const DWORD g_dwPacketKey=0xA55AA55A; //发送映射 const BYTE g_SendByteMap[256]= { 0x70,0x2F,0x40,0x5F,0x44,0x8E,0x6E,0x45,0x7E,0xAB,0x2C,0x1F,0xB4,0xAC,0x9D,0x91, 0x0D,0x36,0x9B,0x0B,0xD4,0xC4,0x39,0x74,0xBF,0x23,0x16,0x14,0x06,0xEB,0x04,0x3E, 0x12,0x5C,0x8B,0xBC,0x61,0x63,0xF6,0xA5,0xE1,0x65,0xD8,0xF5,0x5A,0x07,0xF0,0x13, 0xF2,0x20,0x6B,0x4A,0x24,0x59,0x89,0x64,0xD7,0x42,0x6A,0x5E,0x3D,0x0A,0x77,0xE0, 0x80,0x27,0xB8,0xC5,0x8C,0x0E,0xFA,0x8A,0xD5,0x29,0x56,0x57,0x6C,0x53,0x67,0x41, 0xE8,0x00,0x1A,0xCE,0x86,0x83,0xB0,0x22,0x28,0x4D,0x3F,0x26,0x46,0x4F,0x6F,0x2B, 0x72,0x3A,0xF1,0x8D,0x97,0x95,0x49,0x84,0xE5,0xE3,0x79,0x8F,0x51,0x10,0xA8,0x82, 0xC6,0xDD,0xFF,0xFC,0xE4,0xCF,0xB3,0x09,0x5D,0xEA,0x9C,0x34,0xF9,0x17,0x9F,0xDA, 0x87,0xF8,0x15,0x05,0x3C,0xD3,0xA4,0x85,0x2E,0xFB,0xEE,0x47,0x3B,0xEF,0x37,0x7F, 0x93,0xAF,0x69,0x0C,0x71,0x31,0xDE,0x21,0x75,0xA0,0xAA,0xBA,0x7C,0x38,0x02,0xB7, 0x81,0x01,0xFD,0xE7,0x1D,0xCC,0xCD,0xBD,0x1B,0x7A,0x2A,0xAD,0x66,0xBE,0x55,0x33, 0x03,0xDB,0x88,0xB2,0x1E,0x4E,0xB9,0xE6,0xC2,0xF7,0xCB,0x7D,0xC9,0x62,0xC3,0xA6, 0xDC,0xA7,0x50,0xB5,0x4B,0x94,0xC0,0x92,0x4C,0x11,0x5B,0x78,0xD9,0xB1,0xED,0x19, 0xE9,0xA1,0x1C,0xB6,0x32,0x99,0xA3,0x76,0x9E,0x7B,0x6D,0x9A,0x30,0xD6,0xA9,0x25, 0xC7,0xAE,0x96,0x35,0xD0,0xBB,0xD2,0xC8,0xA2,0x08,0xF3,0xD1,0x73,0xF4,0x48,0x2D, 0x90,0xCA,0xE2,0x58,0xC1,0x18,0x52,0xFE,0xDF,0x68,0x98,0x54,0xEC,0x60,0x43,0x0F }; //接收映射 const BYTE g_RecvByteMap[256]= { 0x51,0xA1,0x9E,0xB0,0x1E,0x83,0x1C,0x2D,0xE9,0x77,0x3D,0x13,0x93,0x10,0x45,0xFF, 0x6D,0xC9,0x20,0x2F,0x1B,0x82,0x1A,0x7D,0xF5,0xCF,0x52,0xA8,0xD2,0xA4,0xB4,0x0B, 0x31,0x97,0x57,0x19,0x34,0xDF,0x5B,0x41,0x58,0x49,0xAA,0x5F,0x0A,0xEF,0x88,0x01, 0xDC,0x95,0xD4,0xAF,0x7B,0xE3,0x11,0x8E,0x9D,0x16,0x61,0x8C,0x84,0x3C,0x1F,0x5A, 0x02,0x4F,0x39,0xFE,0x04,0x07,0x5C,0x8B,0xEE,0x66,0x33,0xC4,0xC8,0x59,0xB5,0x5D, 0xC2,0x6C,0xF6,0x4D,0xFB,0xAE,0x4A,0x4B,0xF3,0x35,0x2C,0xCA,0x21,0x78,0x3B,0x03, 0xFD,0x24,0xBD,0x25,0x37,0x29,0xAC,0x4E,0xF9,0x92,0x3A,0x32,0x4C,0xDA,0x06,0x5E, 0x00,0x94,0x60,0xEC,0x17,0x98,0xD7,0x3E,0xCB,0x6A,0xA9,0xD9,0x9C,0xBB,0x08,0x8F, 0x40,0xA0,0x6F,0x55,0x67,0x87,0x54,0x80,0xB2,0x36,0x47,0x22,0x44,0x63,0x05,0x6B, 0xF0,0x0F,0xC7,0x90,0xC5,0x65,0xE2,0x64,0xFA,0xD5,0xDB,0x12,0x7A,0x0E,0xD8,0x7E, 0x99,0xD1,0xE8,0xD6,0x86,0x27,0xBF,0xC1,0x6E,0xDE,0x9A,0x09,0x0D,0xAB,0xE1,0x91, 0x56,0xCD,0xB3,0x76,0x0C,0xC3,0xD3,0x9F,0x42,0xB6,0x9B,0xE5,0x23,0xA7,0xAD,0x18, 0xC6,0xF4,0xB8,0xBE,0x15,0x43,0x70,0xE0,0xE7,0xBC,0xF1,0xBA,0xA5,0xA6,0x53,0x75, 0xE4,0xEB,0xE6,0x85,0x14,0x48,0xDD,0x38,0x2A,0xCC,0x7F,0xB1,0xC0,0x71,0x96,0xF8, 0x3F,0x28,0xF2,0x69,0x74,0x68,0xB7,0xA3,0x50,0xD0,0x79,0x1D,0xFC,0xCE,0x8A,0x8D, 0x2E,0x62,0x30,0xEA,0xED,0x2B,0x26,0xB9,0x81,0x7C,0x46,0x89,0x73,0xA2,0xF7,0x72 }; // #pragma pack() #endif

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//随机映射 WORD CServerSocketItem::SeedRandMap(WORD wSeed) { DWORD dwHold = wSeed; return (WORD)((dwHold = dwHold * 241103L + 2533101L) >> 16); } //映射发送数据 BYTE CServerSocketItem::MapSendByte(BYTE const cbData) { BYTE cbMap = g_SendByteMap[(BYTE)(cbData+m_cbSendRound)]; m_cbSendRound += 3; return cbMap; } //映射接收数据 BYTE CServerSocketItem::MapRecvByte(BYTE const cbData) { BYTE cbMap = g_RecvByteMap[cbData] - m_cbRecvRound; m_cbRecvRound += 3; return cbMap; }

复制代码
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
//加密数据 WORD CServerSocketItem::EncryptBuffer(BYTE pcbDataBuffer[], WORD wDataSize, WORD wBufferSize) { WORD i = 0; //效验参数 ASSERT(wDataSize >= sizeof(TCP_Head)); ASSERT(wDataSize <= (sizeof(TCP_Head) + SOCKET_TCP_BUFFER)); ASSERT(wBufferSize >= (wDataSize + 2*sizeof(DWORD))); //调整长度 WORD wEncryptSize = wDataSize - sizeof(TCP_Info), wSnapCount = 0; if ((wEncryptSize % sizeof(DWORD)) != 0) { wSnapCount = sizeof(DWORD) - wEncryptSize % sizeof(DWORD); memset(pcbDataBuffer + sizeof(TCP_Info) + wEncryptSize, 0, wSnapCount); } //效验码与字节映射 BYTE cbCheckCode = 0; for (i = sizeof(TCP_Info); i < wDataSize; i++) { cbCheckCode += pcbDataBuffer[i]; pcbDataBuffer[i] = MapSendByte(pcbDataBuffer[i]); } //填写信息头 TCP_Head * pHead = (TCP_Head *)pcbDataBuffer; pHead->TCPInfo.cbCheckCode = ~cbCheckCode + 1; pHead->TCPInfo.wPacketSize = wDataSize; pHead->TCPInfo.cbVersion = SOCKET_TCP_VER; //加密数据 DWORD dwXorKey = m_dwSendXorKey; WORD * pwSeed = (WORD *)(pcbDataBuffer + sizeof(TCP_Info)); DWORD * pdwXor = (DWORD *)(pcbDataBuffer + sizeof(TCP_Info)); WORD wEncrypCount = (wEncryptSize + wSnapCount) / sizeof(DWORD); for (i = 0; i < wEncrypCount; i++) { *pdwXor++ ^= dwXorKey; dwXorKey = SeedRandMap(*pwSeed++); dwXorKey |= ((DWORD)SeedRandMap(*pwSeed++)) << 16; dwXorKey ^= g_dwPacketKey; } //设置变量 m_dwSendPacketCount++; m_dwSendXorKey = dwXorKey; return wDataSize; } //解密数据 WORD CServerSocketItem::CrevasseBuffer(BYTE pcbDataBuffer[], WORD wDataSize) { WORD i = 0; //效验参数 ASSERT(wDataSize >= sizeof(TCP_Head)); ASSERT(((TCP_Head *)pcbDataBuffer)->TCPInfo.wPacketSize == wDataSize); //调整长度 WORD wSnapCount = 0; if ((wDataSize % sizeof(DWORD)) != 0) { wSnapCount = sizeof(DWORD) - wDataSize % sizeof(DWORD); memset(pcbDataBuffer + wDataSize, 0, wSnapCount); } //提取密钥 if (m_dwRecvPacketCount == 0) { ASSERT(wDataSize >= (sizeof(TCP_Head) + sizeof(DWORD))); if (wDataSize < (sizeof(TCP_Head) + sizeof(DWORD))) throw TEXT("数据包解密长度错误"); m_dwRecvXorKey = *(DWORD *)(pcbDataBuffer + sizeof(TCP_Head)); m_dwSendXorKey = m_dwRecvXorKey; MoveMemory(pcbDataBuffer + sizeof(TCP_Head), pcbDataBuffer + sizeof(TCP_Head) + sizeof(DWORD), wDataSize - sizeof(TCP_Head) - sizeof(DWORD)); wDataSize -= sizeof(DWORD); ((TCP_Head *)pcbDataBuffer)->TCPInfo.wPacketSize -= sizeof(DWORD); } //解密数据 DWORD dwXorKey = m_dwRecvXorKey; DWORD * pdwXor = (DWORD *)(pcbDataBuffer + sizeof(TCP_Info)); WORD * pwSeed = (WORD *)(pcbDataBuffer + sizeof(TCP_Info)); WORD wEncrypCount = (wDataSize + wSnapCount - sizeof(TCP_Info)) / 4; for (i = 0; i < wEncrypCount; i++) { if ((i == (wEncrypCount - 1)) && (wSnapCount > 0)) { BYTE * pcbKey = ((BYTE *) & m_dwRecvXorKey) + sizeof(DWORD) - wSnapCount; CopyMemory(pcbDataBuffer + wDataSize, pcbKey, wSnapCount); } dwXorKey = SeedRandMap(*pwSeed++); dwXorKey |= ((DWORD)SeedRandMap(*pwSeed++)) << 16; dwXorKey ^= g_dwPacketKey; *pdwXor++ ^= m_dwRecvXorKey; m_dwRecvXorKey = dwXorKey; } //效验码与字节映射 TCP_Head * pHead = (TCP_Head *)pcbDataBuffer; BYTE cbCheckCode = pHead->TCPInfo.cbCheckCode;; for (i = sizeof(TCP_Info); i < wDataSize; i++) { pcbDataBuffer[i] = MapRecvByte(pcbDataBuffer[i]); cbCheckCode += pcbDataBuffer[i]; } if (cbCheckCode != 0) throw TEXT("数据包效验码错误"); return wDataSize; }

1.加密和解密结构在客户端和服务器是一样的,不会存在客户端的加密对应服务器的解密,服务器的加密对应客户端的解密
2.加密后数据的长度不会进行改变
3.加密方法没有采用比较有名的算法(例如DES),只是进行简单的置换和异或
4.算法采用了两套解密方式(重叠使用),方式一:逐字节置换,方式二:逐四字节进行异或(当然加密和解密顺序是相反的)
5.异或秘钥是根据上次发送的信息生成的,这样可以达到一环扣一环,只监听一次数据包是不可能解出明文的,必须还要有上次通信的内容
6.固定异或秘钥0xA55AA55A,估计应该是提升版本的时候用吧
7.最初异或秘钥有客户端生成,发送给服务器
8.服务器和客户端都保留有两个秘钥,发送秘钥和接收秘钥


首先评论一下该算法,算法的复杂度不是很高,有效的降低了因为加密解密造成的CPU消耗。聊一些乱七八糟的东西,发送网络数据包为什么还要进行加密啊?哈哈,一方面是防止嗅探监听进行破解,更重要的是针对外挂制作者吧。分析加解密代码以前,我曾抓包分析发送密文,就像网上常见的教程那样,先发送一个“1”,再分送一个“1”,再发送一个“2”,再发送一个“11”,观察数据包长度和内容的变化,由此猜测加密方式,后来看到秘钥是变化的,因为前后发送两个“1”的密文是不同的,由此我猜测服务器端和客户端是否约定好了一个很大的秘钥数组,轮着来,后来连续抓包发送的二十个“1”,妈的,崩溃,猜测失败。哈哈,当然现在看来这种只靠猜的方法是不可能猜出来加密解密的方式的,屡次猜测失败的情况下最终放弃,最近在看网狐内核代码,看到了加密解密这段,不由得赞叹当初的白费功夫。虽然看不到C++的源代码,但
是可以反汇编看到客户端的代码,我也挺熟悉OllyDbg的,大学期间常用它破解一些小程序,还有就是免杀常用到它,但是我没有达到用它分析算法的境界,相信牛人分析这些都是小菜一碟,我也只是泛泛学学而已。后来我尝试抓包腾讯的聊天数据包,加密之后的数据包长得样子和网狐擦不多,估计是算结构有雷同之处。
好了,进入正题,我们来一步一步分析加解密代码,我们分析的时候肯定是先分析加密的程序,但是读代码的时候我是先看的解密代码,这样就好容易了很多,当然如果你一理解了该算法,无论从前看还是从后看都是非常容易的,无非就是置换+异或嘛。我们先来整体了解一下加密流程再进行细节分析吧,这样方便读者阅读,有了方向感和目标感,否则不太理解为什么这样做,这样做的好处是什么。首先我们看一下发包结构,
最终发送的结构体是TCP_Buffer,我们观看一下它的一个成员cbBuffer,好家伙将近16KB,当然这个结构体不会整体发送过去,只会发送前边一部分数据,具体数据有多长,TCP_Info的最后面的成员wPacketSize会决定,这个性质决定了TCP_Info结构体不被加密,看看这个结构体的其他两个元素,cbCheckCode,校验和,TCP协议不是已经有检验和了吗,为什么多次一举呢,大概是防止外挂程序恶意修改分包数据的内容吧,因为TCP协议在底层,外挂程序大都在应用层,如果不在应用层进行校验和,本地外挂轻而易举就可修改封包内容,如果不是本地修改而是数据包在网络发送中修改就没有那么简单了(其实也不是很复杂,修改封包之后再修改校验和就可以了),TCP协议的校验和大概就是防止数据包在网络中的修改吧,cbVersion,版本号,这个应该是防止新老版本通信的策略,或者还有其他用途,由于版本是6603.所以版本号都是0x66。TCP_Command,主命令,子命令没有什么好说的,命令太多,进行分组。我们看看EncryptBuffer函数,首先要调整代码长度,调整到长度正好是4的整数倍,不够的后面补0
下面这段代码就是干的这个活,
WORD wEncryptSize = wDataSize - sizeof(TCP_Info), wSnapCount = 0;
if ((wEncryptSize % sizeof(DWORD)) != 0)
{
wSnapCount = sizeof(DWORD) - wEncryptSize % sizeof(DWORD);
memset(pcbDataBuffer + sizeof(TCP_Info) + wEncryptSize, 0, wSnapCount);
}
然后把所有字节加到一起,强转为BYTE类型,
BYTE cbCheckCode = 0;
for (i = sizeof(TCP_Info); i < wDataSize; i++)
{
cbCheckCode += pcbDataBuffer[i];
pcbDataBuffer[i] = MapSendByte(pcbDataBuffer[i]);
}

还有一句是pcbDataBuffer[i] = MapSendByte(pcbDataBuffer[i]);这是个字节映射,先看看这个映射函数m_cbSendRound是自定义偏移,个另一方的m_cbRecvRound是对应的,就是说这边发完数据包那边接收之后两个值是一样的,这样就可以达到秘钥一直在变,为了方便理解我们把它们暂时看做是0,接着聊聊两个映射数组,这两个数组可不是随便写的,该数组有256个元素,各不相同,为什么有两个数组?一个数组不是挺好的吗?加解密有一个数组,这样才是可逆的啊,期初我一直犯嘀咕,仔细看下这里面还有猫腻,比如我要加密一个字节“0x00”,我们看看MapSendByte函数,从g_SendByteMap里取第0x00个元素,就是0x70,这就是加密之后的数值,怎么解密呢,再看g_RecvByteMap数组,第0x70个元素正好是0x00,是不是巧合?再试一个数据,“0x01”,g_SendByteMap的第1个元素值是0x2F,加密值就是0x2F,g_RecvByteMap数组的第0x2F个元素正好是“0x01”,看到了吧,很神奇的数组吧,我们回过头看看为什么会有两个数组呢?保证程序的高效性,如果只有一个数组,解密的时候我们就不得不写一个循环进行破译,现在有了专门破译的数组,效率岂不是快了数百倍,为了实现动态的映射,加入了m_cbSendRound偏移量,没有关系的,有人可能会有疑问,接收端怎么知道这个偏移量,会不会两边不一样,哈哈,不用担心,加密段和解密端两边预定好一开始的偏移量(0),他们还约定每发送一个字节偏移量+3,这样就可以保证两边的偏移量始终一样。万一丢包了怎么办?哈哈发生丢包就不可以正常解密了,所以这种算法只适合TCP协议,UDP是不合适的。

BYTE CServerSocketItem::MapSendByte(BYTE const cbData)
{
BYTE cbMap = g_SendByteMap[(BYTE)(cbData+m_cbSendRound)];
m_cbSendRound += 3;
return cbMap;
}
接着再看异或加密,每次操作4个字节,和谁进行异或呢?我举个例子,比如对一下八个自己23 43 54 65 53 56 78 34异或加密,先把这八个字按照四个字节一块儿的分两节,23 43 54 65 和 53 56 78 34,对后面的四个字节异或时,有前边四个自己进行固定的映射,映射成4个字节xx xx xx xx,再由这4个字节和53 56 78 34进行异或,生成密文,那么前边这四个23 43 54 65和谁进行异或呢,和他前边的字节进行固定映射之后进行异或,这样说总的有个头吧,没错,这个数据包的头异或的目标是上次数据包的尾,那最初的最初和谁异或呢?这个是客户端随机生成的,他会在第一次数据包里面发送给服务器。这真是一环扣一环呀,所以说只抓获一次数据包是无法全部解密的,总是四字节的异或,要是不够四字节怎么办,不够就补0呀,知道当初为什么这样做了吧。知道了加密方式,解密方式就不用讲了。综合来看这套加密机智还是相当不错的,算法不算太复杂,但是很好用呀,想想当初只看密文猜测加密方式和秘钥,简直是扯淡。就说这么多吧,要是有什么说错的地方欢迎大家指正。

最后

以上就是欣喜铃铛最近收集整理的关于一点一滴解读网狐的加解密的全部内容,更多相关一点一滴解读网狐内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(64)

评论列表共有 0 条评论

立即
投稿
返回
顶部