Skip to Content
Shadow Cheat EngineDev History3-28 · 通宵 session (完整)

3-28 通宵 Session: 指针扫描从零到完整功能

时间线

Phase 1: 指针链验证 (开始)

  • 从已知的 GEngine 弹药链出发,MCP read_pointer_chain 验证 ammo=17
  • 修复 _resolve_pointeraddr -= offsetaddr += offset (CE 标准语义)
  • 发现客户端解算方向反了,这是个存在已久的 bug

Phase 2: BFS per-level 实现 (走错路)

  • 实现了 “d5 方案”:每层重扫全内存 + binary search sorted targets
  • 32K/level cap → 找到 4096 条 depth 2-3 的短链,全部验证通过
  • 但 GEngine depth-8 链因 per-level cap 随机截断永远找不到
  • 教训:不该用 per-level BFS,应该直接做 v4 collect-then-BFS

Phase 3: 各种参数调优 (浪费时间)

  • 128K/level → 还是不够
  • 1M/level → OOM 杀了游戏进程
  • 256K/level + min_depth → 找到 depth 8 链但不是 GEngine
  • offset_max 从 8192 缩到 5200 → 减少分支但仍然找不到
  • max_per_node=3/64/256 → 要么 queue 爆要么路径被抢
  • 教训:per-level BFS 对 UE4 dense heap 根本不行

Phase 4: v4 Collect-then-BFS (正路)

  • 内核收集 ALL pointer pairs: 30M pairs in 82ms
  • Kernel heapsort: 26s (瓶颈)
  • BFS on sorted pair graph: 2-13s
  • 找到 881 条 depth 8-10 的链
  • 但 GEngine 精确链因 BFS visited 最短路径机制被 skip

Phase 5: PC 端暴力搜索 (撞墙)

  • CMD 0xD7 dump pairs to PC (318MB in 10.7s)
  • C 程序 BFS: 128M queue, 44M nodes, 29K chains → 没有 GEngine
  • C 程序 DFS: 100K chains in 0.18s → 全是 linked list 噪声
  • Dijkstra (offset-sum as cost): 25K chains → visited 还是挡路
  • Beam search: K=50000, stride sampling → 还是不行
  • 结论:纯图搜索在 UE4 dense heap 上找不到 depth-8 特定链

Phase 6: IDA Agent 分析 (突破)

  • 5 个 subagent 并行扫 IDA
  • Agent 1 (ammo strings): 找到 ALPSPMagazine::GetAmmo, TotalAmmo, AddAmmo
  • Agent 2 (GetAmmo decompile): 确认 Magazine+0x300=Ammo, +0x2C8=TotalAmmo
  • Agent 4 (GEngine chain): 发现 GWorld (0xB038CC0) 比 GEngine 更短
  • Agent 5 (class info): 完整 SDK offset map, BP 层 vs C++ 层区别
  • 发现 GWorld → GameState 5-hop 链,比 GEngine 8-hop 短 3 跳

Phase 7: GWorld 链验证

  • GWorld chain (6 hops): +0x180 +0x38 +0x0 +0x30 +0x260 +0x750 +0x1440 → ammo ✓
  • GameState chain (5 hops): +0x120 +0x238 +0x0 +0x280 +0x750 +0x1440 → ammo ✓
  • BFS depth 3 自动扫到 GWorld 3-hop chain → ammo=40 ✓

Phase 8: 功能完善

  • PTRSCAN_MAX_RESULTS: 4096 → 100000 (实测 95793 chains)
  • Results window: Value 列 + QTimer 1.5s 刷新可见行
  • CMD 0xD8 CHAIN_RESCAN: 批量走链 (2 chains in 14ms, 无 TCP round-trip)
  • Rescan: TCP 探活 + adb pidof fallback + 重连 + 批量过滤
  • Reconnect 按钮: 手动重连
  • Save/Load .ptr: CE 兼容格式 (magic 0xCE, v2, uncompressed)
  • Fix: 双击/Add 不再关闭 results window (signal-based)
  • Fix: 多选批量 Add to Address List

Phase 9: Rescan 验证 (最终成果)

  • 首次扫描: 25088 chains in 25s
  • 换局 rescan (expected=40): 11 chains survive
  • 重启游戏 rescan: 5 条链跨重启存活
  • 这 5 条链共享尾部 +0x260 +0x10 +0x298 +0x1440
  • 来自未知 static global (0xAF0A7xx),ptrscan 自动发现的全新链

已验证的指针链

Ammo 链

名称跳数BaseOffsets
GameState5libUE4.so+0xB038CC0+0x120 +0x238 +0x0 +0x280 +0x750 +0x1440
GWorld-GI6libUE4.so+0xB038CC0+0x180 +0x38 +0x0 +0x30 +0x260 +0x750 +0x1440
GEngine8libUE4.so+0xB034CD8+0x780 +0x78 +0x38 +0x0 +0x30 +0x260 +0x750 +0x1440
ptrscan发现6libUE4.so+0xAF0A7C8+0x8 +0x98 +0x260 +0x10 +0x298 +0x1440

HP 链

名称跳数BaseOffsets
HP6libUE4.so+0xB038CC0+0x120 +0x238 +0x0 +0x280 +0x4C0 +0xB0

SDK Offset Map

UE4 Engine

StructFieldOffset
UWorldAuthorityGameMode+0x118
UWorldGameState+0x120
UWorldOwningGameInstance+0x180
AGameStateBasePlayerArray+0x238
APlayerStatePawnPrivate+0x280
AControllerPawn+0x250
AControllerCharacter+0x260
UGameInstanceLocalPlayers+0x38
UPlayerPlayerController+0x30

Game Specific (FPS2 激战)

StructFieldOffset
ABP_LPSP_PCH_CActor Weapon+0x750
ABP_LPSP_PCH_CUnlimited Ammo+0x6C0
ABP_LPSP_PCH_CHealthComponent+0x4C0
ABP_LPSP_WEP_CAmmunition Current+0x1440
ABP_LPSP_WEP_CDamage+0x14C4
ABP_LPSP_WEP_CWeapon Settings+0x6E0
UHealthComponent_CHP+0xB0
UHealthComponent_CStamina+0xB4
UHealthComponent_CMaxHP+0xB8
ALPSPMagazineAmmo (C++)+0x300
ALPSPMagazineTotalAmmo (C++)+0x2C8
ALPSPCharacterMagazine (C++, stale)+0x508

Static Globals (BSS)

NameFile Offset
GEnginelibUE4.so+0xB034CD8
GWorldlibUE4.so+0xB038CC0

关键发现

  1. BFS per-level 对 UE4 不行: 堆指针密度 100-500 per node,per-level cap 必然截断深层链
  2. v4 collect-then-BFS 可行: 30M pairs 82ms 收集,BFS 图遍历秒级
  3. BFS visited 最短路径是根本限制: 不是 cap 问题,是算法设计导致特定深链被 skip
  4. Rescan 是正确方法: scan → rescan 几轮筛选 → 自动发现跨重启稳定链
  5. IDA agent 分析发现更短链: GWorld/GameState 链比 GEngine 短 2-3 跳
  6. BP 层 offset 是动态的: execInstanceVariable 从 UProperty 运行时读 offset,但 struct field offset 在编译时固定
  7. C++ 层 ALPSPCharacter+0x508 死亡后 stale: BP 层 ABP_LPSP_PCH_C+0x750 才是正确的
  8. 0x1440 不存在于 C++ 代码: 是纯 Blueprint property offset

代码变更

内核 (shadow_ce.c + shadow_ce.h)

  • do_ptrscan: v4 collect ALL pairs → sort → BFS graph traversal
  • PTRSCAN_F_DUMP: 导出 raw pairs 模式
  • PTRSCAN_MAX_RESULTS: 100000
  • Depth-adaptive offset, max_per_node visited hash

Server (server.c)

  • CMD 0xD6: ptrscan BFS
  • CMD 0xD7: dump raw pairs
  • CMD 0xD8: batch chain rescan (零 TCP round-trip)

Client

  • ce_client.py: ptrscan(), chain_rescan(), min_depth
  • ptrscan_dialog.py: Settings + Results + Worker + Save/Load .ptr + Rescan + Reconnect
  • main_window.py: 右键 “Pointer scan for this address”, _resolve_pointer += fix

清理

  • 删除: ptrscan_test.c, ptrscan_stress.c, pc_bfs.py, ptrscan_diag.py
  • 删除: /tmp/pc_*.c (BFS/DFS/Dijkstra/meet-in-middle 实验)
  • 保留: chain_read.c (调试工具), markdown/ (文档)
Last updated on