本帖最后由 FerryMikey 于 2024-11-25 13:43 编辑
血量是否必存在于人物类中? 本篇文章将带来 il2cpp《大侠立志传》人物属性的逆向分析!
游戏简介:
《大侠立志传》是一款开放世界武侠RPG游戏,更是一款像素版江湖生存模拟器,站在一个初出茅庐的市井小人物视角,还原在江湖底层摸爬滚打最终脱颖而出的逆袭经历。
游戏平台:Steam
发行日期:2023 年 11 月 16 日
游戏价格:¥ 41.40
游戏引擎:Unity il2cpp
准备阶段:
所需工具:il2cppDumper 、CheatEngine、DnSpy
运行il2cppDumper(不需要启动游戏) 后通过对话框分别把游戏的两个文件载入
文件1:WulinSH\GameAssembly.dll
文件2:WulinSH\Wulin_Data\il2cpp_data\Metadata\global-metadata.dat
dump完成后 找到 Il2CppDumper\bin\Debug\net6.0\DummyDll\Assembly-CSharp.dll
把它拖到DnSpy中解析
实战开始:
默认程序集(-)中没看到太多有用的信息 并且游戏的进程名为 WuLin 所以我们开始探索(WuLin)程序集
我们起初肯定是搜索Player相关的类 然后发现了 (BattleActor) 里面有很多战斗相关的字段与函数,比如 UseItem这个函数(顺带看一下)他有三个参数 都很值得关注
参数 1:gameItemInstance 猜测为物品对象
参数 2:BattleActor 猜测为使用者 (caster施法者)
参数 3:count 猜测为使用数量或次数GameItemInstance 大概扫了一眼,确实应该是物品类但是我们留到后面再研究 物品相关
[C++] 纯文本查看 复制代码 public void UseItem(GameItemInstance gameItemInstance, BattleActor caster, int count = 1)
{
}
观察了一下BattleActor 类,其中有很多有意思的函数与变量总结到单独文档中数据_人物战斗属性
其中我们尤为关心的可能是血量 能量等基础常用的值,通过游览可以发现 有个函数引起了我的注意从娘胎里就开始逆游戏的我们一眼就可以看到这个函数 healhealth 是生命而heal是治疗的意思,很多游戏治疗函数都是用的这个名字
其中有三个参数
:
参数1:hp 猜测为治疗的血量值
参数2:ep 猜测为治疗的能量值
参数3:[Optional] BattleActor caster 猜测为治疗的对象 而且有 Optional 属性Optional :这是一个可选参数,可能为空(`null`)
第三个参数 为什么有个可空属性呢 我猜测可能是caster ==null 则治疗本身
[C++] 纯文本查看 复制代码 public void Heal(int hp, int ep, [Optional] BattleActor caster)
{
}
现在问题又来了我们观察了BattleActor 类 但是我们并没能发现 hp相关的成员变量
而BattleActor 类中却有一个成员函数可以 增加血量!
猜测BattleActor 的基类中可能包含 人物基础属性,或者在hp可能被包含在复合对象中
(attleActor 类可能通过组合其他类或结构来管理健康值)
基类中包含基础属性
[C++] 纯文本查看 复制代码 public class Actor
{
int hp;
int ep;
int lv;
//..........
}
public class BattleActor : Actor
{
}
通过组合其他类或结构来管理基础属性
[C++] 纯文本查看 复制代码 public class Attribute
{
int hp;
int ep;
int lv;
//..........
}
public class BattleActor
{
Attribute ActorAttribute;
}
经过对BattleActor 的分析我们不难看出,BattleActor 没有继承其他复杂的类所以第一种情况排除
然后我们在BattleActor 中看到了一个类BattleActorProp 这个类直译是战斗演员的道具,
但是很多时候这个类表示属性合集所以我们仔细观察这个BattleActorProp类中是否有我们想要的字段!
其中也没有我们关心的血量能量等字段,那BattleActor 中的heal函数到底是如何实现的呢?
绝对还是有说法的!再仔细观察 我们发现其中有几个函数比较可疑
Getprop 获取属性
Setprop 设置属性
他传进来的参数是 BattleActorPropTypes的枚举类型 从而进行 Get 与Set
其中Get返回 Int ,Set无返回值
猜测可能是类似字典的结构中存储的这些字段,通过Key 获取 或者设置 而 BattleActorPropTypes
就是对应的Key
我们找到枚举 BattleActorPropTypes 确实有很多人物基础属性的枚举
原版
[C++] 纯文本查看 复制代码 public enum BattleActorPropTypes
{
Hp = 1,
HpMax,
Ep,
EpMax,
NegPoison = 100,
NegInjure,
NegSeal,
NegBleed,
NegBlind,
AccuDef = 200,
AccuAtk,
AccuDefMax = 203,
BattleWine = 300,
BattleMedicine
}
标注版
[C++] 纯文本查看 复制代码 public enum BattleActorPropTypes
{
Hp = 1, // 生命值 (Health Points)
HpMax = 2, // 最大生命值 (Maximum Health Points)
Ep = 3, // 能量值 (Energy Points)
EpMax = 4, // 最大能量值 (Maximum Energy Points)
NegPoison = 100, // 毒抗性 (Poison Resistance)
NegInjure = 101, // 伤害抗性 (Injury Resistance)
NegSeal = 102, // 封印抗性 (Seal Resistance)
NegBleed = 103, // 流血抗性 (Bleeding Resistance)
NegBlind = 104, // 失明抗性 (Blindness Resistance)
AccuDef = 200, // 防御精确度 (Accuracy for Defense)
AccuAtk = 201, // 攻击精确度 (Accuracy for Attack)
AccuDefMax = 203, // 最大防御精确度 (Maximum Defense Accuracy)
BattleWine = 300, // 战斗酒状态 (Battle Wine Status)
BattleMedicine = 301 // 战斗药品状态 (Battle Medicine Status)
}
那我们怎么验证呢?我们先找到 SetProp的函数地址(这里可以通过DnSpy 或者 Mono插件去定位)
然后我们在SetProp函数地址处下断!
这里我们思考一下什么动作可能会导致SetProp断下,其实很简单改变以上所有属性都很有可能通过SetProp 函数去执行,由于他封装了这样的函数所以battleProps[key] += value; 直接通过这种方式改变某属性的值就很不科学,我们通过吃药增加血量发现无法再改函数处断下。
后面考虑BattleActor 应该是战斗对象 ,所以我们在战斗中吃药确实断下了
并且当我们攻击怪物,或者受到怪物攻击 也都断下了
这里我们可以获取到的信息是战斗中的怪物对象也都是BattleActor 类型的。
甚至我们后期可以对这里HOOK 判断如果是怪物受到攻击 让他的血量-9999999 这样的话就可以实现秒杀效果了
这里我们在战斗中吃药(增加生命的),然后来分析函数参数
[C++] 纯文本查看 复制代码 public void SetProp(BattleActor.BattleActorPropTypes key, int value)
基于函数原型的话
RDX应该是传入的Key
R8 应该是更新的Value
断下后RDX==0x1 (对应枚举表应该是Hp的索引) R8==0xAA(170)
断点放开后确实生命值变成了170
由于怪物的血量较少 正常攻击也是一击致命 所以我们测试通过怪物攻击我们 让SetSetProp 实现怪物秒杀玩家的效果。
RDX==1 R8==0x21C 这里我们直接把R8改成0然后运行 可以发现玩家直接被怪物秒杀
相反我们也可以利用此手段直接去秒杀怪物
总结:
游戏整体逻辑相对简单,但是也需要大家有一定的分析经验。其中需要我们对C#语法、 il2cppDumper 、Dnspy Mono插件等 熟练掌握。很多时候我们关心的并不仅仅是某个单一的数据,而是基于某个数据的发散联系与拓展,更是对整个游戏逻辑的理解与分析。
其中人物的战斗属性通过隐式字典存储,这是本篇文章的唯一难点!
后面有机会可以教大家,如何使用类似Mono注入的方式编写il2cpp的游戏插件与Mod!
如果对此类游戏感兴趣,请点赞评论!后面我们会针对评论区留言,发布相关游戏的逆向文章!
|