|
感谢给我非常大帮助的Kook、猪头和rahxephon三位前辈!!!以及许多热心的朋友!
感谢GA研究院。
当另一方玩家使用Maphack,并且捡取本来不可见(如在黑雾中)的物品/神符,或用瓶子装取不可见的神符时会用盟友模式报告。
所有记录在同目录的""Save.txt""文件中,如:Player(2) Use MapHack TO Pick Up Invisible Item
可能的问题:
两人游戏中对战没问题,RPG若人不满且位置不连续时会错报;
少数时候会漏报;
多人游戏且人满时Player的序号可能是错误的(未严格测试);
*&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
下面是思路,魔兽版本1.20E,针对2人游戏分析,附件是代码、双开工具,主要用到Olldbg、编辑工具C32Asm、Japi参考手册JassCraft,希望文章对热爱War3反MH的朋友有一些帮助。
'%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
魔兽版本为1.20E
'一、下面全是针对1v1二人游戏的调试:
如何自己调试war3协议?
'War3没有加密协议,内容只有一些编号如指令/行为编号,单位编号或目标坐标或玩家编号,稍微进行一些转化后再进行具体使用,
'首先使用双开在本地机器窗口模式打两个魔兽,分别用OLLY-DBG加载后,建立游戏加入并开始;
'在我的调试中发现魔兽使用Ws2_32.dll的WSARecv()接受数据,使用Ws2_32.dll的Send()发送数据,他们俩不是对应的网络函数,下断时候不能弄错
'*************
主机转化了客户端发来的内容并重新发送给所有客户端;而最终程序是要运行在客户端的,所以我们可以在主机方下Send断或在客户端下WSARecv断:
'以下都针对主机方:
'在主机方的Send下断,会不停的被断,复制数次到记事本并比较后,我们发现接到数据大都为这样:{F7 XX 0C 00 XXXXXXX}和{F7 XX 09 00 XXXXXXX}
'这应该是用来效验数据和表示时间流逝的数据;
'因为不停的被断,我们补丁来解决,补丁内容是当byte[DataAddr+2]<>&h0c或&h09时断下:
'用一个JMP从Send的开头跳到我们的补丁0X6f704c00:
'71A2428A > - E9 7109CEFD jmp Game.6F704C00,这里为Send的开头
'6F704C00 60 pushad
'6F704C01 8B5C24 08 mov ebx, dword ptr [esp+8] ; [esp+8]=发送的DATA
'6F704C05 807B 02 0C cmp byte ptr [ebx+2], 0C ; [ebx+2]=0C或09的位置
'6F704C09 74 08 je short 6F704C13
'6F704C0B 807B 02 09 cmp byte ptr [ebx+2], 9
'6F704C0F 74 02 je short 6F704C13
'6F704C11 CD 03 int 3 ; here INT3=数据内容不为0C,09
'6F704C13 90 nop
'6F704C14 61 popad
'6F704C15 8BFF mov edi, edi ; send被修改为{JMP Pacth}的内容
'6F704C17 55 push ebp
'6F704C18 8BEC mov ebp, esp
'6F704C1A - E9 70F63102 jmp WS2_32.71A2428F ; 跳回Send
'回到游戏,当进行一个指令比如选择单位时会被断下,数据是这样的开头{F7 0C 31 00 XXXXXX},而移动是{F7 0C 2A 00 XXXXXX]开头
'记录很多次数据内容到记事本并比较,就可以慢慢分析了。
'####################################
'2人游戏部分截取内容:
'send1$$$$$$$$$$(跑像泉水装瓶子)
'F7 0C 2A 00 6E 00 A1 2B 01 1F 00 12 40 00 28 00 0D 00 2D B8 00 00 17 3F 04 00 AD 35 D1 C5 0D 11
'BB C5 F9 55 00 00 F9 55 00 00 7C 00
'send2$$$$$$$$$$
'F7 0C 2A 00 6E 00 49 0C 01 1F 00 12 40 00 28 00 0D 00 74 9D 00 00 75 D5 04 00 3A DB D0 C5 03 C0
'B7 C5 F9 55 00 00 F9 55 00 00 00 01 0E
'send3$$$$$$$$$&
'F7 0C 2A 00 6E 00 73 29 01 1F 00 12 40 00 28 00 0D 00 86 EE 00 00 64 4B 06 00 BC 15 D2 C5 3A 00
'B9 C5 F9 55 00 00 F9 55 00 00 28 00
'****************************错误start
(注意以下分析后来发现是错误的,在二人游戏不会有问题,但多人游戏就完全不适用,留着它是为了作个对比希望大家不要像我一样犯错,正确的在下面的多人游戏分析部分)
二人游戏部分分析:
'以SEND1对于DOTA分析:
'指向性指令:
地址 大小 内容
'(1 ) [DWORD] {F7 0C 2A 00}表示此消息为指向性命令(但不包括全部)
'(9 ) [ BYTE ] {01-0C}玩家编号
'(12) [Dword] 表示命令具体代号,已知指向性物品栏N代号为28004012-3C004012;移动=03000012(捡物品/神符都包括在此内)
' Attack=0F000012,
'(27)[DWORD]从此处开始的两个连续DWORD表示坐标Y,X
'(35)[WORd ]若目的时候单位/物品,此处为单位编号,否则为FFFF(这条不太肯定)
'其他:
'(1 )[dword]{F7 0C 31 00}:此消息为非指向性指令,如HOLD,STOP,选择单位,购买物品
'(9 )[byte ]玩家编号
'(12)[dword]当选择单位时为目标单位编号(这条不太肯定)
***********************************错误分析END
***********************************************
***********************************************start
这部分是对War3协议正确的分析,适用所有人数的游戏:
===部分分析:
{F7 0C XX 00}:
(3 )[Byte ]XX表示此条消息数据总长度,可能包括任何”单位能发出“的指令;每条消息由数个玩家的编号及一个对应指令组成,格式为:【玩家,1F,XXXX,指令;.....;玩家,1F,XXXX,指令;.....】
{01 1F}表示此条消息的玩家编号为01,
(1F+2)[Dword]为指令编号,后面跟一些此指令的附加数据
指令的具体代号已知使用指向性物品栏N=12400028-1240003C,移动=12000003,Attack=1200000F,巡逻=12000016
为什么要把很多人的指令聚集到一块发送?可能是为了节省数据量和加快执行速度(减少Send的执行次数)吧。
*******************************************************end
*******************************************************
'$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
'检测原理:
'检测必须用接受数据的函数(这里是WSARecv)进行,因为Send只发送自己的数据,而WSARecv接受主机发来的所有其他人的数据
'在数据中我们可以获得玩家编号,指令编号,单位编号(若有的话),坐标(若有的话)等,
'若想检测其他玩家捡取不可见物品,大概思路是这样【获得数据=》判断时候是捡取=》
'=》是则判断目标物品(或目标物品的坐标)是否对此玩家可见/不是则直接跳回原函数=》跳回原函数】
'前面2步都没问题,第三步如何判断目标物品或目标物品坐标对玩家是否可见呢?使用Japi。
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
非常有用的部分:如何获得和使用JAPI?
'在编写地图中,作者们使用JAPI,JAPI中的函数IsVisibleToPlayer(坐标X,坐标Y,玩家),或IsUnitVisible(单位编号,玩家)
'而JAPI是一个又一个类似输出函数的CALL,例如查找JAPI IsVisibleToPlayer:我们用C32ASM打开魔兽进程=》
'=》搜索ASC字符串IsVisibleToPlayer直到最后一个(必须是在0X6F000000以后的)=》在OLLY-DBG中的数据窗口跳到这个地址,在地址上右键并
'查找参考(isvisibletoPlayer是在6F817760),会发现一共有两个调用此字符串的地方,在汇编代码中取到这个位置,发现是这样的形式:
'{{
'6F2AAC61 |. 68 7477816F push 6F817774 ; /Arg1 = 6F817774 ASCII "(RRHplayer;)B"
'6F2AAC66 |. BA 6077816F mov edx, 6F817760 ; |ASCII "IsVisibleToPlayer"
'6F2AAC6B |. B9 00DC2C6F mov ecx, 6F2CDC00 ; |入口地址
'6F2AAC70 |. E8 FB383F00 call 6F69E570 ; \Game.6F69E570
'}}
'第三行的Mov ecx,6f2cdc00中的6f2cdc00就是此JAPI的入口地址;
'只要参数正确,我们就可以直接调用它了。
'那么如何判断他的参数呢?
'它不是个JAPI吗?我们用地图编辑器做个地图用触发器来调用此函数,如
'【
'Call IsVisibleToPlayer(1.1, 2.2, Player(0))
'Call IsVisibleToPlayer(1.1, 2.2, Player(1))
'Call IsVisibleToPlayer(1.1, 2.2, Player(2))
'】
'然后在此Japi入口处下断慢慢看到底是个什么样,
'发现有一些函数的参数非常简单,但是有一些函数的参数相当麻烦,
'幸运的这个IsVisibleToPlayer很简单,X和Y在截取到的数据中都有了,剩下的参数只有一个,但比较奇怪的是参数Player(0)在函数被调用时被转化成了100008,100009的形式而不是
'我们截取WsaRECV到的数据中的玩家编号01-0C,如何转化?
'再多截取几次,就发现这些函数的参数编号100008、100009等都是固定的对应Player(0),Player(1)等
'这样一来,钩住WsaRecv的补丁中就能对应的将数据中的玩家编号01-0C正确转化成IsVisibleToPlayer中的参数了。
'(要注意的是在DOTA中IsVisibleToPlayer的参数是10000C-1000015与玩家编号01-0C对应,对战是100008-100014与01-0C对应)
'(转化参数与玩家编号的另一个方法是用Japi GetPlayerID,,在游戏开始时枚举出所有参数与玩家对应的编号
',保存起来,然后在补丁中使用,程序用的是这种方法)
'最后还有一个问题,玩家编号01-0C和函数参数Player(x)的10000C-100015对应在RPG两人连续位置或对战中没问题,但在超过俩人
'时可能出现问题对不上号:当作为参数时100008永远对应位置1,100009永远对应位置2,而玩家编号01、02是按"存在的玩家1"、"存在的玩家2"计算的而不是按照位置计算,少一个玩家而空出的位置会打乱这个对应。
^^^^^^^^^^^^^^^^^^^^^^^
检测"点击不可见单位"(Select Invisible Unit):
一种思路是:
1、当接受的数据是选择单位消息时=>从接受的数据中取出unit编号和玩家编号=》调用IsUnitVisible(参数单位编号,Player编号)判断是否可见,不可见就是使用MAPHACK了。
实际操作时发现取出的这个参数"单位编号"调用IsUnitVisible无效。
先来验证数据的单位编号和参数的单位编号是否一样:
用地图编辑器建立地图,调用
【
call IsUnitVisible(unit_001,Player(0)) \\unit_001是编辑时地图上的一个单位的编号
call IsUnitVisible(unit_002,Player(0))
】
然后用老方法获得JAPI并下断,发现unit_001被转化成了10000E,10000F
原来数据单位编号和参数单位编号不一样,如何转化?本来卡在这儿了,感谢rahxephon前辈的文章 http://www.ourga.com/bbs/read.php?tid=10851&keyword=
给我很大启发得以继续下去。
%%%%%%%
录像Replay与协议数据:
录像Replay是用zlib.dll压缩过的,解压以后:对一个指令的记录它俩非常相似,除了头有些不一样以外。因为它俩是对应的,所以其实我们也可以从文档中分析出协议数据的大致内容,如指令标志、坐标等
Replay文档可直接点击下载:
【http://b2n4jg.tuk.livefilestore. ... Replay.rar?download
】
%%%%%%%%%%%%%%%%%%%%%%
二、如何调试多人游戏?
没有条件找那么多朋友的话,我们可以上HF或VS,和大家开始游戏后,
用SockMon2005开始截取,发现一直获得数据,关闭截取后看这些数据大都为{F7 27 09 00 XXXXXXXX},猜测应该是用来效验或表示时间流逝的。
为了尽量过滤掉这些,我们修改SockMon的过滤条件,我的主要过滤条件是【API=>只Recv;TYPE=》仅TCP;DATA=》过滤{F7 27 09 00};Other=>不记录错误的调用】,
然后点SockMon的"开始监视",尽快回到游戏,连续狂使用那个要截取的指令,如Mov、Hold、Attack、Stop等的其中一个,使用了差不多十几个后尽快回到SockMon并点"停止监视",接着慢慢看截下的数据,主要是看开头2字节是否为{F7 0C},然后就可以慢慢分析了,也可以参照上面的分析来。
******************************************
三、不涉及协议检测Maphack Select Unit :
这就几乎全使用Japi 了,思路是【循环:{获得每个玩家当前选中的单位=》若被选中单位数量>1则不是作弊;=1则=》判断此单位对此玩家是否可见=》不可见就是此玩家使用Maphck}】
可以使用“GroupEnumUnitsSelected”这些Japi编写,把这些代码写入War3,最后可以给这些代码远程创建一个线程,也可以钩到War3中常被执行的代码中去。
但麻烦的是难以找出那些Unit、Group之类的数据到底是什么格式,rahxephon前辈的文章对分析数据格式非常有启发性:
http://www.ourga.com/bbs/read.php?tid=10851&keyword=
。
这种方法可能会错过一些非常快速的Maphack点击。
*************************************
程序大致过程(代码只适用二人游戏):
钩住游戏开始:在游戏开始时调用Japi GetPlayerID获得俩个玩家的编号ID,并保存到0X6F87EFF0;
钩住Ws2_32的WSARecv:获得数据后判断是否是玩家右键点击宝物或用瓶子装神符=》是则用Japi IsVisibleToPlayer()判断宝物或神符对此玩家是否可见,参数X,Y坐标从数据内容获得,玩家ID从刚保存的0X6F87EFF0处获得=》不可见则是使用了Maphack,将0X6F87EFFC置为对方玩家ID/可见为正常=》跳回原函数
<3>在程序中用Timer检测0X6F87EFFC,若不为0则表示检测Maphack,
记录并提醒玩家。
1 |
评分
-
查看全部评分
|