http://blog.sina.com.cn/s/blog_6c23c66c0100nddu.html
当在wow输入帐号密码登录后,客户端程序先连接的是登录服务器,(这里是叫realmd.exe的进程)通过sha1密文验证流程后,获取游戏服务器的ip,port,(这里便是mangosd.exe进程对应的ip,port)客户端接着连接这个游戏服务器。
这里阐述的是连接到mangos服务器后的验证过程.
1。 当与mangos服务器建立tcp连接后,服务器会想客户端发送消息SMSG_AUTH_CHALLENGE
int WorldSocket::open (void *a)
{
。。。。
// Send startup packet.
WorldPacket packet (SMSG_AUTH_CHALLENGE, 24);
packet << uint32(1); // 1...31
packet << m_Seed;
packet << uint32(0xF3539DA3); // random data
packet << uint32(0x6E8547B9); // random data
packet << uint32(0x9A6AA2F8); // random data
packet << uint32(0xA4F170F4); // random data
if (SendPacket (packet) == -1)
。。。。。。。
这里实际要用的信息为SMSG_AUTH_CHALLENGE这个OpCode,m_Seed一个随机数。
2.客户端获取这个OpCode为SMSG_AUTH_CHALLENGE的消息后,知道服务器要求客户端提供身份验证信息。
于是在客户端构造消息CMSG_AUTH_SESSION:
//do auth
BigNumber clientSeed;
clientSeed.SetRand(4 * 8);
sha.Initialize();
sha.UpdateData ("ADMINISTRATOR");
uint32 t = 0;
sha.UpdateData ((uint8 *) & t, 4);
sha.UpdateBigNumbers(&clientSeed, NULL);
sha.UpdateData ((uint8 *) & serverSeed, 4);
sha.UpdateBigNumbers (&K, NULL);
sha.Finalize();
uint32 unk2, unk3;
uint64 unk4;
ByteBuffer pkt3;
string account = "ADMINISTRATOR";
uint16 pkt3DataSize = 4+4+4+account.length()+1+4+4+8+20;
EndianConvertReverse(pkt3DataSize);
pkt3 << uint16(pkt3DataSize);
pkt3 << uint32(CMSG_AUTH_SESSION); //opCode
pkt3 << uint32(12484); //build version
pkt3 << unk2;
pkt3 << account;
pkt3 << unk3;
pkt3.append(clientSeed.AsByteArray(4),4);
pkt3 << unk4;
pkt3.append(sha.GetDigest(), 20);
send(sockGame,(char const*)pkt3.contents(), pkt3.size(), 0);
cout << "send AuthRequest" <<endl;
这里关键要构造sha验证密文:
20位密文 = sha(account, serverSeed,clientSeed,K)
account是帐号文本,serverSeed是SMSG_AUTH_CHALLENGE中传过来的服务端生成的随机数,clientSeed是相应的客户端生成的,K是之前验证登录服务器时生成的(客户端与服务端都有一份相同的K,服务端存放在realmd.account.sessionkey字段下)
客户端将account,clientSeed,sha密文传向服务端
3.在服务端处理客户端发送的消息
1)首先服务端读取包头大小的数据,验证包头信息
包头的结构:
struct ClientPktHeader
{
uint16 size; //包长,不算自身16位长度
uint32 cmd; //opCode
};
读取包长的数据
int WorldSocket::handle_input_missing_data (void){
。。。。。
//need to receive the header
const size_t to_header = (message_block.length () > m_Header.space () ? m_Header.space () : message_block.length ());
m_Header.copy (message_block.rd_ptr (), to_header);
message_block.rd_ptr (to_header);
。。。。。
m_Header是WorldSocket类的成员,定义:
/// Fragment of the received header.
ACE_Message_Block m_Header;
并在构造函数中初始化
WorldSocket::WorldSocket (void) :
WorldHandler (),
m_Session (0),
m_RecvWPct (0),
m_RecvPct (),
m_Header (sizeof (ClientPktHeader)),
。。。。
处理包头信息
int WorldSocket::handle_input_header (void)
{
。。。。。。
m_Crypt.DecryptRecv ((uint8*) m_Header.rd_ptr (), sizeof (ClientPktHeader));
ClientPktHeader& header = *((ClientPktHeader*) m_Header.rd_ptr ());
EndianConvertReverse(header.size);
EndianConvert(header.cmd);
。。。。。。
ACE_NEW_RETURN (m_RecvWPct, WorldPacket ((uint16) header.cmd, header.size), -1);
if(header.size > 0)
{
m_RecvWPct->resize (header.size);
m_RecvPct.base ((char*) m_RecvWPct->contents (), m_RecvWPct->size ());
}
。。。。。。
这里调用EndianConvertReverse(header.size)是因为用16位表示的header.size是按正常相反的Endian存放的,
(如果系统是小端存放,则这个size是以大端存放)
m_RecvWPct用来存放客户端发送的数据包,m_RecvPct其实只向m_RecvWPct的同一块内存区域
2)处理数据包
首先读取包头中size长的内容
int WorldSocket::handle_input_missing_data (void)『
。。。。。。。。。
//need more data in the payload
const size_t to_data = (message_block.length () > m_RecvPct.space () ? m_RecvPct.space () : message_block.length ());
m_RecvPct.copy (message_block.rd_ptr (), to_data);
message_block.rd_ptr (to_data);
。。。。。。。。。。
处理数据包
int WorldSocket::handle_input_payload (void)『
。。。。。。。。
const int ret = ProcessIncoming (m_RecvWPct);
。。。。。。。
int WorldSocket::ProcessIncoming (WorldPacket* new_pct)
{
const ACE_UINT16 opcode = new_pct->GetOpcode ();
。。。。。。。。。。。。
switch(opcode)
{
case CMSG_PING:
return HandlePing (*new_pct);
case CMSG_AUTH_SESSION:
if (m_Session)
{
sLog.outError ("WorldSocket::ProcessIncoming: Player send CMSG_AUTH_SESSION again");
return -1;
}
return HandleAuthSession (*new_pct);
。。。。。。。。。
int WorldSocket::HandleAuthSession (WorldPacket& recvPacket)『
。。。。。。。。。。。。。。。。
Sha1Hash sha;
uint32 t = 0;
uint32 seed = m_Seed;
sha.UpdateData (account);
sha.UpdateData ((uint8 *) & t, 4);
sha.UpdateData ((uint8 *) & clientSeed, 4);
sha.UpdateData ((uint8 *) & seed, 4);
sha.UpdateBigNumbers (&K, NULL);
sha.Finalize ();
if (memcmp (sha.GetDigest (), digest, 20))
。。。。。。。。。。。。。。。。
// NOTE ATM the socket is single-threaded, have this in mind ...
ACE_NEW_RETURN (m_Session, WorldSession (id, this, AccountTypes(security), expansion, mutetime, locale), -1);
m_Crypt.Init(&K);
m_Session->LoadGlobalAccountData();
m_Session->LoadTutorialsData();
m_Session->ReadAddonsInfo(recvPacket);
// In case needed sometime the second arg is in microseconds 1 000 000 = 1 sec
ACE_OS::sleep (ACE_Time_Value (0, 10000));
sWorld.AddSession (m_Session);
在服务端以客户端同样方式计算sha密文, 并与客户端传来的比较,如果相同则验证成功,
接着创建WorldSession实例,并初始化m_Crypt对称加密成员,之后服务器发送的消息就是加密过的了。
添加到m_Session游戏世界对象中,这一添加动作将在游戏世界主线程中被得到相应处理。
3)处理WorldSession的添加,在游戏世界对象World中
void World::UpdateSessions( uint32 diff )
{
///- Add new sessions
WorldSession* sess;
while(addSessQueue.next(sess))
AddSession_ (sess);
void
World::AddSession_ (WorldSession* s)
{
。。。。。。。。。。
一些关于PlayerCache的操作
如果Session能作为ActiveSession而不是QueuedSession(等待进入游戏)则
发送SMSG_AUTH_RESPONSE消息,其内容将被加密
WorldPacket packet(SMSG_AUTH_RESPONSE, 1 + 4 + 1 + 4 + 1);
packet << uint8 (AUTH_OK);
packet << uint32 (0); // BillingTimeRemaining
packet << uint8 (0); // BillingPlanFlags
packet << uint32 (0); // BillingTimeRested
packet << uint8 (s->Expansion()); // 0 - normal, 1 - TBC, must be set in database manually for each account
s->SendPacket (&packet);
4. 消息样例
connect to 192.168.111.128 8085
<=192.168.111.128 8085(ACK PUSH) SMSG_AUTH_CHALLENGE
00 1A | EC 01 01 00 | 00 00 57 B9 7B 6C A3 9D | 53 F3 B9 47 | 85 6E F8 A2 | 6A 9A F4 70
F1 A4
=>192.168.111.128 8085(ACK PUSH) CMSG_AUTH_SESSION
01 16 | ED 01 00 00 | C4 30 00 00 00 00 00 00 | 41 44 4D 49 | 4E 49 53 54 | 52 41 54 4F
52 00 00 00 | 00 00 9B DD | 02 04 01 00 | 00 00 00 00
00 00 E0 AB | FE BA B0 81 | 86 C3 32 8E | B5 F9 65 42
20 2B 7C 12 | DE 76 9E 02 | 00 00 78 9C | 75 D2 31 6E
C3 30 0C 05 | 50 F5 14 5D | 72 99 3A 01 | 0C 23 D1 52
2B 73 41 4B | BF 36 61 89 | 32 64 39 4D | 72 9D 5E B4
E8 D6 02 F4 | FC 88 4F E2 | 83 AF C6 98 | 26 F2 F3 49
25 7C BC F9 | 89 71 43 82 | D4 6B 67 5E | D2 D7 E1 62
FE 79 81 90 | 2E 9B AF 9C | 45 B5 86 CA | 80 B2 4E 79
D9 E1 5A 23 | 3E 19 31 58 | 16 4E B4 68 | 43 2C 81 65
54 03 8E 14 | 21 81 8A 46 | 39 0D 54 2F | 79 DC 35 87
7B 55 F0 84 | 61 1B 5D CE | 71 55 B0 8D | 8F 65 52 4F
69 ED 71 22 | BD BB D6 F6 | 5B B9 E1 A1 | E3 C6 31 34
24 B3 AA 9D | AC 0B BC 1E | DB 55 A4 3E | FB 19 75 AF
1E 4B BE 64 | 55 DE 89 83 | 0A EE B7 51 | 7D 9F E3 04
4B 42 23 B4 | BE 5D 9E A1 | 3F 81 2B 14 | D0 CF 1C E3
1E B3 A0 FC | B5 F3 F7 E9 | FC 03 E1 91 | C8 AB
<=192.168.111.128 8085(ACK PUSH) (encrypted)
85 57 | 9E 3C 0C 00 | 00 00 00 00 00 00 00 00 | 00 24 FC 8A | 06 02 01 00 | 00 00 00 00
00 02 01 00 | 00 00 00 00 | 00 02 01 00 | 00 00 00 00
00 02 01 00 | 00 00 00 00 | 00 02 01 00 | 00 00 00 00
00 02 01 00 | 00 00 00 00 | 00 02 01 00 | 00 00 00 00
00 02 01 00 | 00 00 00 00 | 00 02 01 00 | 00 00 00 00
00 02 01 00 | 00 00 00 00 | 00 02 01 00 | 00 00 00 00
00 02 01 00 | 00 00 00 00 | 00 02 01 00 | 00 00 00 00
00 02 01 00 | 00 00 00 00 | 00 02 01 00 | 00 00 00 00
00 02 01 00 | 00 00 00 00 | 00 02 01 00 | 00 00 00 00
00 02 01 00 | 00 00 00 00 | 00 02 01 00 | 00 00 00 00
00 02 01 00 | 00 00 00 00 | 00 02 01 00 | 00 00 00 00
00 02 01 00 | 00 00 00 00 | 00 02 01 00 | 00 00 00 00
00 00 00 00 | 00 E1 6C F5 | 24 00 00 00 | 00 5C 62 2D
24 17 C6 17 | 4D 01 15 00 | 00 00 80 C5 | 17 4D 00 00
00 00 81 C5 | 17 4D 38 D5 | 2C B0 00 00 | 00 00 00 00
00 00 00 00 | 00 00 00 00 | 00 00 00 00 | 00 00 00 00
00 00 00 00 | 00 00 00 00 | 00 00