详解验证Mangos服务器:消息SMSG_AUTH_CHALLENGE,CMSG_AUTH_SESSION,SMSG_AUTH_RESPONSE

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

 

你可能感兴趣的:(游戏,session,服务器,header,Random,input)