胡桃讲编程 | 外挂的另一种方法与防御 —— 对象(JS ES262) 作者龙沅可https://blog.csdn.net/2503_93347234/article/details/161179063?fromshareblogdetailsharetypeblogdetailsharerId161179063sharereferPCsharesource2503_93347234sharefromfrom_link温馨提示本节课为模拟内容仅用于网络安全原理科普、前端底层技术学习严禁用于制作真实游戏外挂、违规作弊工具违者需自行承担全部法律责任。上一篇我们通过钩子函数的攻防逻辑拆解了游戏外挂最经典的实现思路通过拦截、劫持函数调用篡改游戏输入与输出实现恶意作弊。但在 JavaScript遵循 ES262 标准的游戏开发体系里函数只是行为的载体对象才是数据的根源。原神等网页端、H5 端游戏的所有玩家数据、伤害数值、资源信息、角色状态全部存储在 JS 对象中。今天我们就顺着上一节实战课的内容深挖外挂的另一类核心实现手段直接篡改游戏核心对象。相比于钩子函数需要拦截函数执行对象篡改式外挂更加简单粗暴、隐蔽性更强同时我们基于 ECMA-262 官方规范搭建完整的对象层面反作弊防御体系用可直接运行的代码彻底讲透这套攻防逻辑。一、钩子函数的局限性绕开函数直接改数据在上一节实战代码中我们模拟的外挂逻辑是劫持游戏输入函数、伤害计算函数通过修改函数的入参和返回值实现作弊。但很多外挂开发者发现一个漏洞我为什么要拦截函数直接修改存储数据的对象游戏运行时会直接读取篡改后的数据作弊更直接。在 ECMAScript 262 规范中JavaScript 的普通对象默认具备两大特性属性可写writable:true对象的属性可以被随意修改属性可配置configurable:true可以删除、新增、修改对象属性原型链可篡改所有对象共享Object.prototype原型污染可批量篡改全局对象。以原神为例游戏内会存在一个全局player对象存储玩家全部核心数据生命值、体力、原石、伤害倍率、角色暴击率。钩子函数需要监听attack()攻击函数、getHp()获取血量函数而对象外挂直接修改player.damage 9999游戏渲染、战斗逻辑读取对象数据时直接生效完全绕开了函数拦截。二、基于 ES262 规范的 3 种对象外挂实现代码示例我们先搭建极简的原神游戏模拟环境复现真实的 JS 游戏数据结构再依次实现 3 类主流对象外挂全部基于原生 ES262 语法无第三方库。基础游戏环境代码// 模拟原神全局玩家对象游戏原生代码 const player { hp: 100, // 生命值 damage: 10, // 基础伤害 primogem: 1600, // 原石数量 critRate: 0.05 // 暴击率5% } // 模拟原神攻击逻辑读取对象内的伤害值输出战斗结果 function attackMonster(){ console.log(造成伤害${player.damage}当前生命值${player.hp}) } // 原生运行效果 attackMonster() // 输出造成伤害10当前生命值1001. 直接篡改对象属性最基础外挂这是新手外挂最常用的方式直接修改全局对象的属性值绕过所有函数校验// 外挂代码直接篡改玩家对象伤害、原石 player.damage 9999; player.primogem 999999; attackMonster() // 输出造成伤害9999当前生命值100直接实现秒杀作弊原理ES262 规定普通对象属性默认writable: true外部脚本可直接修改属性值游戏没有做任何对象锁定数据直接被篡改。2. 原型链污染外挂批量篡改全局数据比直接修改对象更隐蔽的手段基于 JS 原型链机制修改Object.prototype污染所有对象// 外挂代码污染Object原型所有对象自动继承篡改后的属性 Object.prototype.damage 99999; // 新建玩家对象自动继承篡改后的伤害值 const newPlayer {hp:100}; console.log(newPlayer.damage) // 输出99999批量实现全角色秒杀这类外挂针对游戏内批量生成的角色、怪物对象一次篡改全局生效排查难度远高于直接修改单个对象。3. 完全替换游戏核心对象深度劫持直接覆盖全局对象的引用游戏后续所有逻辑读取的都是外挂伪造的假对象// 外挂代码直接替换全局player对象 window.player { hp:999999, damage:99999, primogem:9999999 } attackMonster() // 直接读取伪造对象作弊生效三、基于 ES262 规范的完整防御体系可直接落地针对以上 3 类对象外挂我们严格遵循 ECMA-262 规范从对象冻结、属性锁定、原型隔离、数据校验4 个层面搭建反作弊防御对应每一种外挂给出精准的防御代码。1. 冻结对象禁止修改、删除、新增属性防御直接篡改ES262 提供Object.freeze()方法冻结对象后属性不可修改、不可删除、不可新增原型不可修改。// 游戏原生防御代码冻结玩家核心对象 const player Object.freeze({ hp: 100, damage: 10, primogem: 1600, critRate: 0.05 }) // 尝试外挂篡改严格模式下直接报错非严格模式静默失败 player.damage 9999; console.log(player.damage) // 输出10篡改失效注意Object.freeze()是浅冻结若对象内嵌套子对象子对象仍可修改我们用递归实现深冻结// 深冻结函数递归冻结所有嵌套对象 function deepFreeze(obj){ Object.keys(obj).forEach(key{ if(typeof obj[key]objectobj[key]!null){ deepFreeze(obj[key]) } }) return Object.freeze(obj) } // 深度冻结玩家对象彻底禁止篡改 const player deepFreeze({hp:100,damage:10})2. 密封对象 属性描述符精准管控每一个属性如果游戏需要部分属性可修改比如生命值随战斗变化不能完全冻结我们用Object.defineProperty()修改属性描述符设置writable:false禁止修改configurable:false禁止删除。const player {} // 精准锁定伤害、原石不可修改生命值允许修改 Object.defineProperty(player,damage,{value:10,writable:false,configurable:false}) Object.defineProperty(player,primogem,{value:1600,writable:false,configurable:false}) Object.defineProperty(player,hp,{value:100,writable:true}) player.damage 9999; // 篡改失败 player.hp 80; // 正常修改3. 原型隔离禁止原型链污染防御原型污染外挂直接锁定Object.prototype禁止修改Object.freeze(Object.prototype) // 外挂尝试污染原型直接失效 Object.prototype.damage 99999; // 失败4. 数据校验 哈希校验终极兜底防御即使对象被篡改我们对核心数据做哈希加密校验读取对象数据时验证哈希值不一致直接判定作弊。// 哈希校验函数简化版真实项目用加密算法 function getHash(obj){ return obj.hpobj.damageobj.primogem } const player Object.freeze({hp:100,damage:10,primogem:1600}) const safeHash getHash(player) // 游戏读取数据前校验 function checkPlayer(){ const nowHash getHash(player) if(nowHash!safeHash){ console.log(检测到对象篡改外挂拦截) return false } return true }四、钩子函数 VS 对象篡改游戏反作弊的完整闭环结合上一节的钩子函数攻防我们可以完整梳理 JS 游戏外挂的两大核心维度函数层面钩子拦截函数调用、篡改入参出参针对游戏行为作弊数据层面对象直接篡改存储数据的对象针对游戏数值作弊。很多开发者只做了函数层面的钩子防御却忽略了对象篡改导致外挂绕开函数直接修改数值。完整的反作弊体系必须函数拦截 对象锁定 数据校验三层防护。从 ECMA-262 底层规范来看JavaScript 的灵活性既是开发优势也是安全漏洞。原神等游戏的网页端、模拟器端外挂80% 都是基于钩子劫持 对象篡改实现。理解了对象的本质、属性描述符、原型链我们就能从根源上规避这类漏洞。本系列课程从钩子函数到对象篡改完整拆解了 JS 游戏外挂的底层逻辑全部基于原生 ES262 规范没有使用任何黑魔法。后续我们会继续深挖闭包、作用域、WebAssembly等更深层的游戏安全攻防用胡桃讲编程的通俗方式讲透前端与网络安全的底层原理。