资讯动态

CE 6.8.1源码编译实战:深入内存扫描与Lua脚本机制

发布时间:2026/10/9 22:41:43 来源:尧图企业网站定制
简介CE 6.8.1 源码包面向逆向工程、游戏安全与内存调试开发者完整呈现动态内存扫描、指针链追踪、Lua 脚本扩展及反调试对抗等核心模块适合希望从原理层面理解 CE 工作机制或基于其进行二次修改的学习者。压缩包共 1523 个文件约 8.45MB主体为 pas、c/h、asm、lua 等源码其中 pas 对应 Delphi/Lazarus 窗体逻辑c/h 为底层实现与声明asm 涉及汇编级内存操作lua 为脚本扩展入口辅以 lfm/lrt 界面资源、sln/vcproj 工程配置和 dll 库文件便于按模块定位读码与编译。已有 501 人学习下载。通过研读源码可掌握精确/模糊/宽范围扫描的算法差异、不同平台下安全高效的内存读写方式、动态指针解析流程以及将脚本系统嵌入调试工具的接口设计同时 CE 的界面与插件结构也提供了完整的桌面调试器开发范例对游戏保护对抗研究和自定义调试器开发都有直接参考价值。1. 拆 ce6.8.1 源码先搞清楚这份东西能解决什么问题拿到一份 Cheat Engine 6.8.1 的源码包第一反应别是「我又不写游戏修改器要它干嘛」。我当初接手这份源码的动机也很朴素手里的工具在调试一个自研的 Windows 服务时想看某个全局变量到底被哪段代码改写了用 CE 6.8.1 的二进制版本能扫出来但不知道为什么能扫出来。于是直接把它源码翻出来从扫描算法到驱动加载一路读下去才真正理解内存扫描背后的机制。这份 cheat-engine-master 包对应的是 CE 6.8.1 分支理论上是整个 6.8 系列里最稳的一个。它能帮你做的事很明确第一把 Lazarus Free Pascal 环境下的完整 CE 重新编译出来得到一个自己可控的版本第二读透内存扫描、指针追踪、Lua 脚本注入这些核心模块的实现逻辑第三针对单机程序做内存调试、CT 脚本定制和逆向学习。适合三种人被调试工具黑匣子折腾够了的测试工程师、想深入 Windows 内存管理的学生、以及需要定制内存分析脚本的开发。2. 源码结构拆解CE 6.8.1 的模块边界与启动流程2.1 顶层目录哪些是主程序哪些是驱动拿到源码包先别急着编译。CE 6.8.1 的顶层目录结构是有讲究的里面不是所有代码都属于主程序。按我读下来的经验先看这几个关键目录Cheat Engine/主程序所在包含全部 lazarus 工程文件、表单单元和核心逻辑。Cheat Engine/DBK/驱动相关负责内核级读写。这个目录单独编译不是和主程序一起的。Cheat Engine/Lua/Lua 脚本引擎封装。CE 的脚本能力全靠它后面我们改脚本也在这里。Cheat Engine/Plugins/插件框架给第三方扩展留的接口。主程序的工程文件在Cheat Engine/Cheat Engine.lpi。注意千万别直接双击 .lpi 就说「打不开」Lazarus 的工程文件需要从 IDE 里打开而且版本要匹配。CE 6.8.1 官方构建环境是 Lazarus 2.x 系列如果你机器上装的是 1.8 的老版本打开工程后第一件事就是报一堆单元找不到。目录结构这块最容易被忽略的是DBK子目录和Files目录里存放的驱动文件。CE 的主程序通过deviceiocontrol跟驱动通信如果你只编译主程序、不编译驱动那「内核级读写」功能就是灰色不可用的。当然普通的内存扫描功能不受影响它走的是 Windows 调试接口。这个区别后面讲功能边界时还会提到。2.2 从 main.lpr 到主窗口启动流程与项目文件CE 的入口是Cheat Engine.lpr这个文件在发布版里叫main.lpr里面就是标准的 Lazarus 启动逻辑。核心代码不长它先做配置加载再创建主窗口frmMain。值得读的是frmMainUnit.pas—— 整个 CE 的主界面逻辑全都堆在这个单元里六千行起步读它要有心理准备。从技术角度看启动流程大概是这样的逻辑链program CheatEngine; uses Interfaces, Forms, frmMainUnit, Globals; {$R *.res} begin Application.Initialize; Application.CreateForm(TfrmMain, frmMain); Application.Run; end.这段代码逻辑上很直白Application.Initialize初始化运行时CreateForm创建主窗口Application.Run进入消息循环。但这里有个细节值得注意——Globals单元负责全局配置的读写CE 的所有配置项包括热键、扫描选项、颜色主题都集中在Globals里。如果你想在编译时把默认配置改成自己的直接改这个单元的初始化部分就行不用在界面里一项项调。从工程文件层面看Cheat Engine.lpi里定义了编译时需要包含的单元列表、条件编译符号和输出路径。有两点要提前确认一是Target OS必须选Win64或Win32CE 6.8.1 主程序本身是 32 位构建的在 64 位系统上跑靠的是 WoW64 重定向二是输出路径最好改成自己的目录避免覆盖官方构建产物否则后面调试时新旧文件混在一起说不清在跑哪个版本。2.3 值怎么找扫描单元与特征码机制内存扫描模块是 CE 的灵魂这部分源码集中在Scanner相关单元里。核心逻辑不是「遍历内存逐字节比对」而是先用 MemoryRegion 枚举出可用内存区域再对每个区域做分块扫描。这个过程里有两个关键类TScan和TScanResult。TScan负责执行一次扫描TScanResult存放扫描命中的地址列表。从源码角度理解扫描机制其实可以简化为这样一个伪代码逻辑procedure TScan.Execute(value: string); var regions: TMemoryRegions; region: TMemoryRegion; begin // 枚举当前进程的可用内存区域 regions : GetMemoryRegions(processHandle); for region in regions do begin // 逐区域进行数值匹配 if region.Readable and not region.Guarded then ScanRegion(region, value); end; end;GetMemoryRegions对应 CE 里的MemScan枚举逻辑它会过滤掉不可读区域和 Guarded 页面。为什么 CE 扫描速度比某些工具快核心就是这一步——它不会对不可读的内存区域做无意义尝试。标记region.Readable的条件是 MEMORY_BASIC_INFORMATION 里的Protect字段包含PAGE_READWRITE或PAGE_EXECUTE_READWRITE。如果你自己写扫描器这一步一定要照抄否则性能差距是数量级的。至于特征码扫描CE 用的是 AOBArray of Byte方案允许通配符??匹配任意字节。源码里对应的方法在PatternScanner相关逻辑中组装字节数组后调FindPattern在内存区里做滑动匹配。这里有个进阶点CE 6.8.1 支持 AVX2 指令集加速特征码查找如果你在Compiler Options里看到相关定义默认是关闭的编译时按 CPU 支持情况打开能提速不少但代价是压缩包体积增大。3. 编译 CE 6.8.1Lazarus 环境与三处必改配置3.1 环境准备Lazarus 版本与 FPC 编译器编译 CE 6.8.1 最省心的组合是 Lazarus 2.0.10 FPC 3.0.4这是我反复试过之后最稳的搭配。版本配错会翻车得很惨Lazarus 1.8 太老界面组件构造方法不兼容Lazarus 2.2 以上的新版本对某些单元做了破坏性调整编译时报Cant find unit Interfaces的概率很高。安装顺序有讲究。先装 FPC再装 Lazarus两者版本必须配套。Lazarus 安装器一般会自带对应版本的 FPC但如果你机器上已经单独装过 FPC安装时要注意环境变量FPCDIR是否会冲突。我遇到过一次诡异问题Lazarus 一直报Fatal: Cannot find unit System折腾半天发现是系统里另一个 FPC 版本的路径抢先被找到了。解决办法是到Tools-Options-Environment-Files里明确指定 FPC 源码目录和编译器路径。环境装好后打开工程之前先做一次快速验证fpc -i输出应该显示 FPC 版本号和目标系统。如果显示的是Target OS: Linux说明你装的是 Linux 版 FPC编译 Windows 程序时需要在 Lazarus 工程选项里把目标系统改为 Win32 或 Win64。3.2 主程序编译项目选项与输出路径调整打开Cheat Engine.lpi后按我习惯的流程先做三件事确认目标系统、确认输出路径、关闭调试信息里的栈帧生成。Project Options - Compiler Options - Config and Target Target OS: Win32或 Win64取决于目标环境 Target CPU: i386 或 x86_64 Output directory: D:\CEBuild\out这个配置的含义是编译器把最终生成的.exe和.dll输出到指定目录。CE 主程序通常编译成 32 位因为它需要同时兼容 32 位和 64 位进程的调试。Target CPU的选择影响指令集i386 是通用选择兼容性最好。输出路径这个事看着小事实际影响很大。官方构建的cheatengine-x86_64.exe和自编译版放在同一个目录时动态库加载顺序会让人抓狂。CE 运行时会从自身目录加载dbk64.dll等辅助模块如果把自编译版和官方版混在一起可能出现「界面是新版核心库是旧版」的诡异组合。所以单独指定输出目录是第一纪律。编译操作在 Lazarus 菜单Run - Build快捷键CtrlShiftF9。第一次完整编译大概 3 到 5 分钟具体看机器性能。整个编译过程会输出所有单元的处理日志。如果报错停在某个 pas 文件上先记下是哪个单元再查依赖不要盲改。3.3 驱动与 DLL 的单独编译DBK 构建流程主程序编译通过后如果你需要内核级读写能力还得单独编译 DBK 驱动。这部分容易被人忽略因为主程序编译成功了界面能开数值扫描、指针扫描都能用唯独「内核级读写」勾选项置灰。这是正常的说明驱动没装好或没编译。DBK 驱动源码在Cheat Engine/DBK目录下构建方式跟主程序不同它不是 Lazarus 工程是一个独立的驱动项目。常见做法是用 DDK/WDK 环境来构建需要安装 Windows Driver Kit。我在这个环节踩过的坑是用Visual Studio打开.sln去编译结果报一堆 WDK 版本不匹配的错误。后来按官方惯例换成Enterprise Windows Driver Kit的命令行方式编译一次通过。build -ceZ copy dbk64.sys D:\CEBuild\out\dbk64.sys copy dbk32.sys D:\CEBuild\out\dbk32.sys这组命令里build -ceZ是 WDK 的标准构建指令c代表 Clean清理旧的编译产物e代表 Errors出错即停止Z代表强制重新编译所有源文件。编译完成后把驱动文件放进主程序输出目录这样主程序启动时才能正确加载驱动。驱动装上后还需要签名支持64 位系统默认强制签名。如果你的系统开着测试签名模式可以bcdedit /set testsigning on但这属于环境的调试选项具体取舍要在自己的测试机上按需开不是必须项。3.4 编译期条件符号认识不同构建变体的区分点读 CE 源码时你会看到源码里大量存在{$IFDEF}条件编译指令比如{$IFDEF WINDOWS} // Windows 专属逻辑 {$ENDIF} {$IFDEF LINUX} // Linux 专属逻辑 {$ENDIF}WINDOWS这个符号不需要手动定义Lazarus 在目标系统为 Win32/Win64 时会自动加上。真正需要关注的是USE_DBKM、USE_LAZARUS这类功能开关。你可以在工程选项的Compiler Options - Other - Other defines里手动添加或去掉这些符号。例如去掉USE_DBKM可以让主程序不尝试加载驱动模块这在调试主程序 UI 逻辑时能减少干扰但如果你后面要做完整功能这个符号必须保留。建议别随意改默认配置的稳定性最高。4. 核心机制实战Cheat Table 与 Lua 脚本怎么改4.1 CT 文件格式与内部结构Cheat Table.CT文件是 CE 最重要的数据载体。不懂它的内部结构就没法用源码思路去扩展它。CT 文件在底层是一个 XML 描述文件里面记录所有地址、数值类型、激活方式、脚本内容。自己构造 CT 文件比在界面上点来点去高效得多尤其是当你要批量管理几十个地址时。下面是一份最简 CT 文件的核心片段用来理解它的结构CheatTable CheatEngineTableVersion26 CheatEntries CheatEntry ID0/ID Description血量指针基址0x48/Description Address0x1A2B3C4D/Address VariableType4 Bytes/VariableType AssemblerScript // 这里可以放一段使用 aobscan 查找地址的脚本代码 /AssemblerScript /CheatEntry /CheatEntries /CheatTable关键看三个字段Address是绝对地址或指针表达式VariableType决定解释方式AssemblerScript里放的是一段自动汇编脚本——在作弊表加载时可以执行 AOB 扫描、指针计算甚至注码。当你不是手工固定地址而是希望每次启动时动态找到目标时AssemblerScript是核心手段。如果你已经在 CE 界面里做好了一个表想把它转成文本内容快速改批量字段直接在 CE 里用File - Export导出即可。导出的就是上面这种 XML 结构可以批量替换。常见做法是写个 Python 脚本把导出的 XML 里所有Description字段批量翻译或重命名这比重开界面一个个点右键快几个量级。4.2 Lua 脚本注入与 CE 主程序的交互边界CE 6.8.1 的 Lua 脚本功能很强但很多人只停留在openProcess()加readInteger()的层面。源码层面的核心能力是你能在 CE 的 Lua 解释器里调用主程序的内部 API比如枚举已打开进程的模块列表、调用内存分配函数、甚至构造内存断点。一个典型的实战脚本用来遍历目标进程模块local pid getOpenedProcessID() if pid 0 then return 请先选择进程 end local modules enumModules() for i, m in ipairs(modules) do print(string.format(%s - 基址:0x%X, 大小:0x%X, m.Name, m.Address, m.Size)) end这个脚本里的enumModules()是 CE 暴露给 Lua 的枚举函数返回每个模块的名称、基址和大小。它的底层实现会调CreateToolhelp32Snapshot但这层封装的意义是——你不用关心句柄管理和释放CE 内部已经处理好生命周期。输出的模块基址信息配合后面的偏移计算就能搭出「基址 偏移 目标地址」的经典寻址链。注意一个边界Lua 脚本运行在主程序进程内不是目标进程内。所以你通过 Lua 修改目标进程内存时本质是让 CE 主程序调用 Windows API 去写另一个进程。这解释了为什么以管理员身份运行 CE 很重要——跨进程写入需要权限支持。4.3 自学扩展用 Lua 写一个数据记录器把 CE 当数据记录工具用的场景很常见。比如你想记录某单机程序运行过程中某个地址值的变化曲线手工盯屏幕不现实写个脚本放在 CE 里每分钟采样一次最省事。下面这段就是我常用的日志脚本-- 目标地址来自之前手工扫描的结果 local targetAddr 0x1A2B3C4D local logFile io.open(D:/data_log.csv, a) if logFile nil then print(无法创建日志文件) return end for i 1, 20 do local val readInteger(targetAddr) logFile:write(os.time() .. , .. val .. \n) sleep(1000) end logFile:close() print(采样完成结果在 D:/data_log.csv)这段脚本逻辑不复杂循环 20 次每秒读一次目标地址的整数值时间戳和值一起写入 CSV。sleep(1000)的括号里参数单位是毫秒。这个脚本的适用场景是目标地址在你手动扫描后还是有效的日志文件不要和目标进程在同一分区避免磁盘写入干扰目标程序运行时。你完全可以在此基础上加条件判断比如只在值变化时记录或者超过阈值时弹窗提醒——这些都是readInteger与sleep组合出来的能力边界不需要改主程序源码。如果你希望记录脚本在 CE 启动时就自动运行把它存成/autorun/目录下的脚本即可CE 启动时会自动加载。5. 避坑记录CE 6.8.1 编译与使用中的五个高频问题5.1 Lazarus 版本不匹配导致编译中断现象打开工程点编译后立刻弹出错误窗口提示Cant find unit InterfaceBase或Fatal: Cannot find unit Forms。原因Lazarus 版本跟 FPC 版本不配套或者工程里的单元路径指向了不存在的目录。Lazarus 的Forms单元来自 LCL 组件库如果 LCL 没有正确编译就会报找不到。解决确认安装的 Lazarus 版本为 2.0.10 左右通过Tools - Options - Files重新指定 LCL 路径必要时卸载重装一整套 Lazarus 和 FPC不要手动单独升级 FPC。我记得第一次为了省事单独下了新版 FPC结果版本不一致踩了一次大坑。后面重装才恢复。5.2 内核级读写选项置灰现象CE 主程序正常启动进程选择、数值扫描都正常但Active内核级读写勾选不了始终是灰色。原因驱动dbk64.sys没有正确加载。CE 主程序是用户态程序内核级读写走驱动通道驱动缺失或签名不被信任都会导致这个功能无法开启。解决如果不需要内核级功能不做处理即可如果需要使用检查输出目录下三个关键文件dbk64.sys、dbk32.sys、DBK64.dll。确认它们存在并且版本一致然后以管理员权限运行 CE。如果是在 64 位系统上跑驱动签名问题尤其常见需要在测试机上临时开放测试签名模式。5.3 附加进程后主程序假死现象点击「打开进程」选择目标后CE 界面卡住十几秒期间目标进程正常CE 无法响应。原因CE 在附加进程时默认尝试创建调试会话这会与目标进程内已有的调试器冲突。如果目标程序自身有反调试机制或者已经被其他调试工具附加CE 会陷入等待状态。解决打开进程时把Windows 7 兼容选项打开并关闭Kernel mode选项。如果目标进程已经有一个调试器先把它摘掉。经验之谈这一步卡住时最好等 20 秒以上再判断有时是目标进程模块太多加载符号表较慢。5.4 扫描结果列表全为空现象数值扫描条件设置完毕点第一次扫描后结果区域一片空白一个命中地址都没有。原因常见原因有两个一是扫描类型选错了比如目标存储的是 8 字节浮点数但下拉框选了 4 Bytes二是目标进程启用了 ASLR 但没有勾选Fast Scan导致扫描范围被过滤掉。解决先确认变量类型与目标数据一致。数值类型这个事看着基础实际最容易犯尤其是看内存里显示的是 4 字节数据但实际存的是 8 字节。其次在扫描选项里勾选Fast Scan它会用更激进的分块扫描策略对多数目标有效如果勾了还是空换用未加密数值扫一次做交叉验证。5.5 AOB 特征码命中但是偏移不对现象用 aobscan 找到了特征码地址但按照教程里写的偏移计算出的目标地址读出来的值不是预期结果。原因版本更新后数据布局变化特征码位置没变但目标字段相对特征码的偏移变了或者你真的找错特征码了命中的是另一处相似数据。解决不要直接信偏移先对比特征码上下文。在 CE 里看命中的区域的反汇编结果确认特征码后面是什么指令结构再重新算出偏移。我总结的习惯是找一个包含两条以上指令的稳定特征而不是单条 5 字节的短特征这样命中率明显更高。6. 进阶验证把一次完整的内存修改流程在 6.8.1 源码里跑通6.1 从扫描到定位验证一条寻址链的完整方法这部分是我用 CE 源码时最实用的一条经验路径。假设要定位一个单机演示程序里某个数值的内存地址完整做法不是直接搜数值而是分三步下探第一步未知初始值扫描。因为你不清楚目标初始大小用「未知的初始值」建立全部候选地址集。第二步对数值变化做过滤。在目标程序里改变数据值之后CE 选择「改变的数值」再次扫描过滤掉未变化的地址重复几次后候选数量会缩到个位数。第三步用「找出是什么改写了这个地址」下硬件断点。CE 会把内存写操作断下来反汇编窗口直接显示写入该地址的指令。这个指令所在的模块和偏移就构成这个数据的完整寻址链。这个流程读源码时对应到的单位依次是TScan、TScanResult和调试断点处理单元。读源码的价值正是在这种节点显现——当你看到 CE 在「找出是什么改写了」时实际是调用了SetThreadContext设置硬件断点寄存器你就理解为什么某些地址追加断点无效因为硬件断点寄存器数量有限一共四个超出部分 CE 会自动换用软件断点但软件断点对执行写操作的支持不稳定。6.2 验证特征码脚本从手改到自动化的最后一步当你走过上面流程找到稳定的寻址链后剩下的工作就是把手工过程固化成 Lua 脚本。下面是我常用的一套模板在 CE 6.8.1 源码结构里运行无冲突-- 首次扫描目标字符串 local myString 特定标志字符串 local scanResult createMemScan() scanResult:firstScan( vtString, myString, , 0, 0x100000000 ) -- 循环读取扫描结果 local found scanResult:getOnlyResult() if found ~ nil then -- 在结果基础上做偏移计算 local base found 0x1A2 print(string.format(目标地址: 0x%X, base)) else print(未找到结果) endcreateMemScan()是 CE 暴露的扫描对象构造函数返回的scanResult并不是地址而是一个扫描器对象。vtString是 CE 内部定义的字符串类型枚举值用整数常量也可以但代码可读性会差不少。firstScan的参数是数据类型、目标值、附加条件、起始地址、结束地址。这里的0x100000000表示扫描 4G 地址空间如果你的目标进程 64 位要把这个数改大。这段脚本的运用场景是游戏或单机程序每次启动时的基址因为 ASLR 会变化但特征码不变。用这个脚本每次启动时自动重新扫描定位比手工附加后点点点要省很多时间。它的稳定性取决于特征码的唯一性——如果特征码太短可能命中多个位置脚本跑出来的地址就不是预期目标。所以脚本里的字符串或 AOB 特征尽量选取 8 字节以上的稳定序列。6.3 最后的收尾习惯从拿到这份 CE 6.8.1 源码到完整编译我踩过的坑已经写进前面的避坑章节但这些坑不是一次性踩完的——每次重装系统、每次换测试机往往都会在同一个地方翻车。所以我后来给自己定了个强制习惯每换一台新机器编译 CE 之前先把环境检查清单走一遍——确认 FPC 版本、确认 Lazarus 版本、确认输出目录是独立的、确认驱动文件拷到位只要这四个条件同时满足后面编译基本都是顺水推舟。从那以后我每次编译前都强制走一遍这个清单基本不再花一个下午去排查环境问题。希望这份实战笔记能帮你在 CE 6.8.1 源码上少走这些弯路早点进入自己想要的调试状态。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑