文章目录概述C 语言的内存模型CE 的假设Python 的内存模型三层间接第一层PyFloatObject第二层dict 存指针不存值第三层不可变对象 每次赋值都换地址CE 搜索失败的完整原因哪些游戏有这个问题总结概述在逆向某游戏Cocos Python 2.7时遇到一个现象游戏中当前年剩余时间LevelRestTime是一个不断变化的 float 值从120倒计时到0但用 Cheat Engine 无论怎么搜都搜不到。值没有加密问题出在Python 的对象内存模型与 CE 的搜索假设根本不兼容。这个问题对所有内嵌 Python/Lua/JS 等脚本引擎的游戏都适用。C 语言的内存模型CE 的假设CE 的设计基于 C/C 的内存模型——变量是一块固定地址上的裸值// C 语言floatLevelRestTime114.99f;// 4字节, 固定地址 0x12345678// 下一帧LevelRestTime-0.016f;// 同一个地址, 值变了内存 0x12345678: 帧1: [42 F9 E6 C2] → 114.99 帧2: [9A F9 E6 C2] → 114.97 帧3: [F2 F8 E6 C2] → 114.95 地址不变, 值在原地递减CE 的首次扫描 → 再次扫描流程完美契合这个模型同一个地址上的值在变化筛几轮就锁定了。Python 的内存模型三层间接Python 中一切皆对象没有裸值。一个 float 变量实际上是这样的结构第一层PyFloatObjectPyFloatObject (Python 2.7, 64位, 共24字节): ┌──────────────────────────────┐ │ 0x00 ob_refcnt (8字节) │ 引用计数 │ 0x08 ob_type (8字节) │ → PyFloat_Type 类型指针 │ 0x10 ob_fval (8字节) │ ← 实际的 double 值 └──────────────────────────────┘注意1Python 用double8字节不是float4字节。CE 默认搜 4 字节 float类型就不对。注意2实际值在对象偏移0x10处前面有16字节的对象头。第二层dict 存指针不存值LevelRestTime存在m_dctProp字典中。Python dict 的条目结构dict entry (24字节): ┌──────────────────────────────────────────────┐ │ 0x00 hash (8字节) │ │ 0x08 key (8字节) → PyString LevelRest…│ │ 0x10 value (8字节) → PyFloatObject* │ ← 指针 └──────────────────────────────────────────────┘dict 里存的是指向 PyFloatObject 的指针不是 float 值本身。第三层不可变对象 每次赋值都换地址Python 的 float 是不可变对象immutable。LevelRestTime - dt这行代码实际执行的是# 不是 修改原地的值# 而是 创建新对象, 替换指针LevelRestTimeLevelRestTime-dt等价于1. old PyFloatObject 0xAAAA0000, ob_fval 114.99 2. new PyFloat_FromDouble(114.99 - 0.016) → 分配新的 PyFloatObject 0xBBBB0000, ob_fval 114.97 3. dict[LevelRestTime] 的 value 指针从 0xAAAA0000 改为 0xBBBB0000 4. old 对象引用计数归零 → 被垃圾回收内存释放或回收到 free listCE 搜索失败的完整原因把三层叠加起来CE 的每一步都踩坑帧1: 搜 Double 114.99 → 找到地址 0xAAAA0010 (PyFloatObject.ob_fval) ✓ 第一次能搜到 帧2: 再次扫描, 期望 0xAAAA0010 处的值变为 ~114.97 → 但 0xAAAA0010 处的对象已被回收 → 该地址的内存可能已被其他对象占用, 值是垃圾 → CE: 值不匹配, 排除 ✗ 筛掉了 → 真正的 114.97 在新地址 0xBBBB0010 → 但这个地址不在 CE 的候选列表里 ✗ 找不到CE 假设Python 现实结果值是4字节float值是8字节double类型不匹配值在固定地址每帧创建新对象地址变化再次扫描失败修改地址处的值即可dict存的是指针需要替换指针即使找到也改不动哪些游戏有这个问题不只是 Python 游戏。所有使用带 GC 的脚本引擎的游戏都有类似问题引擎值对象类型CE 能直接搜吗CPython 2/3PyFloatObject (不可变)搜不到地址每次变Lua 5.xTValue (tagged union)有时能搜到Lua 用原地 TValue较友好LuaJITGCobj / TValue取决于 JIT 优化可能搜到V8 (JS)HeapNumber (不可变)搜不到和 Python 一样Mono (C#)值类型在栈/堆struct 类型通常能搜到IL2CPP编译为 C裸值能搜到最友好经验法则如果脚本语言的数值类型是不可变对象Python float、JS NumberCE 基本搜不到。如果是原地修改的值类型Lua TValue、C# structCE 通常能搜到。总结CE 搜不到 Python 游戏的值不是因为加密而是因为内存模型的根本差异C/C 游戏: 地址固定 → 值原地变化 → CE 完美匹配 Python 游戏: 每次赋值 → 新建对象 → 换指针 → 旧地址失效 → CE 跟丢面对脚本引擎游戏放弃 CE 的扫描-筛选流程转向理解脚本引擎的对象模型PyObject、TValue 等用 Frida 注入到引擎内部通过引擎自己的 API 读写数据操作对象指针而非裸内存值