|
|
|
数据包抓取(拦截)技术,主要应用在信息安全相关领域,特别是防火墙技术。随着网络游戏产业日益兴旺,后来出现很多针对不同游戏的外挂,这些外挂也使用到抓包技术,但是与其他抓包技术很大不同的是,外挂技术中的抓包技术必须达到能顺利截取、完成对数据包的修改、再次发送出去等一系列特殊要求,基于以上要求,所以必须在应用层完成数据包的抓取、修改。
本篇介绍了在WINDOWS环境下,疾风利用第三代反HOOK、DLL等技术,实现网络函数的拦截,从而实现在应用层完成数据包的抓取。
随着网游的发展,出现了越来越多的游戏外挂。从市场的角度来说,游戏外挂打破了游戏运营商的“游戏规则”,所以越来越多的游戏运营商开始设置“反外挂”的部门;从技术的角度上来说,游戏外挂是属于WINDOWS核心编程技术的范畴,对于学习WINDOWS核心编程很有现实性的价值。
需求分析
背景
对于一个商业软件来说,防止侵犯是个很重要的问题。这种问题在网络游戏中体现的比较突出。随着网络游戏被越来越多的人所熟知,网游外挂也渐渐走入了人们的视线。后来游戏运营商为了防止游戏玩家使用外挂,还提出过:一旦系统发现某个玩家使用外挂,那么就对其帐号进行查封。因为游戏运行商的这个做法,甚至还有玩家将运营商告上法庭。那么这里又涉及到虚拟财产的问题。由于中国对这方面的立法还比较欠缺,所以对于运营商也好、对于游戏玩家也好,可能这不是最优的解决方案。
因此,游戏运营商家需要从外挂的开发开始着手,研究其技术特点,从自身完成反外挂的功能,这才是根本的问题解决之道。
而在游戏外挂中,一般为有数据包拦截、解密、修改、加密封包等步骤。一般反外挂的工作都在数据包拦截和加密这两大模块完成。由于加密技术比较难,研究的成本相对较高,加之任何密文都有被破解的一天。所以从防止数据包拦截入手,可以以最少的投入获得理想的效果。
所谓知自知彼,百战不殆。研究在游戏外挂技术中常用的数据包截取方式,为反外挂技术的研究做一个基础的铺垫。
基本需求:完成网络数据包的抓取。留出可修改数据包的接口,以便后期完成数据包修改。
功能需求:完成具有数据包的抓取的DLL模块在前台完成数据包的打印
设计约束:本篇属于WINDOWS核心编程的范畴,而且涉及道内存的属性修改等问题。如果有错误的设计和代码的编写错误可能导致系统的不稳定、甚至崩溃。所以在完成编译的时必须从分考虑系统的稳定性。此外还应考虑是否能方便数据包的修改,是否能承受大量的数据包的抓取等问题。
技术原理 :主要有三个主要的技术要点,以下是各个技术要点的原理简介。
DLL原理简介: DLL 是一个包含可由多个程序同时使用的代码和数据的库。使用 DLL
有助于促进代码的模块化、代码重用、内存的有效使用和减少所占用的磁盘空间。因此,操作系统和程序能够更快地加载和运行,并且在计算机中占用较少的磁盘空间。
一旦DLL的文件映像被映射到进程的地址空间种,DLL函数就可以供进程种运行的所有线程使用。
DLL原理(图4.1)
第一个地址的 DLL的虚拟内存 第二个地址的
进程空间 进程空间
DLL 的优点:
下表说明了当程序使用 DLL 时提供的一些优点:
• 使用较少的资源
当多个程序使用同一个函数库时,DLL 可以减少在磁盘和物理内存中加载的代码的重复量。这不仅可以大大影响在前台运行的程序,而且可以大大影响其他在
Windows 操作系统上运行的程序。
• 推广模块式体系结构
DLL
有助于促进模块式程序的开发。这可以帮助您开发要求提供多个语言版本的大型程序或要求具有模块式体系结构的程序。模块式程序的一个示例是具有多个可以在运行时动态加载的模块的计帐程序。
• 简化部署和安装
当 DLL 中的函数需要更新或修复时,部署和安装 DLL 不要求重新建立程序与该 DLL 的链接。此外,如果多个程序使用同一个
DLL,那么多个程序都将从该更新或修复中获益。当您使用定期更新或修复的第三方 DLL 时,此问题可能会更频繁地出现。
HOOK原理简介
钩子(Hook),是Windows消息处理机制的一个平台,应用程序可以在上面设置子程以监视指定窗口的某种消息,而且所监视的窗口可以是其他进程所创建的。当消息到达后,在目标窗口处理函数之前处理它。钩子机制允许应用程序截获处理window消息或特定事件。钩子实际上是一个处理消息的程序段,通过系统调用,把它挂入系统。每当特定的消息发出,在没有到达目的窗口前,钩子程序就先捕获该消息,亦即钩子函数先得到控制权。这时钩子函数即可以加工处理(改变)该消息,也可以不作处理而继续传递该消息,还可以强制结束消息的传递。HOOK机制如
HOOK 机制
数拦截简介
Api拦截并不是一个新的技术,很多商业软件都采用这种技术。对windows的Api函数的拦截,不外乎两种方法,第一种是Mr. Jeffrey
Richter 的修改exe文件的模块输入节,种方法,很安全,但很复杂,而且有些exe文件,没有Dll的输入符号的列表,有可能出现拦截不到的情况。第二种方法就是常用的JMP
XXX的方法,虽然很古老,却很简单实用。疾风采用第二种方法,利用WINDOWS为我们提供的API,较为简单的实现API函数拦截的功能。
抓包程序原理
外挂程序主要就是将HOOK、DLL和API函数拦截三个技术结合,实现数据包抓取,其基本原理如下:
首先是利用HOOK技术完成消息的截获,提取出我们感兴趣的消息;再利用API拦截技术,拦截相应的网络程序的SOCKET网络函数;最后利用DLL技术将HOOK和API拦截的代码封装。利用EXE程序将DLL映射到进程中。等到由HOOK挂载的程序的网络函数被调用时,引起API函数拦截,从而拦截相应的网络函数。最后,从网络函数中提取我们想要得到的数据包。
抓包程序功能模块设计
该抓包程序分为:DLL模块和EXE模块两大模块。
DLL部分主要完成HOOK钩子的挂载、API函数的拦截和处理拦截到的网络函数。
EXE程序部分主要完成把DLL映射到进程空间,接受数据包内容的输入和管理钩子的安装、卸载。
DLL 模块部分 DLL 加载模块
结构设计
由EXE将DLL加载到当前进程空间以后,DLL模块会注入到目标进程当中。当监测到有网络套节字的发送、接受时,调用DLL模块中的API拦截函数。经过DLL中的API处理,再把网络函数发出。
程序实施
HOOK核心代码
HOOK部分主要完成对特定目标程序的消息钩子的挂载,从而实现与目标进程共享DLL的代码和数据。该抓包程序完成以上功能,主要通过FindWindowEx()函数查找CALSS
NAME(可以用SPY++确定目标程序的CLASS NAME)来确定目标程序的句柄。从而确定目标线程的标识,再使用SetWindowsHookEx()挂载消息钩子。
这样,一旦目标程序(线程)启动,那么该程序的所有消息都必须先经过我们的DLL模块,待我们处理完自己感兴趣的消息之后,再将消息发出。
具体代码如下,以下代码中,还加入了一些异常检测部分:
g_hArmIns = FindWindowEx(NULL,NULL,/*"目标程序CLASS NAME"*/,NULL);
//查找目标线程的句柄
dwThreadID = GetWindowThreadProcessId(g_hArmIns,NULL);
//保存线程的标识
if (dwThreadID==NULL) //异常检测
{
MessageBox(hwnd,"要被注入的程序没有被打开","错误",MB_OK);
}
else if (g_hInsMouse != NULL && g_hInsKeyboard!=NULL)
{
MessageBox(hwnd,"你已经注入了,无需多次注入","错误",MB_OK);
}
else
{
g_hInsMsg=SetWindowsHookEx(WH_GETMESSAGE,GetMsgProc,
GetModuleHandle("dllname"),dwThreadID);
//安装消息钩子,其具体参数意义,请参看MSDN。
}
函数拦截核心代码
函数拦截部分主要完成网络函数的替换。首先动态加载wsock32.dll,所有的网络接受、发送函数都在此DLL中;然后利用GetProcAddress()函数,从DLL中获得我我们想要截获的网络函数的首地址,并保存;其次使用汇编语言再保存一个我们要拦截的函数的副本,以便后期恢复;最后再次使用汇编,使我们的函数的地址覆盖我们要拦截的网络函数。
这样,当系统调用网络函数发送、接受数据的时候,实际上就是调用的我们自己编写的函数,从而实现函数拦截的功能。
具体代码如下:
hModule = LoadLibrary("wsock32.dll");
//加载wsock32.dll模块
pfSend = GetProcAddress(hModule,"send");
//从wsock32.dll模块中获得SEND函数入口地址
if(pfSend==NULL)
return false;
// 保存 pfSend 的一个副本 -- oldSend
_asm
{
lea edi,oldSend
mov esi,pfSend
cld
movsd
movsb
}
newSend[0] = 0xe9; //jmp MySend的相对指令
//利用我们的MySend函数替换pfSend函数入口
_asm
{
lea eax,MySend
mov ebx,pfSend
sub eax,ebx
sub eax,5
mov dword ptr [newSend+1],eax
}
以上两段代码只是完成WSOCK32.DLL中的SEND函数拦截。我们知道网络函数都有SEND和RECV两种状态,而且常用的网络连接方式又有TCP和UDP两种。所以我们还应该完成RECV、SENDTO(UDP发送)、RECVTO(UDP接受)的函数拦截,基本方式通SEND。
内存修改核心代码
内存修改是本程序比较关键的一段待代码。因为WINDOWS的内存是具有保护机制的。每个进程有自己私有的4GB的虚拟地址空间,EXE和DLL程序被映射到底部的2GB的地址上。WINDOWS为了安全,严格的标记此2GB内存空间,所有EXE和DLL的代码都是标记为只读的,因此一个进程想要访问另一个进程的栈、全局内存或者堆内存是不可能的。因此我们必须通过WINDOWS给我们提供的API函数修改内存的属性。
以下就是将内存修改为可写状态的代码:
void _stdcall sendHookOn() //send
{
HANDLE hProc;
dwIdOld=dwIdNew;
hProc=OpenProcess(PROCESS_ALL_ACCESS,0,dwIdOld);
//得到所属进程的句柄
VirtualProtectEx(hProc,pfSend,5,PAGE_READWRITE,&dwIdOld);
//修改所属进程中send的前5个字节的属性为可写
WriteProcessMemory(hProc,pfSend,newSend,5,0);
//将所属进程中send的前5个字节改为JMP 到MySend
VirtualProtectEx(hProc,pfSend,5,dwIdOld,&dwIdOld);
//修改所属进程中send的前5个字节的属性为原来的属性
bHook=true;
}
当我不们不再需要修改内存内容的时候,我们应该将内存还原为以前的只读状态,这样才不会破坏WINDOWS的内存结构,以下代码就是完成所述功能。
void _stdcall sendHookOff()
{
HANDLE hProc;
dwIdOld=dwIdNew;
hProc=OpenProcess(PROCESS_ALL_ACCESS,0,dwIdOld);
VirtualProtectEx(hProc,pfSend,5,PAGE_READWRITE,&dwIdOld);
WriteProcessMemory(hProc,pfSend,oldSend,5,0);
VirtualProtectEx(hProc,pfSend,5,dwIdOld,&dwIdOld);
bHook=false;
}
以上两段代码只是完成TCP中的SEND函数的修改。我们知道网络函数都有SEND和RECV两种状态,而且常用的网络连接方式又有TCP和UDP两种。所以我们在还应该完成RECV、SENDTO(UDP发送)、RECVTO(UDP接受)。基本方式同SEND。
DLL函数导出
当我们完成DLL功能模块时,我们要讲DLL中的功能函数导出成规则的函数名。因为C++编译器在编译的时候会对函数名进行改编,这样我们想调用自己DLL中的函数就会遇到困难,所以我们要指定要导出的函数的名称,这样才能方便我们的调用。
有两种方法可以解决以上问题:
方法一:
在要导出的全局函数名之前加上这样一句: extern "C" _declspec(dllexport)
它表明,将一个DLL中的函数导出,并且遵守标准C的调用约定。
方法二:
使用模块定义文件(.def),并将其加入到工程中,添加如下代码:
LIBRARY (动态链接库名)
EXPORTS (以下列出要导出的函数名)
EXE前台代码功能说明
EXE前台主要完成钩子挂载的安装、卸载和接受DLL的模块的消息,并在其窗口中打印出数据包。
EXE部分使用MFC的对话框,主要完成的工作有:
使用_declspec(dllexport)接受导出的函数。因为在EXE前台要调用DLL中的函数,就必须告诉EXE,这个函数是从DLL中获得,不然系统会报告,未定义函数的错误。
编写消息相应代码,完成钩子的安装和卸载,当EXE得到DLL中的函数时,需要在前台启动钩子。具体步骤为:添加一个按钮、添加其点击事件响应函数、调用DLL中的函数。
处理用户自定义消息,因为我们在DLL中把数据包的地址和大小以消息的形式发送到EXE端处理,所以我们EXE端必须响应DLL端发出的消息,具体步骤为:1、在头文件中定义消息类型。2、在消息映射宏中添加消息映射。3、在CPP文件中根据消息映射,添加消息处理函数。
打印数据包到窗口中,根据消息中的数据包地址和长度,打印出数据包的内容。
现在,相信大家已经基本了解第三代HOOK技术的基本思路了,当然这仅仅是抛砖引玉,真正实现还要结合内嵌DX10技术、防驻留技术、内存流控制技术等等许多相关的知识和方法。
|