Auto Assembler 基礎:了解 Cheat Engine 的 AOB 注入腳本
什麼是腳本項目、[ENABLE] 與 [DISABLE] 如何運作,以及逐行解析 Cheat Engine 的 AOB 注入範本:aobscanmodule、alloc、標籤、符號與萬用字元。
分享的 CE 修改表 (Cheat Table) 裡,大多數實用的項目都不是單純的數值,而是腳本:無限血量、免換彈、免費製作。它們是用 Cheat Engine 的 Auto Assembler 寫成的,這是一種小型腳本格式,結合了 x86 組合語言與用來尋找及修改程式碼的指令。本指南會說明這類腳本的作用,逐步解析 Cheat Engine 為你產生的 AOB 注入範本,並介紹讓腳本穩定可靠的規則。
如果你已經知道如何尋找數值,也了解什麼是指標,會更容易吸收;指標掃描指南涵蓋了這些背景知識。腳本會修改執行中遊戲的程式碼,所以請只在單機和離線遊戲中使用,測試前先存檔,並備份你的存檔檔案。
什麼是腳本項目
腳本項目是位址清單中「數值」欄顯示 <script> 的項目。它不是指向某個數值,而是包含 Auto Assembler 程式碼。勾選該項目的「啟用」方塊會執行腳本的 [ENABLE] 區段;取消勾選則會執行 [DISABLE] 區段。如何使用 CE 修改表 (Cheat Table) 的指南從使用者的角度說明了這部分。
一般的腳本不是只修改一次數值,而是修改遊戲本身寫入該數值的指令,因此腳本啟用期間效果會持續存在。例如,在受到傷害後儲存你新血量的指令,可以被導向到改為儲存最大值的程式碼。
腳本相較於單純的位址有很大的優勢:它們是透過在遊戲程式碼中搜尋位元組特徵來定位目標,所以不需要任何指標,重新啟動遊戲後也能使用。代價是它們依賴程式碼保持不變,因此會與特定的遊戲版本綁定。
[ENABLE] 與 [DISABLE]
每個可切換的腳本都有這兩個區段。[ENABLE] 會找到程式碼、為新程式碼配置記憶體,並寫入一個從遊戲指令跳到新程式碼的跳躍指令。[DISABLE] 則會復原這一切:寫回原始位元組、移除已註冊的名稱,並釋放記憶體。寫得好的 [DISABLE] 會讓遊戲完全恢復原狀。如果它遺失或有錯誤,取消勾選項目後,遊戲會停留在被修改或損壞的狀態,直到你重新啟動為止。
尋找指令並產生範本
- 找到你想控制的數值,並將它加入位址清單。
- 對它按右鍵,選擇找出是什麼寫入這個位址。在遊戲中讓數值產生變化,清單中就會出現一個或多個指令。
- 選取該指令,並在反組譯器(記憶體檢視器)中開啟。
- 在記憶體檢視器中選取該指令後,開啟工具 → Auto Assemble。
- 在 Auto Assemble 視窗中,選擇範本 → AOB Injection。確認位址,並接受或更改符號名稱,預設為
INJECT。 - 編輯腳本,然後使用視窗的檔案選單將它指派到目前的 CE 修改表 (Cheat Table)。它會以新的腳本項目出現在位址清單中。
AOB 注入範本逐行解析
以下是範本所產生結構的範例。其中的處理程序名稱、符號和位元組都是為了說明而虛構的,並不是任何真實遊戲的程式碼。
{ EXAMPLE STRUCTURE ONLY - not code for a real game.
Process: game.exe Symbol: INJECT Bytes: made up }
[ENABLE]
aobscanmodule(INJECT,game.exe,89 83 F0 00 00 00 48 8B 5C 24 ??) // should be unique
alloc(newmem,$1000,INJECT)
label(code)
label(return)
newmem:
mov eax,#999 // your change: store 999 instead
code:
mov [rbx+000000F0],eax // the original instruction
jmp return
INJECT:
jmp newmem
nop
return:
registersymbol(INJECT)
[DISABLE]
INJECT:
db 89 83 F0 00 00 00 // original bytes: mov [rbx+000000F0],eax
unregistersymbol(INJECT)
dealloc(newmem)
aobscanmodule
aobscanmodule(INJECT,game.exe,...) 會在模組 game.exe 的記憶體中搜尋該位元組特徵,找到後會把名稱 INJECT 定義為它起始的位址。將搜尋範圍限制在單一模組內速度更快,也能避免其他地方出現誤判。還有相關的指令:aobscan 會搜尋所有記憶體,aobscanregion 則搜尋兩個位址之間的範圍。如果找不到該特徵,腳本會因錯誤而無法啟用,這通常是遊戲更新後的症狀。
alloc
alloc(newmem,$1000,INJECT) 會為你的程式碼保留 0x1000 位元組的記憶體,並命名為 newmem。第三個參數是要求 Cheat Engine 在 INJECT 附近配置。這在 64 位元遊戲中很重要:5 位元組的短 jmp 只能往前或往後跳約 2 GB,在附近配置能讓跳躍指令夠短,可以放進原始指令的位置。
label
label(code) 和 label(return) 會為稍後才定義的位置宣告名稱。code 標示原始指令重新執行的位置;return 標示被修改指令之後緊接著的那條指令,也就是繼續執行的地方。
newmem 與 code
newmem: 底下的一切都是你的程式碼,後面接著 code: 底下的原始指令。在範例中,mov eax,#999 會把暫存器設為 999(# 表示十進位數字),然後原始的 mov [rbx+000000F0],eax 會把它存入,所以寫入的值永遠是 999。最後 jmp return 會回到遊戲的程式碼中。
INJECT: jmp newmem
在找到的位址上,腳本會用 jmp newmem 覆寫原始指令。這個跳躍指令佔 5 位元組,但這裡的原始指令是 6 位元組,所以用一個 nop 填補剩下的一個位元組;否則處理器會嘗試執行半條指令。範本會幫你計算這一點。return: 緊接著放在後面,因此它標示的是下一條未被改動的指令。
registersymbol 與 unregistersymbol
registersymbol(INJECT) 會讓名稱 INJECT 在 Cheat Engine 中全域可用。這讓稍後單獨執行的 [DISABLE] 區段不必重新掃描就能找到同一個位址,也讓其他項目可以引用它。[DISABLE] 中的 unregistersymbol(INJECT) 則會移除該名稱。
還原原始位元組與 dealloc
在 [DISABLE] 中,INJECT: 後面接著 db 89 83 F0 00 00 00,會把原始位元組寫回跳躍指令上方,完整還原遊戲的指令。接著 dealloc(newmem) 會釋放存放你程式碼的記憶體。順序很重要:先還原跳躍指令,這樣釋放 newmem 時,遊戲就不會再使用它。
萬用字元與唯一特徵碼的重要性
aobscanmodule 中的位元組特徵稱為特徵碼 (signature)。它必須符合兩個條件:
- 必須是唯一的。如果該特徵出現不只一次,腳本會修改第一個符合的位置,那可能是完全不同的函式。這就是腳本讓遊戲當機或改錯東西的原因。範本會加上「should be unique」的註解作為提醒。
- 應該能承受小幅變動。有些位元組在不同遊戲版本之間,甚至每次執行之間都容易改變:指令內的位移量、call 和 jump 的相對位址,以及內嵌的常數。把這些位元組換成
??萬用字元,就能讓特徵在只有這些細節改變時仍然能符合。
這兩個目標是互相牴觸的。位元組太少,特徵會在很多地方符合;萬用字元太多,又會變得太通用。實用的做法是保留運算碼位元組,對容易變動的位移量和位址使用萬用字元,並加入後續指令的幾個位元組作為上下文。然後檢查特徵是否唯一:在主視窗中,將數值類型設為位元組陣列 (Array of byte),貼上特徵,並在記憶體選項設為包含可執行記憶體的情況下掃描。你要的是剛好只有一個結果。AOB 特徵碼產生器可以幫你把複製的位元組轉成含萬用字元的特徵,並找出哪些位元組可能會改變。
測試腳本
- 先存檔,並在第一次測試前備份你的存檔。
- 啟用腳本,檢查效果,然後停用它,確認遊戲恢復正常運作。
- 多切換幾次。如果腳本在第二次啟用時失敗,通常是因為
[DISABLE]區段沒有完整復原[ENABLE]所做的修改。 - 小心共用的指令。寫入你血量的指令可能也會寫入敵人的血量;這種情況下,你的修改會影響所有人,你需要先區分這些情況,才能進行修改。
腳本能正常運作後,請為該項目加上清楚的說明並儲存修改表。當遊戲更新、腳本無法啟用時,第一個要檢查的就是特徵碼。
留言
尚無留言
成為第一個分享想法的人吧。