Dota全图那些事儿
很早就着手研究全图了,也准备很多相关知识。由于自己编程基础比较差,恶补了不少知识,主要是硬生生的啃了大半本的《windows核心编程》。看这本书的过程中,有很多不懂得,但我适当跳过了(其实这是对的,因为有些知识前后联系了才懂,特别是对我这种菜鸟来说)。看了大半本,收获还是挺大的,对windows这个操作系统有了更深的认识,当然我还会继续把它读完,并且要重读,感觉写的太好了,暂时我是打算这么做的,除非出了意外。这个意外就是,听说windows 8出来后,微软有个叫win RT的东西要用来取代win api,到时若真如此而导致win api成为废物。哎,别谈这个了。到下一步吧。
其实dota全图是这种挂是属于修改内存型的。简单分析一下需求和可行性吧,熟悉魔兽争霸的玩家都知道,在创建游戏的时候有一个高级选项,如果把“可见度”改为“总是可见的”,那么玩的时候,什么都可以看到了,就是开全图了。一般玩rpg的玩家都会开全图,顺便说一下,其实dota全图挂说法不正确,说魔兽争霸全图挂才对。玩dota,我们一般创建游戏时一般不会选择全图,因为会影响游戏的可玩性(dota中的gank很重要)。但是某些游戏玩家比较卑鄙的(想不到其他词形容),就想破坏这个规则,就想“如果其他人不是全图可见,而我是的话,那么我的优势就大了”。于是全图挂产生了。根据前面提到的,我们既然可以通过高级选项来开全图,那么说明游戏进程中一定是有个变量或多个变量来控制这种功能的。很明显,这些变量一定在游戏进程的内存空间中,如果我们通过某些手段改变了这些变量的话,那么自己的电脑就是全图的了。
而怎么知道这些变量的内存地址呢?有个很犀利的工具,叫CE。用它就可以跟踪变量内存地址了。而怎么搜?这也是很讲技巧的,过程也是很枯燥的。不过,在我自己为魔兽争霸1.24b版本之前,已经有人做过了,他们把地址给搜出来共享了,网上可以查到(搜“魔兽1.24B内存地址一览表”)。在魔兽争霸中一个叫“Game.dll”文件,这些变量的地址其实变量在Game.dll的地址。通过CE可以直接在Game.dll修改,稍微懂电脑的人就可以 学会怎么修改,这里不提。修改后就可以作弊了。读者是否会觉得奇怪,这不是没有编程么?对,的确,这是作弊的一种手段,不可否认,CE很强大的。
接下来介绍另一种作弊的手段,用C++编程。主要是利用windows api中的WriteProcessMemory来修改内存数据。在使用该函数修改内存之前,需要做一些事情,主要是两件:一是提高本程序的权限为debug权限,而是把Game.dll的基址找出来。解释一下,为什么要提高本程序的权限为debug权限呢?这个怎么说呢?很明显吧,一个程序的权限越大,干“坏事”的能力就越强大,如果不提高本程序的权限为debug权限,有些事是干不了的,即有些函数使用会失败。那么为什么要找出Game.dll的基址呢?上文已经提到,网上公布的变量的地址其实变量在Game.dll的地址,而不是在进程的地址。所以要把Game.dll的基址找出来。变量在Game.dll的地址+Game.dll的基址=变量在进程中的地址。大概就这门多,接下来是对我的源码分析:
#include <cstdlib>
#include <iostream>
#include <windows.h>
#include <Tlhelp32.h>
using namespace std;
//首先是函数声明,看一下各函数的作用吧
#define PATCH(i,w,l) WriteProcessMemory(hProc,reinterpret_cast<LPVOID>(gameBase+i),w,l,&dSize)
//定义一个宏,为何简化写
void patchW3X();//主要函数
void patchwar24b(HANDLE hProc, DWORD gameBase, DWORD dSize);
//对各内存的修改,其实就是一序列PATCH()的集合
DWORD GetPIDForProcess(char* process);
//得到某进程的进程ID
void EnableDebugPriv();
//提高权限为debug
DWORD GetDLLBase(char* DllName, DWORD tPid);
//得到指定动态链接库的基址
int main(int argc, char *argv[])
{
SetConsoleTitle("1.24");
patchW3X();
system("PAUSE");
return EXIT_SUCCESS;
}
//-------------------------------------------------------------------------------------------------------------
void patchW3X()
{
DWORD PID = 0;
puts("正在检验Warcraft 3是否已启动");
HWND hw = FindWindowA("Warcraft III", NULL);
while(hw == NULL)
{
Sleep(500);
hw = FindWindowA("Warcraft III", NULL);
}
puts("搜索进程id");
LPDWORD lp = NULL;
lp = &PID;
GetWindowThreadProcessId(hw,lp);
//用上面那句话或者下面注释的句子都可以得到war.exe进程id
// GetWindowThreadProcessId
// if(GetPIDForProcess("War3.exe") != 0)
// PID = GetPIDForProcess("War3.exe");
// puts("提高dubug权限");
EnableDebugPriv();
puts("打开进程");
HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, false, PID);
if(hProc)
{
puts("寻找Game.dll基址");
DWORD gameBase = GetDLLBase("Game.dll", PID);
DWORD dSize = 0;
puts("开始修改内存");
patchwar24b(hProc, gameBase, dSize);
}
else
{
puts("打开进程失败");
DWORD er = GetLastError();
printf("错误代号%d",er);
}
}
//-------------------------------------------------------------------------------------------------------------
void patchwar24b(HANDLE hProc, DWORD gameBase, DWORD dSize)
{
//(74D103," C6 , 04 , 3E , 01 , 90 , 46");去除战争迷雾
PATCH(0X74D103,"\xC6\x04\x3E\x01\x90\x46",6);
PATCH(0x3A201D,"\xEB",1);
//(3A201D, " EB");大地图显示单位
//因为该程序不是为了真的作弊,所以没修改其他的内存数据
puts("完成!");
}
//-------------------------------------------------------------------------------------------------------------
//GetWindowThreadProcessId(hw,lp);该函数的作用是lp获得创建hw窗口的进程id,返回值为创建hw窗口的线程id
//由于恰好 窗口Warcraft III是war.exe创建的,所以用GetWindowThreadProcessId代替以下函数
DWORD GetPIDForProcess(char* process)
{
BOOL working=0;
PROCESSENTRY32 lppe= {0};
DWORD targetPid=0;
HANDLE hSnapshot=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS ,0);
if (hSnapshot)
{
lppe.dwSize=sizeof(lppe);
working=Process32First(hSnapshot,&lppe);
while (working)
{
if(strcmp(lppe.szExeFile,process)==0)
{
targetPid=lppe.th32ProcessID;
break;
}
working=Process32Next(hSnapshot,&lppe);
}
}
CloseHandle( hSnapshot );
return targetPid;
}
//-------------------------------------------------------------------------------------------------------------
// enable the privilege necessary to patch the process
void EnableDebugPriv()//待理解
{
puts("提高dubug权限");
HANDLE hToken;
LUID sedebugnameValue;
TOKEN_PRIVILEGES tkp;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
puts("获取进程访问令牌失败!");
DWORD er = GetLastError();
printf("错误代号%d",er);
if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &sedebugnameValue))
{
CloseHandle(hToken);
puts("提高debug权限失败!");
system("PAUSE");
}
er = GetLastError();
printf("错误代号%d",er);
tkp.PrivilegeCount = 1;
tkp.Privileges[0].Luid = sedebugnameValue;
tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if (!AdjustTokenPrivileges(hToken, FALSE, &tkp, sizeof tkp, NULL, NULL))
{
puts("提高debug权限失败!");
CloseHandle( hToken );
}
er = GetLastError();
printf("错误代号%d",er);
}
//-------------------------------------------------------------------------------------------------------------
//Gets the base of our dll
DWORD GetDLLBase(char* DllName, DWORD tPid)
{
HANDLE snapMod;
MODULEENTRY32 me32;
if (tPid == 0) return 0;
snapMod = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, tPid);
me32.dwSize = sizeof(MODULEENTRY32);
if (Module32First(snapMod, &me32)){
do{
if (strcmp(DllName,me32.szModule) == 0){
CloseHandle(snapMod);
return (DWORD) me32.modBaseAddr;
}
}while(Module32Next(snapMod,&me32));
}
CloseHandle(snapMod);
return 0;
}
//打开魔兽后,在运行本程序就可以了
此外,还遇到一些风波。因为我经常用非管理员的身份登录(一般用户都是用管理员登录的,所以不会用到我接下来提到的问题),发现作弊失败。开始我是想既然非管理员不行,就用管理员身份打开程序呗,于是用了ShellExecuteEx,但还是失败。于是我用GetLastError函数来分析,发现OpenProcess调用失败,GetLastError返回值为5,即拒绝访问。继续分析,发现之前,提升权限为debug权限失败了,因为GetLastError返回值为1300,即并非所有被引用的特权都指派给呼叫方。真相是调用AdjustTokenPrivileges失败了,因为本身调用AdjustTokenPrivileges函数也是需要权限的。至于要什么权限呢?"开始"-"设置"-"控制面板"-"管理工具"-"本地安全策略"-"本地策略"-"用户权利指派"-"调试程序",用管理员身份登录后,把该权限指派给需要该权限非管理员就可以了。至于ShellExecuteEx为什么不可以呢?我猜,以管理员身份运行,但是并不意味着它某些权限提高了。暂时只能这么解释了。遗憾的事,不可以用编程的方法代替用管理员派发权限的方法。我想,这可能是不可实现的。毕竟管理员才是具有最高权限的,如果普通程序可以随便提升任意权限,那就不得了了。
当然,目前只是实现单机版的,或者局域网也行,但是平台就过不了了,要过平台,还得继续研究,包括软件逆工程,网络编程等等。如有什么指导,敬请告诉我,呵呵
下载地址: http://vdisk.weibo.com/s/14Izh