//让游戏窗口置顶
::SetWindowPos(gameh,HWND_TOPMOST,0,0,0,0,SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); //窗口置顶函数 }
else //如果没有窗口被找到 {
HWND gameh=::FindWindow(NULL,gameCaption); if (gameh==0) { return;} //没有找到游戏窗口
::SetWindowPos(gameh,HWND_NOTOPMOST,0,0,0,0,SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); //取消置顶 } }
再继续添加棋子数功能void CLlk_wgDlg::OnClearAll() 函数在上面教案里,这个就是秒杀功能.但是还需要读出棋子数,函数是int ReadChessNum(),在上面教案里.然后再调整一下秒杀功能.然后再调整一下置顶功能, //让游戏窗口置顶
if (gametop)//新建的变量方便设置 {//选中则置顶
SetWindowPos(gameh,HWND_TOPMOST,0,0,0,0,SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); } else
{//未选中则不置顶
SetWindowPos(gameh,HWND_TOP,0,0,0,0,SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); }
1.4.4 初级篇小结
a、游戏分析小结 b、编程小结 知识点:
1、GetWindowRect//窗口信息的获取 BOOL GetWindowRect(
HWND hWnd, // handle to window
LPRECT lpRect // 存放返回值的首地址 RECT );
2、SetCursorPos//设置鼠标指针 BOOL SetCursorPos( int X, //X int Y //Y );
3、mouse_event(MOUSEEVENTF_LEFTDOWN,0,0,0,0);//硬件模拟鼠标 4、FindWindow //获取窗口句柄 HWND FindWindow(
LPCTSTR lpClassName, //窗口类名 NULL LPCTSTR lpWindowName //窗口标题 NULL );
5、GetWindowThreadProcessId //获取窗口进程ID DWORD GetWindowThreadProcessId(
HWND hWnd, // handle to window
LPDWORD lpdwProcessId // 指向变量的指针 用来返回进程PID );
6、OpenProcess //打开指定进程 HANDLE OpenProcess(
DWORD dwDesiredAccess, // 访问权限 标记 BOOL bInheritHandle, // false;
DWORD dwProcessId // lpdwProcessId 进程ID标识 );
7、ReadProcessMemory //读指定进程 内存数据 BOOL ReadProcessMemory(
HANDLE hProcess, // HANDLE OpenProcess返回值 LPCVOID lpBaseAddress,
// 读取 进程起始地址 基址 LPVOID lpBuffer, // 存放数据的缓冲区 DWORD nSize, // 要读出的字节数 LPDWORD lpNumberOfBytesRead
// 实际读出字节数 );
8、WriteProcessMemory //写内存,参数同读内存 9、SendMessage //可以软模拟 鼠标 键盘操作 10、SetTimer UINT SetTimer(
HWND hWnd, // 指向窗口的句柄 UINT nIDEvent, // 定时器 标识ID
16
UINT uElapse, // 时间间隔(毫秒) TIMERPROC lpTimerFunc //回调函数 );
VOID CALLBACK TimerProc(
HWND hwnd, // handle of window for timer messages UINT uMsg, // WM_TIMER message UINT idEvent, // timer identifier DWORD dwTime // 当前系统时间 );
11、KillTimer() BOOL KillTimer(
HWND hWnd, // 指向窗口的句柄 UINT uIDEvent // 定时器 标识ID );
12、SetWindowPos //HWND_TOPMOST 窗口置顶 控件讲解:
CButton slider//滑块控件
this->m_ctl_slider.SetRange(50,3000); //设置滑块的 最小值 最大值 this->m_ctl_slider.SetTicFreq(150); //分隔线 宽度 this->m_ctl_slider.SetPos(1000); //滑块 位置 //复选框控件
this->m_ctl_check.SetCheck(true); //选中复选框 基础知识:
a、数据类型:Bit,Byte,Word,Dword,float,double b、用CE查找数据 c、CE工具使用技巧 d、OD调试
老师总结,我也总结.初级班的课程我已经全部看完了, 相对于DELPHI班来讲记录的也比较仔细,相信我的记录能够帮助其它同学和网友,其实记录主要就是记一下课程的内容,方便以后回忆学习.总体来讲,VC班的初级教程内容比较多,相对于DELPHI班初级教程来讲,游戏也复杂了些,编写代码就更加复杂,就算是有DELPHI基础,想学起VC代码来我也感觉到非常吃力.不过我也觉着这个学习的顺序是对的,如果一开始就学VC,那恐怕我就没有信心了.想起自己在D班里的感想,弄外挂,编程会则可成. 2.1.1、CALL的概念(远程调用CALL)
a、写个调用示例(假想游戏客户端)
b、用OD找CALL,初探(用OD找出我们自己写的CALL) c、代码注入器,远程CALL调用 1、先写一个假想的游戏客户端. //VC++
SetWindowText(h,\窗口标题\ char s[33];
itoa(BoolValue,s,10);//整型转字串 m_edt1.SetWindowText(s); //找加血CALL
1//CE找的 血值 基址:416680 2 OD硬件 断点 hw 416680 3 //代码注入器 call 402360 //加血
call 4023d0 //减血 40108c call 40108c //减血 //
HANDLE CreateRemoteThread(
HANDLE hProcess, // OpenProcess
LPSECURITY_ATTRIBUTES lpThreadAttributes, // 安全结构指针 NULL DWORD dwStackSize, // 0
LPTHREAD_START_ROUTINE lpStartAddress, // 指向我们的CALL 地址 LPVOID lpParameter, // 传递的参数指针 NULL DWORD dwCreationFlags, // 0
LPDWORD lpThreadId // 返回一个 线程ID标识 );
首先讲一下CALL的基本知识,在VC里新建一个窗口,并增加编辑框,关联一个控件类型的变量m_edt1 int BoolValue=3000;//新建一个血值,假设为3000 HWND edit_hwnd; //设置全局变量 然后添加如下代码
void addBlood()//加血代码 {
char s[33]; //建立缓冲变量 BoolValue+=22;//每次加22血
itoa(BoolValue,s,10); //整型转换为10进制字符串
17
SetWindowText(edit_hwnd,s); //设置编辑框数值 }
void decBlood()//减血代码 {
char s[33]; BoolValue-=22; itoa(BoolValue,s,10);
SetWindowText(edit_hwnd,s); }
[attachment=548]
那么我们就要用CE来查找这个加血和减血按钮的CALL,打开CE,搜索一下当前的血值,再改变一下血值再搜索,如果找到的地址是绿色的,那么就证明其值是全局变量,也就是一级基址/主基址,改变一下血值,就找到其地址了,那么我们在OD里下一个内存断点 Hw 416680 , 被断下之后在00402385位置,向上看,我们来到函数的头部,也就是上面全是int 3的第一句那么我们找到的加血的CALL就是402360. [attachment=549]
打开代码注入器,输入 call 402360 发现确实加血了 同理,再查找出减血的 CALL 4023d0
当然除此之外,在此函数头部的上一句是一个JMP语句,而这句也可以调用
最后又讲了一下CreateRemoteThread创建线程函数的用法,参数在上面教案里有详解. 2.1.2、远程CALL调用代码实现
a、CreateRemoteThread API函数
b、无参数的远程CALL调用(代码实现) 1、先写一个假想的游戏客户端. call 402360 //加血
call 4023d0 //减血 40108c call 40108c //减血
HANDLE CreateRemoteThread(
HANDLE hProcess, // OpenProcess
LPSECURITY_ATTRIBUTES lpThreadAttributes, // 安全结构指针 NULL DWORD dwStackSize, // 0
LPTHREAD_START_ROUTINE lpStartAddress, // 指向我们的CALL 地址 LPVOID lpParameter, // 传递的参数指针 NULL (stdcall) DWORD dwCreationFlags, // 0
LPDWORD lpThreadId // 返回一个 线程ID标识 int lptid;=(LPDWORD) &lptid; );
HWND h=FindWindow(NULL,\DWORD id;LPDWORD pid=&id;
GetWindowThreadProcessId(h,Pid);
HANDLE hp=OpenProcess(PROCESS_ALL_ACCESS,false,Pid); DWORD tid;
CreateRemoteThread(hp,NULL,0, (LPTHREAD_START_ROUTINE)(0x402360 ) ,NULL,0,&tid);
上节课我们是用的代码注入器来调用的CALL,那么这次我们自己编写代码来调用CALL,这需要几个函数的联合运用,参数的详解在上面的教案中. 1, FindWindow
2, GetWindowThreadProcessId 3, OpenProcess
4, CreateRemoteThread 新建一个窗口 [attachment=551] 具体的调用代码如下:
void RemoteCall(int CallAddr)//整理出来的CALL调用函数 {
HWND h; //窗口变量
h=::FindWindow(NULL,\查找窗口句柄需要全局标识符 DWORD id; //进程ID
LPDWORD Pid=&id; //定义临时变量方便调用
::GetWindowThreadProcessId(h,Pid); //取得指定窗口的进程ID 存放到变量id里边 HANDLE hp=OpenProcess(PROCESS_ALL_ACCESS,false,id);//获取访问进程权限存放至hp DWORD tid; //定义临时变量方便调用 //在进程里调用 CallAddr
CreateRemoteThread(hp,NULL,0,(LPTHREAD_START_ROUTINE)CallAddr ,NULL,0,&tid); }
void CCALLDlg::OnRemoteAdd() //加血调用 {
RemoteCall(0x402360); //加血CALL 16进制写法 }
void CCALLDlg::OnBTNDecBlood() //减血调用 {
RemoteCall(0x40108c );//减血CALL 16进制写法
18
}
备注:LPTHREAD_START_ROUTINE 指向的函数是回调函数,并且必须由宿主应用程序的编写器实现。
因为我有DELPHI基础,上面的4个函数我在DELPHI当中可是经常用,所以很熟悉,我只需要了解VC中的写法即可 2.1.3、调试工具OD简介(人物角色)血值,魔力值; a、CE找出当前血值偏移 b、OD 分析出魔力值 c、导出游戏关键代码 血值 基址 0x45657F0 +4: 魔力值 +8: 持久力 +C:血值上限 +10:魔力值上限
+14:持久力上限 1000 +1C:善恶度 +18:当前经验值
+20:升级到下一级所需经验值 +24: 灵兽 持有数量 上限2 +2C:历练值 +30:心 +34:力 +38:体 +3C:身
终于要正式搞游戏了,下载RXJH吧,首先从血值入手,用CE查找当前的血值631,打两下怪,再CE查找当前的血值564,再吃点药,又变成631了,继续用CE一搜就找到了,基址都是绿色的,当前搜索到的值不是绿色,那么这就是一个变量的值,还好我们找到了一个绿色地址045657F0 [attachment=553]
再用OD附加一下,确定此位置,显示一下内存区域 [attachment=554]
通过猜测,我们找到了相关的血值上限和蓝值等,详细在上面的教案中.但是我们需要找到一级基址,我们在血值处下一个断点,被断在0058957C处,但是没再继续找,下节课再见.
我发现RXJH的各类偏移真的很好找,至少比50好找,如果各位朋友想练习查找基址的基本功,那就拿个别的游戏再练练手. 2.1.4、游戏基址概念;
a、基址+偏移 概念
b、读写内存函数 参数简介
c、编程实现读出(血值,魔力值) 血值 基址 45657F0 :一级 全局结构变量 首地址 +4: 魔力值 //45657F4 二级基址 +8: 持久力 +C:血值上限 +10:魔力值上限
+14:持久力上限 1000 +1C:善恶度 +18:当前经验值
+20:升级到下一级所需经验值 +24: 灵兽 持有数量 上限2 +2C:历练值 +30:心 +34:力 +38:体 +3C:身
//API函数 远程读取数据的方法
上一节的基址还没找完,本节课继续,先建一个小程序用来读取血值和魔值,我们通过API函数来读取 其实就是四个函数的联合运用,前面都有讲过 1, FindWindow
2, GetWindowThreadProcessId 3, OpenProcess
4, ReadProcessMemory
为了方便调用, 再新建一个头文件GameProc.h(他总愿意建这个名字的) 将游戏的标题定义好#define GameCaption \在窗口中关联变量 m_role_bloodvalue m_role_MagicValue 然后编写具体代码如下:
void CReadRoleBloodDlg::OnBtnReadblood() //读取血值函数 {
// TODO: Add your control notification handler code here HWND gameh=::FindWindow(NULL,GameCaption); //获取窗口句柄 DWORD processid; //建立ID变量
::GetWindowThreadProcessId(gameh,&processid); //获取窗口进程ID
HANDLE processH=::OpenProcess(PROCESS_ALL_ACCESS,false,processid); //打开指定进程
19
//读指定进程内存数据
DWORD byread; //读取字节数变量 LPCVOID pbase=(LPCVOID)0x45657F0; //读取血值的基址
LPVOID nbuffer=(LPVOID)&m_role_bloodvalue; //存放读出数据的缓冲区 ::ReadProcessMemory(processH,pbase,nbuffer,4,&byread);//读血值 pbase=(LPCVOID)(0x45657F0+4); //读取的基址
nbuffer=(LPVOID)&m_role_MagicValue; //存放读出数据的缓冲区 UpdateData(false); }
void CReadRoleBloodDlg::OnBtnMagicValue()//读取魔值函数 {
// TODO: Add your control notification handler code here //获取窗口句柄
HWND gameh=::FindWindow(NULL,GameCaption); //获取窗口进程ID DWORD processid;
::GetWindowThreadProcessId(gameh,&processid); //打开指定进程
HANDLE processH=::OpenProcess(PROCESS_ALL_ACCESS,false,processid); //读指定进程内存数据 DWORD byread;
LPCVOID pbase=(LPCVOID)0x45657F0; //读取血值的基址
LPVOID nbuffer=(LPVOID)&m_role_bloodvalue; //存放读出数据的缓冲区 pbase=(LPCVOID)(0x45657F0+4); //读取的基址
nbuffer=(LPVOID)&m_role_MagicValue; //存放读出数据的缓冲区 ::ReadProcessMemory(processH,pbase,nbuffer,4,&byread); UpdateData(false); }
[attachment=555]
试验一下血和魔已经读取正确了,那么我们如何才能够时时的读取呢,还可以用以前用过的时间函数. 2.1.5、常用汇编指令详解(VC++内联汇编) a、Mov指令的几种形式
b、汇编指与高级语言的转换 c、push指令
eax,ebx,ecx,edx,edi,esi;//32位=4字节 esp,ebp; eax //
ax,bx,cx,dx,di,si,sp,bp低16位 al,bl,cl,dl 低8位
void mycall(int x,int y) {
_asm {
// mov eax,33 // eax=a // mov ebx,11 // ebx=b // mov b,eax // b=eax // mov a,ebx // a=ebx mov edi,edi mov edi,edi mov eax,x
mov a,eax // a=b mov eax,y mov b,eax } }
本节课讲解汇编基础知识,也就是VC++内联汇编,就是在VC代码中直接调用汇编代码,新建一个MFC基础对话框,用于测试汇编代码,并对几个寄存器进行了介绍,先介绍一下MOV指令,添加汇编代码在按钮事件中: _asm {
Mov edi,edi Mov edi,edi Mov a,11 Mov b,33 }
测试成功之后,再来到OD里反汇编看一下 [attachment=556]
20