使用 Cheat Engine 修改 Kingdom Rush 中的金錢、生命、星

語言: CN / TW / HK

最新部落格連結

最近想學習一下 CE,剛好看見遊戲庫裡裝了 Kingdom Rush 就拿它來研究吧。這裡寫的東西,需要一些 Cheat Engine 的基礎,可以看看教程。

這裡主要是看寫的註釋,來理解指令碼。(如果什麼都不想看,可以直接複製貼上 CE 自動彙編(AA)指令碼)

我學習的連結:

你能學會的 Cheat Engine 零基礎入門教程

Cheat Engine 基礎教程 CE Tutorial | Ganlv's Blog

CE 教學例項應用-由淺入深學習遊戲修改 [全十課]

參考連結:

Cheat Engine 官方 WIKI

x86 彙編指令列表

技術理解

我對一些用到的技術的簡單理解:

  • 程式碼注入 :在程式執行時,將自己寫的程式碼,替換掉原有的程式碼
  • AOB :(Array Of Byte)在記憶體中搜索特定的一串資料,以決定注入程式碼的位置
  • 人造指標 :單獨找個地方,記錄變數所在的地址

資料儲存結構

資料在記憶體中的儲存結構:

Cheat Engine 相關彙編知識

此指令碼用到的 CE 彙編指令:

x86 彙編指令列表

命令例子 功能
mov ebx,0000FFFF Move,暫存器直接賦值
mov ebx,eax Move,將右邊直接給左邊
mov ebx,[eax] Move,將右邊所指的值給左邊。[ ]代表括號內的是指標,操作時,操作其指向的記憶體值
cmp ebx,eax Compare,比較兩暫存器值,若相等則 ZF 位 置 1(左減右)
je <label> Jump if Equal, ZF = 1 時跳轉,可跳轉至標記段程式碼
jne <label> Jump if Not Equal, ZF = 0 時跳轉,可跳轉至標記段程式碼
jmp <label> Jump

此指令碼用到的 CE 自動彙編函式:(Auto Assembler)

Cheat Engine 官方 WIKI

函式 引數 作用
alloc alloc(SymbolName, Size, AllocateNearThisAddress OPTIONAL) Allocates a memory block of Size bytes and defines the SymbolName in the script, pointing to the beginning of the allocated memory block.
dealloc dealloc(SymbolName) Deallocates a block of memory allocated with alloc.
label label(LabelName) Enables the word 'LabelName' to be used as a symbol.
aobScanModule aobScanModule(SymbolName, ModuleName, AOBString) Scans the memory used by the module ModuleName for a specific byte pattern defined by AOBString and sets the resulting address to the symbol SymbolName.
registerSymbol registerSymbol(SymbolName) Adds a symbol to the user-defined symbol list so cheat tables and the memory browser can use that name instead of an address.
unregisterSymbol unregisterSymbol(SymbolName) Removes a symbol from the user-defined symbol list. No error will occur if the symbol doesn't exist.

此指令碼用到的 CE 組合語言資料型別:

型別 佔用空間
Bit(整型) 1 位
Byte(整型) 8 位(位元組)
2 byte(整型) WORD(字)
4 Bytes(整型) DWORD(雙字)
8 Bytes(整型) QWORD(四字)
Float(單浮點) DWORD(雙字)
Double(雙浮點) QWORD(四字)
String(字串) 任意長度
Array of bytes(AOB) 任意長度

CE 常用暫存器:

The x86 architecture has 8 General-Purpose Registers (GPR)

x86 架構有 8 個通用暫存器(32 位系統)

"E" (for "extended"), 32 bits.

General-Purpose Registers:EAX, EBX, ECX, EDX, ESP, EBP, ESI, EDI

Cheat Engine 彙編程式碼

學過 CE 的,通過註釋,應該能看懂

{ Game   : Kingdom Rush.exe
  Version:
  Date   : 2022-06-19
  Author : Tsanfer

  更改金錢、生命、星
  (使用 AOB 和人造指標)
}

[ENABLE]

aobscanmodule(INJECT,lua51.dll,8B 29 8B 49 04 89 2C C2) // AOB 匹配入口
alloc(newmem,1024,INJECT) // 分配 1024 位元組個記憶體,用於程式碼注入
alloc(man_pointer,24) // 動態分配記憶體,用於存放3個人造指標

// 註冊全域性符號
registersymbol(INJECT)
registersymbol(man_pointer) // 人造指標

// 宣告標號
label(original_code)
label(return)
label(restore_reg)
label(check_is_gold)
label(check_is_gold_END)
label(check_is_live)
label(check_is_live_END)
label(check_is_star)
label(check_is_star_END)

// 資料儲存結構:
// 偏移   資料                 變數型別
// 0      具體值               Double
// 8      指向值所屬的公共結構 指標
// └─> 0
//     10 此公共結構的名稱     字串

// 程式執行順序:(彙編程式碼如沒有跳轉,預設從上往下執行)
// INJECT -> newmem -> check_is_gold -> check_is_live
// -> check_is_star -> reset_reg -> original_code -> return

// 注入程式碼段
newmem:
  pushfd // 儲存所有標誌位
  push eax // eax 壓棧儲存,為後續操作騰出一個暫存器
  mov eax,[ecx+08] // 將當前值所屬的公共型別所在的地址,給 eax

// 判斷此值的型別是否為金錢(player_gold)
check_is_gold:
  cmp dword ptr [eax+10],'play' // 記憶體雙字比較
  jne check_is_gold_END // 如不匹配,則停止後續比較,跳到此比較的結尾
  cmp dword ptr [eax+14],'er_g'
  jne check_is_gold_END
  cmp word ptr [eax+18],'ol' // 記憶體字比較
  jne check_is_gold_END
  cmp byte ptr [eax+1A],'d' // 記憶體位元組比較
  jne check_is_gold_END
  mov [man_pointer],ecx // 匹配成功,將指向此值的指標儲存在申請的記憶體中(製作人造指標)
check_is_gold_END:

// 判斷此值的型別是否為生命(lives)
check_is_live:
  cmp dword ptr [eax+10],'live'
  jne check_is_live_END
  cmp byte ptr [eax+14],'s'
  jne check_is_gold_END
  mov [man_pointer+8],ecx // 將指標儲存在第二個記憶體位置
  // (64位系統的指標大小為 64 bit,每個記憶體地址大小為 8bit,則需要平移8個記憶體地址,8x8=64)
check_is_live_END:

// 判斷此值的型別是否為升級用的星(total_stars)
check_is_star:
  cmp dword ptr [eax+10],'tota'
  jne check_is_star_END
  cmp dword ptr [eax+14],'l_st'
  jne check_is_star_END
  cmp word ptr [eax+18],'ar'
  jne check_is_star_END
  cmp byte ptr [eax+1A],'s'
  jne check_is_star_END
  mov [man_pointer+10],ecx
check_is_star_END:

// 恢復臨時使用的暫存器的值
restore_reg:
  pop eax
  popfd  // 還原所有標誌位
  jmp original_code

// 原始程式碼
original_code:
  mov ebp,[ecx]
  mov ecx,[ecx+04]
  jmp return // 跳到返回

// 程式入口
INJECT:
  jmp newmem
return: // 返回

[DISABLE]
// 還原始碼
INJECT:
  db 8B 29 8B 49 04

// 登出全域性符號
unregistersymbol(INJECT)
unregistersymbol(man_pointer)
// 釋放記憶體
dealloc(newmem)
dealloc(man_pointer)

{
// ORIGINAL CODE - INJECTION POINT: lua51.dll+1BDA

lua51.dll+1BBC: 23 48 08     - and ecx,[eax+08]
lua51.dll+1BBF: 6B C9 18     - imul ecx,ecx,18
lua51.dll+1BC2: 03 4D 14     - add ecx,[ebp+14]
lua51.dll+1BC5: 83 79 0C FB  - cmp dword ptr [ecx+0C],-05
lua51.dll+1BC9: 75 3A        - jne lua51.dll+1C05
lua51.dll+1BCB: 39 41 08     - cmp [ecx+08],eax
lua51.dll+1BCE: 75 35        - jne lua51.dll+1C05
lua51.dll+1BD0: 83 79 04 FF  - cmp dword ptr [ecx+04],-01
lua51.dll+1BD4: 74 36        - je lua51.dll+1C0C
lua51.dll+1BD6: 0F B6 46 FD  - movzx eax,byte ptr [esi-03]
// ---------- INJECTING HERE ----------
lua51.dll+1BDA: 8B 29        - mov ebp,[ecx]
lua51.dll+1BDC: 8B 49 04     - mov ecx,[ecx+04]
// ---------- DONE INJECTING  ----------
lua51.dll+1BDF: 89 2C C2     - mov [edx+eax*8],ebp
lua51.dll+1BE2: 89 4C C2 04  - mov [edx+eax*8+04],ecx
lua51.dll+1BE6: 8B 06        - mov eax,[esi]
lua51.dll+1BE8: 0F B6 CC     - movzx ecx,ah
lua51.dll+1BEB: 0F B6 E8     - movzx ebp,al
lua51.dll+1BEE: 83 C6 04     - add esi,04
lua51.dll+1BF1: C1 E8 10     - shr eax,10
lua51.dll+1BF4: FF 24 AB     - jmp dword ptr [ebx+ebp*4]
lua51.dll+1BF7: 0F B6 46 FD  - movzx eax,byte ptr [esi-03]
}

然後再手動新增 3 個人造指標的地址(man_pointer 和 man_pointer+8 和 man_pointer+10),就行了

Cheat Engine 使用介面:

本文由 Tsanfer's Blog 釋出!