逆向信息保存方法——软件地图、知识图谱与找功能的手法

一句话

逆向里最值钱的不是地址和偏移,而是对象之间的关系,以及怎么证明这个关系存在。 单个软件的逆向信息用「软件地图」分十层保存;知识用「对象地图 / 发现过程 / 规律库」三种文档组织,记关系不记地址;找功能靠「功能发现策略库」记录为什么这样找,而不是找到了什么。


一、软件地图:单个软件逆向信息的十个保存层次

做单个软件的逆向分析,最重要的不是保存所有细节,而是建立一个能快速定位功能的知识库——软件地图(Software Map)。十个层次:

  1. 软件基本信息——名称/版本/编译时间/大小/MD5/开发商/保护方式(无壳/UPX/VMProtect/Themida)。
  2. 模块结构图——主程序 → 登录/配置/网络/数据库/升级/UI 模块,或 MainWindow → 菜单/工具栏/配置页。这是后面所有分析的索引。
  3. 功能列表(最重要)——表格:功能/菜单路径/入口函数/关键类/备注。每个功能记录 UI、入口、关键函数、调用链。
  4. 函数分类库——不要只记地址(sub_140123450 几天后全忘);建「地址 → 重命名 → 功能」表(sub_140123450 → VerifyUserPassword → 校验密码)。
  5. 类结构——C++ 程序尤其重要:类成员、虚表地址(VFTABLE)、成员偏移(+0x08 m_UserInfo)。
  6. 关键数据结构——结构体:大小/字段偏移表(+0x00 Magic / +0x04 Cmd / +0x08 User)。
  7. 消息流程——网络程序极其重要:点击登录 → 组包 → 加密 → 发送 → 接收 → 解析 → 界面。
  8. Hook 点和关键断点——后期最有价值:登录成功/发送网络包/AES 加密/MD5 等位置。
  9. 字符串索引——"login failed" → sub_140123450,以后直接搜索。
  10. 最终软件地图——分析完成后形成总览树(Main → Login/Device/Update/Network),半年后重开 IDA/Ghidra/x64dbg 也能几分钟恢复上下文。

工具建议:IDA(重命名/结构体/注释)、Ghidra(Data Type Manager/Bookmark)、x64dbg(断点列表/注释库)。项目目录:.i64 / notes.md / funcs.xlsx / structs.h / protocol.md / screenshots——notes.md 比 IDA 数据库更重要


二、逆向知识图谱:记录关系而不是地址

三种文档(比「分析日志」价值高得多)

1
2
3
对象地图(最重要)    Player 有哪些成员、怎么得到 Player(对象树)
发现过程 分析步骤 + 结论(为什么这样找)
规律库 可复用套路(多入口汇聚同一对象等)

指针链怎么记

错误方式:[[[[base+20]+18]+30]+10]——版本更新全变。

正确方式:

1
2
访问路径:Module → GameManager → PlayerManager → Player
加上:怎么证明这个关系存在(哪条调用链、哪个断点现场)

偏移是编译结果(字段顺序/对齐/编译器优化/版本更新/继承都会改变),逻辑关系几年不变。 记语义位置不记数字。

为什么不要用 Excel

名称+地址表半年后基本废了;推荐 Obsidian/Markdown,目录:Objects / Analysis / Patterns / Structs


三、数据视角与功能视角

  • 数据视角:HP 在哪(Player+0x10)。
  • 功能视角:恢复生命有哪几种途径(改 HP / call 吃药 / hook 扣血 / 加防御 / 减伤 Buff / 无敌 / 改伤害计算)。
1
一个功能对应多个数据,一个数据对应多个功能

攻击力通常不是一个变量,会出现在人物面板/伤害计算/技能预览/装备评分/AI 评估/战斗日志等多处——记录引用点而不是单个地址:

1
2
3
4
5
6
攻击力引用点
CharacterPanel 读取
DamageSystem 读取
AI 读取
EquipSystem 修改
BuffSystem 修改

终极结构是功能树形成的知识网络:功能 → 对象 → 数据 → 函数


四、功能发现策略库:记录「为什么这样找」而不是「找到了什么」

很多逆向笔记最后变成 Attack = Player+0x1A8,但真正有价值的是我是怎么想到去找 Attack 的——地址会变,思路不会变。

记录格式:

1
2
3
4
攻击力
可观察现象:装备变化 / 等级变化 / Buff变化 / 技能变化 / 伤害变化
尝试路径:1. 装备武器 100→120 失败 → 2. 升级 120→125 失败 → 3. Buff 125→150 发现变化
结论:攻击力参与 Buff 计算

功能三层:显示值 / 计算值 / 最终值

1
2
3
显示值  角色面板攻击力(Attack = 100)
计算值 BaseAttack + WeaponAttack + BuffAttack
最终值 实际打出去的 Damage

很多时候找到的是 UI 显示,而不是真实值——笔记应写明类型。

各属性切入点表

1
2
3
4
5
血量      受伤 / 治疗 / 死亡
蓝量 放技能 / 回蓝 / 升级
攻击力 装备变化 / Buff变化 / 等级变化 / 伤害变化
防御力 装备变化 / Buff变化 / 受到伤害变化
经验值 击杀怪物 / 任务奖励 / 升级

判别方法与功能定位套路库

攻击力为何总能搜到大量相似代码——因为 Attack 出现在面板/伤害计算/技能预览等多处。判别方法:

1
2
装备变化时触发、等级变化时触发、Buff变化时触发、攻击时不触发 → 属性重算函数
攻击时触发、每次伤害前触发、读取攻击力 → 伤害计算函数

四类套路库(新游戏直接套):

1
2
3
4
属性类  攻击/防御/血量/蓝量   优先观察:装备 / Buff / 等级
战斗类 伤害/暴击/闪避 优先观察:攻击瞬间 / 扣血瞬间 / 死亡瞬间
物品类 名称/数量/品质 优先观察:背包 / 鼠标悬停 / 掉落
状态类 隐身/眩晕/中毒/无敌 优先观察:Buff列表 / 状态机 / 标志位

人物状态切入点:出生/移动/攻击/受伤/死亡/复活/升级/装备变化/Buff 增删/地图切换/目标切换/选中——很多关键对象在这些事件附近暴露。

每个功能额外记录「最快路径」:

1
2
定位成功方案:✓ 装备变化 → 重算属性 → Attack
定位失败方案:✗ 伤害变化 / 敌人扣血

几年后分析另一个 RPG,这种推理过程比地址还值钱。


五、找功能的实战手法

1. 难以搜索的数据:从对象和引用入手

「人物在=1、退出=0」的浮点,不要暴力搜几十万个 1.0f。五种方法:

1
2
3
4
5
方法一  先找 Player 对象,展开结构(+0x000 ~ +0x400 逐个标注)
方法二 分析引用 HP 的代码(movss [rcx+10] → 向上看 cmp [rcx+150],1)
方法三 分析创建/销毁(Player Create/Destroy 一定会写入该字段)
方法四 找使用者(绘制/更新/同步/碰撞/AI 都需要判断该状态)
方法五 多实例结构体对比(在线人物 +0x1C=1.0,离线人物 +0x1C=0.0)

规律:

1
2
3
难以搜索的数据
↓ 不要从数据入手
先找所属对象 → 找结构体 → 找引用者 → 找写入者

2. 成倍增加数据的五种解释

「退出后全变 0」往往只是 Player 对象被清空/释放,整个结构体归零——不能证明是存在标志。数据 1,2,4,8,16 倍增的五种可能:

1
2
3
4
5
① 经验表      等级1→100 / 等级2→200 / 等级3→400
② 倍率系数 1.0 / 2.0 / 4.0(Multiplier 而不是攻击力)
③ 位标志 Flags:00000001 / 00000010 / 00000100,每位一个状态
④ 对象数量 capacity *= 2(动态数组扩容)
⑤ 浮点指数 DamageRate / ExpMultiplier

老逆向最常见的坑:浮点读成整数位掩码——内存 3F800000(显示 1.0f)实际上程序在当 uint32_t flag 用。检查清单:① 是否真被当作 float(movss vs mov)② 变化时是否对应状态开关 ③ 附近成员是否也是 1/2/4/8 模式。

3. 倍率字段的识别与验证

「默认 1.0、改成 2 就 2 倍攻击、5 就 5 倍」→ 大概率是 AttackMultiplier / DamageMultiplier / FinalDamageRate,而不是 Attack——因为正常伤害公式 Damage = Attack - DefenseAttack * SkillRate - Defense 下,攻击翻倍伤害不会精确翻倍。

验证方法:不要继续观察伤害,观察谁在读取它——断在该地址,若每次攻击触发 movss xmm0,[rcx+150] + mulss xmm1,xmm0(伤害 × 这个值),基本实锤是倍率。

常见误判:默认值刚好是 1.0 的 DamageMultiplier 被当成 OnlineFlag(改成 10 变 10 倍伤害才发现)。

4. 缩小搜索空间与记录失败路径

前人的优势不是「试得多」,而是缩小搜索空间:从对象关系出发(字段是否在 Player 附近)、从功能关联出发(哪些东西和攻击有关:装备/Buff/技能/等级/伤害结算)——建立 功能 → 对象 → 数据 而不是 数据 → 功能

一改就崩溃:很多看起来像数值的内容实际是状态/索引/引用/内部控制数据。

值得记录的是失败路径(哪些路走不通):

1
2
3
4
功能:攻击倍率
直接搜索:失败(默认值过于常见)
可利用关系:Player / 装备 / Buff / 伤害系统
已排除方法:全内存枚举

5. 观察数据变化:快照对比与结构体展开

不打印几十万个地址,而是:

1
2
3
4
5
① 内存快照对比   状态A(攻击前)→ 状态B(攻击后)差分
+0x10 100→95 / +0x48 1.0→2.0 / +0x150 0→1
② 记录对象的所有写入 offset 0x10 changed: old=100 new=95(只观察 Player 结构体)
③ 结构体展开 Player +0x00 ~ +0x400 逐个标注
④ 调用链比数据更重要 知道 Damage = BaseDamage * Rate 就知它是倍率

隐藏属性大多通过结构体邻域分析被发现:HP 附近还有什么字段(DamageRate/CritRate/MoveSpeed 大概率也在附近),研究 Player 周围几百字节,而不是整个进程几百 MB。

6. 结构体重建:还原整个结构体

不能一键还原,但可部分还原。五种方法:

1
2
3
4
5
① 内存展开        直接 dump Player+0x000 ~ +0x1000
② 找引用(最重要) mov eax,[rcx+10] 触发条件=受伤 → +10 是 HP
③ 自动结构体恢复 IDA / Ghidra Struct Reconstruction
④ RTTI MSVC 未关 RTTI 时直接看到类名
⑤ 虚表 Player* 开头是代码地址 → 完整对象(Update/Render/Attack/Move)

高手路线(拿到 Player* 之后优先做这个,而不是继续搜数值):

1
2
3
4
5
1. 连续 Dump 整个 Player 对象
2. 不同状态下做差分(装备武器 DumpB、升级 DumpC)
3. 标记变化偏移(+150 攻击、+154 力量)
4. 建立 Player 结构体
5. 分析这些偏移被哪些代码访问

六、核心结论

1
2
3
4
5
6
7
单个软件逆向信息 → 软件地图十层(功能列表最重要)
知识组织 → 对象地图 / 发现过程 / 规律库;记关系不记地址,记语义位置不记偏移
找功能 → 功能发现策略库(为什么这样找)+ 切入点表 + 判别方法 + 定位套路库 + 最快路径
难搜数据 → 从对象和引用入手,不要从数据入手
倍增数据 → 经验表 / 倍率系数 / 位标志 / 对象数量 / 浮点指数
倍率字段 → 观察谁读取它(movss + mulss)
还原结构 → 内存展开 / 找引用 / 自动恢复 / RTTI / 虚表 / dump + 差分

真正要找的不是几十万个地址,而是少量对象、成员和访问关系;逆向文档里最值钱的不是地址和偏移,而是对象之间的关系以及如何证明这个关系存在。


与其他线的关系

  • 与逆向工程论(07-工程控制/07):07 讲逆向工程是什么、记什么(对象关系>地址)、怎么证明(证据链)、怎么工程化;本文展开「记什么」的完整细节(软件地图十层、三种文档、功能发现策略库)与找功能的实战手法
  • 与复刻软件(07-工程控制/04):复刻是产品层面的黑盒逆向,本文是二进制层面的信息保存;复刻的规格还原与本文的功能发现策略互补
  • 与拆解程序(12-游戏/13):拆解程序是本文在游戏领域的应用(四层递进、软件地图、记录关系、功能树、数据/功能视角)