调试环境:
游戏版本号1.32.8.15801
一、前言
War3 132重置版内有自带旧版UI界面,包括旧版协议通讯,但是毕竟是新版本,所以略有不同。至于怎么进入旧版模式回头整理完会另外发博文,本文用到的数据包可以通过拦截游戏通讯(UDP协议) 后获取,由于通讯协议众多,本文只针对Host广播的建主信息进行分析、扩展。
二、封包分析
游戏内所有相关操作的通讯包都以下面这个结构开头:
typedef struct _w3x_header {
byte bMagic; //固定头 F7
byte bCmd; //操作码
WORD wSize; //包内数据长度
}W3X_HEADER,*PW3X_HEADER;
这里要分析的封包操作码是 0x30,其结构如下:
typedef struct _w3x_host_gameinfo {
W3X_HEADER Header; //头部
DWORD dwHostOs; //Host操作系统
DWORD dwGameType; //游戏类型
DWORD dwGameId; //游戏ID
DWORD dwHostSystemTime; //Host系统时间
char szHostRoomName[16]; //Host建房名称(就是看到的局域网内游戏列表名字),非固定的长度以 \0 结束
byte def_0; //固定为0
char szEncodeString[50]; //这段是加密的数据,地图信息,非固定的长度以 \0 结束
DWORD dwPlayerMaxNum; //房间支持总人数
DWORD dwGameSortFlag; //游戏模式 0x01 = 剧情游戏;0x09 = 自定义游戏
DWORD dwPlayerNum; //房间内当前人数
DWORD dwHumenSoltNum; //可进入玩家人数
DWORD dwUnknow; //未知数据
WORD wPort; //Host监听端口
}W3X_HOST_GAMEINFO,*PW3X_HOST_GAMEINFO;
其中有个加密字段,这里提供下解密函数如下:
void DecodeString(IN char* EncodedString,OUT char* DecodedString) {
char mask;
int pos = 0, dpos = 0;
while (EncodedString[pos] != 0)
{
if (pos % 8 == 0) mask = EncodedString[pos];
else
{
if ((mask & (0x1 << (pos % 8))) == 0)
DecodedString[dpos++] = EncodedString[pos] - 1;
else
DecodedString[dpos++] = EncodedString[pos];
}
pos++;
}
}
有了解密函数后,将数据解密又得到了个地图信息结构:
typedef struct _w3x_host_mapinfo {
DWORD dwMapConfig; //房间配置
BYTE def_0; //固定0
WORD wMapWidth; //地图宽度
WORD wMapHight; //地图高度
DWORD dwMapHash; //地图Hash,用于校验地图是否用户本地地图是否相同
char szMapName[50]; //地图名称,非固定的长度以 \0 结束
char szHostName[20]; //建主玩家名字,非固定的长度以 \0 结束
BYTE def_1; //固定0
char szSHA1[20]; //地图文件 SHA-1,用于地图下载
//后面都是缺省,无用数据
}W3X_HOST_MAPINFO,*PW3X_HOST_MAPINFO;
具体的封包数据样本就不发了,总要自己动下手。
三、地图校验分析
有了以上的结构说明,解析建主信息包还是比较简单的。但是其中有一个数据比较以往版本略有不同,就是MapHash的值,有些小伙伴应该看过开源的war3服务端ghost代码,其中就有对MapHash的计算,在war3 132版本开始这里的算法有了改变。导致ghost的建主地图无法被client认可了,这里逆向看看132版本是怎么算的。
通过搜索特征 ".w3e"字符串特征,我们会来到这片代码
00007FF75477032D 83 C5 3A add ebp,0x3A
00007FF754770330 0F 83 AE 25 00 00 jnc Warcraft III.00007FF7547728E4
00007FF754770336 81 B6 79 C4 5C 06 41 B8 04 01 xor dword ptr ds:[rsi+0x065CC479],0x0104B841
00007FF754770340 00 00 add byte ptr ds:[rax],al
00007FF754770342 48 8D 55 E0 lea rdx,qword ptr ss:[rbp-0x20]
00007FF754770346 E8 15 33 79 00 call Warcraft III.00007FF754F03660
00007FF75477034B 84 C0 test al,al
00007FF75477034D 0F 84 F2 11 00 00 je Warcraft III.00007FF754771545
00007FF754770353 48 89 BC 24 20 02 00 00 mov qword ptr ss:[rsp+0x00000220],rdi
00007FF75477035B 48 8D 4D E0 lea rcx,qword ptr ss:[rbp-0x20] ;War3Map.j
00007FF75477035F 4C 89 A4 24 18 02 00 00 mov qword ptr ss:[rsp+0x00000218],r12
00007FF754770367 4C 89 AC 24 10 02 00 00 mov qword ptr ss:[rsp+0x00000210],r13
00007FF75477036F 4C 89 B4 24 08 02 00 00 mov qword ptr ss:[rsp+0x00000208],r14
00007FF754770377 4C 89 BC 24 00 02 00 00 mov qword ptr ss:[rsp+0x00000200],r15
00007FF75477037F E8 1C F5 39 00 call Warcraft III.00007FF754B0F8A0 ;-->>XORRotateLeft2
00007FF754770384 89 06 mov dword ptr ds:[rsi],eax
00007FF754770386 66 66 0F 1F 84 00 00 00 00 00 nop word ptr ds:[rax+rax+0x00000000]
00007FF754770390 72 36 jc Warcraft III.00007FF7547703C8 ;---
00007FF754770392 8A E4 mov ah,ah
00007FF754770394 73 32 jnc Warcraft III.00007FF7547703C8
00007FF754770396 83 C0 BB add eax,0xFFFFFFBB
00007FF754770399 83 C5 B6 add ebp,0xFFFFFFB6
00007FF75477039C 57 push rdi
00007FF75477039D F6 D9 neg cl
00007FF75477039F E8 F9 05 01 00 call Warcraft III.00007FF75478099D
00007FF7547703A4 C6 C7 4A mov bh,0x4A
00007FF7547703A7 81 C5 2A DF 02 CA add ebp,0xCA02DF2A
00007FF7547703AD 80 E9 85 sub cl,0x85
00007FF7547703B0 83 C3 1A add ebx,0x1A
00007FF7547703B3 F6 DF neg bh
00007FF7547703B5 81 C2 C1 90 94 E8 add edx,0xE89490C1
00007FF7547703BB 6A CE push 0xFFFFFFCE
00007FF7547703BD 81 C6 CA 9C B2 B4 add esi,0xB4B29CCA
00007FF7547703C3 4F 83 9C 57 26 48 8B D6 48 sbb qword ptr ds:[r15+r10*2-0x2974B7DA],0x48
00007FF7547703CC 8D 0D B6 C7 BC 01 lea ecx,dword ptr ds:[0x00007FF75633CB88] ;Ascii "war3map.w3e"
00007FF7547703D2 E8 79 FD FF FF call XORRotateLeft
00007FF7547703D7 48 8B D6 mov rdx,rsi
00007FF7547703DA 48 8D 0D B7 C7 BC 01 lea rcx,qword ptr ds:[Warcraft III.00007FF75633CB98];Ascii "war3map.wpm"
00007FF7547703E1 E8 6A FD FF FF call XORRotateLeft
00007FF7547703E6 48 8B D6 mov rdx,rsi
00007FF7547703E9 48 8D 0D B8 C7 BC 01 lea rcx,qword ptr ds:[Warcraft III.00007FF75633CBA8];Ascii "war3map.doo"
00007FF7547703F0 E8 5B FD FF FF call XORRotateLeft
00007FF7547703F5 48 8B D6 mov rdx,rsi
00007FF7547703F8 48 8D 0D B9 C7 BC 01 lea rcx,qword ptr ds:[Warcraft III.00007FF75633CBB8];Ascii "war3map.w3u"
00007FF7547703FF E8 4C FD FF FF call XORRotateLeft
00007FF754770404 48 C7 44 24 20 7F FB FF FF mov qword ptr ss:[rsp+0x20],0xFFFFFFFFFFFFFB7F
00007FF75477040D 48 8B 44 24 20 mov rax,qword ptr ss:[rsp+0x20]
00007FF754770412 48 05 C1 05 00 00 add rax,0x00000000000005C1
00007FF754770418 48 89 44 24 20 mov qword ptr ss:[rsp+0x20],rax
00007FF75477041D 4C 8B 64 24 20 mov r12,qword ptr ss:[rsp+0x20]
然后通过回溯就可以得到检查本地地图的函数
00007FF754B2E1F8 C3 retn
00007FF754B2E1F9 0F B6 4E 30 movzx ecx,byte ptr ds:[rsi+0x30]
00007FF754B2E1FD 33 D2 xor edx,edx
00007FF754B2E1FF 80 E1 01 and cl,0x01
00007FF754B2E202 E8 C9 62 7A 00 call Warcraft III.00007FF7552D44D0
00007FF754B2E207 8B 86 48 01 00 00 mov eax,dword ptr ds:[rsi+0x00000148]
00007FF754B2E20D C1 E8 09 shr eax,0x09
00007FF754B2E210 A8 01 test al,0x01
00007FF754B2E212 0F 85 E5 00 00 00 jne Warcraft III.00007FF754B2E2FD
00007FF754B2E218 48 8D 4E 54 lea rcx,qword ptr ds:[rsi+0x54]
00007FF754B2E21C 41 B8 04 01 00 00 mov r8d,0x00000104
00007FF754B2E222 48 8D 54 24 30 lea rdx,qword ptr ss:[rsp+0x30] ;MapFileBuffer
00007FF754B2E227 E8 34 86 7B FF call Warcraft III.00007FF7542E6860 ;GetMapFilePath
00007FF754B2E22C 44 8B 86 48 01 00 00 mov r8d,dword ptr ds:[rsi+0x00000148]
00007FF754B2E233 48 8D 46 40 lea rax,qword ptr ds:[rsi+0x40]
00007FF754B2E237 44 8B 4E 3C mov r9d,dword ptr ds:[rsi+0x3C] ;HASH
00007FF754B2E23B 48 8D 54 24 30 lea rdx,qword ptr ss:[rsp+0x30] ;MapFilePath
00007FF754B2E240 41 81 E0 FF 03 00 00 and r8d,0x000003FF ;1
00007FF754B2E247 48 89 44 24 20 mov qword ptr ss:[rsp+0x20],rax ;SHA-1
00007FF754B2E24C 48 8B CF mov rcx,rdi
00007FF754B2E24F E8 DC A9 C4 FF call Warcraft III.00007FF754778C30 ;InLocalNetGame_CheckMap
00007FF754B2E254 84 C0 test al,al
00007FF754B2E256 0F 84 76 FF FF FF je Warcraft III.00007FF754B2E1D2
基本该有的都有了,进过分析基本可以确定目前参与HASH计算的文件与顺序如下:
scripts\\war3map.j
war3map.w3e
war3map.wpm
war3map.doo
war3map.w3u
war3map.w3b
war3map.w3d
war3map.w3a
war3map.w3q
由于Hash是以迭代方式计算,所以文件的顺序会影响结果。其中J文件的计算方式与其他不同,这里直接给出计算代码
DWORD MapFileHashCalc(char *szFile, DWORD dwHash) {
DWORD dwValue = 0;
DWORD dwSrcHash = dwHash;
//读取文件
HANDLE hFile = CreateFile(szFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, NULL, NULL);
if (hFile != INVALID_HANDLE_VALUE) {
DWORD dwFileSize = GetFileSize(hFile, NULL);
DWORD dwReadNum = 0;
byte *szBuffer = new byte[dwFileSize];
if (ReadFile(hFile, szBuffer, dwFileSize, &dwReadNum, NULL)) {
printf("File:%s,Size:%d\r\n", szFile, dwReadNum);
if (strstr(szFile,".j") != NULL) {
dwValue = XORRotateLeft(szBuffer, dwReadNum);
if (dwSrcHash != 0) {
dwSrcHash = ROTL((dwValue ^ dwSrcHash), 3);
}else {
dwSrcHash = dwValue;
printf("=========>%s New Hash = 0x%X\r\n", szFile, dwSrcHash);
}
}else {
int n = dwReadNum / CALC_BLOCK_SIZE;
for (int i = 0; i < n; i++) {
dwValue = XORRotateLeft(&szBuffer[i * CALC_BLOCK_SIZE], CALC_BLOCK_SIZE);
dwSrcHash = ROTL((dwValue ^ dwSrcHash), 3);
}
}
delete[]szBuffer;
}
CloseHandle(hFile);
}else {
printf("!!!OpenFile %s Error[%d]\r\n",szFile, GetLastError());
}
return dwSrcHash;
}
PS.由于计算方式的改变,这里地图校验有着一个很大的漏洞,后面看看有没有时间写的DEMO验证一下。