| |
游戏外挂设计技术探讨 |
|
时间: 2003-10-29 来自: |
 |
|
2.挡截API
挡截API技术与挡截WinSock技术在原理上很相似,但是前者比后者提供了更强大的功能。挡截WinSock仅只能挡截WinSock接口函数,而挡截API可以实现对应用程序调用的包括WinSock API函数在内的所有API函数的挡截。如果您的外挂程序仅打算对WinSock的函数进行挡截的话,您可以只选择使用上小节介绍的挡截WinSock技术。随着大量外挂程序在功能上的扩展,它们不仅仅只提供对数据包的挡截,而且还对游戏程序中使用的Windows API或其它DLL库函数的挡截,以使外挂的功能更加强大。例如,可以通过挡截相关API函数以实现对非中文游戏的汉化功能,有了这个利器,可以使您的外挂程序无所不能了。
挡截API技术的原理核心也是使用我们自己的函数来替换掉Windows或其它DLL库提供的函数,有点同挡截WinSock原理相似吧。但是,其实现过程却比挡截WinSock要复杂的多,如像实现挡截Winsock过程一样,将应用程序调用的所有的库文件都写一个模拟库有点不大可能,就只说Windows API就有上千个,还有很多库提供的函数结构并未公开,所以写一个模拟库代替的方式不大现实,故我们必须另谋良方。
挡截API的最终目标是使用自定义的函数代替原函数。那么,我们首先应该知道应用程序何时、何地、用何种方式调用原函数。接下来,需要将应用程序中调用该原函数的指令代码进行修改,使它将调用函数的指针指向我们自己定义的函数地址。这样,外挂程序才能完全控制应用程序调用的API函数,至于在其中如何加入外挂代码,就应需求而异了。最后还有一个重要的问题要解决,如何将我们自定义的用来代替原API函数的函数代码注入被外挂游戏程序进行地址空间中,因在Windows系统中应用程序仅只能访问到本进程地址空间内的代码和数据。
综上所述,要实现挡截API函数,至少需要解决如下三个问题:
● 如何定位游戏程序中调用API函数指令代码?
● 如何修改游戏程序中调用API函数指令代码?
● 如何将外挂代码(自定义的替换函数代码)注入到游戏程序进程地址空间?
下面我们逐一介绍这几个问题的解决方法:
(1) 、定位调用API函数指令代码
我们知道,在汇编语言中使用CALL指令来调用函数或过程的,它是通过指令参数中的函数地址而定位到相应的函数代码的。那么,我们如果能寻找到程序代码中所有调用被挡截的API函数的CALL指令的话,就可以将该指令中的函数地址参数修改为替代函数的地址。虽然这是一个可行的方案,但是实现起来会很繁琐,也不稳健。庆幸的是,Windows系统中所使用的可执行文件(PE格式)采用了输入地址表机制,将所有在程序调用的API函数的地址信息存放在输入地址表中,而在程序代码CALL指令中使用的地址不是API函数的地址,而是输入地址表中该API函数的地址项,如想使程序代码中调用的API函数被代替掉,只用将输入地址表中该API函数的地址项内容修改即可。具体理解输入地址表运行机制,还需要了解一下PE格式文件结构,其中图三列出了PE格式文件的大致结构。
 图三:PE格式大致结构图(003.jpg)
PE格式文件一开始是一段DOS程序,当你的程序在不支持Windows的环境中运行时,它就会显示“This Program cannot be run in DOS mode”这样的警告语句,接着这个DOS文件头,就开始真正的PE文件内容了。首先是一段称为“IMAGE_NT_HEADER”的数据,其中是许多关于整个PE文件的消息,在这段数据的尾端是一个称为Data Directory的数据表,通过它能快速定位一些PE文件中段(section)的地址。在这段数据之后,则是一个“IMAGE_SECTION_HEADER”的列表,其中的每一项都详细描述了后面一个段的相关信息。接着它就是PE文件中最主要的段数据了,执行代码、数据和资源等等信息就分别存放在这些段中。
在所有的这些段里,有一个被称为“.idata”的段(输入数据段)值得我们去注意,该段中包含着一些被称为输入地址表(IAT,Import Address Table)的数据列表。每个用隐式方式加载的API所在的DLL都有一个IAT与之对应,同时一个API的地址也与IAT中一项相对应。当一个应用程序加载到内存中后,针对每一个API函数调用,相应的产生如下的汇编指令:
JMP DWORD PTR [XXXXXXXX]
或
CALL DWORD PTR [XXXXXXXX]
其中,[XXXXXXXX]表示指向了输入地址表中一个项,其内容是一个DWORD,而正是这个DWORD才是API函数在内存中的真正地址。因此我们要想拦截一个API的调用,只要简单的把那个DWORD改为我们自己的函数的地址。
(2) 、修改调用API函数代码
从上面对PE文件格式的分析可知,修改调用API函数代码其实是修改被调用API函数在输入地址表中IAT项内容。由于Windows系统对应用程序指令代码地址空间的严密保护机制,使得修改程序指令代码非常困难,以至于许多高手为之编写VxD进入Ring0。在这里,我为大家介绍一种较为方便的方法修改进程内存,它仅需要调用几个Windows核心API函数,下面我首先来学会一下这几个API函数:
DWORD VirtualQuery( LPCVOID lpAddress, // address of region PMEMORY_BASIC_INFORMATION lpBuffer, // information buffer DWORD dwLength // size of buffer ); | 该函数用于查询关于本进程内虚拟地址页的信息。其中,lpAddress表示被查询页的区域地址;lpBuffer表示用于保存查询页信息的缓冲;dwLength表示缓冲区大小。返回值为实际缓冲大小。
BOOL VirtualProtect( LPVOID lpAddress, // region of committed pages SIZE_T dwSize, // size of the region DWORD flNewProtect, // desired access protection PDWORD lpflOldProtect // old protection ); | 该函数用于改变本进程内虚拟地址页的保护属性。其中,lpAddress表示被改变保护属性页区域地址;dwSize表示页区域大小;flNewProtect表示新的保护属性,可取值为PAGE_READONLY、PAGE_READWRITE、PAGE_EXECUTE等;lpflOldProtect表示用于保存改变前的保护属性。如果函数调用成功返回“T”,否则返回“F”。
有了这两个API函数,我们就可以随心所欲的修改进程内存了。首先,调用VirtualQuery()函数查询被修改内存的页信息,再根据此信息调用VirtualProtect()函数改变这些页的保护属性为PAGE_READWRITE,有了这个权限您就可以任意修改进程内存数据了。下面一段代码演示了如何将进程虚拟地址为0x0040106c处的字节清零。
BYTE* pData = 0x0040106c; MEMORY_BASIC_INFORMATION mbi_thunk; //查询页信息。 VirtualQuery(pData, &mbi_thunk, sizeof(MEMORY_BASIC_INFORMATION)); //改变页保护属性为读写。 VirtualProtect(mbi_thunk.BaseAddress,mbi_thunk.RegionSize, PAGE_READWRITE, &mbi_thunk.Protect); //清零。 *pData = 0x00; //恢复页的原保护属性。 DWORD dwOldProtect; VirtualProtect(mbi_thunk.BaseAddress,mbi_thunk.RegionSize, mbi_thunk.Protect, &dwOldProtect); |
|
|
|
|
|
|
|
|