查看: 2869|回复: 6
收起左侧

[其他] 决战反外{过}{滤}挂系统之秘密通讯原理(由于文件太大不能上传)

[复制链接]
我爱熊猫
发表于 2013-1-14 13:01:14 | 显示全部楼层 |阅读模式
作者:Shawn (L. Spiro) Wilcoxen
译者:riusksk(泉哥)

前言
逃避反外{过}{滤}挂系统的检测是当今新生游戏黑客最大的障碍,而存活最久的逃避方法当属DLL注入。在外面流传的黑客技术/工具是最容易被封杀的,这仅在与目标游戏进行频繁交互的系统函数上挂钩即可实现。本文涵盖了这些技术及工具绕过检测的方法,仅通过向目标进程注入DLL来避免调用挂钩的系统函数,进而使这些黑客工具可以访问任何目标进程,如果直接调用的话就可能会被反外{过}{滤}挂系统发现,进而被查杀。在本文中有两份独立代码:一份是用于注入到游戏进程的DLL文件,一份是与DLL交互的工具,主要用于秘密获取目标进程的相关信息。
……

创建DLL
        DLL文件是整个进程的最基本的组成部分。我们先创建出一个DLL的基本框构,然后将其注入到进程中。关于DLL代码也没有什么特别之处,其源码如清单1所示:

通过简单地调用函数::Beep()以便让我们知道此DLL是否被注入到目标进程了,这个可借助DLL注入工具,对任意进程注入我们的DLL,如果听到响声就说明DLL已经被成功注入并执行了。
        注意:在Windows 7中,Beep()函数使用默认声卡,不像其它版本的Windows,将声音中继到主板喇叭上。
        注意:为了使用Microsoft® Visual Studio®调试DLL,需先打开工程属性(Alt+F7),然后选择“Debugging”属性页,将“Command”设置为“winmine.exe”(Windows XP)或者“Minesweeper.exe”(Windows Vista或Windows 7)(译注:此为扫雷程序,必须输入完整路径才能执行,可通过浏览文件选择程序),以上都是在Debug build下创建的(读者可自行选择Release build)。在Debug版本下按F5启动Minesweeper,然后使用任意软件(比如内存搜索修改工具MHS、CheatEngine等等)注入创建的DLL。如果此时在DLLMain()上下断,你就可以看到当DLL被注入后即被断下。接着你可以从这里进行单步或普通调试。

注入DLL
在准备测试DLL注入前,需要先找出一种可偷偷注入所有进程的方法。下面有几种可将DLL注入目标的方法,当DLL一注入进程后,DLL即可正常工作而不会被检测到。反外{过}{滤}挂软件通常是检测使用CreateRemoteThread()和SetWindowsHookEx()暴力注入的方法,但如果你想随意地使用这些注入进程,可以看看本文所揭露的方法:频繁被非侵入式(non_intrusive)程序所使用的Applnit_DLLs注册表键值(译注:AppInit_DLLs 表值是用于初始化动态链接库,它可以为任一进程调用一个dll列表,当进程启动后,AppInit_DLLs中的dll文件就会注入到该程序里面去启动)。测试这种方法最简单地方式是手动将DLL文件路径添加到注册表中的HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Windows\AppInit_DLLs,然后加载一个进程,比如记事本或计算器。
注意:在Windows XP中,这项工作甚是简单,但在Windows Vista中,由于各项安全机制的保护,这种方法可能会受到限制。而在Windows 7上虽可实现,但只能通过一些循环跳转来修改注册表中的相同键值(LoadAppInit_DLLs和RequireSignedAppInit_DLLs)。对于这些系统,最好是使用一些替代DLL注入的方法。在将AppInit_DLLs设置为“F:\temp\MyDll.dll”(没有引号)时,由于此值是通过空格进行分隔的,因此只能使用不包含空格的路径。更改注册表后,打开Windows系统上的计算器即可听到响声了,可见DLL已经被加载了。若想恢复原样,只需删除注册表中的此值然后重启即可。

通讯原理
DLL文件需要向系统中的每一个进程广播它的存在,如果有一个(或者更多)进程响应,它将会去“连接”此进程,并进行更多的通讯。有很多方法可以建立一个私用通信网络,这里所谓的“私用(Private)”是指通讯网络不会在软件内部引发警告。如果你使用附带HWND_BROADCAST和(WM_USER+0x100)参数的SendMessage(),那么反外{过}{滤}挂系统就会捕获此消息,并假设你的通讯网络正处于活动状态,接着将会终止游戏进程。
        有许多方法可用来掩盖网络通信,其中一个方法是基于LAN通讯来侦测通信网络;其它方法可根本不发送消息到目标窗口。但由于你的DLL文件名应该随机的,因此只有DLL本身和客户端软件真正地知道DLL名称。如果客户端软件卸载了此DLL,那么该DLL需要向其它未加载此DLL的进程发送秘密信息,以实现进一步的通讯,这也是本文所选择的方法。客户端软件不能以其它方法进行通讯,因为这可能会破坏游戏中某些保护机制。与此同时该DLL事先并不知道给定的进程是否是客户端进程,因此进行数据共享的地址也是无法预先确定的。此种方法可先通过SendMessage()进行首次连接,然后再使用ReadProcessMemory()和WriteProcessMemory()进行后续的通讯。

做好准备
        在实现通信层前有几个关键问题需要处理。首先至关重要的是,先创建一个用于与目标打交道的类。这个类里面封装了一些系统函数,便于后面进行重写和修改。例如,为了直接替代了函数ReadProcess(),可通过类中某实例来调用封装函数,它会转而来调用函数ReadProcess()。接着,当你想添加一个内核驱动程序来改变读取进程内存的方式时,你只需简单地重写类函数,并创建一个该类的实例即可,那么使用该类中封装函数的代码均会自动得到更正。下面的代码即是这样的一个类:


建立连接
最后我们通过DLL与客户端程序建立连接,并使用一个类来管理每次的连接。然而,为了到达这一点,我们必须先检测一下客户端程序。这似乎很简单,只是简单地将消息发送给每个进程,并判断进程是否响应。这种方法只分配一个缓冲区用于存放客户端响应信息,然后将此地址发送到每个进程中。若其中某进程的缓冲区被响应信息给填充了,那么它就是客户端进程。然而不幸的是,我们可以打开多个客户端程序实例,如果他们同时将响应信息发送到同一个缓冲区,那么就有一份响应信息会丢失,而DLL只能与其中一个实例进行连接。因此我们需要为每个可能会响应的进程(基本上都有可能)提供一个缓冲区。一旦发现响应,我们将缓冲区发送给其受理的类,该类会处理所有与DLL间的通讯,并对客户端程序进行响应。

通讯缓冲区
        我们的通讯系统的工作原理是让每个程序(DLL和客户端)将信息写入RAM内部指定的接收地址,此接收地址会被持续监测。每条消息以能够被DLL和客户端软件解析的特定格式来发送的,这个我们可以通过结构体、联合体和枚举来构造这样的格式。首先,实际的消息类型需要被枚举,如清单3所示:

其次,每条消息格式的定义如清单4所示:

最后使用一个联合体让单个结构体以清单4中所示的格式来包含数据。具体见代码清单5:

注意,在客户端和DLL中均将使用这种结构体。

首次连接
当DLL想盯梢未注入DLL的程序时,就需要先尝试初始化连接。由于DLL欲注入到每一个启动进程中(但并局限于此),因此我们可以假设未被注入DLL的进程会有意地从其自身卸载DLL,并有可能成为我们想要建立连接的客户端软件。此外,假设游戏进程受反外{过}{滤}挂系统保护,那么恶意程序将很难发送可疑的探测消息给游戏进程。一般来说所有的连接工作都是相同的。客户端软件将有一个内存区域,此内存受到DLL的严格监管,当检测到有变更时,就会利用同一缓冲区来回应消息,然而初始化连接就需要发送一个Windows消息来设置这一切。更为复杂的是,DLL并不知道哪个客户端窗口是首次连接的窗口,因此它必须发送消息给每一个由客户端线程创建的窗口。这代码虽简单,但很长,关于代码解释参见清单6中的注释:
  
调用::EnumThreadWindows()需要以下辅助函数,这个函数实际是用于发送消息给客户端,以请求获得响应,代码见清单7:

其中m_lpcbBuffers是一个由std::vector<LPHITB_COMMUNICATION_BUFFER> m_lpcbBuffers定义的类成员。我们用它记录下每一次的初始化连接,并用它来判断是否得到响应。关于这个函数,一旦我们拥有一个可能是客户端进程的ID,就可以通过调用CDllMagic::InitiateCommunication()开始进行通讯。现在我们需要做的就是找出客户端进程。

查找客户端
从理论上来讲,查找客户端相对比较简单。由于查找过程必须持续进行,因此搜索例程必须是双线程的,以无限循环交替地进行搜索工作,直至被通知中止。另外,它必须不能消耗过多的CPU资源,因此它必须处于低优先级,并在循环中存在一定的休眠过程。在循环搜索中,对于无响应的进程,将不再打开,并移除相关的通讯记录,然后释放资源。出于简洁没有附上这段代码,但在下面代码中会有所讲解。如代码清单8所示,它是个静态函数,因为后面它将调用::CreateThread()函数。

DllIsInProc简单地扫描进程中与当前DLL同名的模块,代码参见清单9:

然后我们从新的主入口点来运行DLL,如代码清单10所示:


客户端响应
当DLL广播消息时,客户终端的首要任务是用于接收这些信息。由于消息被发送到每一个窗口,因此很容易捕获,其代码如清单11所示:

我们在客户端程序的主窗口过程中捕获消息,参数_lParam用于保存进程上下文中的通信缓冲区地址。为了测试你的系统是否处于工作状态,可在break上面使用一段无用代码(比如”int jhgdjhg = 0;”,并在此代码上下断,然后在Microsoft® Visual Studio®中以调试模式来运行客户端,接着再注入DLL到其它进程)。若注入后触发断点,说明系统正处于运行状态。
        如在DLL中一样,我们同样使用一个类来处理与DLL的通讯。这个类确实很简单,它仅需要一个通讯缓冲区的指针,以及与此缓冲区交互的接口。对于每个发送到DLL的请求均会使用到此接口。例如,当我们想读取一个被DLL感染的进程内存时,通过这个类就可以处理进程所有可能引发的情况,包括成功完成读取操作和读取失败的情况。这个类如清单12所示:

        由于最终会有很多运行程序连接到DLL,因此需要使用一个数组来记录它们,这个可交由std::vector<CClientConnection *> m_pmmcConnections来完成,关于客户端连接数组的管理则交由读者来完成。我们这里只简单地使用上述vector和临界区(critical section)来实现,当通讯类一创建,就会清楚地知道如何使用它们,并结合其它方法来管理这些对象。该对象的构造函数会请求通讯响应,在这点上只是将缓冲区类型设置为HITB_COMMUNICATION_BUFFER::HITB_INITIALREPLY。由于缓冲区可能是个无效的内存地址,因此读写它时可能会导致客户端程序崩溃,而不是中断在构造函数中,我们可以使用一个静态函数来对此进行检测,并返回一个被创建的对象的指针,以判断地址是否有效。参见清单13:

在这个静态函数的帮助下,添加一个通讯对象就变得容易多了,清单14列出一个使用我们自己管理系统的示例函数:

剩下的就是对window消息挂钩,参见清单15:


Sealing The Deal
当客户端正对来自DLL的初始连接进行响应时,DLL会捕获此响应,并在DLL与客户端之间创建一个专用线程用于通讯。我们修改搜索线程以检测这些响应信息是否来自客户端。检测函数如清单16所示:

由于客户端运行在其自身的地址空间中,因此我们将客户端本地的响应缓冲区数据复制到DLL中,以便于操作。当缓冲区位于本地时,需要检测它的缓冲区类型。下面是用于在主DLL线程中检测响应信息的辅助函数,代码如清单17所示:
   
        在文章开头处我们就创建了一个类,用于处理DLL与客户端软件间的单一连接。CreateLink()函数就是用于创建这样的一个类,并让它在自身线程中运行。该类运行在自身线程中,并循环运行直至连接中断,或者DLL程序关闭,或者客户端程序关闭。每一循环都会检测远程通讯缓冲区,以判断客户端程序创建的缓冲区是否被填充满。该类的结构如清单18所示:
     
这个类可处理的基本功能:附加和分离客户端进程,并持续检测客户端进程是否关闭。当连接被创建时,该类将目标进程(客户端程序)中的消息状态设置为空闲(idle)。这是必须的,否则DLL将不停地去尝试连接客户端。接下来我们添加一个处理规则的函数,用于处理来自客户端程序的请求。当此函数被调用时,就会去检测是否存在请求,如果发现存在请求,就对其进行响应以满足请求。该函数的代码如清单19所示:

这个函数会在它自身的线程中不断地被循环调用,然后我们添加一个线程函数,它是个公共静态函数。代码如清单20所示:

最后,使用CreateLink()函数创建一个类对象,并在自身线程中启动,然后再创建一个嵌套结构,用于存储类对象和运行线程的句柄,参见清单21:
   
接着为这一连接数组创建两个管理例程,其中一个用于创建连接(CreateLink()),一个用于关闭连接,如清单22所示:
   
上面另外添加了一个Stop()函数。

WHAT IS HAPPENING
当主DLL循环函数检测到初始连接的响应信息时,CreateLink()就会被调用了。当远程进程将缓冲区中的mType成员设置为HITB_COMMUNICATION_BUFFER::HITB_INITIALREPLY时,初始连接的响应信息也会被检测到。当DLL检测到这些改变时,CtargetProcess对象就会被创建,用于处理与客户端的后续通讯。为了避免对同一客户端重建连接,客户端进程的缓冲区可被DLL远程将mType成员设置为HITB_COMMUNICATION_BUFFER::HITB_IDLE。这也意味着客户端进程可以使用本地缓冲区进行通讯。接下来,DLL发送新建的CTargetProcess对象给自身线程中的循环函数,用于检查和处理远程客户端中的缓冲区数据是否被更改。每次检查,都必须对本地缓冲区数据进行复制。它可以直接修改客户端应用程序,因为客户端程序并不能修改DLL进程,这在网络通讯上是为了避免去修改被反外{过}{滤}挂保护的DLL进程。

LET’S COMMUNICATE!
现在DLL与客户端程序可进行通讯了,剩下的就是决定创建何种类型的请求。本文只演示2种请求:读取和写入DLL进程的RAM。为了添加新的请求类型,就必须修改HITB_COMMUNICATION_BUFFER结构。我们可添加一个新的请求类型给枚举结构,并将包含新建的请求类型的数据结构添加进去,在清单23中,我们添加了ReadProcessMemory()和WriteProcessMemory()请求:

为了处理这些消息,我们修改了CtargetProcess类中的Tick()函数,如清单24所示:
   
当客户端请求读取内存时,就需要从DLL进程中复制内存到客户端进程中。从DLL进程的角度看,这个使用WriteProcessMemory()即可解决。反之,亦可处理从客户端向DLL写入内存的请求。在响应每一个请求后,返回的数据必须发送到客户端程序,并覆写之前的缓冲区,但只需修改与被执行的请求类型相关的数据即可。每次请求过后都需要将客户端程序的缓冲区重置回空闲状态,可使用清单25的代码来初始化客户端程序的请求。
   
注意,上面添加了HITB_COMMUNICATION_BUFFER::HITB_CLOSING这一缓冲区类型。这告诉我们目标进程即被关闭。在初始化检测后,本地缓冲区可能变成HITB_COMMUNICATION_BUFFER::HITB_CLOSING。如果我们简单地通过复制来覆盖本地缓冲区(比如m_lpcbBuffer->mType = HITB_COMCOMMUNICATION_BUFFER::HITB_WPM),那么我们就必须承担限入死循环的风险,因为DLL从不会响应我们的请求,这也是使用InterlockedCompareExchangeAcquire()的原因所在。最后,通过设置HITB_COMMUNICATION_BUFFER::HITB_CLOSING以避免资源泄漏,另外我们在DLL的CtargetProcess类中添加了一个析构函数,参见清单26:

当从DLL到客户端的连接关闭后,就不会再对它响应任何请求,因此最后我们发送一条消息HITB_COMMUNICATION_BUFFER::HITB_CLOSING给它。由于类中的析构函数只在其管理线程和主线程完全终止时才会被调用,因此在等待请求时覆写缓冲区状态不存在任何风险。在编码客户端程序时应当意识到,缓冲区可在任一时刻被更改为HITB_COMMUNICATION_BUFFER::HITB_CLOSING。

结论
利用以上代码,客户端可以在任一时刻读取电脑上的任一进程的内存,这只需简单地调用通讯对象中的ReadProcessMemory()函数即可实现,并且可悄悄地运行,而不被当前运行的反外{过}{滤}挂系统发现。虽然这种方法比直接访问进程速度会慢几倍,但可突破它的保护机制。这里使用比较基础的编程知识,完全在ring-3下运行,读者可在此基础上对其进行改进。在DLL与客户端之间的密码在每次启动后都必须是随机变化的,并对DLL使用硬编码。客户端程序实际上是可以修改DLL的,它可在下次对进程进行DLL注入前更改密码,但这也会更改DLL的MD5/检验和。DLL的大小在每次启动中也应该是随机的,这个通过在文件末尾添加随机大小的字节即可实现。注意,在DLL中并未存在字符串,因为字符串常常被反外{过}{滤}挂系统用来检测DLL。DLL也可升级到内核模式。通过重写CProcess类中的方法,即可在ring-0上实现DLL与客户端间的信息交互,并去掉除其中最可能被检测到的方法。几乎所有欺骗游戏保护系统的方法都是通过对游戏进程进行DLL注入,将这种思路扩展出来,我们还可以利用它实现远程控制进程。


狴犴睚眦
发表于 2013-1-14 20:16:30 | 显示全部楼层
可以发网盘,共享给大家看看
我爱熊猫
 楼主| 发表于 2013-1-14 20:17:43 | 显示全部楼层
狴犴睚眦 发表于 2013-1-14 20:16
可以发网盘,共享给大家看看

好的,白天我把文件上传到网盘再发链接上来
狴犴睚眦
发表于 2013-1-14 20:20:32 | 显示全部楼层
我爱熊猫 发表于 2013-1-14 20:17
好的,白天我把文件上传到网盘再发链接上来

感谢你的分享
我爱熊猫
 楼主| 发表于 2013-1-15 21:39:02 | 显示全部楼层
狴犴睚眦 发表于 2013-1-14 20:20
感谢你的分享

今天没有上传,实在不好意思。耽误了,明天没事的情况下。一定补上
我爱熊猫
 楼主| 发表于 2013-1-16 19:44:49 | 显示全部楼层
狴犴睚眦 发表于 2013-1-14 20:20
感谢你的分享

已经上传到百度网盘了:http://pan.baidu.com/share/link?shareid=184699&uk=185945294

不好意思,晚了一天上传。

希望你原谅下
狴犴睚眦
发表于 2013-1-16 22:48:56 | 显示全部楼层
我爱熊猫 发表于 2013-1-16 19:44
已经上传到百度网盘了:http://pan.baidu.com/share/link?shareid=184699&uk=185945294

不好意思,晚了 ...

谢谢分享哦
您需要登录后才可以回帖 登录 | 快速注册

本版积分规则

手机版|杀毒软件|软件论坛| 卡饭论坛

Copyright © KaFan  KaFan.cn All Rights Reserved.

Powered by Discuz! X3.4( 沪ICP备2020031077号-2 ) GMT+8, 2025-2-1 03:00 , Processed in 0.125140 second(s), 16 queries .

卡饭网所发布的一切软件、样本、工具、文章等仅限用于学习和研究,不得将上述内容用于商业或者其他非法用途,否则产生的一切后果自负,本站信息来自网络,版权争议问题与本站无关,您必须在下载后的24小时之内从您的电脑中彻底删除上述信息,如有问题请通过邮件与我们联系。

快速回复 客服 返回顶部 返回列表