[原创]CoolCmd接入教程(某游戏协议破解及完整可编译插件源码)
20151012 支持插件的免费版已放出 需要的可进QQ交流群下载版本和插件
------------------------------------20150921新加Begin--------------------------------------------------------------------
新活动,20150921开始,任何人根据此示例接入一款协议加密的游戏,并提交插件给官方,可获得一个永久授权
须进交流QQ群101564837,联系官方确认游戏,防止重复.
------------------------------------20150921新加End----------------------------------------------------------------------
前言:
本有个通杀多款几十万在线游戏的插件教程,效果也不错,奈何多种原因不便公开.
So决定用个国产精品游戏做示例,物色了剑网3,又奈何游戏是收费模式,为了让感兴趣人员可以实际练习,选用剑侠世界2(清爽版)做示例.
任何有逆向和开发基础人员,学习此文,均可自主接入任何网游或其他网络软件,且近期将有机会靠接入必得工具永久授权@千游斩活动(一个工具破解市面所有网游还会远吗?).
CoolCmd是什么? --> http://bbs.pediy.com/showthread.php?t=203487
CoolCmd挖掘游戏漏洞? --> http://bbs.pediy.com/showthread.php?t=203777
CoolCmd交流Q群 --> 101564837
一、协议破解
1.下载运行游戏,登录账号,进入创建角色界面,OD附加之
选用创建角色协议的优势:
a.此时封包少,便于无干扰分析
b.已知加密前明文(创建的角色名),便于对照分析
2.堆栈回溯法回溯明文发包函数
⑴bp send 下断点
⑵输入角色名:加密解密,点进入游戏,OD断下
⑶OD Ctrl + F9 4次即可定位到明文发包函数
汇编代码:
00565405 8B45 FC mov eax, dword ptr [ebp-4]
00565408 50 push eax //压入参数
00565409 8B4D F4 mov ecx, dword ptr [ebp-C]
0056540C 8B51 28 mov edx, dword ptr [ecx+28]
0056540F 8B45 F4 mov eax, dword ptr [ebp-C]
00565412 8B48 28 mov ecx, dword ptr [eax+28] //寄存器参数
00565415 8B12 mov edx, dword ptr [edx]
00565417 8B42 14 mov eax, dword ptr [edx+14]
0056541A FFD0 call eax ; //eax = 0x008551D0 即明文发包函数
0056541C 8945 F8 mov dword ptr [ebp-8], eax
0056541F 8B4D FC mov ecx, dword ptr [ebp-4]
00565422 8B11 mov edx, dword ptr [ecx]
00565424 8B45 FC mov eax, dword ptr [ebp-4]
00565427 50 push eax
00565428 8B4A 08 mov ecx, dword ptr [edx+8]
0056542B FFD1 call ecx
0056542D 837D F8 00 cmp dword ptr [ebp-8], 0
00565431 75 23 jnz short 00565456
00565433 68 A8E7A800 push 00A8E7A8 //SendMsg2GateWay (对0x008551D0函数头、尾以及send同时下断点,可确认此函数同时具备加密和发送功能)

分析下此发包函数,为多语言通用,设定函数原型为:
typedef DWORD (__stdcall *TMMSendProc)(PSendParamSt pParam);
参数1 压栈参数 包含明文信息的结构体指针
对应结构体如下:
typedef struct _SendParamSt
{
DWORD dwMMSend_A; //参数A,固定基址
DWORD dwMMSend_B; //参数B,固定基址
DWORD dwUnknow; //未知,默认1
DWORD dwLen0; //协议长度
DWORD dwLen; //协议长度
DWORD dwBuff; //协议数据内存地址
}TSendParamSt,*PSendParamSt;内存断点分析dwMMSend_A,dwMMSend_B来源
dwMMSend_A = 0x00ABE510
dwMMSend_B = 0x00ABE4F8
对应汇编代码:
0085A88A C700 10E5AB00 MOV DWORD PTR DS:[EAX],game.00ABE510
0085A890 C740 04 F8E4AB00 MOV DWORD PTR DS:[EAX+4],game.00ABE4F8
0085A897 C740 08 01000000 MOV DWORD PTR DS:[EAX+8],1
0085A89E 8948 14 MOV DWORD PTR DS:[EAX+14],ECX
0085A8A1 5B POP EBX
0085A8A2 C3 RETN参数2 寄存器ECX 加密Key和加密函数相关的指针
内存断点分析Ecx对应基址和偏移
基址g_Adr_LoginKey = 0xB65FE4
对应汇编代码:
00428657 83F8 0A CMP EAX,0A
0042865A 75 07 JNZ SHORT game.00428663
0042865C 33C0 XOR EAX,EAX
0042865E E9 A5040000 JMP game.00428B08
00428663 A1 E45FB600 MOV EAX,DWORD PTR DS:[B65FE4]//B65FE4即基址
00428668 8B10 MOV EDX,DWORD PTR DS:[EAX]
0042866A 8B0D E45FB600 MOV ECX,DWORD PTR DS:[B65FE4]
00428670 8B42 10 MOV EAX,DWORD PTR DS:[EDX+10]
00428673 FFD0 CALL NEAR EAX偏移g_Oft_LoginKey1= 0x28
对应汇编代码:
0056540C 8B51 28 mov edx, dword ptr [ecx+28] //0x28偏移
0056540F 8B45 F4 mov eax, dword ptr [ebp-C]
00565412 8B48 28 mov ecx, dword ptr [eax+28] ECX = *(PDWORD)(*(PDWORD)(g_Adr_LoginKey) + g_Oft_LoginKey1);
(此游戏,游戏场景内的Ke使用另外的基址和偏移,分析过程类似,此处直接贴出)
基址g_Adr_SceneKey = 0x1C285F0
对应汇编代码:
005977B4 8B0D F085C201 MOV ECX,DWORD PTR DS:[1C285F0]//1C285F0
005977BA 8B11 MOV EDX,DWORD PTR DS:[ECX]
005977BC 8B0D F085C201 MOV ECX,DWORD PTR DS:[1C285F0]
005977C2 8B02 MOV EAX,DWORD PTR DS:[EDX]
005977C4 FFD0 CALL NEAR EAX偏移g_Oft_SceneKey1 = 0x30;
对应汇编代码:
00529F20 8B51 30 MOV EDX,DWORD PTR DS:[ECX+30]
00529F23 8B45 F4 MOV EAX,DWORD PTR SS:[EBP-C]
00529F26 8B48 30 MOV ECX,DWORD PTR DS:[EAX+30] ECX = *(PDWORD)(*(PDWORD)(g_Adr_SceneKey) + g_Oft_SceneKey1);
明文发包函数接口和参数分析完毕
3.内存断点追溯明文收包函数
bp recv,过程类似,此处略去
函数原型:
typedef DWORD (__cdecl *TMMRecvProc)(DWORD dwLen,DWORD dwBuff,DWORD dwParam);
参数1: dwParam 指向解密Key
参数2: dwBuff 存储接受的数据(解密前后都存于此)
参数3: dwLen 接收的数据长度(解密前后等长)
对应汇编代码:
0085B200 8B4424 0C MOV EAX,DWORD PTR SS:[ESP+C] //明文收包函数
0085B204 8B08 MOV ECX,DWORD PTR DS:[EAX]
0085B206 8B4424 08 MOV EAX,DWORD PTR SS:[ESP+8]
0085B20A 8D5424 0C LEA EDX,DWORD PTR SS:[ESP+C]
0085B20E 52 PUSH EDX
0085B20F 894C24 10 MOV DWORD PTR SS:[ESP+10],ECX
0085B213 8B4C24 08 MOV ECX,DWORD PTR SS:[ESP+8]
0085B217 50 PUSH EAX
0085B218 51 PUSH ECX
0085B219 E8 52FFFFFF CALL game.0085B170
0085B21E 83C4 0C ADD ESP,0C
0085B221 C3 RETN明文收包函数接口和参数分析完毕
二、编写CoolCmd插件:
只需把分析的内容填充到插件代码中即可,核心代码如下:
VC版代码:
#include "CmdApi.h"
#include <winsock2.h>
#pragma comment(lib,"ws2_32.lib")
//SendParam结构体
typedef struct _SendParamSt
{
DWORD dwMMSend_A; //参数A,固定基址
DWORD dwMMSend_B; //参数B,固定基址
DWORD dwUnknow; //未知,默认1
DWORD dwLen0; //协议长度
DWORD dwLen; //协议长度
DWORD dwBuff; //协议数据内存地址
}TSendParamSt,*PSendParamSt;
//明文发送
typedef DWORD (__stdcall *TMMSendProc)(PSendParamSt pParam);
//明文接收
typedef DWORD (__cdecl *TMMRecvProc)(DWORD dwLen,DWORD dwBuff,DWORD dwParam);
TMMSendProc OldMMSend = NULL;
TMMRecvProc OldMMRecv = NULL;
DWORD g_Adr_MMSend = 0;
DWORD g_Adr_MMRecv = 0;
DWORD g_Param_MMSend_A = 0;
DWORD g_Param_MMSend_B = 0;
DWORD g_Adr_LoginKey = 0;
DWORD g_Oft_LoginKey1 = 0x28;
DWORD g_Oft_LoginKey2_Send = 0xC;
DWORD g_Oft_LoginKey2_Recv = 0x10;
//
DWORD g_Adr_SceneKey = 0;
DWORD g_Oft_SceneKey1 = 0x30;
DWORD g_Oft_SceneKey2_Send = 0xC;
DWORD g_Oft_SceneKey2_Recv = 0x10;
DWORD __stdcall NewMMSend(PSendParamSt pParam)
{
TFilterRet FltRet;
DWORD dwEcx, dwBuff, dwLen;
//保存寄存器参数
_asm{
mov dwEcx,ecx
}
//
dwLen = pParam->dwLen;
dwBuff = pParam->dwBuff;
//调用滤镜接口,解析序号对应CheckBox创建序号
g_InitIO.Api.FilterOper((char*)(dwBuff),dwLen,0,&FltRet);
if (FltRet.bVis){
if (strlen(FltRet.cName) != 0){
g_InitIO.Api.WriteLog("Send明",FltRet.cName,(char*)(dwBuff),dwLen,0);
}
else{
g_InitIO.Api.WriteLog("Send明",CN_PUB_DEFHINT,(char*)(dwBuff),dwLen,0);
}
}
//屏蔽发送则直接返回
if (!FltRet.bCom){
return 1;
}
//恢复寄存器参数
_asm{
mov ecx,dwEcx
}
//调用原发包函数
return OldMMSend(pParam);
}
void __stdcall HookMMSend(bool bHook)
{
void* pRet;
if (bHook){
g_InitIO.Api.HookCodeNormal((void*)g_Adr_MMSend, &NewMMSend,&pRet);
OldMMSend = (TMMSendProc)pRet;
}
else{
g_InitIO.Api.UnHookCodeNormal(OldMMSend);
}
}
DWORD __cdecl NewMMRecv(DWORD dwLen,DWORD dwBuff,DWORD dwParam)
{
TFilterRet FltRet;
DWORD dwRet = 0;
//进解密处理
dwRet = OldMMRecv(dwLen,dwBuff,dwParam);
//调用滤镜接口,解析序号对应CheckBox创建序号
g_InitIO.Api.FilterOper((char*)(dwBuff),dwLen,1,&FltRet);
if (FltRet.bVis){
if (strlen(FltRet.cName) != 0){
g_InitIO.Api.WriteLog("Recv明",FltRet.cName,(char*)(dwBuff),dwLen,0);
}
else{
g_InitIO.Api.WriteLog("Recv明",CN_PUB_DEFHINT,(char*)(dwBuff),dwLen,0);
}
}
//
if (!FltRet.bCom){
//此处可自己通过替换封包的方式变相实现禁止收指定包
return 1;
}
return dwRet;
}
void __stdcall HookMMRecv(bool bHook)
{
void* pRet;
if (bHook){
g_InitIO.Api.HookCodeNormal((void*)g_Adr_MMRecv, &NewMMRecv,&pRet);
OldMMRecv = (TMMRecvProc)pRet;
}
else{
g_InitIO.Api.UnHookCodeNormal(OldMMRecv);
}
}
//自定义的执行函数
//获取SendEcx Key指针
DWORD __stdcall GetSendEcx()
{
DWORD dwTmp,dwEcx;
//登录SendEcx
if (!IsBadReadPtr((void*)g_Adr_LoginKey,4))
{
dwTmp = *(PDWORD)(g_Adr_LoginKey) + g_Oft_LoginKey1;
if (!IsBadReadPtr((void*)dwTmp,4)){
dwEcx = *(PDWORD)(dwTmp);
dwTmp = dwEcx + g_Oft_LoginKey2_Send;
if (!IsBadReadPtr((void*)dwTmp,4)){
return dwEcx;
}
}
}
//游戏内SendEcx
if (!IsBadReadPtr((void*)g_Adr_SceneKey,4))
{
dwTmp = *(PDWORD)(g_Adr_SceneKey) + g_Oft_SceneKey1;
if (!IsBadReadPtr((void*)dwTmp,4)){
dwEcx = *(PDWORD)(dwTmp);
dwTmp = dwEcx + g_Oft_SceneKey2_Send;
if (!IsBadReadPtr((void*)dwTmp,4)){
return dwEcx;
}
}
}
return 0;
}
void __stdcall ExecuteCmd(char* sHexCmd)
{
if (NULL == sHexCmd){
return;
}
try{
DWORD dwEcx = GetSendEcx();
if (0 == dwEcx){
return;
}
DWORD dwLen = *(WORD*)(sHexCmd);
DWORD dwBuff = DWORD(&g_ExecuteCmdBuff[0x20]);
memcpy((void*)dwBuff,&sHexCmd[2],dwLen);
PSendParamSt pParam = PSendParamSt(&g_ExecuteCmdBuff);
pParam->dwMMSend_A = g_Param_MMSend_A;
pParam->dwMMSend_B = g_Param_MMSend_B;
pParam->dwUnknow = 1;
pParam->dwLen0 = dwLen;
pParam->dwLen = dwLen;
pParam->dwBuff = dwBuff;
//
_asm{
push ecx
push edx
push pParam
mov ecx,dwEcx
mov edx,g_Adr_MMSend
call edx
pop edx
pop ecx
}
}
catch (...){
}
}
DWORD __stdcall Init_Plugin_MyThread(VOID* lpArgc)
{
while ((0 == g_Adr_MMSend) || (0 == g_Adr_MMRecv) || (0 == g_Adr_LoginKey) || (0 == g_Adr_SceneKey) || (0 == g_Param_MMSend_A) || (0 == g_Param_MMSend_B)){
if (0 == g_Adr_MMSend){
g_Adr_MMSend = g_InitIO.Api.MemSigScan("game.exe", "[1]g_Adr_MMSend,74 ?? 3B F5 74 ?? 8B 16 8B 42 ?? 53,-0x20,0,4");
}
if (0 == g_Adr_MMRecv){
g_Adr_MMRecv = g_InitIO.Api.MemSigScan("game.exe", "[2]g_Adr_MMRecv,8B 44 24 0C 8B 08 8B 44 24 08 ?? ?? ?? 0C,0,0,4");
}
if (0 == g_Adr_LoginKey){
g_Adr_LoginKey = g_InitIO.Api.MemSigScan("game.exe", "[3]g_Adr_LoginKey,83 F8 ?? 75 07 33 C0 E9 ?? ?? ?? ?? A1,0xD,1,4");
}
if (0 == g_Adr_SceneKey){
g_Adr_SceneKey = g_InitIO.Api.MemSigScan("game.exe", "[4]g_Adr_SceneKey,0F B7 51 01 83 C2 01 52,0xE,1,4");
}
if (0 == g_Param_MMSend_A){
g_Param_MMSend_A = g_InitIO.Api.MemSigScan("game.exe", "[5]g_Param_MMSend_A,C7 00 ?? ?? ?? ?? C7 40 ?? ?? ?? ?? ?? C7 40 ?? 01 00 00 00 89 48 ?? ?? C3,2,1,4");
}
if (0 == g_Param_MMSend_B){
g_Param_MMSend_B = g_InitIO.Api.MemSigScan("game.exe", "[6]g_Param_MMSend_B,C7 00 ?? ?? ?? ?? C7 40 ?? ?? ?? ?? ?? C7 40 ?? 01 00 00 00 89 48 ?? ?? C3,9,1,4");
}
Sleep(10);
}
//
LogDbg("g_Adr_MMSend:%.8X g_Adr_MMRecv:%.8X g_Adr_LoginKey:%.8X g_Adr_SceneKey:%.8X g_Param_MMSend_A:%.8X g_Param_MMSend_B:%.8X",
g_Adr_MMSend,g_Adr_MMRecv,g_Adr_LoginKey,g_Adr_SceneKey,g_Param_MMSend_A,g_Param_MMSend_B);
//调用创建CheckBox接口
g_InitIO.Api.CreateCheckBox("Send明", HookMMSend, true);
g_InitIO.Api.CreateCheckBox("Recv明", HookMMRecv, true);
//设置执行发包函数
g_InitIO.Api.SetFunc_ExecuteCmd(ExecuteCmd);
LogDbg("Init_Plugin_MyThread End");
return true;
}
void Init_Plugin_My()
{
DWORD dwThreadId = 0;
HANDLE hTmp = CreateThread(NULL, 0, Init_Plugin_MyThread, NULL, 0, &dwThreadId);
CloseHandle(hTmp);
}
void Init_Plugin()
{
LogDbg("_init in");
if (Init_Plugin_Pub()){
Init_Plugin_My();
LogDbg("_init ok");
}
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
if (0 == g_hModuleThisDll){
g_hModuleThisDll = hModule;
}
switch(ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
Init_Plugin();
break;
case DLL_PROCESS_DETACH:
UnInit_Plugin();
break;
default:
break;
}
return TRUE;
}
Delphi版代码:
library CmdPlugin_JXSJ;
uses
SysUtils,
Windows,
Winsock2 in 'Winsock2.pas',
Uni_CmdApi in 'Uni_CmdApi.pas';
type
TMMSendProc = function(dwParam : DWORD):DWORD; stdcall;
TMMRecvProc = function(dwLen : DWORD; dwBuff : DWORD; dwParam : DWORD):DWORD; cdecl;
{$R *.res}
var
OldMMSend : TMMSendProc = nil;
OldMMRecv : TMMRecvProc = nil;
//
g_Adr_MMSend : DWORD = 0;
g_Adr_MMRecv : DWORD = 0;
//
g_Param_MMSend_A : DWORD = 0;
g_Param_MMSend_B : DWORD = 0;
//
g_Adr_LoginKey : DWORD = 0;
g_Oft_LoginKey1 : DWORD = $28;
g_Oft_LoginKey2_Send : DWORD = $C;
g_Oft_LoginKey2_Recv : DWORD = $10;
//
g_Adr_SceneKey : DWORD = 0;
g_Oft_SceneKey1 : DWORD = $30;
g_Oft_SceneKey2_Send : DWORD = $C;
g_Oft_SceneKey2_Recv : DWORD = $10;
type
TSendParamSt = packed record
dwMMSend_A : DWORD; //参数A,固定基址
dwMMSend_B : DWORD; //参数B,固定基址
dwUnknow : DWORD; //未知,默认1
dwLen0 : DWORD; //协议长度
dwLen : DWORD; //协议长度
dwBuff : DWORD; //协议数据内存地址
end;
PSendParamSt = ^TSendParamSt;
function NewMMSend(dwParam : DWORD):DWORD; stdcall;
var
TmpRet : TFilterRet;
dwEcx, dwBuff, dwLen : DWORD;
begin
asm
mov dwEcx,ecx
end;
dwLen := pDword(dwParam + $10)^;
dwBuff := pDword(dwParam + $14)^;
g_InitIO.Api.FilterOper(PChar(dwBuff),dwLen,0,@TmpRet);
//显示记录
if TmpRet.bVis then
begin
if TmpRet.cName <> '' then
g_InitIO.Api.WriteLog('Send明',TmpRet.cName,PChar(dwBuff),dwLen,0)
else
g_InitIO.Api.WriteLog('Send明',CN_PUB_DEFHINT,PChar(dwBuff),dwLen,0);
end;
if not TmpRet.bCom then
begin
Result := 1;
Exit;
end;
asm
mov ecx,dwEcx
end;
Result := OldMMSend(dwParam);
end;
procedure HookMMSend(bHook :Boolean);stdcall;
var
pRet : Pointer;
begin
if bHook then
begin
g_InitIO.Api.HookCodeNormal(Pointer(g_Adr_MMSend), @NewMMSend,@pRet);
OldMMSend := pRet;
end
else
g_InitIO.Api.UnHookCodeNormal(@OldMMSend);
end;
function NewMMRecv(dwLen : DWORD; dwBuff : DWORD; dwParam : DWORD):DWORD; cdecl;
var
TmpRet : TFilterRet;
begin
Result := OldMMRecv(dwLen,dwBuff,dwParam);
g_InitIO.Api.FilterOper(PChar(dwBuff),dwLen,1,@TmpRet);
//显示记录
if TmpRet.bVis then
begin
if TmpRet.cName <> '' then
g_InitIO.Api.WriteLog('Recv明',TmpRet.cName,PChar(dwBuff),dwLen,0)
else
g_InitIO.Api.WriteLog('Recv明',CN_PUB_DEFHINT,PChar(dwBuff),dwLen,0);
end;
if not TmpRet.bCom then
begin
//此处可自己通过替换封包的方式变相实现禁止收指定包
Result := 1;
Exit;
end;
end;
procedure HookMMRecv(bHook :Boolean);stdcall;
var
pRet : Pointer;
begin
if bHook then
begin
g_InitIO.Api.HookCodeNormal(Pointer(g_Adr_MMRecv), @NewMMRecv,@pRet);
OldMMRecv := pRet;
end
else
g_InitIO.Api.UnHookCodeNormal(@OldMMRecv);
end;
function GetSendEcx():DWORD;stdcall;
var
dwTmp,dwEcx : DWORD;
begin
Result := 0;
//登录SendEcx
if (not IsBadReadPtr(Pointer(g_Adr_LoginKey),4)) then
begin
dwTmp := PDWORD(g_Adr_LoginKey)^ + g_Oft_LoginKey1;
if (not IsBadReadPtr(Pointer(dwTmp),4)) then
begin
dwEcx := PDWORD(dwTmp)^;
dwTmp := dwEcx + g_Oft_LoginKey2_Send;
if (not IsBadReadPtr(Pointer(dwTmp),4)) then
begin
Result := dwEcx;
Exit;
end;
end;
end;
//游戏内SendEcx
if (not IsBadReadPtr(Pointer(g_Adr_SceneKey),4)) then
begin
dwTmp := PDWORD(g_Adr_SceneKey)^ + g_Oft_SceneKey1;
if (not IsBadReadPtr(Pointer(dwTmp),4)) then
begin
dwEcx := PDWORD(dwTmp)^;
dwTmp := dwEcx + g_Oft_SceneKey2_Send;
if (not IsBadReadPtr(Pointer(dwTmp),4)) then
begin
Result := dwEcx;
Exit;
end;
end;
end;
end;
procedure ExecuteCmd(sHexCmd: PChar); stdcall;
var
dwLen, dwBuff, dwEcx: DWORD;
pParam: PSendParamSt;
begin
try
if not Assigned(sHexCmd) then
Exit;
dwEcx := GetSendEcx();
if dwEcx = 0 then
Exit;
//
dwLen := pWord(sHexCmd)^;
dwBuff := Dword(@g_ExecuteCmdBuff[$20]);
CopyMemory(Pointer(dwBuff),Pointer(DWORD(sHexCmd) + 2), dwLen);
//
pParam := PSendParamSt(@g_ExecuteCmdBuff);
pParam^.dwMMSend_A := g_Param_MMSend_A;
pParam^.dwMMSend_B := g_Param_MMSend_B;
pParam^.dwUnknow := 1;
pParam^.dwLen0 := dwLen;
pParam^.dwLen := dwLen;
pParam^.dwBuff := dwBuff;
//
asm
push ecx
push edx
push pParam
mov ecx,dwEcx
mov edx,g_Adr_MMSend
call edx
pop edx
pop ecx
end;
except
end;
end;
procedure Init_Plugin_My();
procedure Init_Plugin_MyThread();
begin
while ((g_Adr_MMSend = 0) or (g_Adr_MMRecv = 0) or (g_Adr_LoginKey = 0) or (g_Adr_SceneKey = 0) or (g_Param_MMSend_A = 0) or (g_Param_MMSend_B = 0)) do
begin
if g_Adr_MMSend = 0 then
g_Adr_MMSend := g_InitIO.Api.MemSigScan('game.exe', '[1]g_Adr_MMSend,74 ?? 3B F5 74 ?? 8B 16 8B 42 ?? 53,-$20,0,4');
if g_Adr_MMRecv = 0 then
g_Adr_MMRecv := g_InitIO.Api.MemSigScan('game.exe', '[2]g_Adr_MMRecv,8B 44 24 0C 8B 08 8B 44 24 08 ?? ?? ?? 0C,0,0,4');
if g_Adr_LoginKey = 0 then
g_Adr_LoginKey := g_InitIO.Api.MemSigScan('game.exe', '[3]g_Adr_LoginKey,83 F8 ?? 75 07 33 C0 E9 ?? ?? ?? ?? A1,$D,1,4');
if g_Adr_SceneKey = 0 then
g_Adr_SceneKey := g_InitIO.Api.MemSigScan('game.exe', '[4]g_Adr_SceneKey,0F B7 51 01 83 C2 01 52,$E,1,4');
if g_Param_MMSend_A = 0 then
g_Param_MMSend_A := g_InitIO.Api.MemSigScan('game.exe', '[5]g_Param_MMSend_A,C7 00 ?? ?? ?? ?? C7 40 ?? ?? ?? ?? ?? C7 40 ?? 01 00 00 00 89 48 ?? ?? C3,2,1,4');
if g_Param_MMSend_B = 0 then
g_Param_MMSend_B := g_InitIO.Api.MemSigScan('game.exe', '[6]g_Param_MMSend_B,C7 00 ?? ?? ?? ?? C7 40 ?? ?? ?? ?? ?? C7 40 ?? 01 00 00 00 89 48 ?? ?? C3,9,1,4');
Sleep(10);
end;
LogDbg('g_Adr_MMSend:%.8X g_Adr_MMRecv:%.8X g_Adr_LoginKey:%.8X g_Adr_SceneKey:%.8X g_Param_MMSend_A:%.8X g_Param_MMSend_B:%.8X',
[g_Adr_MMSend,g_Adr_MMRecv,g_Adr_LoginKey,g_Adr_SceneKey,g_Param_MMSend_A,g_Param_MMSend_B]);
//
g_InitIO.Api.CreateCheckBox('Send明', HookMMSend, True);
g_InitIO.Api.CreateCheckBox('Recv明', HookMMRecv, True);
//
g_InitIO.Api.SetFunc_ExecuteCmd(ExecuteCmd);
LogDbg('Init_Plugin_MyThread End',[]);
end;
var
DwHnd, dwTid: DWord;
begin
DwHnd := CreateThread(nil, 0, @Init_Plugin_MyThread, nil, 0, dwTid);
CloseHandle(DwHnd);
end;
procedure Init_Plugin();
begin
LogDbg('_init in',[]);
if (Init_Plugin_Pub()) then
begin
Init_Plugin_My();
LogDbg('_init ok',[]);
end;
end;
procedure MyDLLMain(ul_reason_for_call: Integer);
begin
case ul_reason_for_call of
DLL_PROCESS_ATTACH:
begin
Init_Plugin();
end;
DLL_THREAD_ATTACH:
begin
end;
DLL_THREAD_DETACH:
begin
end;
DLL_PROCESS_DETACH:
begin
UnInit_Plugin();
end;
end;
end;
begin
DLLProc := @MyDLLMain;
MyDLLMain(DLL_PROCESS_ATTACH);
end.编译插件,丢进CoolCmd的Plugin目录,至此,针对该游戏明文协议的修改测试、屏蔽测试、时序逻辑测试、模糊测试等功能均可正常使用.

后记:
再深入分析下会发现,此游戏加解密算法是相同的,只需Hook一个加解密函数实现明文首发包相关工具.
汇编代码如下(算法很简单,汇编代码可快速扣出使用):
0085B170 53 PUSH EBX 0085B171 55 PUSH EBP 0085B172 8B6C24 14 MOV EBP,DWORD PTR SS:[ESP+14] 0085B176 8B4D 00 MOV ECX,DWORD PTR SS:[EBP] 0085B179 56 PUSH ESI 0085B17A 8B7424 10 MOV ESI,DWORD PTR SS:[ESP+10] 0085B17E 8BDE MOV EBX,ESI 0085B180 83E3 03 AND EBX,3 0085B183 C1EE 02 SHR ESI,2 0085B186 57 PUSH EDI 0085B187 8B7C24 18 MOV EDI,DWORD PTR SS:[ESP+18] 0085B18B 74 28 JE SHORT game.0085B1B5 0085B18D 8D49 00 LEA ECX,DWORD PTR DS:[ECX] 0085B190 83EE 01 SUB ESI,1 0085B193 8D0431 LEA EAX,DWORD PTR DS:[ECX+ESI] 0085B196 33D2 XOR EDX,EDX 0085B198 F735 6CCEB500 DIV DWORD PTR DS:[B5CE6C] 0085B19E 83C7 04 ADD EDI,4 0085B1A1 8B0C95 B075B500 MOV ECX,DWORD PTR DS:[EDX*4+B575B0] 0085B1A8 81C1 C1236D2E ADD ECX,2E6D23C1 0085B1AE 314F FC XOR DWORD PTR DS:[EDI-4],ECX 0085B1B1 85F6 TEST ESI,ESI 0085B1B3 ^ 77 DB JA SHORT game.0085B190 0085B1B5 33D2 XOR EDX,EDX 0085B1B7 8BC3 MOV EAX,EBX 0085B1B9 F735 6CCEB500 DIV DWORD PTR DS:[B5CE6C] 0085B1BF 330C95 B075B500 XOR ECX,DWORD PTR DS:[EDX*4+B575B0] 0085B1C6 85DB TEST EBX,EBX 0085B1C8 76 15 JBE SHORT game.0085B1DF 0085B1CA 8D9B 00000000 LEA EBX,DWORD PTR DS:[EBX] 0085B1D0 300F XOR BYTE PTR DS:[EDI],CL 0085B1D2 83EB 01 SUB EBX,1 0085B1D5 83C7 01 ADD EDI,1 0085B1D8 C1E9 08 SHR ECX,8 0085B1DB 85DB TEST EBX,EBX 0085B1DD ^ 77 F1 JA SHORT game.0085B1D0 0085B1DF 8B45 00 MOV EAX,DWORD PTR SS:[EBP] 0085B1E2 8BC8 MOV ECX,EAX 0085B1E4 C1E1 05 SHL ECX,5 0085B1E7 2BC8 SUB ECX,EAX 0085B1E9 5F POP EDI 0085B1EA 81C1 05840808 ADD ECX,8088405 0085B1F0 5E POP ESI 0085B1F1 894D 00 MOV DWORD PTR SS:[EBP],ECX 0085B1F4 5D POP EBP 0085B1F5 B8 01000000 MOV EAX,1 0085B1FA 5B POP EBX 0085B1FB C3 RETN
于是插件可以另外一种写法(Hook1个函数:自己加密发送):
核心代码如下:
VC版:
DWORD __cdecl NewMMSend(DWORD dwLen,DWORD dwBuff,DWORD dwParam)
{
TFilterRet FltRet;
DWORD dwRet = 0;
if (CheckSend(*(PDWORD)(dwParam))){
//进加密相关处理
if (g_B_MMSend){
g_InitIO.Api.FilterOper((char*)(dwBuff),dwLen,0,&FltRet);
if (FltRet.bVis){
if (strlen(FltRet.cName) != 0){
g_InitIO.Api.WriteLog("Send明",FltRet.cName,(char*)(dwBuff),dwLen,0);
}
else{
g_InitIO.Api.WriteLog("Send明",CN_PUB_DEFHINT,(char*)(dwBuff),dwLen,0);
}
}
//
if (!FltRet.bCom){
//此处可自己通过替换封包的方式实现禁止发包
return 1;
}
}
return OldMMSend(dwLen,dwBuff,dwParam);
}
else{
//进解密相关处理
dwRet = OldMMSend(dwLen,dwBuff,dwParam);
if (g_B_MMRecv){
g_InitIO.Api.FilterOper((char*)(dwBuff),dwLen,1,&FltRet);
if (FltRet.bVis){
if (strlen(FltRet.cName) != 0){
g_InitIO.Api.WriteLog("Recv明",FltRet.cName,(char*)(dwBuff),dwLen,0);
}
else{
g_InitIO.Api.WriteLog("Recv明",CN_PUB_DEFHINT,(char*)(dwBuff),dwLen,0);
}
}
//
if (!FltRet.bCom){
//此处可自己通过替换封包的方式实现禁止收包
return 1;
}
}
return dwRet;
}
}
void __stdcall ExecuteCmd(char* sHexCmd)
{
static int cn_send_oft = 2;
if (NULL == sHexCmd){
return;
}
try{
DWORD dwKey = GetSendKey();
if (0 == dwKey){
return;
}
DWORD dwLen = *(WORD*)(sHexCmd);
memcpy(&g_ExecuteCmdBuff[cn_send_oft],&sHexCmd[2],dwLen);
DWORD dwBuff = DWORD(&g_ExecuteCmdBuff[cn_send_oft]);
//执行加密组包
__asm{
pushad
lea edx,dwKey
push edx
push dwBuff
push dwLen
mov edx,g_Adr_MMSend
call edx
ADD ESP,0x0C
popad
}
dwLen = dwLen + cn_send_oft;
*(WORD*)(&g_ExecuteCmdBuff) = dwLen;
//执行发送
if (g_InitIO.Api.GetSendSock() != 0){
send(g_InitIO.Api.GetSendSock(),g_ExecuteCmdBuff,dwLen,0);
}
else{
send(g_ExecuteCmd_Sock,g_ExecuteCmdBuff,dwLen,0);
}
}
catch (...){
}
}Delphi版:
function NewMMSend(dwLen : DWORD; dwBuff : DWORD; dwParam : DWORD): DWORD; cdecl;
var
TmpRet : TFilterRet;
begin
//判断是否发送
if CheckSend(pDword(dwParam)^) then
begin
//勾选了明文发送
if g_B_MMSend then
begin
g_InitIO.Api.FilterOper(PChar(dwBuff),dwLen,0,@TmpRet);
//显示记录
if TmpRet.bVis then
if TmpRet.cName <> '' then
g_InitIO.Api.WriteLog('Send明',TmpRet.cName,PChar(dwBuff),dwLen,0)
else
g_InitIO.Api.WriteLog('Send明',CN_PUB_DEFHINT,PChar(dwBuff),dwLen,0);
if not TmpRet.bCom then
begin
//此处可自己通过替换封包的方式实现禁止发包
Result := 1;
Exit;
end;
end;
//执行加密
Result := OldMMSend(dwLen,dwBuff,dwParam);
end
else
begin
//执行解密
Result := OldMMSend(dwLen,dwBuff,dwParam);
//勾选了明文接收
if g_B_MMRecv then
begin
g_InitIO.Api.FilterOper(PChar(dwBuff),dwLen,1,@TmpRet);
//显示记录
if TmpRet.bVis then
if TmpRet.cName <> '' then
g_InitIO.Api.WriteLog('Recv明',TmpRet.cName,PChar(dwBuff),dwLen,0)
else
g_InitIO.Api.WriteLog('Recv明',CN_PUB_DEFHINT,PChar(dwBuff),dwLen,0);
if not TmpRet.bCom then
begin
//此处可自己通过替换封包的方式实现禁止收包
Result := 1;
Exit;
end;
end;
end;
end;
procedure ExecuteCmd(sHexCmd: PChar); stdcall;
const
cn_send_oft = 2;
var
dwLen, dwBuff, dwKey: DWORD;
begin
try
if not Assigned(sHexCmd) then
Exit;
//
dwKey := GetSendKey();
if dwKey = 0 then
Exit;
//
dwLen := pWord(sHexCmd)^;
CopyMemory(@g_ExecuteCmdBuff[cn_send_oft],Pointer(DWORD(sHexCmd) + 2), dwLen);
dwBuff := Dword(@g_ExecuteCmdBuff[cn_send_oft]);
//执行加密组包
asm
pushad
lea edx,dwKey
push edx
push dwBuff
push dwLen
mov edx,g_Adr_MMSend
call edx
ADD ESP,$0C
popad
end;
//
dwLen := dwLen + cn_send_oft;
PWORD(@g_ExecuteCmdBuff)^ := dwLen;
//执行发送
if g_InitIO.Api.GetSendSock <> 0 then
send(g_InitIO.Api.GetSendSock,pDword(@g_ExecuteCmdBuff)^,dwLen,0)
else
send(g_ExecuteCmd_Sock,pDword(@g_ExecuteCmdBuff)^,dwLen,0);
except
end;
end;此游戏有多级函数均可获取明文以及完成加密发送,其中亦不乏很多虚函数,改代码或不改代码等多种花式实现插件功能可自行尝试,估计写18种处理插件都可以,当然用VB、易语言写插件也同样是支持的.
