本人曾经帮朋友开发了一款DNF外挂程序,其重要功能是使用账户列表中的用户名密码自动登录DNF,后面的事情交给按键精灵完成。
外挂是一条黑色产业链,由于腾讯的保护程序相当强悍,如今要做出一个外挂将非常困难。外挂是怎么赢利的?很多人了解的模式是直接把外挂卖给需要外挂的人。而在本例中,外挂的作用是给那些机器自动代打,获取游戏币,再通过渠道将游戏币换成人民币。运行这些代打机器的作坊有一个专用的名称叫“工作室”。当然此工作室非彼工作室。这些工作室会购买最廉价的电脑,每台电脑可以同时运行几个对系统要求不高的游戏,24小时不间断自动代打,虽然有的游戏会限制在线时间,但是由于账号众多,可以轮番使用。这些账号从哪里来的?有一批人专门会养号,然后再卖给工作室。
在早期,游戏知名度比较低的情况下,游戏厂商希望有更多的人气,所以对工作室的行为是睁一只眼闭一只眼。
言归正传,要实现自动登录DNF,需要以下几个步骤
- 清理系统缓存
- 选择网卡拨号(多个宽带账号,防止被识别为同一个使用者)
- 自动启动DNF游戏程序
- 选择登录账号
- 启动外挂主程序(通过启动rootkit服务,隐藏外挂进程)
这些功能都没有什么太多技术含量,前期使用MFC开发,为了开发方便,后面就转成WPF实现。
守护程序除了上述正常流程外,还有日志记录,通过邮件发送,自动重启等功能。
自动登录
外挂主程序用到了一个大名鼎鼎的大漠插件,而且使用该插件需要付费。付费后会给你一个dll文件。
首先,我们需要从配置文件中读取游戏大区数据,然后我们需要注册大漠插件dm.reg()
。
然后,通过调用dm.find_wnd
函数查找游戏主窗口。找到后需要绑定主窗口,方便使用按键精灵进行自动点击游戏中的区域。
状态自动机
在自动登录的过程中,有些流程会有所变化,比如可能会弹出输入验证码窗口,还有就是每一个步骤需要等待一个不确定的动画等。所以就使用了状态自动机去做。提前设置好每一种状态在遇到特定动作的时候进入某一个下一个状态。
确定当前步骤
如何确定当前画面中是否有输入框呢?
这就要用到dm的图片匹配功能。我们事先将游戏画面中有特征的画面截取一小部分,作为判断依据。例如判断是否是选择大区的状态。则用提前准备的截图去匹配屏幕。
总体流程就是,选择服务器->选择大区->输入用户名密码->登录成功。
验证码自动输入
有时候登录的时候会弹出输入验证码,这验证码很变态,是四个轮流闪烁的图。机器肯定无法识别。这时候就诞生了产业链中的另外一环,就是人工识别验证码。
在某个偏远的地区,公司雇佣一堆平时没事做的大妈,盯着屏幕发送过来的验证码,人工识别然后把验证码打入消息框传回外挂程序中,程序再用按键精灵去打入验证码框中。
这个服务也需要付费的。所以外挂也是需要付出成本的。利润会分配的产业链中的各个环节。
避免被腾讯保护程序查杀
腾讯的保护程序——DProtector会随着游戏的启动而启动,不可以被杀,否则游戏不会正常运行。这个保护程序可以监控进程列表中的可疑程序,并阻止可疑程序的执行。那么如何才能避免被它发现呢?这就需要用到流氓软件360使用的技术了——驱动级编程!
进程保护驱动程序
驱动程序的权限很高,可以访问系统内核,而普通程序则不行。所以很多计算机病毒都是驱动级的程序,查杀比较困难,普通的操作无法清除。
使用visual studio 开发windows驱动的时候,需要安装WindowsKernelModeDriver8.1 工具集。配置类型选择Driver。最后我们会生成一个sys文件。
在内核驱动中,我们可以侦测到对进程的所有的操作,我们可以筛选出需要保护的进程,当遇到诸如挂起进程,终止进程之类的操作的时候,我们可以拦截,这样就无法终止该进程了。
进程保护进阶
还有更为变态的方法,就是修改系统的SSDT表。
ssdt全称为System Services Descriptor Table,中文为系统服务描述符表,ssdt表就是把ring3的Win32 API和ring0的内核API联系起来。SSDT并不仅仅只包含一个庞大的地址索引表,它还包含着一些其它有用的信息,诸如地址索引的基地址、服务函数个数等。
#pragma pack(1) //SSDT表的结构
typedef struct ServiceDescriptorEntry {
unsigned int *ServiceTableBase;
unsigned int *ServiceCounterTableBase; //Used only in checked build
unsigned int NumberOfServices;
unsigned char *ParamTableBase;
}ServiceDescriptorTableEntry_t, *PServiceDescriptorTableEntry_t;
#pragma pack()
__declspec(dllimport) ServiceDescriptorTableEntry_t KeServiceDescriptorTable; //变量名是不能变的,因为是从外部导入
//这个是查询某个函数的地址的一个宏
#define SYSTEMSERVICE(_function) KeServiceDescriptorTable.ServiceTableBase[*(PULONG)((PUCHAR)_function+1)]
我们可以定义一个自己的函数,然后修改地址,让系统调用API的时候调用我们自己的函数。
//修改 ZwOpenProcess 函数地址
OldZwOpenProcess = (ZWOPENPROCESS)(SYSTEMSERVICE(ZwOpenProcess));
(SYSTEMSERVICE(ZwOpenProcess)) = NewZwOpenProcess;
NTSTATUS NewZwOpenProcess(OUT PHANDLE ProcessHandle, IN ACCESS_MASK DesiredAccess, IN POBJECT_ATTRIBUTES ObjectAttributes, IN PCLIENT_ID ClientId OPTIONAL)
{//用来替换的新函数
NTSTATUS nStatus = STATUS_SUCCESS;
if ((long)ClientId->UniqueProcess == pid)
{
DbgPrint("保护进程 PID:%ld\n", pid);
return STATUS_ACCESS_DENIED;
}
//剩下的交给我们的原函数
nStatus = OldZwOpenProcess(ProcessHandle, DesiredAccess, ObjectAttributes, ClientId);
return STATUS_SUCCESS;
}
驱动程序安装程序
驱动程序写好了,我们还需要一个安装程序去安装这个驱动程序。这个也没啥技术含量,就是一个自动化的过程,包括启动驱动程序注册好的系统服务,还有卸载功能。
反外挂
游戏公司的反外挂和外挂之前就如同病毒和杀毒软件一样,是一个道高一尺魔高一丈的过程,上使用的这些手段,早已不是什么新鲜玩意儿了。这场较量会一直持续下去,希望国家在这方面的法律能够不断健全,消灭灰色、黑色产业链,让大家有一个健康的市场环境,把智慧用到正途上。