所谓主协议也就是主要的协议类别,对于这款游戏来说可以分为4个类别它们的头分别是:
0xC1:
* 常用于固定封包格式(例如:移动,NPC对话,使用物品,拾取,普通攻击 等等)
* 特征:这类封包的长度一般较短
* 包头结构如下:
_Buffer = Packed Record
Head:Byte; //0xC1
Len:Byte; //包长度
end;
0xC2:
* 常用于动态长度封包,常见于返回包(例如:怪物列表,背包列表,伤害包)
* 特征:这类封包的长度一般较长
*包头结构如下:
_Buffer = Packed Record
Head:Byte; //0xC2
Len:Word; //包长度
end;
0xC3:
*C1的二次加密包
0xC4:
*C2的二次加密包
2、加密解密
大家看了上面的封包分析也发现了,这款游戏有2次加密,其实就是为了保护一些敏感的数据做的处理,其实后期游戏公司更加的猥琐了下,有部分发包有3次加密。
第一层加密:
这层加密最简单,一般就用于C1包的简单加密,下面就是逆向还原的算法
const
c_key :array [0..31] of byte = ($AB,$11,$CD,$FE,$18,$23,$C5,$A3,$CA,$33,
$C1,$CC,$66,$67,$21,$F3,$32,$12,$15,$35,
$29,$FF,$FE,$1D,$44,$EF,$CD,$41,$26,$3C,
$4E,$4D);
//加密 参数:IN OUT数据源, IN长度 返回:加密长度
function MyEncode(Buffer: Pointer;Len: Integer): Integer;
var
Src:pbyte;
i,j:Integer;
x:byte;
begin
Src:=Buffer;
if Src^ = $C1 then
begin
Inc(Src);
Inc(Src);
x:=Src^;
Inc(Src);
for i := 3 to Len - 1 do
begin
j:=i and $8000001F;
Src^:=Src^ xor x xor c_key[j];
x:=Src^;
Inc(Src);
end;
Result:=Len;
end
else
Result:=-1;
end;
//解密 参数:IN OUT数据源, IN长度 返回:解密长度
function MyDecode(Buffer: Pointer; Len: Integer): Integer;
var
Src:array [0..100] of byte;
i:Integer;
begin
CopyMemory(@Src,Buffer,Len);
if Src[0] = $C1 then
begin
for i := Len - 1 downto 3 do
begin
Src[i]:=Src[i] xor Src[i-1] xor c_key[i];
end;
Result:=Len;
CopyMemory(Buffer,@Src,Len);
end
else
Result:=-1;
end;
即是C1完成加密后的变成C3的2次加密,由于加密过程被VM了,这里就提供下逆向片段
00605FFD 0F8D F3010000 JGE main.006061F6
00606003 33C0 XOR EAX,EAX
00606005 0F85 EB010000 JNZ main.006061F6
0060600B 8B85 5CE3FDFF MOV EAX,DWORD PTR SS:[EBP+FFFDE35C]
00606011 40 INC EAX
00606012 40 INC EAX
00606013 8985 54CBFDFF MOV DWORD PTR SS:[EBP+FFFDCB54],EAX
00606019 C685 64FBFDFF C3 MOV BYTE PTR SS:[EBP+FFFDFB64],0C3
00606020 8A85 54CBFDFF MOV AL,BYTE PTR SS:[EBP+FFFDCB54]
00606026 8885 65FBFDFF MOV BYTE PTR SS:[EBP+FFFDFB65],AL
0060602C 8B85 0CCBFDFF MOV EAX,DWORD PTR SS:[EBP+FFFDCB0C]
00606032 2B85 58CBFDFF SUB EAX,DWORD PTR SS:[EBP+FFFDCB58]
00606038 50 PUSH EAX //InLen 需要加密的长度
00606039 8B85 58CBFDFF MOV EAX,DWORD PTR SS:[EBP+FFFDCB58]
0060603F 8D8405 5CCBFDFF LEA EAX,DWORD PTR SS:[EBP+EAX+FFFDCB5C]
00606046 50 PUSH EAX //InBuffer 加密的数据源
00606047 8D85 66FBFDFF LEA EAX,DWORD PTR SS:[EBP+FFFDFB66]
0060604D 50 PUSH EAX //OutBuffer 加密后存放的缓存
0060604E FF35 5C2E2301 PUSH DWORD PTR DS:[1232E5C] ; //KeyTable 默认变量
00606054 B9 1C6FBB08 MOV ECX,main.08BB6F1C //类指针
00606059 E8 B28C4F00 CALL main.00AFED10 ; pC3EnCode
0060605E A1 5C2E2301 MOV EAX,DWORD PTR DS:[1232E5C]
00606063 8985 2CCBFDFF MOV DWORD PTR SS:[EBP+FFFDCB2C],EAX
00606069 8B85 54CBFDFF MOV EAX,DWORD PTR SS:[EBP+FFFDCB54]
0060606F 8985 38CBFDFF MOV DWORD PTR SS:[EBP+FFFDCB38],EAX
这里给出这个函数的类型
Type
FEnCodeC3 = function (DefBase:Cardinal;OutBuffer:Pointer;InBuffer:Pointer;InLen:Integer):Integer;Stdcall;
这个加密其实是基于1,2层中间的,是根据不同的包查询获得虚函数进行的加密,所以在这里提供一个查询的后调用虚函数的地方,给大家参考
00780522 E8 AC200000 CALL main.007825D3
00780527 0FB6C0 MOVZX EAX,AL
0078052A 85C0 TEST EAX,EAX
0078052C 74 02 JE SHORT main.00780530
0078052E EB 1F JMP SHORT main.0078054F
00780530 8D4D F4 LEA ECX,DWORD PTR SS:[EBP-C]
00780533 E8 76200000 CALL main.007825AE
00780538 8B40 04 MOV EAX,DWORD PTR DS:[EAX+4]
0078053B 8945 FC MOV DWORD PTR SS:[EBP-4],EAX
0078053E FF75 10 PUSH DWORD PTR SS:[EBP+10]
00780541 FF75 0C PUSH DWORD PTR SS:[EBP+C]
00780544 8B45 FC MOV EAX,DWORD PTR SS:[EBP-4]
00780547 8B00 MOV EAX,DWORD PTR DS:[EAX]
00780549 8B4D FC MOV ECX,DWORD PTR SS:[EBP-4]
0078054C FF50 04 CALL DWORD PTR DS:[EAX+4] //这里就是查表后得到的3次加密处理
0078054F C9 LEAVE
00780550 C2 0C00 RETN 0C
00780553 55 PUSH EBP
00780554 8BEC MOV EBP,ESP
00780556 83EC 18 SUB ESP,18
00780559 894D E8 MOV DWORD PTR SS:[EBP-18],ECX
0078055C 8D45 08 LEA EAX,DWORD PTR SS:[EBP+8]
0078055F 50 PUSH EAX
00780560 8D45 F4 LEA EAX,DWORD PTR SS:[EBP-C]
00780563 50 PUSH EAX
00780564 8B4D E8 MOV ECX,DWORD PTR SS:[EBP-18]
这里给出这个函数的通用类型
Type
FParamEncode = Procedure (var Buffer;Len:Integer);Stdcall;
这款游戏的返回包中C1,C2头的是明文数据,没有任何加密,C3和C4则是2次加密。返回包中没有用到第3次加密!返回包是以粘连方式返回,如果HOOK Api Recv需自行拆分封包。