利用AOB通过特征码动态劫持寄存器指向的地址 - Terraria背包监控实现

技术背景

动态寄存器劫持原理

寄存器级内存寻址的动态拦截技术,通过特征码定位关键指令流,建立符号化内存锚点(Symbol Anchor),实现寄存器指向地址的运行时重定向。相较于传统基址+偏移量模式,具有以下核心优势:

  1. 动态内存适应

    通过aobscan特征签名匹配,规避ASLR和动态内存分配带来的基址失效问题

  2. 寄存器上下文捕获

    在指令执行现场直接劫持寄存器值,避免多层指针追朔的地址稳定性风险

  3. 热更新支持

    通过registersymbol声明动态符号,支持运行时内存布局变更的热适应

  4. 跨版本兼容

    特征码扫描机制可兼容不同编译版本,减少游戏更新后的维护成本

实现步骤

1. 指令流定位

; Terraria物品数量更新指令原型
0045F2D1 - 01 81 B4000000 - add [ecx+000000B4],eax

通过Cheat Engine的Find out what writes to this address功能定位物品数量更新点,使用AOB签名:

01 81 B4 00 00 00 80 7D 15

2. 内存注入框架

aobscan(INJECT, 01 81 B4 00 00 00 80 7D 15) // 特征码模糊匹配
alloc(newmem, $1000) // 分配代码洞穴
alloc(vinv0item, 8) // 声明物品数量指针容器
registersymbol(vinv0item) // 注册动态符号

3. 寄存器劫持逻辑

newmem:
add [ecx+000000B4],eax ; 原始指令恢复
mov [vinv0item], ecx ; 捕获ECX寄存器值
add [vinv0item], B4 ; 计算实际物品数量地址
jmp return code:
add [ecx+000000B4],eax ; 备用执行路径
jmp return INJECT:
jmp newmem // 劫持执行流
nop // 指令对齐

实时监控实现

内存指针维护

registersymbol(vinv0item)  // 声明为全局符号

通过寄存器符号动态绑定,实现:

  1. CE脚本内vinv0item变量的地址热更新
  2. 外部调试器可通过vinv0item符号直接访问
  3. 多线程环境下的地址一致性保证

数据流验证

  1. 执行物品数量变动操作
  2. 观察vinv0item指针值变化(CE内存查看器)
  3. 验证[vinv0item]指向地址的数值实时性

优势对比分析

维度 传统基址模式 AOB寄存器劫持
内存稳定性 依赖固定偏移链 动态寄存器上下文捕获
更新成本 需重新追朔指针链 特征码自动适配
执行效率 多层指针解引用 寄存器直接操作
多版本支持 需适配每个版本 特征匹配自动定位
注入稳定性 易受内存布局变化影响 指令流级精准拦截

注意事项

  1. 寄存器生命周期

    确保在ECX有效期内完成劫持操作,避免使用易失性寄存器

  2. 指令对齐

    使用nop填充被覆盖的原始指令空间,防止执行流错位

  3. 符号管理

    通过registersymbol/unregistersymbol规范符号生命周期

  4. 内存释放

    dealloc需与alloc严格对应,防止内存泄漏

本方案已在Terraria 1.4.4.9实测通过,可稳定捕获物品数量变更事件

[ENABLE]

aobscan(INJECT,01 81 B4 00 00 00 80 7D 15)
alloc(newmem,$1000) alloc(vinv0item, 8)
registersymbol(vinv0item) label(code)
label(return) newmem:
add [ecx+000000B4],eax
mov [vinv0item], ecx
add [vinv0item], B4
jmp return code:
add [ecx+000000B4],eax
jmp return INJECT:
jmp newmem
nop
return:
registersymbol(INJECT) [DISABLE] INJECT:
db 01 81 B4 00 00 00 dealloc(vinv0item)
unregistersymbol(vinv0item) unregistersymbol(INJECT)
dealloc(newmem) {
// ORIGINAL CODE - INJECTION POINT: Terraria.Player::GetItem_FillIntoOccupiedSlot+107 Terraria.Player::GetItem_FillIntoOccupiedSlot+DB: 8B 96 D8 00 00 00 - mov edx,[esi+000000D8]
Terraria.Player::GetItem_FillIntoOccupiedSlot+E1: 3B 5A 04 - cmp ebx,[edx+04]
Terraria.Player::GetItem_FillIntoOccupiedSlot+E4: 0F 83 52 01 00 00 - jae Terraria.Player::GetItem_FillIntoOccupiedSlot+23C
Terraria.Player::GetItem_FillIntoOccupiedSlot+EA: 8B 4C 9A 08 - mov ecx,[edx+ebx*4+08]
Terraria.Player::GetItem_FillIntoOccupiedSlot+EE: 8B B9 B4 00 00 00 - mov edi,[ecx+000000B4]
Terraria.Player::GetItem_FillIntoOccupiedSlot+F4: 03 C7 - add eax,edi
Terraria.Player::GetItem_FillIntoOccupiedSlot+F6: 8B 91 B8 00 00 00 - mov edx,[ecx+000000B8]
Terraria.Player::GetItem_FillIntoOccupiedSlot+FC: 3B C2 - cmp eax,edx
Terraria.Player::GetItem_FillIntoOccupiedSlot+FE: 0F 8F 7F 00 00 00 - jg Terraria.Player::GetItem_FillIntoOccupiedSlot+183
Terraria.Player::GetItem_FillIntoOccupiedSlot+104: 8B 45 EC - mov eax,[ebp-14]
// ---------- INJECTING HERE ----------
Terraria.Player::GetItem_FillIntoOccupiedSlot+107: 01 81 B4 00 00 00 - add [ecx+000000B4],eax
// ---------- DONE INJECTING ----------
Terraria.Player::GetItem_FillIntoOccupiedSlot+10D: 80 7D 15 00 - cmp byte ptr [ebp+15],00
Terraria.Player::GetItem_FillIntoOccupiedSlot+111: 75 1E - jne Terraria.Player::GetItem_FillIntoOccupiedSlot+131
Terraria.Player::GetItem_FillIntoOccupiedSlot+113: 8B 45 0C - mov eax,[ebp+0C]
Terraria.Player::GetItem_FillIntoOccupiedSlot+116: 8B 80 B4 00 00 00 - mov eax,[eax+000000B4]
Terraria.Player::GetItem_FillIntoOccupiedSlot+11C: 89 45 EC - mov [ebp-14],eax
Terraria.Player::GetItem_FillIntoOccupiedSlot+11F: 50 - push eax
Terraria.Player::GetItem_FillIntoOccupiedSlot+120: 6A 00 - push 00
Terraria.Player::GetItem_FillIntoOccupiedSlot+122: 0F B6 45 14 - movzx eax,byte ptr [ebp+14]
Terraria.Player::GetItem_FillIntoOccupiedSlot+126: 50 - push eax
Terraria.Player::GetItem_FillIntoOccupiedSlot+127: 8B 55 18 - mov edx,[ebp+18]
}

脚本解释:

  1. aobscan(INJECT,01 81 B4 00 00 00 80 7D 15): 这行代码在目标进程内存中搜索与指定字节序列匹配的地址,并将第一个匹配结果的地址赋予符号 INJECT
  2. alloc(newmem,$1000): 这行代码在可执行内存中分配了 1000 字节的空间,并将其标记为 newmem。我们将在这个区域存放我们自定义的汇编代码。
  3. alloc(vinv0item, 8): 这行代码分配了 8 字节的内存空间,并将其标记为 vinv0item。我们将使用这个内存地址来存储我们想要监视的物品数量的地址。之所以分配 8 字节,是为了确保可以存储 64 位地址(在 64 位游戏中)。
  4. registersymbol(vinv0item): 这行代码将 vinv0item 注册为 CE 的符号,这样我们可以在 CE 的地址列表中看到它,并监视其值。
  5. label(code)label(return): 这两行代码定义了代码标签,用于在汇编代码中进行跳转。
  6. newmem:: 这是我们自定义的新代码段的起始标签。
    • add [ecx+000000B4],eax: 这条指令是原始被劫持的指令。我们首先执行它,以保证游戏的正常逻辑不受影响。
    • mov [vinv0item], ecx: 这条指令将 ECX 寄存器当前的值(我们假设它指向与背包物品相关的某个数据结构)存储到 vinv0item 指向的内存地址。
    • add [vinv0item], B4: 这条指令将 vinv0item 中存储的地址加上偏移 B4。根据我们的假设,这个偏移指向物品数量在数据结构中的位置。执行完这条指令后,vinv0item 中存储的地址将直接指向物品数量的值。
    • jmp return: 这条指令跳转到 return 标签指向的地址,即原始被劫持指令的下一条指令,以恢复程序的正常执行流程。
  7. code:: 这是原始代码段的标签。
    • add [ecx+000000B4],eax: 这是原始被劫持的指令。
    • jmp return: 跳转回原始流程。
  8. INJECT:: 这是我们通过 AOB 扫描找到的目标指令的地址标签。
    • jmp newmem: 这条指令将程序的执行流程从原始指令跳转到我们自定义的新代码段 newmem
    • nop: 这条指令是一个空操作,它占据了被我们跳转的原始指令的第一个字节。这是为了防止程序因为执行了部分原始指令而崩溃。
  9. return:: 这是原始被劫持指令的下一条指令的地址标签。
  10. [DISABLE]: 这个部分定义了当禁用该脚本时需要执行的操作。
  11. INJECT: db 01 81 B4 00 00 00: 这行代码将 INJECT 地址处的字节恢复为原始值,撤销我们的跳转。注意我们只恢复了被 jmp newmem 覆盖的字节。
  12. dealloc(vinv0item)unregistersymbol(vinv0item): 这两行代码释放了我们分配的 vinv0item 内存空间,并取消了其符号注册。
  13. unregistersymbol(INJECT)dealloc(newmem): 这两行代码释放了我们分配的 newmem 内存空间,并取消了 INJECT 符号的注册。

AssemblyX自动分析推测结果:

Terraria.Player::GetItem_FillIntoOccupiedSlot+85 - 50                    - push eax                                 ; 将 EAX 寄存器的值压入堆栈,可能是函数调用的参数
Terraria.Player::GetItem_FillIntoOccupiedSlot+86 - 6A 01 - push 01 { 1 } ; 将立即数 1 压入堆栈,可能是音效的音量参数
Terraria.Player::GetItem_FillIntoOccupiedSlot+88 - 68 0000803F - push 3F800000 { (0) } ; 将单精度浮点数 1.0 (3F800000) 压入堆栈,可能是音效的音调参数
Terraria.Player::GetItem_FillIntoOccupiedSlot+8D - 6A 00 - push 00 { 0 } ; 将立即数 0 压入堆栈,可能是音效的类型参数或标志位
Terraria.Player::GetItem_FillIntoOccupiedSlot+8F - B9 26000000 - mov ecx,00000026 { 38 } ; 将立即数 38 (音效ID,可能是拾取物品通用音效) 移动到 ECX 寄存器,准备作为函数参数
Terraria.Player::GetItem_FillIntoOccupiedSlot+94 - FF 15 58E55908 - call dword ptr [0859E558] { ->Terraria.Audio.SoundEngine::PlaySound } ; 调用 SoundEngine::PlaySound 函数,根据前面的 push 指令,推测是在播放拾取物品的音效
Terraria.Player::GetItem_FillIntoOccupiedSlot+9A - EB 33 - jmp Terraria.Player::GetItem_FillIntoOccupiedSlot+CF ; 无条件跳转到地址 Terraria.Player::GetItem_FillIntoOccupiedSlot+CF,跳过一段代码,可能是根据条件决定是否执行播放另一种音效
Terraria.Player::GetItem_FillIntoOccupiedSlot+9C - D9 46 28 - fld dword ptr [esi+28] ; 加载 ESI 寄存器指向地址偏移 28 字节处的单精度浮点数值到 FPU 寄存器,可能是物品的 X 坐标或相关位置信息
Terraria.Player::GetItem_FillIntoOccupiedSlot+9F - DD 5D E4 - fstp qword ptr [ebp-1C] ; 将 FPU 寄存器中的值以双精度浮点数形式存储到 [EBP-1C] 内存地址,并清空 FPU 寄存器,可能是保存物品 X 坐标到局部变量
Terraria.Player::GetItem_FillIntoOccupiedSlot+A2 - F2 0F10 45 E4 - movsd xmm0,[ebp-1C] ; 将 [EBP-1C] 处双精度浮点数值加载到 XMM0 寄存器,准备转换为整数
Terraria.Player::GetItem_FillIntoOccupiedSlot+A7 - F2 0F2C D0 - cvttsd2si edx,xmm0 ; 将 XMM0 寄存器中的双精度浮点数转换为有符号整数,结果存储到 EDX 寄存器,可能是将物品 X 坐标转换为整数像素坐标
Terraria.Player::GetItem_FillIntoOccupiedSlot+AB - D9 46 2C - fld dword ptr [esi+2C] ; 加载 ESI 寄存器指向地址偏移 2C 字节处的单精度浮点数值到 FPU 寄存器,可能是物品的 Y 坐标或相关位置信息
Terraria.Player::GetItem_FillIntoOccupiedSlot+AE - DD 5D E4 - fstp qword ptr [ebp-1C] ; 将 FPU 寄存器中的值以双精度浮点数形式存储到 [EBP-1C] 内存地址,并清空 FPU 寄存器,复用 [ebp-1C] 地址,这次是保存物品 Y 坐标
Terraria.Player::GetItem_FillIntoOccupiedSlot+B1 - F2 0F10 45 E4 - movsd xmm0,[ebp-1C] ; 将 [EBP-1C] 处双精度浮点数值加载到 XMM0 寄存器,准备转换为整数
Terraria.Player::GetItem_FillIntoOccupiedSlot+B6 - F2 0F2C C0 - cvttsd2si eax,xmm0 ; 将 XMM0 寄存器中的双精度浮点数转换为有符号整数,结果存储到 EAX 寄存器,可能是将物品 Y 坐标转换为整数像素坐标
Terraria.Player::GetItem_FillIntoOccupiedSlot+BA - 50 - push eax ; 将 EAX 寄存器的值 (Y 坐标) 压入堆栈,作为 PlaySound 函数的参数
Terraria.Player::GetItem_FillIntoOccupiedSlot+BB - 6A 01 - push 01 { 1 } ; 将立即数 1 压入堆栈,音量参数
Terraria.Player::GetItem_FillIntoOccupiedSlot+BD - 68 0000803F - push 3F800000 { (0) } ; 将单精度浮点数 1.0 压入堆栈,音调参数
Terraria.Player::GetItem_FillIntoOccupiedSlot+C2 - 6A 00 - push 00 { 0 } ; 将立即数 0 压入堆栈,音效类型参数或标志位
Terraria.Player::GetItem_FillIntoOccupiedSlot+C4 - B9 07000000 - mov ecx,00000007 { 7 } ; 将立即数 7 (音效ID,可能是另一种拾取物品音效,例如金币) 移动到 ECX 寄存器
Terraria.Player::GetItem_FillIntoOccupiedSlot+C9 - FF 15 58E55908 - call dword ptr [0859E558] { ->Terraria.Audio.SoundEngine::PlaySound } ; 调用 SoundEngine::PlaySound 函数,播放另一种拾取物品音效
Terraria.Player::GetItem_FillIntoOccupiedSlot+CF - 8B 45 0C - mov eax,[ebp+0C] { 断点分析 - 拾取物品时断住 EAX=7ED19CF8 EBP=0233E3B8 } ; 将 [EBP+0C] 内存地址的值移动到 EAX 寄存器,根据断点分析,[EBP+0C] 可能是函数参数,指向拾取的物品对象
Terraria.Player::GetItem_FillIntoOccupiedSlot+D2 - 8B 80 B4000000 - mov eax,[eax+000000B4] ; 将 [EAX+B4] 内存地址的值移动到 EAX 寄存器,EAX 现在是指向物品对象的指针,+B4 偏移处可能是物品堆叠数量 (stack)
Terraria.Player::GetItem_FillIntoOccupiedSlot+D8 - 89 45 EC - mov [ebp-14],eax ; 将 EAX 寄存器的值 (物品堆叠数量) 存储到 [EBP-14] 内存地址,作为局部变量保存
Terraria.Player::GetItem_FillIntoOccupiedSlot+DB - 8B 96 D8000000 - mov edx,[esi+000000D8] ; 将 [ESI+D8] 内存地址的值移动到 EDX 寄存器,ESI 可能是 Player 对象指针,+D8 偏移处可能是物品栏数组的起始地址或相关结构
Terraria.Player::GetItem_FillIntoOccupiedSlot+E1 - 3B 5A 04 - cmp ebx,[edx+04] ; 比较 EBX 寄存器的值和 [EDX+04] 内存地址的值,EBX 可能是当前OccupiedSlot的索引,[EDX+04] 可能是物品栏数组的边界或大小
Terraria.Player::GetItem_FillIntoOccupiedSlot+E4 - 0F83 52010000 - jae Terraria.Player::GetItem_FillIntoOccupiedSlot+23C ; 如果 EBX >= [EDX+04] (无符号大于等于),则跳转到地址 Terraria.Player::GetItem_FillIntoOccupiedSlot+23C,可能是检查 OccupiedSlot 索引是否超出物品栏范围
Terraria.Player::GetItem_FillIntoOccupiedSlot+EA - 8B 4C 9A 08 - mov ecx,[edx+ebx*4+08] ; 将 [EDX+EBX*4+08] 内存地址的值移动到 ECX 寄存器,EDX 是物品栏数组起始地址,EBX*4 是索引偏移,+08 可能是数组元素大小的调整,ECX 获取到的可能是指定 OccupiedSlot 中的物品槽位对象指针
Terraria.Player::GetItem_FillIntoOccupiedSlot+EE - 8B B9 B4000000 - mov edi,[ecx+000000B4] ; 将 [ECX+B4] 内存地址的值移动到 EDI 寄存器,ECX 是物品槽位对象指针,+B4 偏移处可能是当前槽位中物品的堆叠数量
Terraria.Player::GetItem_FillIntoOccupiedSlot+F4 - 03 C7 - add eax,edi ; 将 EDI 寄存器的值 (当前槽位物品堆叠数量) 加到 EAX 寄存器 (拾取物品堆叠数量),EAX 累加后表示合并后的总数量
Terraria.Player::GetItem_FillIntoOccupiedSlot+F6 - 8B 91 B8000000 - mov edx,[ecx+000000B8] ; 将 [ECX+B8] 内存地址的值移动到 EDX 寄存器,ECX 是物品槽位对象指针,+B8 偏移处可能是当前槽位物品的最大堆叠数量
Terraria.Player::GetItem_FillIntoOccupiedSlot+FC - 3B C2 - cmp eax,edx ; 比较 EAX 寄存器的值 (合并后的总数量) 和 EDX 寄存器的值 (最大堆叠数量)
Terraria.Player::GetItem_FillIntoOccupiedSlot+FE - 0F8F 7F000000 - jg Terraria.Player::GetItem_FillIntoOccupiedSlot+183 ; 如果 EAX > EDX (大于),则跳转到地址 Terraria.Player::GetItem_FillIntoOccupiedSlot+183,表示合并后超过最大堆叠数量,需要进行特殊处理
Terraria.Player::GetItem_FillIntoOccupiedSlot+104- 8B 45 EC - mov eax,[ebp-14] ; 将 [EBP-14] 内存地址的值 (之前保存的拾取物品堆叠数量) 移动到 EAX 寄存器
Terraria.Player::GetItem_FillIntoOccupiedSlot+107- 01 81 B4000000 - add [ecx+000000B4],eax { 断点分析 - 拾取物品时断住 ECX=47181D88 EAX=2 } ; 将 EAX 寄存器的值 (拾取物品堆叠数量) 加到 [ECX+B4] 内存地址,ECX 是目标物品槽位对象指针,+B4 偏移处是物品堆叠数量,实际上是将拾取的物品数量添加到已占用的槽位中,断点分析 EAX=2 说明本次拾取的物品数量为 2
Terraria.Player::GetItem_FillIntoOccupiedSlot+10D- 80 7D 15 00 - cmp byte ptr [ebp+15],00 { 0 } ; 比较 [EBP+15] 内存地址的一个字节值和 0,[EBP+15] 处可能是一个标志位,用于控制是否显示拾取文本
Terraria.Player::GetItem_FillIntoOccupiedSlot+111- 75 1E - jne Terraria.Player::GetItem_FillIntoOccupiedSlot+131 ; 如果 [EBP+15] != 0 (不等于),则跳转到地址 Terraria.Player::GetItem_FillIntoOccupiedSlot+131,根据 jne 指令,可以推测标志位非零时跳过显示拾取文本的代码
Terraria.Player::GetItem_FillIntoOccupiedSlot+113- 8B 45 0C - mov eax,[ebp+0C] ; 再次将 [EBP+0C] 内存地址的值 (拾取物品对象指针) 移动到 EAX 寄存器
Terraria.Player::GetItem_FillIntoOccupiedSlot+116- 8B 80 B4000000 - mov eax,[eax+000000B4] ; 再次将 [EAX+B4] 内存地址的值 (拾取物品堆叠数量) 移动到 EAX 寄存器
Terraria.Player::GetItem_FillIntoOccupiedSlot+11C- 89 45 EC - mov [ebp-14],eax ; 将 EAX 寄存器的值 (物品堆叠数量) 存储到 [EBP-14] 内存地址,再次保存
Terraria.Player::GetItem_FillIntoOccupiedSlot+11F- 50 - push eax ; 将 EAX 寄存器的值 (物品堆叠数量) 压入堆栈,作为 PopupText::NewText 函数的参数 (物品数量)
Terraria.Player::GetItem_FillIntoOccupiedSlot+120- 6A 00 - push 00 { 0 } ; 将立即数 0 压入堆栈,可能是颜色参数或类型参数
Terraria.Player::GetItem_FillIntoOccupiedSlot+122- 0FB6 45 14 - movzx eax,byte ptr [ebp+14] { 断点分析 - 拾取物品时断住 EAX=2 EBP=0233E3B8 } ; 将 [EBP+14] 内存地址的字节值 (byte 类型) 零扩展移动到 EAX 寄存器,根据断点分析 EAX=2,[EBP+14] 可能是之前存储的少量数据,这里可能复用了局部变量的地址,但其字节值可能与拾取物品数量相关,此处存疑,或者 byte ptr [ebp+14] 可能是某个物品属性
Terraria.Player::GetItem_FillIntoOccupiedSlot+126- 50 - push eax ; 将 EAX 寄存器的值压入堆栈,作为 PopupText::NewText 函数的参数 (数量或类型,具体含义需进一步分析)
Terraria.Player::GetItem_FillIntoOccupiedSlot+127- 8B 55 18 - mov edx,[ebp+18] ; 将 [EBP+18] 内存地址的值移动到 EDX 寄存器,[EBP+18] 可能是 PopupText::NewText 函数的参数 (文本内容,例如物品名称)
Terraria.Player::GetItem_FillIntoOccupiedSlot+12A- 33 C9 - xor ecx,ecx ; 将 ECX 寄存器清零,ECX 寄存器通常作为 this 指针或函数调用的第一个参数,此处清零可能是表示没有 this 指针或者作为某种标志
Terraria.Player::GetItem_FillIntoOccupiedSlot+12C- E8 27737CFC - call Terraria.PopupText::NewText ; 调用 PopupText::NewText 函数,根据前面的 push 和 mov 指令,推测是在显示拾取物品的弹出文本
Terraria.Player::GetItem_FillIntoOccupiedSlot+131- 8B CE - mov ecx,esi ; 将 ESI 寄存器的值 (Player 对象指针) 移动到 ECX 寄存器,作为 Player::DoCoins 函数的 this 指针
Terraria.Player::GetItem_FillIntoOccupiedSlot+133- 8B D3 - mov edx,ebx ; 将 EBX 寄存器的值 (OccupiedSlot 索引) 移动到 EDX 寄存器,作为 Player::DoCoins 函数的参数 (槽位索引)
Terraria.Player::GetItem_FillIntoOccupiedSlot+135- E8 16F5FFFF - call Terraria.Player::DoCoins ; 调用 Player::DoCoins 函数,处理金币类物品的拾取逻辑,可能涉及到金币的增加和显示
Terraria.Player::GetItem_FillIntoOccupiedSlot+13A- 8B 45 F0 - mov eax,[ebp-10] ; 将 [EBP-10] 内存地址的值移动到 EAX 寄存器, [EBP-10] 可能是一个标志位或状态值
Terraria.Player::GetItem_FillIntoOccupiedSlot+13D- 3B 05 24107105 - cmp eax,[05711024] { (0) } ; 比较 EAX 寄存器的值和全局内存地址 [05711024] 处的值,[05711024] 可能是一个全局变量,用于控制是否查找新的配方
Terraria.Player::GetItem_FillIntoOccupiedSlot+143- 75 07 - jne Terraria.Player::GetItem_FillIntoOccupiedSlot+14C ; 如果 EAX != [05711024] (不等于),则跳转到地址 Terraria.Player::GetItem_FillIntoOccupiedSlot+14C,表示条件不满足,跳过查找配方
Terraria.Player::GetItem_FillIntoOccupiedSlot+145- 33 C9 - xor ecx,ecx ; 将 ECX 寄存器清零
Terraria.Player::GetItem_FillIntoOccupiedSlot+147- E8 EC3F6B0D - call Terraria.Recipe::FindRecipes ; 调用 Recipe::FindRecipes 函数,查找新的配方,可能是在拾取物品后检查是否解锁了新的合成配方
Terraria.Player::GetItem_FillIntoOccupiedSlot+14C- 8B CE - mov ecx,esi ; 将 ESI 寄存器的值 (Player 对象指针) 移动到 ECX 寄存器,作为 AchievementsHelper::NotifyItemPickup 函数的 this 指针
Terraria.Player::GetItem_FillIntoOccupiedSlot+14E- 8B 55 0C - mov edx,[ebp+0C] ; 将 [EBP+0C] 内存地址的值 (拾取物品对象指针) 移动到 EDX 寄存器,作为 AchievementsHelper::NotifyItemPickup 函数的参数 (拾取的物品)
Terraria.Player::GetItem_FillIntoOccupiedSlot+151- FF 15 B0CD4C1F - call dword ptr [1F4CCDB0] { ->Terraria.GameContent.Achievements.AchievementsHelper::NotifyItemPickup } ; 调用 AchievementsHelper::NotifyItemPickup 函数,通知成就系统玩家拾取了物品,用于触发和更新成就
Terraria.Player::GetItem_FillIntoOccupiedSlot+157- 8B 86 D8000000 - mov eax,[esi+000000D8] ; 再次将 [ESI+D8] 内存地址的值 (物品栏数组起始地址) 移动到 EAX 寄存器
Terraria.Player::GetItem_FillIntoOccupiedSlot+15D- 3B 58 04 - cmp ebx,[eax+04] ; 再次比较 EBX 寄存器的值 (OccupiedSlot 索引) 和 [EAX+04] 内存地址的值 (物品栏数组边界)
Terraria.Player::GetItem_FillIntoOccupiedSlot+160- 0F83 D6000000 - jae Terraria.Player::GetItem_FillIntoOccupiedSlot+23C ; 如果 EBX >= [EAX+04] (无符号大于等于),则跳转到地址 Terraria.Player::GetItem_FillIntoOccupiedSlot+23C,再次进行索引范围检查
Terraria.Player::GetItem_FillIntoOccupiedSlot+166- 8B 4C 98 08 - mov ecx,[eax+ebx*4+08] ; 再次获取指定 OccupiedSlot 的物品槽位对象指针到 ECX 寄存器
Terraria.Player::GetItem_FillIntoOccupiedSlot+16A- 8B D1 - mov edx,ecx ; 将 ECX 寄存器的值 (物品槽位对象指针) 复制到 EDX 寄存器,作为 GetItemSettings::HandlePostAction 函数的参数
Terraria.Player::GetItem_FillIntoOccupiedSlot+16C- 8D 4D 10 - lea ecx,[ebp+10] ; 将 [EBP+10] 内存地址的有效地址加载到 ECX 寄存器,[EBP+10] 可能是 GetItemSettings::HandlePostAction 函数的另一个参数 (可能是物品拾取设置或上下文信息)
Terraria.Player::GetItem_FillIntoOccupiedSlot+16F- E8 CC65E9FE - call Terraria.GetItemSettings::HandlePostAction ; 调用 GetItemSettings::HandlePostAction 函数,处理物品拾取后的后续动作,例如触发物品的特殊效果、更新玩家状态等
Terraria.Player::GetItem_FillIntoOccupiedSlot+174- B8 01000000 - mov eax,00000001 { 1 } ; 将立即数 1 移动到 EAX 寄存器,可能是设置函数返回值,表示成功
Terraria.Player::GetItem_FillIntoOccupiedSlot+179- 8D 65 F4 - lea esp,[ebp-0C] ; 将 [EBP-0C] 的地址加载到 ESP 寄存器,调整堆栈指针,清理局部变量空间
Terraria.Player::GetItem_FillIntoOccupiedSlot+17C- 5B - pop ebx ; 弹出堆栈顶端的值到 EBX 寄存器,恢复 EBX 寄存器的值 (函数调用约定的一部分)
Terraria.Player::GetItem_FillIntoOccupiedSlot+17D- 5E - pop esi ; 弹出堆栈顶端的值到 ESI 寄存器,恢复 ESI 寄存器的值
Terraria.Player::GetItem_FillIntoOccupiedSlot+17E- 5F - pop edi ; 弹出堆栈顶端的值到 EDI 寄存器,恢复 EDI 寄存器的值
Terraria.Player::GetItem_FillIntoOccupiedSlot+17F- 5D - pop ebp ; 弹出堆栈顶端的值到 EBP 寄存器,恢复 EBP 寄存器的值,恢复函数栈帧
Terraria.Player::GetItem_FillIntoOccupiedSlot+180- C2 1400 - ret 0014 { 20 } ; 函数返回,并清理堆栈上的 20 字节 (0x14 字节 = 20 字节),通常是清理函数参数占用的堆栈空间

利用AOB通过特征码动态劫持寄存器指向的地址 - Terraria背包物品监控实现 - Cheat Engine - AssemblyX的更多相关文章

  1. [.net 面向对象程序设计进阶] (20) 反射(Reflection)(上)利用反射技术实现动态编程

    [.net 面向对象程序设计进阶] (20) 反射(Reflection)(上)利用反射技术实现动态编程 本节导读:本节主要介绍什么是.NET反射特性,.NET反射能为我们做些什么,最后介绍几种常用的 ...

  2. 利用反射生成JDK动态代理

    利用反射生成JDK动态代理 在Java的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过使用这个类和接口可以生成JDK动态代理类和动态代理 ...

  3. 利用mysql的inet_aton()和inet_ntoa()函数存储IP地址的方法

    原文:https://www.jb51.net/article/29962.htm 当前很多应用都适用字符串char(15)来存储IP地址(占用16个字节),利用inet_aton()和inet_nt ...

  4. 前后端分离,如何在前端项目中动态插入后端API基地址?(in docker)

    开门见山,本文分享前后端分离,容器化前端项目时动态插入后端API基地址,这是一个很赞的实践,解决了前端项目容器化过程中受制后端调用的尴尬. 尴尬从何而来 常见的web前后端分离:前后端分开部署,前端项 ...

  5. element的upload手动submit前动态设置上传请求地址

    标签地址绑定一个变量 动态修改上传请求地址代码: nextTick是DOM更新后触发,不使用nextTick直接submit,上传地址仍然会使用初始url地址 _this = this; this.u ...

  6. 静态代理和利用反射形成的动态代理(JDK动态代理)

    代理模式 代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问.在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用. 静态代理 1.新建 ...

  7. C++学习之路—多态性与虚函数(一)利用虚函数实现动态多态性

    (根据<C++程序设计>(谭浩强)整理,整理者:华科小涛,@http://www.cnblogs.com/hust-ghtao转载请注明) 多态性是面向对象程序设计的一个重要特征.顾名思义 ...

  8. 利用CSS3 animation绘制动态卡通人物,无需使用JS代码

    此外博主原创,转载请注明出处:谢谢~ 效果图: 其中云.风车.尾巴是动态的: 以下是代码: <!DOCTYPE html> <html lang="en"> ...

  9. 利用http协议对搜索引擎劫持

    主要是利用了http协议的refereer头 另外一个头user-agnet 主要是用来做流量劫持 referer 头告诉服务器用户从哪里找来的 当用户通过搜索引擎打开网站时会出现源网页 refere ...

  10. 如何利用Power BI 制作动态搜索界面

    最近Power BI有了最新更新,想着利用 Power BI 工具制造一个动态的搜索界面,比如动态切换搜索引擎,分别从百度.360.搜狗等搜索苹果最新新闻.通过一番测试,最终实现了相关功能. 数据加载 ...

随机推荐

  1. 探秘Transformer系列之(33)--- DeepSeek MTP

    探秘 Transformer系列之(33)--- DeepSeek MTP 目录 探秘 Transformer系列之(33)--- DeepSeek MTP 0x00 概述 0x01 EAGLE 1. ...

  2. 网络编程:阻塞I/O和进程模型

    父进程和子进程 进程是程序执行的最小单位,一个进程有完整的地址空间.程序计数器等,如果想创建一个新的进程,使用函数 fork 就可以 pid_t fork(void) 返回:在子进程中为0,在父进程中 ...

  3. Advanced pandas

    Advanced pandas import numpy as np import pandas as pd Categorical Data This section introduces the ...

  4. The length of parametric curve (x + sin x, cos x)

    问题引入 一个硬币(圆)的周长上有一个点,将硬币在一条线上无滑动地滚动.假设那个点开始时在最上面,滚了半圈到了最下面,求这个点相对于地面的运动轨迹的长度. 或者说,再简单点,自行车总骑过吧.假如你在骑 ...

  5. Git 操作进阶

    1. git基本操作 1.1 查看文件 还未被add的更改 使用git status命令,它会显示哪些文件已经被修改并添加到暂存区,以及那些还未被添加的修改. git status -uno //不显 ...

  6. vivo Pulsar 万亿级消息处理实践(2)-从0到1建设 Pulsar 指标监控链路

    作者:vivo 互联网大数据团队- You Shuo 本文是<vivo Pulsar万亿级消息处理实践>系列文章第2篇,Pulsar支持上报分区粒度指标,Kafka则没有分区粒度的指标,所 ...

  7. Linux系统配置windows可访问的共享文件夹

    一.简单说明 某些情况下,我们需要配置Linux系统的目录为共享文件夹,windows下可以直接访问.这里可以直接安装samba进行.(samba是一款软件,主要提供cifs协议,基于文件系统传输) ...

  8. ChatMoney让你不再恋爱脑!

    本文由 ChatMoney团队出品 你是否曾经想过,为什么我们会在恋爱中变得如此"上头",仿佛整个世界都围绕着那个TA旋转? 恋爱脑,通常是指一个人在恋爱中过度投入.过度依赖对方, ...

  9. 记录.Net 8 发布增加 PublishTrimmed 裁剪选项,调用WMI 的ManagementObject 异常

    最近在做OTA的功能,需要获取到sn做一些业务的逻辑.我们自己实现的库里边的,大部分都是调用 System.Management 的 ManagementObjectSearcher 获取 Bios ...

  10. CTC蜀道会:第一次线下分享活动圆满结束

    近期,成都.NET俱乐部核心成员经过讨论会,我们成立了CTC蜀道会,它是一个专注于创业历程.研发管理.AIGC.副业之路..NET.Vue.微软技术.开源技术等领域的社区,立足于蓉城成都,致力于连接同 ...