close
做外掛示例"對不起,我想這是不可能的,因為VB是一個如此簡單的編程語音。"如果有人這麼告訴你,別去理他。我可以肯定告訴你,對於製作修改器這種簡單的程式,VB完全可以勝任。
然而,有個問題必須首先考慮:使用VB編寫的修改器需要VB的執行庫才能執行。如果考慮到有些使用者(實際上可能是大部分使用者)沒有執行庫,那麼在最後製作的ZIP壓縮檔中就必須包含這些龐大的檔。在下面的教程裏我將製作一個修改器,如果為它再製作一個安裝程式,那麼整個修改器的體積將超過1MB。其中包括一個很好的安裝和反安裝程式,但大部分還是VB40032.DLL這個檔。
除了以上這點,使用VB製作修改器是非常簡單的。一旦製作了多次後,你會發現能很快地製作出一個修改器。而且使用VB製作的修改器能夠毫無困難地解決遊戲執行時的動態記憶體分配問題,因此即使是最新的遊戲,也可以使用VB製作修改器。在本教程中將不涉及動態記憶體分配,因為雖然簡單,但仍然屬於一個高級的選項。

一些背景知識
不象C語音,VB不會自動包括普通的API函數的聲明,因此我們必須把他們加入我們的項目檔。在幾乎所有的修改器中會使用到6個主要的函數,討論如下:
1. FindWindow(ClassName, WindowTitle) - FindWindow 返回符合指定的類名( ClassName
)和視窗名( WindowTitle )的窗口控制碼。對我們來說,可以讓 ClassName 為空( Null ),只給出遊戲的
WindowTitle。函數應該這樣聲明: Declare Function FindWindow Lib "user32" Alias
"FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String)
As Long
2. GetWindowThreadProcessId(WindowHandle, ProcessId) - 在這裏我們把 FindWindow
函數中得到的控制碼作為參數,來獲得進程識別字(ProcessId )。聲明如下: Declare Function
GetWindowThreadProcessId Lib "user32" (ByVal hwnd As Long, lpdwProcessId
As Long) As Long
3. OpenProcess(DesiredAccess, Inherit, ProcessId) -
這個函數將返回一個我們目標進程的控制碼,可以用來對目標進行讀寫操作。 DesiredAccess
參數的值決定了控制碼對進程的存取權利,對我們來說,要使用 PROCESS_ALL_ACCESS (完全存取許可權)。Inherit 應該總是
False。 ProcessId 是從 GetWindowThreadProcessId 函數中取得的。 Declare Function
OpenProcess Lib "kernel32" (ByVal dwDesiredAccess As Long, ByVal
bInheritHandle As Long, ByVal dwProcessId As Long) As Long
4. CloseHandle(ProcessHandle) - 每一個打開的控制碼必須呼叫這個函數來關閉。 Declare Function
CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
5. WriteProcessMemory(ProcessHandle, Address, value, Sizeofvalue,
BytesWritten) - 把指定的值 value 寫入由 Address 指定的目標位址。 Declare Function
WriteProcessMemory Lib "kernel32" (ByVal hProcess As Long, ByVal
lpBaseAddress As Any, ByVal lpBuffer As Any, ByVal nSize As Long,
lpNumberOfBytesWritten As Long) As Long
6. ReadProcessMemory(ProcessHandle, Address, value, Sizeofvalue,
BytesWritten) - 把 Address 指定的目標位址的值存入 value 位置的變數中。 Declare Function
WriteProcessMemory Lib "kernel32" (ByVal hProcess As Long, ByVal
lpBaseAddress As Any, ByVal lpBuffer As Any, ByVal nSize As Long,
lpNumberOfBytesWritten As Long) As Long
這些函數一環扣一環,缺一不可。更詳細的內容可以參考VB的幫助檔。
一個簡單的修改器範例
如何使上面介紹的這些函數一起工作,製作出我們需要的修改器呢?下面是一個為Windows的計算器程式製作修改器的例子。這個修改器將讀出計算器視窗中顯示的數值,並在點擊一個按鈕後在計算器視窗中顯示我們的名字。
首先我們需要找到計算器顯示視窗中顯示值的位址。本教程不是關於如何進行記憶體搜索,因而我將只作簡單的說明:
• 在計算器視窗中輸入123456
• 使用你喜歡的任何一種記憶體位址搜索程式尋找字串123456
• 使用另一個值重複上面的過程直到只返回1個地址
那是製作我們的修改器需要的唯一一個位址。在我的計算器程式裏這個位址是40B181 hex, 4239745 dec。用你找到的位址替代在下面的代碼裏使用的這個位址。
現在讓我們開始設計修改器的介面:
• 在VB中新建一個專案,加入一個文本框( Textbox )、一個按鈕和一個計時器( timer
)。文本框用來顯示從計算器視窗取得的字串,按鈕用來把我們的名字傳到計算器視窗
• 把表單( form )的標題( Caption )內容設為 Calculator Trainer
• 把文本框改名為 txtDisplay 並清除 Text 內容
• 把計時器改名為 ReadTimer 並把間隔( interval )設為500
• 把按鈕的標題改為 Display Name,按鈕的名字改為 btnPasteName
在這個修改器中我們將使用所有6個函數,ReadProcessMemory、WriteProcessMemory、OpenProcess、GetWindowThreadProcessId、FindWindow
和 CloseHandle。在專案中插入一個新的模組,增加下列代碼。(下面的一些行自動換行了,在你的模組中每一句必須在一行裏,或使用延長符_)
Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal
lpClassName As String, ByVal lpWindowName As String) As Long
Declare Function GetWindowThreadProcessId Lib "user32" (ByVal hwnd As
Long, lpdwProcessId As Long) As Long
Declare Function OpenProcess Lib "kernel32" (ByVal dwDesiredAccess As
Long, ByVal bInheritHandle As Long, ByVal dwProcessId As Long) As Long
Declare Function WriteProcessMemory Lib "kernel32" (ByVal hProcess As
Long, ByVal lpBaseAddress As Any, ByVal lpBuffer As Any, ByVal nSize As
Long, lpNumberOfBytesWritten As Long) As Long
Declare Function ReadProcessMemory Lib "kernel32" (ByVal hProcess As Long,
ByVal lpBaseAddress As Any, ByVal lpBuffer As Any, ByVal nSize As Long,
lpNumberOfBytesWritten As Long) As Long
Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
下面我們要開始寫在計時器視窗中顯示我們名字的代碼了。首先我們使用 FindWindow函數取得目標視窗的控制碼。把這個返回值保存在一個變數中,並檢查它的值是否出錯來確保計時器程式正在執行。(FindWindow函數出錯時返回0)
Dim hwnd As Long
hwnd = FindWindow(vbNullString, "Calculator")
If (hwnd = 0) Then
MsgBox "Window not found!"
Exit Sub
End If
注意在這裏我們傳遞了一個 Null 值給 FindWindow 函數,而不是 ClassName。因此任何名為 Calculator的視窗都符合條件。如果知道計算器程式視窗的 ClassName,你可以傳給它,但這不是必須的。
現在使用得到的視窗控制碼來取得進程識別字( ProcessId )。注意 pid 是作為參數傳遞給函數的,而不是被賦以函數返回值。
Dim pid As Long
GetWindowThreadProcessId hwnd, pid
再利用變數pid得到計算器程式的進程控制碼。再次檢查函數的返回值,如果是非法資料則退出程式。
Dim pHandle As Long
pHandle = OpenProcess(PROCESS_ALL_ACCESS, False, pid)
If (pHandle = 0) Then
MsgBox "Couldn’t get a process handle!"
Exit Sub
End If
在我們的修改器中 WriteProcessMemory 函數是最重要的部分,而且非常容易出錯。不妨讓我們再仔細討論一下它的參數。
WriteProcessMemory (ByVal hProcess As Long, ByVal lpBaseAddress As Any,
ByVal lpBuffer As Any, ByVal nSize As Long, lpNumberOfBytesWritten As)
hProcess 是目標進程的控制碼,從上面的 OpenProcess 函數中取得的。
lpBaseAddress 是在計算器程式的虛擬記憶體中將要被修改的位址,也就是使用記憶體搜索程式找到的那個位址。(在我的程式裏是&H40B181)lpBuffer 是將要寫如上述位址的資料,可以是一個數值、陣列、字串或其他任何資料類型。
nSize 是希望寫入 lpBaseAddress 的位元組數。這個位置應該與你的資料類型相符。如果寫入的是一個長整數( long),這裏應該是4。如果寫入的是一個字串,那麼這裏應該是字串的長度。
lpNumberOfBytesWritten 是函數執行返回後,寫入目標位址的實際位元組數。它能被用來確認函數實際的執行情況。
把我們的資料放到函數中,得到 WriteProcessMemory pHandle, &H40B181, "Beans", 5, 0&。我把0傳遞到lpNumberOfBytesWritten 位置是因為不需要檢查兩次實際寫入的位元組數。
最後通過傳遞進程控制碼給 CloseHandle() 函數來關閉由 OpenProcess 打開的控制碼。
CloseHandle hProcess
現在將所有的代碼輸入我們的編輯器中。雙擊按鈕,顯示它的代碼編輯視窗。代碼應該加到名為 btnPasteName 的 Click事件中。(不必輸入注釋)
Private Sub btnPasteName_Click()
’ 聲明一些需要的變數
Dim hwnd As Long ’ 儲存 FindWindow 函數返回的控制碼
Dim pid As Long ’ 儲存進程識別字( Process Id )
Dim pHandle As Long ’ 儲存進程控制碼
’ 首先取得目標視窗的控制碼
hwnd = FindWindow(vbNullString, "Calculator")
If (hwnd = 0) Then
MsgBox "Window not found!"
Exit Sub
End If
’ 取得進程識別字
GetWindowThreadProcessId hwnd, pid
’ 使用進程識別字取得進程控制碼
pHandle = OpenProcess(PROCESS_ALL_ACCESS, False, pid)
If (pHandle = 0) Then
MsgBox "Couldn’t get a process handle!"
Exit Sub
End If
’ 在記憶體位址中寫入名字
WriteProcessMemory pHandle, &H40B181, "Beans", 5, 0&
’ 關閉進程控制碼
CloseHandle hProcess
End Sub
完畢。現在單擊按鈕將使計算器視窗文本變為我們鍵如的名字。(可能需要最小化計算器程式,再還原,以便程式更新顯示)
下面將給我們的修改器增加一個新功能。我們將檢測計算器程式的視窗顯示資料,並在修改器中顯示。雙擊計時器,顯示它的代碼編輯視窗,然後輸入以下代碼:
Private Sub ReadTimer_Timer()
’ 聲明變數
Dim hwnd As Long ’ 儲存 FindWindow 函數返回的控制碼
Dim pid As Long ’ 儲存進程識別字
Dim pHandle As Long ’ 儲存進程控制碼
Dim str As String * 20 ’ 存儲顯示文本
’ 取得目標視窗的控制碼
hwnd = FindWindow(vbNullString, "Calculator")
If (hwnd = 0) Then Exit Sub
’ 取得進程識別字
GetWindowThreadProcessId hwnd, pid
’ 取得進程控制碼
pHandle = OpenProcess(PROCESS_ALL_ACCESS, False, pid)
If (pHandle = 0) Then Exit Sub
’ 讀取記憶體資料
ReadProcessMemory pHandle, &H40B181, str, 20, 0&
’ 在文本框顯示
txtDisplay = str
’ 關閉進程控制碼
CloseHandle hProcess
End Sub
在這裏出現的新東西是 ReadProcessMemory 函數。從 &H40B181 位址中讀出的資料被存入變數 str 中,然後顯示在名為txtDisplay 的文本框中。

本教程中所講的是非常簡單的東西,主要是想起抛磚引玉的目的。最重要的是不斷學習,不斷實踐,瞭解其他的API並在修改器中使用。練習越多,就會覺得越容易。