本文转自:http://www.cppblog.com/niewenlong/archive/2007/07/20/28446.aspx
(一)外挂一般都能在游戏的界面中按一个热键(比如F12,HOME等),就可以呼出外挂的窗口,然后在里面进行外挂的功能设置,这个外挂的窗口是怎么弄出来的呢?
要想在游戏里显示出窗口,那么我们要显示的这个窗口就要和游戏本身“混”在一起,也就是说我们的外挂窗口要“混入”游戏的内部,让游戏不排斥外挂窗口,把外挂窗口当做“自己人”,这样我们的外挂才能去“影响”游戏本身的运行。行话把这个叫“注入”。
那怎么“注入”呢?
Windows操作系统有个API函数SetWindowsHookEx,该函数的可以在系统上安装一个“钩子(HOOK)”。也就是把我们自己编写的一个回调函数设置为系统“钩子”。“钩子(HOOK)”有什么用呢?系统发送给各种程序窗口的消息,都要先经过“钩子”先处理之后再送到它本来要去的窗口。而在“钩子”处理来的消息的时候,Windows操作系统就已经自动把“钩子”“钩”在了消息即将到达的目的程序窗口上了,此时“钩子”就已经“混入”了目的窗口的内部了
==========================================
以下shaker注明:
这个教程存在一个漏洞,以使一些对DLL编程不是很了解的人不能顺利的完成编译。
BUG如下:原文中的S3DHOOK.DEF文件中的内容如下
; S3DHook.def : Declares the module parameters for the DLL.
LIBRARY "S3DHook"
DESCRIPTION "S3DHook Windows Dynamic Link Library"
EXPORTS
; Explicit exports can go here
使得生成的DLL没有任何输出函数,在编译EXE工程出现错误,要解决这个问题只要改变原来的S3DHook.def文件的内容如下:
; S3DHook.def : Declares the module parameters for the DLL.
LIBRARY "S3DHook"
DESCRIPTION "S3DHook Windows Dynamic Link Library"
EXPORTS
; Explicit exports can go here
InstallHook
UninstallHook
如此,问题便得到解决!
//
关于版主shaker的注明:
这个并不能说是个BUG
由于:
在S3DHook.h头文件中加入
#ifndef S3DHOOKAPI
#define S3DHOOKAPI extern "C" __declspec(dllimport)
#endif
在S3DHook.cpp中
#include "S3DHook.h"
这一句之前加入
#define S3DHOOKAPI extern "C" __declspec(dllexport)
也就是这个样子成了这个
#define S3DHOOKAPI extern "C" __declspec(dllexport)
#include "S3DHook.h"
这样一来就不需要用.def文件来导出函数了
关于这种导出方法,DLL编程的初学者,最好先去去看看
windows核心编程,这本书,
这种方法就是作者所推崇的
把有关外挂功能的代码和“钩子”函数一起放到同一个DLL中,那么我们的外挂也就一同被注入到游戏里面去了
在“我的文档”中建立一个文件夹名字叫“神迹外挂”然后打带VC6,建立新工程
点OK,选择
Regular DLL using shared mfc DLL
在S3DHook.h头文件中加入
#ifndef S3DHOOKAPI
#define S3DHOOKAPI extern "C" __declspec(dllimport)
#endif
在S3DHook.cpp中
#include "S3DHook.h"
这一句之前加入
#define S3DHOOKAPI extern "C" __declspec(dllexport)
也就是这个样子成了这个
#define S3DHOOKAPI extern "C" __declspec(dllexport)
#include "S3DHook.h"
在S3DHook.cpp中加入全局共享数据
#pragma comment(linker,"section:Shared,rws")
#pragma data_seg("Shared")
HHOOK g_hhook;
#pragma data_seg()
在S3DHook.cpp加入钩子回调函数
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
BOOL bKeyUp = lParam & (1 << 31);
if (bKeyUp && wParam == VK_F12 && nCode == HC_ACTION) {
AfxMessageBox("ok");
}
return ::CallNextHookEx(g_hhook, nCode, wParam ,lParam);
}
在文件前面加入函数的原形以便后面引用
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam);
在S3DHook.H里加入“导出(export)”的钩子安装卸载函数原形
S3DHOOKAPI BOOL WINAPI InstallHook();
S3DHOOKAPI BOOL WINAPI UninstallHook();
在S3DHook.CPP里加入钩子安装卸载函数的实现
S3DHOOKAPI BOOL WINAPI InstallHook()
{
if (g_hhook == NULL) {
g_hhook = ::SetWindowsHookEx(WH_KEYBOARD, (HOOKPROC)KeyboardProc, theApp.m_hInstance, 0);
if (g_hhook != NULL)
return TRUE;
}
return FALSE;
}
S3DHOOKAPI BOOL WINAPI UninstallHook()
{
return ::UnhookWindowsHookEx(g_hhook);
}
好了,现在我们建立的这个DLL具有基本的键盘钩子的功能,编译生成S3DHook.dll
下面建立一个EXE来调用这个DLL
这个是对话框型的工程
在MainDlg.cpp中加入对DLL的调用
插入头文件包含
#include "../s3dhook/s3dhook.h"
更改工程设置
Project->settings->link->Object/library modules:
输入../s3dhook/debug/s3dhook.lib
在对话框的OnInitDialog中加入InstallHook();安装键盘钩子
在OnClose中加入UninstallHook();关闭程序时卸载键盘钩子
编译这个对话框EXE
把这两个工程生成的S3DHook.dll和Main.exe放到同一个文件夹中,运行
在游戏窗口中按F12,会出现一个消息框,游戏会暂时定住
注意消息框的标题是游戏程序的名字
这说明这个消息框是在游戏内部显示出来的
我们已经从游戏程序的内部显示了一个消息框出来了
但还不够,我们要在游戏能够呼出外挂的界面,至少要显示一个对话框出来
下面我们在S3DHook这个DLL工程中添加一个从CDialog派生的CS3DHookDlg类
把DIALOG ID改为IDD_S3DHOOK_DIALOG
添加的对话框类的操作如下
主菜单->Insert->New Form
为了方便,把CS3DHookDlg的源程序文件名字分别改为
s3dhook.h
s3dhook.cpp
把刚才自动生成的对话框的Caption改为"外挂呼出窗口"
下面定义一个全局窗口指针来保存我们要生成的这个窗口的指针,以便后面对"外挂呼出窗口"进行控制,把它和全局变量
CS3DHookApp theApp;
写在一起,也就是这个样子
CS3DHookApp theApp;
CS3DHookDlg *pCWndWGMain;
下面对钩子回调函数进行改造,以便使我们的"外挂呼出窗口"能够在按F12时呼出
如下:
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
//按F12弹起时呼出外挂
BOOL bKeyUp = lParam & (1 << 31);
if (bKeyUp && wParam == VK_F12 && nCode == HC_ACTION) {
if (pCWndWGMain == NULL)
{
//更改当前有效模块状态到DLL中
//以便正确的读取对话框的资源
AFX_MANAGE_STATE(AfxGetStaticModuleState());
//找到当前的有效激活窗口
CWnd *pCWnd = CWnd::GetForegroundWindow();
//生成CS3DHookDlg类的对象实例
//此处应该生成一个非模态对话框
pCWndWGMain = new CS3DHookDlg();
pCWndWGMain->Create(IDD_S3DHOOK_DIALOG, pCWnd);
}
else
{
//根据当前呼出窗口的状态来显示或隐藏呼出窗口
pCWndWGMain->ShowWindow(pCWndWGMain->IsWindowVisible() ? SW_HIDE : SW_SHOW);
}
}
return ::CallNextHookEx(g_hhook, nCode, wParam ,lParam);
}
有关上面的这一句
在Regular MFC DLL中使用资源时非常重要
AFX_MANAGE_STATE(AfxGetStaticModuleState());
更多说明请参看
http://www.csdn.net/develop/article/25/25358.shtm
当关掉外挂的主程序时,还要做点善后工作
重载CS3DHookApp类的ExitInstance函数,在其中删除对话框
int CS3DHookApp::ExitInstance()
{
// TODO: Add your specialized code here and/or call the base class
delete pCWndWGMain;
return CWinApp::ExitInstance();
}
重新编译生成S3DHOOK.dll并和Main.exe放到一起,运行它试试看
运行后,随便打开一个其他的什么窗口,按F12,看到什么了?
哈哈,我们的"外挂呼出窗口"呼出来了,真是千呼万唤始出来啊
我在记事本中试了试:
上次的程序已经可以在其他窗口中呼出我们的"外挂呼出窗口"了
是不是已经有那么点"外挂"的样子了
当我做到这一步的时候,当时非常
兴奋!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
广大菜鸟们是不是有同感?
让高手们见笑了:)
但是,多试几次,发现还有不少问题
随便哪个窗口中按F12键都能呼出,还会造成程序的崩溃
我用的是WIN2000如果是在WIN98中恐怕还会造成系统的崩溃吧?
这可不行啊:(
要让他只在指定的程序窗口中呼出,每次都进神迹的游戏客户端试验很麻烦
我们就在记事本中试验,让他只能在记事本中呼出,最好还要能像真正的外挂那样
对挂入的程序做点手脚,
说做就做,开工!!!
继续对钩子回调函数进行改造
如下:
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
//按F12弹起时呼出外挂
BOOL bKeyUp = lParam & (1 << 31);
if (bKeyUp && wParam == VK_F12 && nCode == HC_ACTION) {
if (pCWndWGMain == NULL)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
CWnd *pCWnd = CWnd::GetForegroundWindow();
//当前窗口是否为记事本窗口
char buf[MAX_PATH];
::GetClassName(pCWnd->GetSafeHwnd(), buf, MAX_PATH);
if (lstrcmpi(buf, "notepad") == 0) {
pCWndWGMain = new CS3DHookDlg();
//创建"外挂呼出窗口"时把记事本窗口作为他的父窗口
pCWndWGMain->Create(IDD_S3DHOOK_DIALOG, pCWnd);
pCWndWGMain->ShowWindow(SW_SHOW);
}
}
else
{
//根据当前呼出窗口的状态来显示或隐藏呼出窗口
pCWndWGMain->ShowWindow(pCWndWGMain->IsWindowVisible() ? SW_HIDE : SW_SHOW);
}
}
return ::CallNextHookEx(g_hhook, nCode, wParam ,lParam);
}
重新编译并运行,我们的"外挂呼出窗口"可以多个记事本中呼出
如图,并还没有发现会造成程序崩溃,有发现的,请告诉我!后面会附上
这里是重新编译的Main.exe和s3dhook.dll
哈哈,解决了一个小问题,爽!!!!!!!!!!!!!
下面让我们的外挂对记事本做点小动作吧!
做点什么呢?
就让他在记事本的窗口中画个圆,怎么样?
来试试看了
为CS3DHookDlg添加WM_INITDIALOG的消息处理器
并在其中添加一个定时器,同时窗口关闭时要销毁定时器
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
设置
BOOL CS3DHookDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// TODO: Add extra initialization here
SetTimer(1000,100,0);
return TRUE; // return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~销毁,先要添加WM_CLOSE消息的处理器
void CS3DHookDlg::OnClose()
{
// TODO: Add your message handler code here and/or call default
KillTimer(1000);
CDialog::OnClose();
}
响应CS3DHookDlg的WM_TIMER消息
代码如下:
void CS3DHookDlg::OnTimer(UINT nIDEvent)
{
// TODO: Add your message handler code here and/or call default
//得到父窗口也就是记事本的指针
CWnd *pCWnd = GetParent();
//得到记事本的窗口设备上下文指针
CDC *pDC = pCWnd->GetWindowDC();
//画圆
pDC->Ellipse(100,100,200,200);
CDialog::OnTimer(nIDEvent);
}
在记事本中的实验非常成功,爽!!!!!!!!
下面让我们的程序只在神迹中呼出
主要是需要改造键盘钩子回调程序
只需要把
lstrcmpi(buf, "notepad")
改为
lstrcmpi(buf, "SG Engine")
就可以了
"SG Engine"是神迹客户端的窗口的类名
重新编译运行,即可在神迹中呼出了,并且也在其中的窗口上画了个圆,
但是画面在闪烁,具体解决方法还没有找到
估计是因为:游戏使用DirectX作图,而我们这里是用GDI作图
先不管它了,留在以后再解决了
暂时我们还不需要在游戏里作图
,把我们程序中刚才有关作图的部分都删除掉,
1.OnInitDialog中的
2.OnClose中的
3.OnTimer删掉
利用madCHook进行API挂接:如:WSASend,WSARecv等
去这下载
http://www.madshi.net/
安装刚才下载的madCollection.exe,安装后注意到在
C:/Program Files/madCollection/madCodeHook/Dll
中有3个文件需要引入到我们的S3DHook.dll工程中去
如下:
madCHook - dynamic.h
madCHook - dynamic - microsoft.lib
madCHook.dll
为了便于使用,把他们的名字改一下,改为:
madCHook.h
madCHook.lib
madCHook.dll
把.h和.lib放到S3DHook工程所在的文件夹中
在S3DHook.cpp中包含madCHook.h头文件,加入
#include "madCHook.h"
在Project->Settings->Link->Object/library modules中
加入madCHook.lib
下面就可以在我们的DLL中使用madCHook对API进行挂接了
注意:编译后要把Main.exe,S3DHook.dll和madCHook.dll放在一起才能运行
下面,正式开始加入代码对WSASend进行挂接
首先在S3DHook.cpp中加入
#include "Winsock2.h"
然后加入对原始API函数的指针及自定义API HOOK函数的原形的声明:
int (WINAPI *oWSASend)(
SOCKET,
LPWSABUF,
DWORD,
LPDWORD,
DWORD,
LPWSAOVERLAPPED,
LPWSAOVERLAPPED_COMPLETION_ROUTINE
);
int cWSASend(
SOCKET s,
LPWSABUF lpBuffers,
DWORD dwBufferCount,
LPDWORD lpNumberOfBytesSent,
DWORD dwFlags,
LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);
这个一定要和原始的API函数WSASend的形参一致才行,可以参看MSDN
为了把截获的数据显示出来,用资源编辑器在S3DHook中的外挂呼出窗口中加入一个ListBox,将其ID改为IDC_LIST_SEND,将其SORT属性去掉,
然后加入自定义API钩子的实现
int cWSASend(SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount,
LPDWORD lpNumberOfBytesSent, DWORD dwFlags,
LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine)
{
char buf[1024];
lstrcpyn(buf,lpBuffers->buf,lpBuffers->len);
CListBox *pListBox = (CListBox *)pCWndWGMain->GetDlgItem(IDC_LIST_SEND);
pListBox->AddString(buf);
//API钩子返回之前,对原始的API进行调用,
return oWSASend(s, lpBuffers, dwBufferCount, lpNumberOfBytesSent, dwFlags, lpOverlapped, lpCompletionRoutine);
}
非常感谢!行舟的支持,
其实在(一)中我就说过,其实我也是个菜鸟
去年的这个时候在玩传奇2的私服时,当时那个私服用了"乐都"的客户端,现成的外挂一般都用不了,于是到处找外挂,无意中看到了个WS2_32.DLL的外包的源代码(C++写的),还有相关的一些文章,刚好又懂点VC,于是就利用这个改了下,在发送数据是做了点手脚,实现了如双倍魔法,攻击,通买,通卖,刷钱等等通过修改封包就可以实现的功能自己写的外挂,自己用,别人没有,自己有,那怎一个爽字了得啊:)哈哈,由于菜的原因,那个外包是WIN32的DLL,里面全部都用WIN32 API编写,麻烦,基本的对话框,控件都不知道怎么用WIN32 API去控制,弄了很久终于在里面弄了对话框可以显示出来,但是也很是兴奋了一段时间
今年玩神迹的时候,也是在找外挂的时候,无意中发现了无影的神迹外挂源代码同时发现了我们这个GAMERES论坛,为了看看怎么弄的,半夜下载了个DELPHI 7.0装上,发现原来他利用的madCHook的组件,这个组件在DELPHI里用很方便,可是我不会pascal更不会DELPHI,于是又用了几天才弄明白怎么在VC中用这个madCHook.在近一个月的时间里经过多次的试验,终于大致上(有些东西还不是很清楚)知道了怎么注入在游戏进程,怎么编MFC Regular dll(可以在里面使用MFC了,比纯WIN32的DLL方便多了),怎么在里面使用对话框等资源,再加上madCHook怎么挂接API,
终于自己编的东西,也勉勉强强有那么点"像"个外挂了
于是心中充满了喜悦:)!!!!!!!!!!!!!!!!
决定把从头把所有的东西都放到一起来,重新做了一次(以前那些都是分开试验的),也萌生了把它贴出来让大家一起分享我的喜悦:),
自己一边写程序,一边帖帖子,
于是就有了现在的这个帖子
<菜鸟的VC6神迹外挂的DIY之路>
现在回到我们的程序中来,
下面我们利用madCHook安装WSASend这个API的钩子
成功后,游戏每次调用WSASend这个API的时候,都会去先执行我们写的
cWSASend,在这里,我们就可以截取到游戏发往服务器的数据封包,然后用游戏调用到cWSASend时使用的参数去调用我们刚才写的指向原始WSASend的函数指针oWSASend去调用原始的API将数据发到服务器,这样才能保证游戏能继续正常运行,否则游戏很有可能会掉线
在CS3DHookDlg::OnInitDialog() 加入这一句来安装API钩子
HookAPI("Ws2_32.dll", "WSASend", cWSASend, (PVOID *) &oWSASend);
参数说明:
1.要挂接的API所在的DLL
2.要挂接的API
3.自定义的函数,用来替换要挂接的API:cWSASend//游戏每次调用Ws2_32.dll中的WSASend,就会先进入我们自定义的cWSASend
4.指向原始API函数WSASend的指针//自定义的cWSASend截取封包并进行必要处理后,通过这个指针去调用原始的WSASend@Ws2_32.dll,以保证游戏的正常运行
BOOL CS3DHookDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// TODO: Add extra initialization here
HookAPI("Ws2_32.dll", "WSASend", cWSASend, (PVOID *) &oWSASend);
return TRUE; // return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
}
编译之后,把Main.exe S3DHook.dll madCHook.dll放在一起,看看效果
运行Main.exe,启动进入神迹,按F12呼出
令人激动的时刻啊!看到没有?截获的数据封包已经在ListBox控件中显示出来了!!!
其实这才是刚刚开始,要想有强大的功能,后面还要做许多工作
另外,创建“外挂呼出窗口”时,要注意为它选好父窗口,不同的游戏不一样,
记得传奇2中把
游戏界面的那个窗口
作为“外挂呼出窗口”就不行,
要选他的父窗口才行,具体情况要自己用SPY++多看看