ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

AI辅助逆向分析游戏内存:从结构体推断到Python实现

AI辅助逆向分析游戏内存:从结构体推断到Python实现 AI逆向分析游戏数据在今天已经不算新鲜事但很多人仍然习惯把这个词和外挂、破解画上等号。实际工程中逆向分析内存数据还有更多正当用途比如游戏Mod开发、崩溃问题排查、数据可视化、引擎调试以及安全防护方向的分析。传统做法门槛不低要懂C语言结构体要理解Windows进程虚拟内存要会用调试器还要做大量手工扫描和十六进制阅读。而现在借助AI大模型可以把这些重复劳动大幅压缩让AI从一段内存Dump中推断结构体布局让AI生成跨进程读取内存的Python脚本让AI解释编译器对齐和字节序具体是怎么回事。这篇文章用一个可复现的学习案例把“AI逆向分析游戏数据”和“AI编程实现功能”这两件事串成一条完整链路。我们会先在本地编写一个模拟“玩家角色”数据的小型C程序把它当作分析目标然后通过Windows API跨进程读取它的内存数据再用AI辅助推断字段偏移和数据类型最终用AI辅助编写出一个能读取、解析、打印结果的Python工具。整个流程会覆盖概念、环境、实现、验证、排错和合规边界。先说一个原则本文所有示例都基于自己编写、自己启动的进程只做读取和解析不涉及对目标进程的写入。未经授权修改商业游戏数据可能违反用户协议和相关法律不在本文讨论范围内。1. 先搞清游戏数据逆向分析到底在分析什么1.1 游戏数据不只存在于内存里但内存最关键游戏运行时的数据会分散在多个位置理解这一点很重要因为不同位置的数据格式差异非常大。数据位置典型内容分析特点二进制资源包模型、贴图、音频、关卡文件静态文件重点在于容器格式和资源压缩方式配置与存档文件JSON、XML、二进制序列化数据可离线分析适合先看字段语义再看存储格式运行时内存玩家对象、技能状态、NPC、场景数据结构最完整也最动态通常需要跨进程读取网络数据包客户端与服务器交互协议需要抓包并用已知消息推断协议字段在Mod开发和安全分析中内存数据往往是最优先关注的对象。原因是磁盘上的存档可能经过压缩、加密或序列化但程序一旦运行数据一定会在内存中以接近源码结构体布局的形式存在。比如一个玩家对象的等级、血量、物品列表在内存里就对应某个进程地址空间中的一段连续字节。只要能读出来并对照结构体定义就能完整还原这个对象的运行时状态。1.2 跨进程读取内存的底层机制Windows系统里每个进程都有独立的虚拟地址空间。进程A不能直接访问进程B的地址空间但操作系统提供了一组调试和诊断接口其中最常用的是ReadProcessMemory。这个API允许一个进程在拥有足够权限时读取另一个进程的指定虚拟地址内容。调试器、内存分析工具、进程管理器底层都是类似机制。这里有一个容易误解的地方从外部读取到的地址是目标进程的虚拟地址不是物理内存地址。虚拟地址经过CPU和操作系统的内存管理单元映射到物理内存外部进程不需要关心物理页落在哪里只需要向系统提供目标进程句柄和虚拟地址即可。在实际读取前还要理解内存页和内存保护属性的概念。Windows把虚拟地址空间划分成若干页每页有状态和保护属性例如MEM_COMMIT表示已分配且可访问PAGE_READWRITE表示可读可写。跨进程读取时如果目标地址所在页不可读或者地址根本不属于目标进程ReadProcessMemory就会失败。所以排错时第一步往往不是怀疑脚本写错而是怀疑地址是否正确、页面是否可读。1.3 AI在逆向分析流程里的能力边界AI大模型在逆向分析中能做的是把分析流程中“重复、模式化、强规则”的部分自动完成。典型场景包括根据十六进制Dump推断C结构体定义并计算字段偏移。生成Python ctypes代码实现进程打开、内存读取、结构解析。解释编译器对齐、字节序、调用约定等规则。根据错误信息判断失败原因并给出修复方案。但AI不能替代的部分同样清晰它无法保证推断出的结构体一定正确无法判断目标进程是否允许分析无法代替开发者在真实项目里做最终验证。换句话说AI适合做“解释和生成”不适合做“决策和背书”。2. 搭建一个可合法练习的实验环境2.1 为什么不直接拿商业游戏练手很多刚接触逆向分析的人会直接打开一款商业游戏试图分析它的内存数据。这样做的问题有三个第一多数游戏的用户协议明确禁止第三方工具读写进程内存存在合规风险第二商业游戏结构复杂、加密和混淆普遍新手很难判断是结构理解错了还是工具写错了第三游戏版本一旦更新数据和地址都会变化学习过程难以复现。更稳妥的做法是自己编写一个目标程序把真实逆向分析中的核心难点保留下来但把安全性和可复现性放在自己手里。本文的目标程序就是一个模拟“玩家对象”的C语言进程逻辑简单结构清晰又足以覆盖跨进程读取、偏移计算、结构解析、地址漂移这些关键知识点。2.2 编写一个模拟玩家对象的目标程序下面是目标程序的完整代码。它定义了一个Player结构体包含玩家名、等级、生命值、速度、物品数组最后打印出当前进程的PID和player变量在内存中的地址然后阻塞等用户按回车退出。#include stdio.h #include string.h #include windows.h typedef struct { char name[32]; // 玩家名NUL结尾 int level; // 等级 int hp; // 当前生命值 int maxHp; // 最大生命值 float speed; // 移动速度 int items[8]; // 物品ID数组 } Player; int main(void) { Player player { TestPlayer, 42, 360, 500, 12.5f, {100, 107, 114, 121, 128, 135, 142, 149} }; printf(PID: %lu\n, (unsigned long)GetCurrentProcessId()); printf(Player Address: %p\n, (void *)player); fflush(stdout); getchar(); return 0; }编译命令如下推荐使用 MinGW-w64 的 gcc也可以使用 Visual Studio 的 cl 编译器gcc -O0 -o target_demo.exe target_demo.c这里要解释为什么使用-O0。编译优化在某些情况下会改变变量的存储位置甚至把整个对象优化掉。作为学习目标-O0能让结构体在内存中的布局直观且稳定方便和后续读到的数据相互验证。真实项目里即使开启优化也可以用调试器确认最终布局但学习阶段越简单越好。运行目标程序后会看到类似下面的输出PID: 12345 Player Address: 0x00007FF7B512F800PID和地址每次运行可能不同这是ASLR机制导致的。后面的Python工具需要输入这两个值。2.3 环境要求与检查清单本文案例对系统要求不高但有一个关键点需要提前确认Python位数必须和目标进程位数一致。如果目标程序是64位进程Python也必须是64位否则ReadProcessMemory在地址转换时可能截断64位指针导致读取失败。项目推荐版本用途检查方式Windows10/11 x64运行目标程序和读取脚本系统设置中查看设备信息C编译器MinGW-w64 gcc 或 MSVC编译目标程序gcc --version或clPython3.8 x64运行AI生成的读取工具python --versionAI工具任意大模型对话产品推断结构体、生成代码本文示例提示词可直接使用可选用调试器x64dbg 或 WinDbg人工确认内存布局附加到目标进程后查看内存注意如果OpenProcess成功但ReadProcessMemory一直报错误码 299ERROR_PARTIAL_COPY优先检查Python位数和目标进程位数是否一致。3. AI辅助分析目标数据结构3.1 制造一个“不完全了解”的视角真实逆向分析的起点很少是一张白纸通常已经有若干线索游戏截图中看到的数值、配置文件里的字段名、引擎文档中的类型定义、程序运行时的已知输出等。本案例中我们假设不知道结构体定义但从目标程序输出知道PID和player变量的地址也从常识推测“玩家对象”大概率包含名字、等级、生命值、物品列表这些字段。这种“假设驱动的分析”方式很重要。如果一上来就全内存扫描效率低不说也很难判断扫到的数字是不是目标字段。反过来先假设结构体的大致组成再读取一段内存用AI辅助验证假设路径就会清晰很多。3.2 把内存Dump交给AI推断结构体先用一段简化的Python读取逻辑从目标进程地址读取80字节原始数据。为什么是80字节按照字段大小估算32字节名字、4字节等级、4字节生命值、4字节最大生命值、4字节速度、32字节物品数组合计80字节。这次读取只是为了拿原始数据完整读取工具在下一节实现。import ctypes from ctypes import wintypes kernel32 ctypes.WinDLL(kernel32, use_last_errorTrue) PROCESS_QUERY_INFORMATION 0x0400 PROCESS_VM_READ 0x0010 pid 12345 address 0x00007FF7B512F800 handle kernel32.OpenProcess( PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, 0, pid, ) buf ctypes.create_string_buffer(80) bytes_read ctypes.c_size_t(0) ok kernel32.ReadProcessMemory( handle, ctypes.c_void_p(address), buf, 80, ctypes.byref(bytes_read), ) if ok: print(buf.raw.hex( )) else: raise ctypes.WinError(ctypes.get_last_error()) kernel32.CloseHandle(handle)在本文目标程序中这段地址80字节的内容是0x0000: 54 65 73 74 50 6C 61 79 65 72 00 00 00 00 00 00 0x0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0020: 2A 00 00 00 68 01 00 00 F4 01 00 00 00 00 48 41 0x0030: 64 00 00 00 6B 00 00 00 72 00 00 00 79 00 00 00 0x0040: 80 00 00 00 87 00 00 00 8E 00 00 00 95 00 00 00把这段十六进制数据交给AI提示词可以这样写我有一段从 Windows x64 进程内存读取的十六进制数据。 已知信息 1. 这是一个模拟游戏玩家对象的内存片段 2. 前面可能包含玩家名字符串 3. level 可能是32位整数值很可能是42 4. 后面还包含 hp、maxHp、speed、items 等字段。 请推断可能的 C 结构体定义标注每个字段的偏移量并解释为什么。 数据 54 65 73 74 50 6C 61 79 65 72 00 ...AI通常会给出一份和源码几乎一致的结构体定义typedef struct { char name[32]; // offset 0 int level; // offset 32 int hp; // offset 36 int maxHp; // offset 40 float speed; // offset 44 int items[8]; // offset 48 } Player;这个结果和实际结构体完全吻合因为16进制中2A 00 00 00就是小端序的4268 01 00 00是360F4 01 00 00是50000 00 48 41是12.5f。注意AI推断出的结构体不能盲信必须用后面的可读解析结果和真实源码相互验证。AI擅长识别规律但无法确认编译器的最终布局是否包含隐藏填充。3.3 用AI生成特征扫描脚本应对地址漂移上面示例中我们直接使用了目标程序打印的地址。但真实场景里许多游戏为了安全问题会开启ASLR每次启动进程后同一对象所在的绝对地址都会变化。此时就不能写死地址而要在目标进程的内存空间中扫描特征字节。扫描思路是用VirtualQueryEx遍历目标进程的可读内存区域对每个可读页调用ReadProcessMemory然后在读取结果中查找特征串。比如要查找玩家名可以搜索ASCII字节54 65 73 74 50 6C 61 79 65 72要查找物品数组可以搜索特征整数序列。AI生成的扫描脚本骨架可以这样# 伪代码真实代码需要补充 MEMORY_BASIC_INFORMATION 的定义 addr 0 while True: mbi query_virtual_memory(handle, addr) if mbi.BaseAddress is None: break if mbi.State MEM_COMMIT and is_readable(mbi.Protect): size min(mbi.RegionSize, MAX_READ_SIZE) chunk read_memory(handle, mbi.BaseAddress, size) pos chunk.find(pattern) if pos 0: print(found at, hex(mbi.BaseAddress pos)) break addr mbi.BaseAddress mbi.RegionSize使用AI生成这类代码时要让AI明确知道运行环境Windows x64、Python 3.8、ctypes、目标进程是自研学习程序。这样AI会倾向于生成带错误处理的健壮版本而不是一个不能运行的玩具。3.4 让AI解释对齐和字节序结构体偏移计算中最容易出错的是内存对齐。下面是一个典型例子struct Example { char a; int b; short c; };在默认对齐规则下int b不会紧跟在char a后面而会从偏移4开始因为4字节类型的地址必须4字节对齐。AI可以直接给出这类结构的字段偏移表字段类型大小默认对齐实际偏移achar110bint444cshort228结构体总大小12让AI做这种计算比手算更快而且不容易漏掉尾部填充。但关键仍是“会验证”写一段代码打印结构体大小和成员偏移和AI的结论比对。4. AI编程实现内存数据分析工具4.1 工具设计输入、处理和输出现在我们把目标程序结构理解了开始实现一个真正可复用的Python分析工具。工具要做的事情很简单输入目标进程PID和玩家对象地址。打开目标进程请求只读权限。从指定地址读取80字节。按照第三节推断出的结构体解析字段。打印可读结果。字段数据字典如下字段偏移类型说明name0char[32]玩家名称NUL结尾level32int32等级hp36int32当前生命值maxHp40int32最大生命值speed44float32移动速度items48int32[8]物品ID数组4.2 AI生成读取进程内存的核心代码跨进程读取的核心是OpenProcess和ReadProcessMemory。使用Python的ctypes标准库即可实现不需要安装第三方模块。import ctypes from ctypes import wintypes kernel32 ctypes.WinDLL(kernel32, use_last_errorTrue) kernel32.OpenProcess.argtypes [wintypes.DWORD, wintypes.BOOL, wintypes.DWORD] kernel32.OpenProcess.restype wintypes.HANDLE kernel32.ReadProcessMemory.argtypes [ wintypes.HANDLE, wintypes.LPCVOID, wintypes.LPVOID, ctypes.c_size_t, ctypes.POINTER(ctypes.c_size_t), ] kernel32.ReadProcessMemory.restype wintypes.BOOL kernel32.CloseHandle.argtypes [wintypes.HANDLE] kernel32.CloseHandle.restype wintypes.BOOL PROCESS_QUERY_INFORMATION 0x0400 PROCESS_VM_READ 0x0010 def open_process(pid): handle kernel32.OpenProcess( PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, 0, pid, ) if not handle: raise ctypes.WinError(ctypes.get_last_error()) return handle def read_memory(handle, address, size): buf ctypes.create_string_buffer(size) bytes_read ctypes.c_size_t(0) ok kernel32.ReadProcessMemory( handle, ctypes.c_void_p(address), buf, size, ctypes.byref(bytes_read), ) if not ok: raise ctypes.WinError(ctypes.get_last_error()) return buf.raw def close_process(handle): if handle: kernel32.CloseHandle(handle)这段代码有几个关键设计需要解释。PROCESS_QUERY_INFORMATION | PROCESS_VM_READ表示只申请进程查询和内存读取权限不申请PROCESS_VM_WRITE。这是有意为之的我们的工具目标是分析和解析不是修改目标进程。显式声明argtypes也很重要。ctypes默认在传递整数时不校验类型而在64位进程中地址参数超过32位范围后如果不声明LPCVOID指针可能被截断导致ReadProcessMemory失败或读取到错误地址。create_string_buffer(size)在这里只是充当一块可写的本地缓冲区真正被填满的数据来自目标进程。bytes_read用来接收系统实际读取的字节数虽然当前场景下每次读取80字节但保留这个参数能让排错更容易。4.3 AI生成结构体解析代码拿到原始字节后需要按结构体偏移解析。Python标准库的struct模块很适合做这件事。import struct def parse_player(data): if len(data) 80: raise ValueError(data length must be at least 80 bytes) name_bytes data[0:32] name name_bytes.split(b\x00)[0].decode(utf-8, errorsreplace) level, hp, max_hp struct.unpack_from(iii, data, 32) speed struct.unpack_from(f, data, 44)[0] items struct.unpack_from(8i, data, 48) return { name: name, level: level, hp: hp, max_hp: max_hp, speed: speed, items: list(items), }格式串iii表示按小端序解析三个int32。为什么是小端Windows x64默认使用小端字节序所以内存中42显示为2A 00 00 00而不是00 00 00 2A。f表示小端float8i表示连续8个int32。名称字段使用split(b\x00)[0]是为了在NUL处截断避免把名字后面未初始化的00字节都解码成字符串。errorsreplace是兜底策略遇到异常字节时用替换字符而不是直接抛异常。4.4 组装主流程并验证结果主流程把上面几步串起来输入PID和地址打开进程读取内存解析打印。if __name__ __main__: pid int(input(PID: )) address int(input(Address(hex): ), 16) handle open_process(pid) try: raw read_memory(handle, address, 80) player parse_player(raw) for key, value in player.items(): print(f{key}: {value}) finally: close_process(handle)运行目标程序获取PID和地址然后运行Python脚本输入这两个值。预期输出PID: 12345 Address(hex): 0x00007FF7B512F800 name: TestPlayer level: 42 hp: 360 max_hp: 500 speed: 12.5 items: [100, 107, 114, 121, 128, 135, 142, 149]和C程序的初始化数据完全一致说明整条“读取 - 解析 - 验证”链路已经打通。4.5 与AI协作编写代码的节奏用AI编程时不建议直接把整个工具需求丢给它然后照单全收。更稳妥的节奏是分步协作让AI根据数据结构定义生成open_process、read_memory和parse_player三个独立函数。自己组装主流程保持对数据流的控制。用一个已知数据样本测试解析函数确认字段偏移正确。有了报错信息后把完整错误回输给AI让它修复。这种方式的收益是AI负责重复劳动你负责系统设计和最终判断。如果AI一次生成几百行代码审查成本反而更高。5. 常见问题与排查路径5.1 先看整体错误对照表跨进程内存读取的报错通常比较集中下面是一张速查表。问题现象可能原因检查方式处理方案OpenProcess返回空错误码5权限不足或Python位数与目标进程不一致以管理员身份运行确认目标进程位数用管理员终端重跑换一致位数PythonReadProcessMemory返回False错误码299地址不可读或地址跨了无效内存页对照目标程序打印的地址用调试器查看从地址前后偏移读取先枚举可读区域读取成功但name乱码结构体偏移猜错或字节序错误打印原始hex对照ASCII字符把hex给AI重新推断确认小端序每次运行地址都不稳定ASLR导致绝对地址变化对比多个进程实例中的地址用模块基址加偏移或特征码扫描AI生成代码跑不起来缺少参数类型声明、ctypes指针截断、位数不匹配看报错检查函数签名把完整报错回给AI让其修正5.2 权限错误错误码5现象是OpenProcess返回空ctypes.get_last_error()得到5。常见原因有两个。第一个是权限不足。虽然读取自己启动的控制台进程通常不需要管理员权限但某些进程或系统配置会限制跨进程访问。排查时可以先用管理员身份打开终端再运行目标程序和Python脚本。如果管理员身份下能读取说明普通权限被拦截。第二个是位数不匹配。64位Python读64位进程正常但32位Python读64位进程时OpenProcess有可能成功而后续读取失败或地址截断反过来读32位进程也可能遇到问题。排查方法是查看目标进程位数打开任务管理器找到目标进程确认“平台”列是x64还是x86。5.3 读取失败错误码299ReadProcessMemory返回False且错误码为299时表示部分复制失败通常意味着目标地址在读取范围内存在不可读页面。处理顺序是确认地址属于目标进程最简单的方法是和目标程序打印的Player Address做对照。如果地址正确尝试只读取地址附近的小块数据例如偏移0、偏移16、偏移64。使用调试器附加目标进程在地址处查看内存确认页面是否可读。如果目标地址确实不可读说明结构体不在这个地址或者分析对象已经被系统移走。在写扫描脚本时真正的健壮版本必须先枚举可读页面再逐个页面读取不能在不可读页面上直接ReadProcessMemory。5.4 数据解析结果不对读取成功但parse_player输出的字段严重偏离预期比如name是一串乱码、level变成几十万。这大概率不是读取问题而是结构体偏移猜测错误。处理方法是回头把原始hex打印出来先人工确认几个线索。比如查找ASCII可见字符查找2A 00 00 00这种整数特征再重新让AI推断。如果AI第一次给出的结构体不对可以把“字段的值范围”告诉它例如“level应该在0到100之间hp应该在200到1000之间”AI会重新基于约束调整推断。5.5 地址漂移的处理同一份目标程序每次启动后打印的Player Address都不同这是ASLR的正常表现。解决方式有两种。一种是用模块基址加相对偏移。对于自己写的简单程序可以在启动时打印传出模块基址和结构体相对偏移计算规则是绝对地址 模块基址 相对偏移。另一种是特征码扫描。在目标进程的可读页面中搜索54 65 73 74 50 6C 61 79 65 72或2A 00 00 00 68 01 00 00这类特征字节找到后返回命中地址。这也是真实逆向分析中最常用的定位方式之一。6. 合规边界、AI协作提示词与工程化建议6.1 合规边界哪些场景可以分析哪些不能碰逆向分析游戏数据本身是中性的最终性质取决于目标和用途。可以做的场景包括分析自己编写、自己启动的进程数据。分析已获得授权或开源的程序。基于官方Mod API或开放接口进行扩展开发。在获批的安全研究项目中定位运行时数据问题。不能做的场景包括制作游戏外挂、自动化作弊工具。绕过付费、授权或安全验证机制。读取、修改未经授权的商业游戏数据。分析并破解商业软件的授权逻辑。这里要特别说明读取内存和写内存之间技术上的确只差一个WriteProcessMemory调用。但修改未经授权的游戏数据正是外挂和作弊的核心动作也是多数游戏用户协议明确禁止的。本文示例从一开始就没有申请PROCESS_VM_WRITE权限全程只读。如果你将来需要修改自己拥有权限的程序也应该在确认授权和合规之后单独设计写入逻辑在商业游戏环境中这条路径不应被使用。6.2 可复用的AI协作提示词模板如果你也打算用AI辅助分析自研程序的运行时数据可以直接使用下面这套提示词模板。当你要让AI推断结构体时你是Windows进程内存分析助手。 运行环境Windows 11 x64Python 3.11 x64。 我使用 ctypes 调用 ReadProcessMemory 读取了一个进程的数据。 目标进程是我自己编写的学习程序。 读取地址0x00007FF7B512F800 读取长度80字节。 原始十六进制... 请完成以下任务 1. 推断可能的C结构体并标注字段偏移 2. 说明对齐和字节序 3. 给出Python struct解析代码。当你要生成跨进程读取脚本时请用 Python ctypes 生成一个模块功能是 1. 按PID打开进程权限只读 2. 用 ReadProcessMemory 读取指定地址的80字节 3. 按以下结构体解析... 4. 打印可读结果。 要求 - 包含参数类型声明 - 包含错误处理 - 不导入第三方库。使用AI生成代码时尽量把环境信息、输入格式、输出格式、约束条件一次说清楚。这样做出来的代码往往比“帮我写一个读内存的Python脚本”这类模糊问题靠谱得多。6.3 从练习到项目的工程化检查清单如果这个技能要应用到真实项目中而不是只做课堂练习下面这些检查项值得逐条过一遍。权限最小化脚本只申请真正需要的权限绝不连带申请写权限。配置外置PID、结构体偏移、字段定义不要硬编码放在配置文件或命令行参数中。日志完整记录每次打开进程、读取内存、解析字段的地址、长度、错误码方便事后回查。异常隔离区分权限失败、地址不可读、解析失败三类异常分别给出可读提示。样本验证准备一份已知结构的数据样本先做单元测试再连接真实进程。回归测试程序改版后结构体字段可能变化必须重新跑一遍验证流程。6.4 后续学习路径这条路线如果继续深入建议按顺序补充以下知识C语言基础指针、数组、结构体、内存对齐。Windows进程与虚拟内存PID、句柄、虚拟地址、内存页、保护属性。调试器基础x64dbg或WinDbg的附加进程、查看内存、查找引用。Python工程能力ctypes、struct、日志、命令行参数。AI辅助开发方法提示词描述、代码审查、回归验证。扩展方向官方Mod框架、运行时数据可视化、安全分析流程。AI在逆向分析中的价值是把过去需要数小时手工完成的重复劳动缩到几分钟但它并没有取消逆向分析的核心能力要求理解数据布局、理解进程模型、理解编译器和操作系统如何协作。如果你打算往这个方向深入先把C语言结构体和Windows虚拟内存基础补扎实再用AI辅助写脚本、解释日志、推导结构体。AI可以帮你写代码但最终判断和分析方向仍然要由你自己负责。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进