詳解遊戲輔助編程
【目錄】
1-什麽是Windows API
2-Windows進程
3-Windows 的內存的運行原理
4-windows 中句柄的概念
5-Windows的變量類型
6-輔助實現的原理
7-編程實現遊戲輔助
8-怎樣查找內存地址
9-總結
准備軟件:VC,CheatEngineer5.5
學習這部分內容,你必須要掌握C語言的基礎知識,非常基礎的語法就行了。這篇文章的內容適合剛開始接觸編程的人,高手請飄過。
【1】什麽是windows API
Windows API 中文翻譯過來就是windows應用程序接口(Application Programming Interface)
我們知道,我們在使用C/C++的時候,會包含很多的頭文件,例如最常用的stdio.h,裏面有很多函數的聲明,例如使用scanf函數能輸入數據,使用printf函數能打印文本到屏幕上顯示出來。又例如math.h裏面有很多數學計算函數,如sqrt求平方根的函數。我們只要包含這些頭文件,就能調用這些函數。只要給函數傳遞相應的參數,就能實現我們想要的效果,這些函數就是C語言給我們提供的“接口”。
所谓windows應用程序接口,其实就是windows提供给程序员的一组函数。
Windows api 包含幾千個可調用的函數,這些函數能讓我們程序員操作系統的方方面面。
我們就可以調用者幾千個函數實現各種功能。如調用ExitWindowsEx來關閉計算機,CopyFile來複制文件,MessageBox來彈出系統提示框,用BitBlt來繪制圖像等等。而調用這幾千函數的條件,就是在代碼的開頭加上#include <windows.h>就行了,因爲絕大部分的函數聲明都在windows.h裏面,我們只要包含了這個頭文件,就能夠使用這些函數。
所以可以這麽理解
Windows API =windows提供的幾千個函數
【windows進程】
關于進程,我想大家都應該熟悉這個名詞。在應用程序無響應無法關閉的時候,我們常常使用CTRL + ALT +DELETE來叫出任務管理器
我們可以看到一個進程列表,其中列出了所有正在運行的進程
進程的概念:進程就是一個正在執行的程序。
假如我們要運行一個程序,首先找到文件(如qq.exe)或者快捷方式,雙擊打開。操作系統就會把這個程序的文件從硬盤上加載到內存中,等加載完畢後,CPU開始執行包含在程序中的代碼,然後系統進程列表多了一個qq.exe,這就是進程——正在運行的應用程序。等到這個程序qq.exe運行結束了,系統就會釋放這個程序在內存中占用的空間,並且從進程列表裏移除這個程序。
進程的特點:每個進程都是相互獨立的,互不幹擾的。通常情況下,一個進程只能操作自己的代碼和數據,無法對其他進程造成影響。這也保證了windows系統運行的穩定性。
進程=正在內存中運行的程序
【windows內存的運行原理】
這個是外挂編程的最重要部分,我們分幾個方面討論:1 什麽是內存?2 進程的邊界--虛擬內存空間 3 打開進程的邊界
什麽是內存
內存是计算机中重要的部件之一,它是与CPU进行沟通的桥梁,其存取速度比硬盘快很多倍。內存对应是电脑的內存条,能存储数据,但是与硬盘不同,在內存中的数据一断电就会消失,并且存取數據的速度是硬盘的几十倍,所以将程序加载到內存中运行将大大提高程序的运行速度。现在一般的电脑內存1GB,2GB,4GB或者更高的8GB。
計算機中所有程序的運行都是在內存中进行的,因此內存的性能对计算机的影响非常大,并且CPU只能执行和处理在內存中代码和资源。
程序的存在形式實際上是這樣的:我們的程序文件,首先是以二進制存在于硬盤上的,有的遊戲特別大,幾個GB,當我們打開資源管理器時,查看的,其實就是硬盤上的文件。當我們運行程序時,程序會適當的從硬盤加載自己需要的數據,然後開始運行。
由于內存比较少,当程序不需要这些数据的时候,就会从內存中释放一些不需要的资源,以保证內存的充足。
总的看来当执行一个程序的时候,系统会将exe文件中的代码和资源从硬盘加载到內存中,加载完成后,CPU开始执行程序入口点的代码,一个程序开始运行。
程序结束时,将释放所有这个进程占用的资源,以避免內存的浪费。
概括来说:內存,其实存储运行中的程序代码和数据的地方(CPU只能处理內存中的代码和数据),当进程结束时,该进程所有占用的內存空间将被系统释放。
进程的边界--虚拟內存空间、
我们设想一下,假如一个系统中有很多的程序在运行,我们只有一个內存空间,这样的话,一个程序在读写数据的过程中,由于程序自己本身的缺陷,错误的读写了其他程序的数据,这样就很容易影响其他程序,造成其他程序的崩溃。这当然是我们不愿意见到的。
为了解决程序之间使用內存互相干扰的问题,于是便有了虚拟內存。
Windows的虚拟內存机制为每个进程分配了4GB的虚拟內存空间。这里我们不免产生疑问,我们的实际物理內存空间只有1GB或者2GB的,等等,怎么能给每个进程分配4GB內存空间呢?
实际上,这4GB空间最下面的64KB是空指針赋值区域,系统保留。內存最上面的2G被系统占用。我们能够访问和使用的,也只有中间这一部分內存而已。这部分內存将近2GB,我想对所有程序来说,存储代码和数据都是够用的。
但是每个进程都有这么大內存可以用,这些內存空间从何而来呢。Windows在执行一个程序的时候,为了节约內存空间,将程序內存中暂时用不到的数据,以二进制存储到硬盘上(这些二进制文件其实就是页面文件)。等到需要的时候,再从硬盘上加载到內存中,这样就以牺牲少量CPU运行时间为代价,将硬盘当做內存使用。使得多程序同时执行成为了可能。这4GB中的数据,有的是存在于硬盘上的,有的是存在于內存中,所以说,每个进程的4GB內存空间,是“虚拟”內存。
虚拟內存的地址一般用十六进制表示,以字节为单位,地址范围是0x00000000~0xFFFFFFFF(0x表示十六进制)。
由于这样的机制,我们不难发现,进程之间就不能相互影响,保证了各进程的稳定性。例如有两个进程A和B,进程A可以在它自己內存空间0x12345678地址存储数据,进程B也可以在0x12345678地址处存储数据。当进程A访问0x12345678处的內存时,访问的是进程A的內存空间;当进程B访问0x12345678处的內存时,访问的是进程B的內存空间。进程A无法访问位于进程B的內存空间中的数据,反之亦然。
这就是进程的边界--每个进程分配了4GB的虚拟內存空间,它们在自己的內存空间内运行,不会相互干扰,保护了系统和各进程的稳定性。
爲什麽要以16進制表示地址
編程中,我們一般用十進制表示一個數,因爲C/C++是高級語言。
例如x=78;
不過,由于數據在計算機中的表示,最終以二進制的形式存在,所以有時候使用二進制,可以更直觀地解決 問題。但二進制數太長了。比如int 類型占用4個字節,32位。比如100,用int類型的二進制數表達將是:
0000 0000 0000 0000 0110 0100
面對這麽長的數進行思考或操作,沒有人會喜歡。因此,C,C++ 沒有提供在代碼直接寫二進制數的方法。用16進制可以解決這個問題。因爲,進制越大,數的表達長度也就越短。
例如同樣一個地址。用十進制和十六效果對比下:
十进制 十六进制
1258965451 4B0A49CB
5600 000015E0
85693 00014EBD
8965231 0088CC6F
这样可以看到用十六进制表现更直观一些。并且,在很多软件中,数值的表示都是用十六进制的,例如WinHex,CE,OllyDBG,几乎所有与內存有关的数据都是用十六进制表示。用十六进制最大的优点就是缩小了表达长度。
打開進程的邊界
當然所有的事情不是絕對的,我們有很多時候也需要訪問其他進程中的數據。例如,殺毒軟件要監視其他進程的行爲,防止其他進程做有害系統的事,殺毒軟件往往就會在其他進程裏注入自己的代碼,以此來監視其他進程的行爲。這就是訪問其他進程數據的最好例子。
当然,想要访问其他进程的內存空间,需要强大的windows api。我们知道,windows api是系统提供给程序员的一组强大的函数,几乎能实现任何关于系统的任何操作。
我們要打開進程的邊界,修改其他進程的數據,當然需要調用相應的函數。
我們的目的很明確,就是要修改其他進程(如遊戲進程)的數據。
下面的一些函数是访问其他进程数据和外挂编程必备的函数,在后面介绍外挂编程的时候,我会詳細的介绍这几个函数,现在先熟悉一下。
1. 查找窗口函數
FindWindow
2. 通過窗口獲取進程ID函數
GetWindowThreadProcessId
3. 打開進程函數
OpenProcess
4. 寫其他進程數據的函數
WriteProcessMemory
5. 讀其他進程數據的函數
ReadProcessMemory
6. 關閉句柄的函數
CloseHandle
这六个函数,就是写一个简单外挂的所有函数。在后面我会詳細说明打开进程边界的步骤,以及如何使用这些函数。
【windows 中句柄的概念】
句柄,是整個windows編程的基礎。一個句柄是指使用的一個唯一的整數值,即一個四字節長的數值,來標志系統中的不同對象。
打個比喻,一個學校有很多很多的學生,爲了識別這些學生,我們會給每個學生分配一個學號。當我們要找一個學生的時候,我們只需要知道這個學生的學號,然後查閱相關信息就能找到這個學生。
同样,在windows系统中有很多很多的对象,如一个窗体,一个进程,一个图标,一张图片,甚至一块內存空间等。Windows 给它们都分配了不同的一个unsigned int型数(即无符号整型数)来标识和区分它们。这个标识号,就叫做句柄。
我們可以通過系統分配的這個標識號,也就是句柄,來訪問這些對象。
例如,我們可以通過一個窗口的句柄,找到這個窗口,然後可以修改這個窗口的大小,標題名字等等。
我們可以通過一個進程的句柄,來結束這個進程。
我們可以通過一張圖片的句柄,來將這張圖片畫在屏幕上。
等等等等。
句柄,就是系統中每個對象的標識號,我們可以通過這些標識號,來訪問相應的系統對象,如窗體,進程等等。
【windows變量類型】
在windows編程中,我們往往會看到很多變量類型,如HANDLE,HWND,BYTE,DWORD等等。
這些變量類型是什麽呢,和我們熟悉的char,short,int等等的變量類型有什麽區別呢。
而經常要用到的 句柄 HANDLE類型,實質上是無類型指針void,HANDLE定義爲:
typedef PVOID HANDLE;
HANDLE實際上就是一個PVOID,那PVOID又是什麽呢?
Typeef void *PVOID;
PVOID就是指向void的指針(void *)。
所以HANDLE = void*
那爲什麽要多此一舉呢,直接用void*代替就行了,爲什麽要用HNADLE呢?其實,這是爲了讓程序員更能讀懂程序。只要看到變量類型就能知道這個變量是用來幹什麽的。
例如:我要用a,b來表示長方形的寬和高,如果都用int型我們要花費一番功夫才能理解這些意思。
但是我們分別定義兩種變量類型Width,Height就能一目了然了。
typedef int Width;
typedef int Height;
Width a; //定義width型變量a,一眼就能看出a變量是來表示寬度的
Height b; /定義wheight型變量b,一眼就能看出b變量是來表示高度的
聲明特定的變量類型其好處就不言而喻了,就是爲了讓程序員更好的理解任何變量的作用,使人一目了然。
下面我們需要知道的變量類型以及這些變量類型的作用;
HANDLE =void * HANDLE型變量是一個對象句柄
HWND =void *HANDLE WINDOW 窗口句柄
DWORD = unsigned int 無符號整型,windows通常用來表示一個對象的序號ID
BYTE =unsigned char 無符號字符型,0255
我們做外挂,只需要了解上面3種windows數據類型就行。
另外我需要声明一点:void *型的变量,一般都是用来表明內存地址的,
如 void * Ptr=0x12345678
这个ptr指針,表示的是0x12345678这个內存地址
指針我现在不想多说了,这部分在后面的编程中会看到
【輔助的的實現原理】
我們現在做的只是遊戲外挂,有一句古話說的好:知己知彼,方能百戰不殆。我們要做遊戲的輔助,就要先知道遊戲是怎樣運行的。
我们知道,一个游戏进程的有很多数据,例如,一个角色的HP,一个角色的经验,他的金钱,等级,以及装备都是通过变量来存储的。我们只要找到这些变量在內存中的地址,然后通过某种方法去修改这些数的数值,就能达到修改游戏的目的。
我们先来说说这些游戏中德数据,怎么判断是什么类型,怎么得到在內存中占的空间大小。
例如,一個人的經驗一般用int型變量(4字節)來存儲,爲什麽呢?
因爲int型變量(4字節)的取值範圍是2147483648~2147483647,而short型變量(2字節)的範圍是,由于short的範圍太小,而很多遊戲的經驗值一般都超過這個範圍,例如在地下城與勇士的遊戲中,我的經驗值是108866674523,一千多萬,所以,在這個遊戲中,一個人物的經驗值是必須是int型的,用short型變量會超出範圍導致程序運行出錯。
我们为什么要知道这些变量占多大內存空间呢?那是因为我们在修改其他进程的数据的时候,我们必须首先要有三个参数:1:在哪个地址 2要修改成多少 3有多大的內存数据要被修改。例如:我们要修改的地址是0x00EFFAE0,要修改成1000000,由于这个变量是int型的,所以,有4字节的內存数据要被修改。这很容易让我们想到游戏程序的源代码里面有这条代码
int exp; //人物經驗
然後&exp就等于0x00EFFAE0
好吧,開始進入正題了。遊戲輔助一般分爲兩大部分:
一就是找出我们想要的內存地址。
二就是写程序去修改这个內存地址的数到一个相应的数值。
我们先不谈第一个部分,因为第一个部分变化性大,因为很多游戏做了不同程度的保护,使我们找內存地址大费周折,例如CS。不过也有游戏没做任何保护,例如,植物大战僵尸。但是,我们写程序修改其他进程內存中的数据,这个方法是不变的。所以我先来说说如何修改其他进程中的內存数据。
第一步:查找遊戲窗口句柄
第二步:通過窗口句柄,獲取目標進程的ID
第三步:通過目標進程的ID,打開目標進程,獲得句柄
第四步:通过目标进程的句柄,修改目标进程的內存数据
第五步:關閉目標進程
我想有必要说一下,第一步,第二步是为第三步服务的,我们要修改某一进程的內存数据,必须获得这个进程在系统的身份证,即进程的句柄。获取一个进程的句柄有很多方法,我在这里说的第一步,第二步,第三步是最常用的获取目标进程句柄的方法。
但是,爲什麽如此曲折的才能獲得目標進程的句柄呢,我想,主要和我們要使用的windows api 有關。
接下來我們就要具體說說這些強大的windows api函數了。我們之前說的所有知識,很多windows運行原理,都是爲了理解下一節這些函數的調用。
並且下一節用到的函數較多,我們只有經常使用,我們才能掌握它們。
[編程實現輔助]
上一节我们说了:要修改一个进程的內存数据必须先获得这个进程的句柄,就像我们要找一个人一样,我们可以通过这个人的身份证,知道这个人住在哪里,才能找到这个人。在windows系统里面也一样,我们只有知道这个进程的身份证--句柄,系统才能找到这个进程,并相应的按照我们的需求修改数据。
在開始說輔助編程之前,我先要說說一個很有用的函數,這個函數就是MessageBox,先來看看這個函數有什麽效果。
是不是有種熟悉的感覺,沒錯這就是我經常看見的windows提示,現在我們程序員可以自由操作windows提示。現在來具體說說MessageBox函數
首先來看看函數原型
int WINAPI MessageBox(
HWND hWnd,//消息窗口父窗口句柄
LPCTSTR lpText,//顯示的消息內容
LPCTSTR lpCaption, //消息框的標題
UINT uType);//消息框風格
第一個參數:消息框的父窗口句柄,爲了方便,我們可以設爲NULL,不影響輔助的使用。
第二個參數:顯示的消息內容,上面圖片示例中遊戲已經運行,遊戲沒有運行都屬于消息內容
第三個參數:消息窗口標題,上面圖片示例中都是“提示”。
第四個參數:消息框的風格,現在我只介紹三個常量
MB_OK:表示有確定按鈕
MB_ICONINFORMATON:表示有信息圖標,上左圖
MB_ICONERROR:表示有錯誤圖標上右圖
用 ” | ”符號同時使用多種風格,例如使用MB_OK|MB_ICONINFORMATION,確定按鈕和信息圖標,上左圖所示。MB_OK|MB_ICONERROR,確定按鈕和錯誤圖標,上右圖所示。
下面,进入正题我们来说说修改其他进程內存数据的第一,二,三步——获取目标进程的句柄,并且学习相应的函数。
第一步:獲取目標遊戲窗口的句柄
在windows中,一個窗口的句柄數據類型是HWND,就是handle window的簡寫,我們可以這樣,前面我們已經說過windows變量類型的概念,HWND其實就是void*類型的,這裏我就不多說了。重點我們知道怎麽用。
Window API裏面有這麽一個函數,函數原型如下
HWND FindWindow(
LPCSTR lpClassName,
LPCSTR lpWindowName);
我們可以知道,返回值HWND類型的變量是存儲一個窗口的句柄的。那麽LPCSTR是什麽變量類型呢?
LPCSTR=char *,这个变量类型其实就是char *类型,是一个字符串的指針,以后我们只要看到LPCSTR类型的变量,我们就可以知道这个变量是存储一个字符床地址的指針的。这就是声明很多变量类型的好处,看到这个变量是什么类型的,就知道这个变量是用来干什么的。
它有两个参数,两个参数都是字符串的指針
第一个参数lpClassName,这是要查找窗口的类名,关于窗口的类名,这里没有说明,主要是因为我们可以将此参数设为NULL,也基本不影响我们查找游戏窗口的句柄。有兴趣的可以看看《windws核心编程第5版》,上面说的很詳細。
第二個參數lpWindowName,這個就是主要參數了,目標窗口的標題,例如,植物大戰僵屍的窗口標題就是“植物大戰僵屍中文版”,紅色警戒窗口的標題就是“Red Alert 2”.但是有很多遊戲是全屏幕顯示的,不是窗體形式的,沒有標題,那我們怎麽知道它的標題呢?其實有一種很簡單的方法,遊戲全屏運行後,就是按開始菜單鍵將遊戲最小化。然後將鼠標移動到任務欄的遊戲圖標上,系統就會提示該窗體的標題。
知道了這兩個參數,我們可以這麽寫代碼調用這個函數。
HWND gameWindow=FindWindow(NULL,”植物大戰僵屍中文版”);
當這行代碼執行完畢後我們gameWindow這個變量就保存遊戲窗口的句柄。但是我們需要注意,當目標進程沒有運行,也就是不存在窗體標題爲”植物大戰僵屍中文版”的窗體時,遊戲系統找不到這個窗口,FindWindow調用失敗,返回值爲0,即gameWindow爲0。所以這個函數,可以判斷遊戲有沒有運行。加上下面代碼就有這個效果
If(gameWindow==NULL)
//提示遊戲沒有運行
Else
//提示遊戲已運行
第二步:通過窗口句柄,獲得進程ID
Windows api提供了這樣一個函數,函數定義如下
DWORD GetWindowThreadProcessId(
HWND hWnd,
LPDWORD lpdwProcessId
);
我們可以看到,這個函數只有兩個參數,第一個參數,就是目標窗口的句柄(上例中的gameWindow),我們只要填上我們獲取到的窗口句柄就OK
第二个参数LPDWORD,LP代表指針,DWORD代表unsigned int,所以这个参数就代表unsigned int *,是一个无符号整数型的指針。那么这个参数是什么呢?
每個進程不僅有自己的句柄,還有自己的序號,在系統中叫ID,這個ID是DWORD型整數,在windows中就叫ProcessID
上面這張圖就是我用tasklist命令列舉出系統中正在運行的進程,上面的PID(ProcessID)就是該進程的標識號,可以看到,wininit.exe進程的ID是516 ,csrss.exe進程的ID是528,等等。我們可以通過目標窗口的句柄,知道這個窗體是屬于哪個進程的,然後通過這個函數我們就可以知道,這個窗體所在進程的ID,我們可以這樣使用。
DWORD pid;
GetWindowThreadProcessID(gameWindow,&pid);
第二個參數傳入一個DWORD變量的地址就行了。
當這行代碼運行完畢後。pid就自動填充了目標窗體所在進程的ID。
第三步通過目標進程的ID,打開目標進程,獲得句柄
Windows api提供了這樣一個函數
HANDLE OpenProcess(
DWORD dwDesiredAccess, //渴望得到的訪問權限(標志)
BOOL bInheritHandle, // 是否繼承句柄
DWORD dwProcessId// 進程標示符即,進程ID
);
首先它的返回值是HANDLE類型的,返回值就是目標進程的句柄。再來看看這三個參數。
第一个参数,我们想要得到的访问权限。这里我们使用常量PROCESS_ALL_ACCESS,这个是在windows.h里面定义的常量,注意要全部大写。用了这个常量,就等于我们对系统说:“我要获取对该进程操作的所有权限”,例如读写內存空间等等。等这个函数调用成功后,我们就可以获取对该进程进行任何操作了。
第二個參數,我們不考慮,設爲NULL。
第三個參數,就是我們獲得的進程ID。
我們可以這麽調用:
HANDLE hProcess=OpenPrcess(PROCESS_ALL_ACCESS,NULL,pid);
這樣這行代碼運行後,hProcess被填充了目標進程的句柄。當然,如果這個函數因爲某種原因調用失敗的話hProcess就爲NULL,所以我們可以加上下面的錯誤處理代碼
If(hProcess==NULL)
//提示打開進程失敗
好現在總結一下這個步驟。
我們先通過FindWindow找出目標窗體的句柄
再通過GetWindowThreadProcessId獲得目標進程的ID
最後通過OpenPocess打開進程,獲得句柄
最後需要特別注意一點,在windows xp上這麽寫代碼會運行正常,但是在windows7或者windows8上,程序必須要管理員權限。FindWindow會因爲你沒有管理員權限而調用失敗,最後導致你的程序無法正確的獲得目標進程句柄。
第四步修改进程的內存数据
这是最关键的一步了,修改內存数据。前面我们已经说过,外挂主要分为两个重要步骤,第一个是找內存地址,哪些数据是我们要修改的,如一个人的金钱,经验,属性,等级。我们要找出这些数据在进程中的內存地址。第二步就是写程序去修改这些数据。写程序修改数据的方法是一成不变的,但是找內存地址却有很大的技巧性。这里限于篇幅就不多说了,后面我会简单的介绍一下找代码的原理,然后我会推荐一些专门的文章给大家看的。
Window提供了下面一个函数来修改进程的內存数据,函数定义如下
BOOL WriteProcessMemory(
HANDLE hProcess,//目標進程句柄
LPVOID lpBaseAddress,//目標進程寫入地址
LPVOID lpBuffer,//自己進程中緩沖區地址
DWORD nSize,//緩沖區大小
LPDWORD lpNumberOfBytesWritten//實際數據長度,設爲NULL
);
返回值是BOOL型,返回TRUE調用成功,FALSE調用失敗
第一個參數:目標進程的句柄,我們可以通過前三步獲得目標進程的句柄
第二个参数:目标写入的起始地址,即要将数据写到目标进程哪个位置。这个就是我们找到的內存地址,例如一个人的血量內存地址是0x40000000
第三個參數:寫入的緩沖區地址
第四個參數;寫入的緩沖區大小
這第三個,第四個參數是什麽意思呢?其實WriteProcessMemory工作原理是這樣的
我們將自己進程中的數據,拷貝到其他進程中。自己這個緩沖區的地址就是第三個參數lpBuffer,緩沖區的大小事第四個參數nSize。這個函數將我們進程中lpBuffer地址起始處nSize大小的數據,原封不動的拷貝到目標進程的 lpBaseAddress(第二個參數)處。
假如,我們找到遊戲進程人物金錢的地址是0x40000000,,我們就可以這樣寫代碼
Int Money=10000000;//定義一個變量後自己進程已經分配一塊區域存這個變量
WriteProcessMemory(hProcess,(LPVOID)0x40000000,(LPVOID)&Money,,(DWORD)4,NULL);
第一個參數我們填入目標進程的句柄
第二个参数是目标进程的写入地址,编译器认识“0x”,知道0x40000000是一个16进制的数,我们将这个数强制转换成LPVOID型,也就是void*型,无值型指針
第三個參數我們填入了我們進程Money變量的地址,並且將這個變量地址強制轉換成LPVOID型。
第四个参数由于在c++中int型变量默认占四个字节內存空间,所以我们填入4,并将这个数转换成LPVOID型
第五個參數 填NULL
這就是WriteProcessMemory的用法,功能強大,能寫其他進程的數據。
这样,我们就完成了第四步,写游戏內存数据,我们再次回到游戏会发现,游戏中相应的数据已经变成我们想要的数值了,看下图,植物大战僵尸修改后的结果
第五步關閉進程句柄
我們需要調用CloseHandle函數來關閉我們打開的進程,這個函數的使用方法很簡單。
CloseHandle(hProcess);
它只有一個參數------要關閉的句柄這裏我們填入我們打開的進程句柄即可。
至此我們已經看到了一個完整的程序代碼
我們來回顧一下
我想沒有什麽能比真實的遊戲輔助源代碼更好的了解外挂了,下面,我將給出Vc++6.0環境下,植物大戰僵屍輔助的真實源代碼。我把修改代碼放到了ChangeGame函數裏面,我們只要在main函數或者WinMain中調用這個函數就ok
void ChangeGame()
{
//通過標題獲取窗口
HWND gameWindow=FindWindow(NULL,"植物大戰僵屍中文版");
if(!gamewindow)
MessageBox(NULL,”遊戲未運行”,”錯誤”,MB_OK|MB_ICONERROR);
//獲取進程標示符pid
DWORD pid;
GetWindowThreadProcessId(gameWindow,&pid);
//打開進程
HANDLE hprocess=OpenProcess(PROCESS_ALL_ACCESS,0,pid);
//修改數據
int sun=56789;
WriteProcessMemory(hprocess,(void *)0x1429E2B0,&sun,4,0);
//關閉進程
CloseHandle(hprocess);
}
[怎样查找內存地址]
CE是一个强大的工具,它的搜索速度非常快,一般的游戏程序用它来搜索內存能在短短数秒内搜索几百万甚至几千万个內存地址。为我们编写外挂提供了很大的帮助。
好,废话不多说,边上图边解说,先来学学简单的內存修改,熟悉熟悉CE的操作。
第一步:選擇目標進程
先點擊左上角的第一個按鈕,會出現右圖進程列表。該列表中列舉出了系統中所有正在運行的程序。我們選擇植物大戰僵屍程序(PlantVsZombies.exe)。這樣就完成了選擇目標進程。
第二部:搜索阳光的內存地址
先運行植物大戰僵屍,開始一局遊戲,我們的目的是要修改陽光。于是先看看陽光現在是多少,150,很好(下左圖)。我們在CE值裏面的填入150,點擊首次掃描(下右圖)
看CE左邊的地址欄,我們可以看到,我們用CE搜索到了145個地址(下左圖)。這說明在這個遊戲中,有這麽多個地址的值是150.。但是我們所需要的陽光的變量,只是這麽多其中的一個,所以我們種一顆植物,現在陽光變成了50,然後我們輸入50,點擊再次掃描(下右圖)
再次掃描其實是在上次掃描的結果中,再次搜索這些結果的值。當我們通過遊戲操作修改我們所需要的變量的值的時候,通過再次搜索就很容易把我們想要的變量找出來。之後會出現下面的結果
我們可以看到,結果地址裏面只有一個了,沒錯,這個搜索到的地址0x1429E2B0就是我們要找到陽光的地址。我們雙擊這個地址,這個地址會出現在下面。的編輯列表裏,我們再雙擊編輯列表裏這個地址的值,輸入56789,點確定。這個時候我們回遊戲就會發現已經變成我們想要的值了。
好了,这是找內存的最简单的方法。但是还有一些问题。
第一個問題:
事实上,有很多游戏都做了一些保护措施。例如你的植物大战僵尸里面金币显示的是1120,但是实际上在內存中这个值是112。如果你一开始搜1120,并且按照这个方法搜下去的话会一个地址也搜不到。相同的例子还有很多。例如侠盗飞车人物的血量上面显示的是87,在內存中这个数值是17023+87,流星蝴蝶剑中你看到你的血是230,但是在內存中你的血的值是2300等等。
第二個問題:
如果你重啓植物大戰僵屍後會發現,陽光變量的地址發生了改變,變成0x143780B4,這是爲什麽呢。因爲植物大戰僵屍中,存儲陽光的變量地址是動態分配的,每次分配的地址都是不一樣呢。
这就是编程中的局部变量,这个局部变量所属的函数执行完了,这个局部变量就会从內存中释放。等下次再次执行这个函数时,这个局部变量又会从內存中重新分配空间。
這樣每次運行植物大戰僵屍,由于動態分配,每次陽光變量的的地址都是不一樣的。
所以說,搜索也是有一定複雜性的。第一個問題可以通過模糊搜索,多次搜索解決。第二個問題,可以通過查找變量的偏移地址,計算出准確的陽光地址(這個需要ReadProcessMemory函數)或者通過反彙編。
虽然听起来很复杂,但是这些搜索技巧其实都是很简单的,这里就不一一介绍了。因为要说搜索內存的方法的话,说清楚没有几千字是不行的。这篇文章主要是说外挂编程的原理,关于內存搜索的方法,我找到几篇很好的文章,大家看一看就足够了:
CE找內存偏移地址图文教程:
http://bbs.52miji.com/thread-1224-1-1.html
CE搜索內存技巧:
http://www.v5pc.com/thread-572-1-1.html
关于找內存地址就说到这里。
[總結]
遊戲輔助,其實就是用來修改遊戲,方便玩家玩遊戲的的一個軟件工具。
这篇文章所说的游戏修改都是初级的游戏修改,只涉及到修改內存变量。事实上,像高级的游戏修改,例如消除冷却时间,修改游戏界面,改变游戏运行逻辑等等,这些知识还远远不够。这些都需要最低级而又最强大的汇编语言来解决。通过反汇编逆向分析游戏代码,修改游戏代码而达到令人咋舌效果。
例如LOL盒子,至少运用了DLL远线程注入,函数挡截,反彙編代碼修改等等技术,所以能够给游戏添上一些辅助功能。
學習編程的道路很漫長,有很多的非常有用知識不是課堂上能學到的,等著你自己去發現,去挖掘。等你深入到某一領域的時候,你會發現,這裏還有非常廣闊的天空,你要用一生的時間去探索它,掌握它。