事情是这样的……闲的无聊玩了一盘卡尔的DOTA,结果发现卡尔的按键真的是十分XX啊……于是就在各个网站找那种类似于11的魔兽改键工具,不是改快捷键,而是直接按照技能的位置来改键的。可是没有找到,而11的改键也只能适用于网络对战。于是就萌生了自己写一个改键的想法。
做法当然是用HOOK了。一开始选用的是HOOK按键然后再POSTMESSAGE鼠标消息。结果发现不知道为什么魔兽不能立即响应鼠标的消息,导致画面上必须出现鼠标移动到面板上在移回来的过程,这十分影响操作的……
后来也想过11是怎么做到的,用SPY++看了,也没有发现他修改了什么消息,后来突然顿悟到他可能是通过修改网络消息做的,于是作罢。最后,我开始在内存上打主意了。具体想法是,读取魔兽的内存判断当前的面板上技能的快捷键是多少,然后再用改键。
废话了这么多,下面才是正题。因为是第一次hack别的程序的内存,用的是CheatEngine,所以,收获了不少的经验,这里记录下来:
要实现上面提出的目标要解决两个问题
1.什么地址表示技能的快捷键。
2.从那里可以到达这个地址。
具体的做法就是:
1.不断改变技能(同时就改变了快捷键、比如卡尔),利用CE中寻找变动的数值、寻找未变动的数值等功能,找到内存中和这个改变最相关的地址。因为现在程序中用了许多指针、结构体等等的方法,所以找到的这个最相关的地址可能内容不是数据而是某个指针。
2.通过查看“什么指令访问、修改了这个地址”功能,向上追溯,可以找到访问到这个地址的基指针以及偏移,再从这个基指针入手,查找存放这个值的地址,用之前的方法,不断向上找,直到找到一个处于常量区不变的基址,因为这个在每次游戏载入的时候是固定的,所以加上之前用的偏移以及一些猜测(有时候偏移不会是固定的,有可能碰到的是二维数组之类的东西,这就靠猜了),得到了一条固定到这条地址的路径。参考CE教程6。这过程中可能会遇到很多不同的地址存着相同的值,这时候就需要用到各种推理(比如访问频率、内存内容、猜测数据结构……)来筛选出真正的基址。基本就是一个DFS的过程。
3.在第一步找到的指针中浏览其指向的内存区域,做出合理的假设、推断(数据类型、相同的不同的内容、字符串、猜结构、找规律……)来猜出到底什么地方才是真正的快捷键数据,这就靠一点运气了。其实在探索的过程中会发现很多有趣有规律的东西。这些东西可以帮助探索。
4.当然要是你会汇编的话最好,这样直接看代码,减少很多不必要的假设(我很水。。。我不太会T_T)。
5.综合2,3,4步骤最后找到一条从模块载入地址加上偏移开始的若干层指针指向快捷键数据。。。就好比我得到的是([XXX]表示XXX地址指向的内容)
[[[[[[[[[[[[[[[Game.dll+ACC3E4]+8]+4]+0]+8]+8]+4]+0]+8]+3C8]+154]+28]+0]+190]+5AC]=第三行第一列的快捷键。
这就是答案了。
过程蛮艰辛的,不过有种做侦探的感觉,每一次小小的成功都带来无比的兴奋啊!!!
我只是新手,这次真的学了不少东西。顺便提一下,CE的教学程序十分棒,值得一试!
这里顺便贴源代码。还是要感谢CSDN上各种大神的代码啊,这里借用了好多,不一一感谢了,仅以开源以致感谢!!!
不过还有个问题,就是我的代码在VS2010中执行的时候是没有问题的。但是生成的程序自己单独执行就会碰到读不到内存的问题??不知道为什么,求大神指教啊,运行环境win7,是不是什么UAC权限问题?至今未解决。。。
// changeWar3KeyCmd.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <Windows.h>
#include <stdio.h>
#include <stdarg.h>
#include <ctype.h>
#include <WinError.h>
#include "TLHelp32.h"
//发送消息以及钩子
HINSTANCE g_Instance; // Handler of current instance
HHOOK g_Hook; // Handler of hook
HWND m_war3hWnd;
//读取内存需要的
DWORD pid=NULL; //魔兽进程ID
bool g_bPrivilegeImproved=false; //标记特权是否已提升
HANDLE hProcess=NULL; //魔兽进程句柄
bool readReady=false; //是否准备好读取
BOOL SetHook();
BOOL UnSetHook();
// The hook function (will be called by other processes)
static LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam);
//
//提高特权,使本进程具备debug特权
bool ImprovePrivilege()
{
HANDLE hTokenHandle=NULL;
OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES,&hTokenHandle);
int bChangePrivSuc=false;
if(hTokenHandle)
{
TOKEN_PRIVILEGES tp;
LUID luid;
int bRes = LookupPrivilegeValue(NULL, TEXT("SeDebugPrivilege"), &luid);
if(bRes) //读取luid成功
{
tp.PrivilegeCount=1;
tp.Privileges[0].Luid = luid;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
bChangePrivSuc = AdjustTokenPrivileges(hTokenHandle,false,&tp,
sizeof(tp), NULL, NULL);
}
}
if(bChangePrivSuc)
g_bPrivilegeImproved = true;
else
g_bPrivilegeImproved=false;
return bChangePrivSuc;
}
bool findGameDll();
//判断是否获得读内存权限
bool getReadReady(){
if(m_war3hWnd==NULL) return false;
if (pid==NULL)
GetWindowThreadProcessId(m_war3hWnd,&pid); //获取进程的ID
findGameDll();
if(!g_bPrivilegeImproved) //如果特权没有提升,则提升权限,否则无法读取魔兽内存
ImprovePrivilege();
if(g_bPrivilegeImproved) //权限提高成功
hProcess = OpenProcess(PROCESS_ALL_ACCESS,false,pid); //打开进程
else
return false;
if (hProcess) return true;
else return false;
}
int gameDllBase=0x6F000000; //game.dll 装入的基地址,应对windows内存随机化保护机制
bool findGameDll(){ //寻找game.dll装入地址
HANDLE hSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPMODULE,pid);
if (hSnap!=INVALID_HANDLE_VALUE ){
MODULEENTRY32 lpme;
lpme.dwSize = sizeof(MODULEENTRY32);
BOOL bRet = ::Module32First(hSnap, &lpme);
while (bRet) {
if (!wcscmp((lpme.szModule),TEXT("Game.dll"))) {
gameDllBase=(int)lpme.modBaseAddr;
// printf("got base: %X !\n",gameDllBase);
break;
}
bRet=::Module32Next(hSnap, &lpme);
}
::CloseHandle(hSnap);
}
return 0;
}
#define HAD_curPanBase (0x00ACC3E4+gameDllBase)
#define HAD_charPanBase (0x00AE8450+gameDllBase)
#define HAD_getBaseThis (readMemery(readMemery(readMemery(readMemery(readMemery(HAD_curPanBase)+0x08)+0x04)+0x00)+0x08)) //当前选中单位面板的数据的内存地址
#define HAD_getSkillPan_LV1 (readMemery(readMemery(readMemery(readMemery(HAD_getBaseThis+0x08)+0x04)+0x00)+0x08))
#define HAD_getSkillPan (readMemery(readMemery(readMemery(HAD_getSkillPan_LV1+0x3C8)+0x154)+0x18)) //当前选中单位面板的数据的内存地址
#define HAD_getShilPan_HotKey(a,b) (readMemery(readMemery(readMemery(HAD_getSkillPan+44-24*(a)+(b)*4)+0x190)+0x5AC)) //a是行,b是列 a=1-3 b=1-4,快捷键内存地址
#define CHAT_ADDRESS_24E (HAD_charPanBase) //1.24E 版本聊天状态的内存地址
//读取内存
long int readMemery(long int address){
if (address==0) return address;
if (!hProcess) { //魔兽没有打开
if (!getReadReady()){
return 0;
}
}
int res=0;
int buf = 0; //存储读取的内存地址
res = ReadProcessMemory(hProcess,(LPVOID)address ,&buf,4,NULL); //读取内存
if(res!=0) {
return buf;
}
else return 0;
}
int getVK_HotKey(int a,int b){
int pointer;
pointer= HAD_getShilPan_HotKey(a,b);
return pointer;
}
DWORD skills[6]={87,69,82,70,84,68}; //W E R F T D
DWORD goods[6]={81,89,71,VK_TAB,VK_SPACE,66}; //Q Y G Tab SPACE B
int vk[]={VK_NUMPAD7,VK_NUMPAD8,VK_NUMPAD4,VK_NUMPAD5,VK_NUMPAD1,VK_NUMPAD2};
int skillsPos[6][2]={
3,1,
3,2,
3,3,
3,4,
2,2,
2,3
};
char writeTemplate[]={
"**This file is config file of change War3KeyCmd**\r\n"
"\r\n"
"[SkillHotKey]\r\n"
"pos[2,2]=T\r\n"
"pos[2,3]=D\r\n"
"pos[3,1]=W\r\n"
"pos[3,2]=E\r\n"
"pos[3,3]=R\r\n"
"pos[3,4]=F\r\n"
"\r\n"
"[PackageHotKey]\r\n"
"pos[Num7]=Q\r\n"
"pos[Num8]=Y\r\n"
"pos[Num4]=G\r\n"
"pos[Num5]=TAB\r\n"
"pos[Num1]=SPACE\r\n"
"pos[Num2]=B\r\n"
"\r\n"
"**File end! ^_^ !**\r\n"
};
DWORD analyzeKey(char *chBuffer,bool &isRight){
if (chBuffer[0]=='T'){
if ((chBuffer[1]=='A')&&(chBuffer[2]=='B')){
isRight=true;
return VK_TAB;
}
else {
isRight=true;
return (DWORD)chBuffer[0];
}
}
else if (chBuffer[0]=='S'){
if ((chBuffer[1]=='P')&&(chBuffer[2]=='A')&&(chBuffer[3]=='C')&&(chBuffer[4]=='E')){
isRight=true;
return VK_SPACE;
}
else {
isRight=true;
return (DWORD)chBuffer[0];
}
}
else if ((chBuffer[0]>='A')&&(chBuffer[0]<='Z')){
isRight=true;
return (DWORD)chBuffer[0];
}
else if (chBuffer[0]==' '){
chBuffer++;
return analyzeKey(chBuffer,isRight);
}
else return 0;
}
bool readKey(){ //从配置文件读取按键配置
HANDLE file=0;
file=::CreateFile(TEXT("war3keyCmd.ini"),GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL); //文件不存在则创建
if ( file == INVALID_HANDLE_VALUE)
{
MessageBox( NULL,TEXT("打开文件失败") ,TEXT("Error"),MB_OK);
CloseHandle(file); // 一定注意在函数退出之前对句柄进行释放。
return false;
}
DWORD fileSize=GetFileSize(file,NULL);
if (fileSize==0){ //才创建的文件
int len=strlen(writeTemplate);
unsigned long b;
WriteFile(file,writeTemplate,len,&b,NULL);
CloseHandle(file);
file=::CreateFile(TEXT("war3keyCmd.ini"),GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL); //文件不存在则创建
}
fileSize=GetFileSize(file,NULL);
char *chBuffer=new char[fileSize];
::ZeroMemory(chBuffer,fileSize);
unsigned long b;
int bRet= ::ReadFile(file,chBuffer,fileSize,&b,NULL);
if (!bRet){
MessageBox( NULL,TEXT("读取文件失败") ,TEXT("Error"),MB_OK);
CloseHandle(file); // 一定注意在函数退出之前对句柄进行释放。
return false;
}else{
bool isRight=false;
char pattern[]="pos[1,3]";
for (int i=0;i<6;i++){
pattern[4]=(char)(skillsPos[i][0])+'0';
pattern[6]=(char)(skillsPos[i][1])+'0';
char *temp=strstr(chBuffer,pattern);
temp+=9;
skills[i]=analyzeKey(temp,isRight);
}
char pattern1[]="pos[Num7]";
for (int i=0;i<6;i++){
pattern1[7]=7-(i/2)*3+i%2+'0';
char *temp=strstr(chBuffer,pattern1);
temp+=10;
goods[i]=analyzeKey(temp,isRight);
}
CloseHandle(file);
return isRight;
}
}
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
if(m_war3hWnd==NULL)
m_war3hWnd = ::FindWindow(TEXT("Warcraft III"),NULL);
HWND ahWnd = GetForegroundWindow(); //当前激活的窗口
if (ahWnd!=m_war3hWnd)
return CallNextHookEx(NULL,nCode,wParam,lParam);
if(nCode<0)
return CallNextHookEx(NULL,nCode,wParam,lParam);
if (readMemery(CHAT_ADDRESS_24E)) return CallNextHookEx(NULL,nCode,wParam,lParam);
KBDLLHOOKSTRUCT* kb = (KBDLLHOOKSTRUCT*)lParam;
if (!kb) return CallNextHookEx(NULL,nCode,wParam,lParam);
DWORD key=kb->vkCode;
if (key==VK_F12) { //表示刷新配置数据
readKey();
return true;
}
//技能
for (int i=0;i<6;i++){
if (key==skills[i])
{
if(wParam==WM_KEYDOWN){
int key=getVK_HotKey(skillsPos[i][0],skillsPos[i][1]); //
if ((key>=0x41)&&(key<=0x5a)){
PostMessage(m_war3hWnd, WM_KEYDOWN,key,0); //postMessage不会重复截获了
PostMessage(m_war3hWnd, WM_KEYUP,key,0);
}
}
return true; //结束这个消息的传递
}
}
//物品栏
for(int i=0;i<6;i++)
{
if (key==goods[i])
{
if(wParam==WM_KEYDOWN){ //产生一次单击事件 用postMessage不会重复截获
PostMessage(m_war3hWnd, WM_KEYDOWN,vk[i],0); //按下
}
if(wParam==WM_KEYUP){
PostMessage(m_war3hWnd, WM_KEYUP,vk[i],0); //松开
}
return true; //结束这个消息的传递
}
}
// Call next hook in chain
return ::CallNextHookEx(g_Hook, nCode, wParam, lParam);
}
HMODULE base;
BOOL WINAPI DllMain(
HMODULE hinstDLL,
DWORD fdwReason,
LPVOID lpvReserved
)
{
if(fdwReason==1)base=hinstDLL;
return 1;
}
BOOL SetHook()
{
if (g_Instance || g_Hook) // Already hooked!
return TRUE;
g_Instance = (HINSTANCE)::GetModuleHandle(NULL);
g_Hook = ::SetWindowsHookEx(WH_KEYBOARD_LL, (HOOKPROC)KeyboardProc,base,0); //这个是对的
if (!g_Hook)
{
printf("Hook set wrong! %ld\n",GetLastError());
Sleep(1000);
return FALSE;
}
return TRUE; // Hook has been created correctly
}
BOOL UnSetHook()
{
if (g_Hook) { // Check if hook handler is valid
::UnhookWindowsHookEx(g_Hook); // Unhook is done here
g_Hook = NULL; // Remove hook handler to avoid to use it again
}
return TRUE; // Hook has been removed
}
int _tmain(int argc, _TCHAR* argv[])
{
readKey();
if (!SetHook())
return -1;
MSG msg;
while(::GetMessage(&msg,NULL,0,0)> 0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
//clean:
UnSetHook();
return 0;
}
转载自原文链接, 如需删除请联系管理员。
原文链接:关于魔兽改键。。。类似11的,转载请注明来源!