# GitHub 代码级补环境设计笔记 这份笔记不是“仓库推荐清单”,而是把几套 GitHub 补环境框架拆成更接近未来 skill 设计稿的模块说明。 目标只有一个:为后续构建“JS 逆向补环境 skill”沉淀可复用的代码结构、流程和取舍标准。 ## 1. 这份笔记解决什么问题 围绕下面八个模块做代码级拆解: - 原型链模块 - native `toString` 模块 - 描述符模块 - `navigator` 模块 - `document` 模块 - `Storage` 模块 - 指纹模块 - Proxy 吐环境模块 同时把这些横切难点一起纳入设计视角: - Node 环境检测 - `document.all` 不可检测对象 - 报错异常与堆栈清洗 - `toString.toString` 二次检测 - getter / setter / 方法描述符保护 - 函数 `name` / `length` 对齐 - `webdriver` / `plugins` / `mimeTypes` / `getBattery` - `canvas` / `WebGL` 指纹 - `cookie` / `localStorage` / `sessionStorage` 初始状态 ## 2. 仓库价值映射 这批仓库里,最值得吸收的不是“整仓照抄”,而是分工: - `node-sandbox` - 最像“底层修复层” - 强项是 `document.all`、堆栈清洗、不可变原型、复杂 `navigator.plugins/mimeTypes` 图结构、`getBattery`、`canvas/WebGL` - `qxVm` - 最像“工程化框架层” - 强项是模块拆分、`safefunction`、描述符保护、可配置 Proxy 吐环境、对象级文件布局 - `catvm` - 最像“教学拆解层” - 强项是把补环境拆成非常清楚的步骤,适合沉淀为 skill 的解释文本 - `boda_jsEnv` - 最像“配置采集与批量生成层” - 强项是浏览器侧采集、指纹种子、dispatch 分发、海量对象生成 - `sdenv` - 最像“jsdom + 定点增强层” - 强项是 `document.all` 的 native addon 路线、`navigator` getter native 化、定向浏览器对象修补 - `v_jstools` - 更像“前置采集工具链” - 更适合用于网页分析、注入、导出环境,而不是直接当补环境框架骨架 ## 3. 原型链模块 ### 3.1 最有参考价值的来源 - `node-sandbox/util/init_env.js` - `qxVm/qxVm_sanbox/env/*` - `catvm/__bak/step.js` - `boda_jsEnv/bodaEnv/env/*` ### 3.2 GitHub 里反复出现的核心套路 第一类是“先构造函数,再原型,再实例”。 - 先定义构造函数,并让非法 `new` 时抛 `Illegal constructor` - 再把原型链接对 - 最后才创建实例对象并挂到 `window` 第二类是“实例属性与原型属性分层”。 - 原型上放方法、访问器、只读能力 - 实例上放真实环境值和状态 - 不要把原本应该在原型上的东西全部平铺到实例上 第三类是“复杂对象图要同时补链和补引用关系”。 - `navigator.plugins` 不是普通数组 - `PluginArray -> Plugin -> MimeType` 和 `MimeTypeArray -> MimeType -> enabledPlugin` 是互相引用的图 - 只补值不补图结构,很容易被检测 ### 3.3 代码级启发 `node-sandbox` 的路线更偏真实运行态: - 在 `util/init_env.js` 里大量使用 `Object.setPrototypeOf` - `document.all` 直接挂到 `HTMLAllCollection.prototype` - `navigator.plugins`、`navigator.mimeTypes`、`location`、`Storage` 都明确接回对应原型 `qxVm` 的路线更偏“模块化对象工厂”: - 每个对象一个文件 - 每个文件内都区分构造函数、属性映射、函数映射、保护逻辑 - 适合未来 skill 输出“补某个对象模块” `catvm` 的教学价值非常高: - 明确提出先看真浏览器原型链,再决定属性该放原型还是实例 - 这很适合写进 future skill 的流程说明 ### 3.4 应该沉淀到 future skill 的结论 - skill 必须先做“对象拓扑建模”,再做属性填充值 - skill 输出不能只是一段平铺赋值脚本,至少要区分: - 构造函数层 - prototype 层 - instance 层 - `window/globalThis/self/top` 挂载层 - skill 需要有“复杂图对象模板”: - `PluginArray` - `MimeTypeArray` - `Storage` - `Location` - `Navigator` - `Document` ## 4. native `toString` 模块 ### 4.1 最有参考价值的来源 - `qxVm/qxVm_sanbox/env/TOOLS/vm_protection.js` - `catvm/CatVm2/tools/vm_safefunction.js` - `sdenv/browser/chrome/navigator.js` - `node-sandbox/README.md` ### 4.2 这一模块为什么是补环境的核心 绝大多数初级补环境失败,不是因为值补少了,而是因为: - 函数 `toString()` 露馅 - getter / setter 的 `toString()` 露馅 - `toString.toString()` 二次检测露馅 - 方法名、`name`、`length`、`prototype` 描述符不对 ### 4.3 GitHub 里最值得吸收的套路 `qxVm` 和 `catvm` 都采用了同一类思路: - 接管 `Function.prototype.toString` - 用私有符号保存伪造的 native 字符串 - 调 `func.toString()` 时返回该伪造串 - 再对新的 `Function.prototype.toString` 自身做一层保护 - 从而尽量过掉 `location.reload.toString.toString()` 这一类检测 这是 future skill 里必须作为独立模块存在的能力。 ### 4.4 skill 设计上必须拆出的子能力 - `protectFunctionNative(fn, displayName?)` - 保护普通函数 - `protectGetter(target, key)` - 保护 getter 的 `toString` - `protectSetter(target, key)` - 保护 setter 的 `toString` - `protectFunctionToStringSelf()` - 专门处理 `toString.toString` - `repairFunctionMeta(fn, expectedName, expectedLength)` - 修正 `name`、`length` ### 4.5 这里的关键判断 如果 future skill 没有统一的 native 保护层,那么后面补: - `navigator.getBattery` - `document.cookie` 的 getter/setter - `location.reload` - `Storage.prototype.getItem` - `WebSocket` - `Worker` 这些对象时,几乎都会重复暴露。 也就是说,`toString` 保护层必须是底座,不是附加项。 ## 5. 描述符模块 ### 5.1 最有参考价值的来源 - `qxVm/qxVm_sanbox/env/TOOLS/vm_protection.js` - `node-sandbox/README.md` - `boda_jsEnv/bodaEnv/tools/globalThis.js` - `boda_jsEnv/bodaEnv/env/*` ### 5.2 GitHub 里出现的三条主线 第一条是“统一走 `Object.defineProperty` / `defineProperties`”。 原因很简单: - 直接赋值很难精确控制 `enumerable` - 不方便处理只读属性 - 不方便保持 getter/setter 形态 第二条是“给 prototype 和 constructor 单独做保护”。 `qxVm` 里专门有 `safeDescriptor_addConstructor`: - 保护 `Obj.prototype` - 保护 `Obj.prototype.constructor` 第三条是“getter/setter/function 的描述符保护不能分散写”。 `qxVm.safe_Objattribute` 的价值就在这里: - 输入一组 getter 名 - 输入一组 setter 名 - 输入一组函数名 - 统一做 `safefunction` ### 5.3 未来 skill 必须具备的能力 - 读取目标浏览器对象的描述符快照 - 输出“实例描述符层”和“原型描述符层” - 区分: - 数据属性 - 访问器属性 - 方法属性 - 自动补: - `configurable` - `enumerable` - `writable` - getter / setter 引用 - `constructor` - `Symbol.toStringTag` ### 5.4 不能忽略的细节 - `Function.prototype.toString` 本身的 `name`、`length`、`prototype` 描述符要处理 - 访问器函数本身也要 native 化 - 某些对象在 `window` 上是不可枚举的,不能简单 `window.Foo = Foo` - 仅把值补对而描述符错掉,仍然会被 `getOwnPropertyDescriptor`、`ownKeys`、`propertyIsEnumerable` 打穿 ## 6. `navigator` 模块 ### 6.1 最有参考价值的来源 - `node-sandbox/util/init_env.js` - `node-sandbox/util/cover_function.js` - `qxVm/qxVm_sanbox/env/BOM/Navigator.js` - `sdenv/browser/chrome/navigator.js` - `boda_jsEnv/bodaEnv/env/Navigator.js` - `boda_jsEnv/bodaEnv/config/changeDom.js` ### 6.2 这一模块的真实难点 不是把 `userAgent` 填上就结束,而是要同时处理: - `webdriver` - `userAgent` - `platform` - `vendor` - `languages` - `plugins` - `mimeTypes` - `userAgentData` - `connection` - `webkitPersistentStorage` - `webkitTemporaryStorage` - `getBattery` ### 6.3 哪套代码最值得学什么 `node-sandbox` 最值得学“真实对象图”: - 不是简单数组,而是完整接回 `PluginArray.prototype` - 每个 `Plugin` 下再挂 `MimeType` - 每个 `MimeType` 再反向指回 `enabledPlugin` 这对过 `plugins/mimeTypes` 检测非常重要。 `qxVm` 最值得学“配置映射”: - 用 `$attribute_map` 收口环境值 - getter 内统一做 `Illegal invocation` 检查 - 适合 future skill 产出模块化代码 `sdenv` 最值得学“getter native 化”: - 给 `userAgent`、`platform`、`appVersion`、`vendor`、`connection` 等 getter 做 native 保护 `boda_jsEnv` 最值得学“浏览器侧采集”: - `changeDom.js` 直接从真实浏览器导出 `navigator`、`screen`、`cookie`、`storage` - 适合作为 future skill 的采集前置步骤 ### 6.4 未来 skill 的设计建议 把 `navigator` 拆成五个子模块: - `navigator-basic` - `userAgent`、`appVersion`、`platform`、`vendor`、`language` - `navigator-automation` - `webdriver` - `permissions` - 可能的 headless 标志 - `navigator-plugin-graph` - `plugins` - `mimeTypes` - 双向引用关系 - `navigator-device` - `hardwareConcurrency` - `deviceMemory` - `maxTouchPoints` - `pdfViewerEnabled` - `navigator-async-capability` - `getBattery` - `storage` - `connection` - `userAgentData` ### 6.5 对你当前目标最重要的结论 未来 skill 不能把 `navigator` 当成一个单文件补完任务,而要把它当成“最复杂的检测入口”。 优先级应当是: 1. `webdriver` 2. `plugins/mimeTypes` 3. `userAgent/platform/vendor/languages` 4. `getBattery` 5. `userAgentData` 6. `connection` ## 7. `document` 模块 ### 7.1 最有参考价值的来源 - `sdenv/browser/chrome/document.js` - `sdenv/test/documentAll.test.js` - `node-sandbox/util/init_env.js` - `qxVm/qxVm_sanbox/env/DOM/Document.js` - `qxVm/qxVm_sanbox/env/DOM/document_.js` - `boda_jsEnv/bodaEnv/env/Document.js` ### 7.2 这个模块里最关键的是 `document.all` 这是补环境里必须单独对待的对象。 从现有仓库看,有两条路线: 第一条是 `sdenv` 的 native addon 路线: - 直接通过 `getDocumentAll()` 生成对象 - 测试里明确覆盖: - `da == undefined` 为 `true` - `da === undefined` 为 `false` - `typeof da` 为 `"undefined"` - `da()` 返回 `null` 第二条是 `node-sandbox` 的底层对象路线: - `document.all = new wanfeng.xtd` - 再把其原型接到 `HTMLAllCollection.prototype` - README 里明确说明重新引入底层 `setUndetectable`,就是为了 `document.all` ### 7.3 这里应当得出的结论 纯 JS 路线可以模拟很多对象,但 `document.all` 这种“不可检测对象”很难完全靠普通脚本等价复刻。 因此 future skill 应该把 `document.all` 标为: - `Tier-1: 可完全脚本化` - 普通 `document` 属性 - `cookie` - 常规 getter/setter - `Tier-2: 需底层支持或降级策略` - `document.all` - undetectable object - 特殊 `typeof` ### 7.4 `document` 模块本身怎么拆 建议未来 skill 至少拆成: - `document-basic` - `URL` - `referrer` - `documentURI` - `readyState` - `visibilityState` - `document-cookie` - `cookie` getter/setter - 同步 `jsonCookie` - `document-query` - `getElementById` - `querySelector` - `createElement` - `createExpression` - `document-special` - `all` - `scripts` - `plugins` - 其他站点敏感接口 ### 7.5 skill 应该吸收的流程点 - 先从 Proxy 吐环境里看哪个 `document.xxx` 是 `undefined` - 再确认它是真缺对象、缺值、缺 getter,还是缺原型 - 如果是 `document.all`,应立刻标为特殊分支,而不是继续普通补值 ## 8. `Storage` 模块 ### 8.1 最有参考价值的来源 - `qxVm/qxVm_sanbox/env/BOM/Storage.js` - `catvm/CatVm2/browser/Storage.js` - `node-sandbox/util/init_env.js` - `boda_jsEnv/bodaEnv/env/Storage.js` - `boda_jsEnv/bodaEnv/config/configFormChrome.js` ### 8.2 这一模块看似简单,实际有三个层面 第一层是对象链: - `localStorage.__proto__ === Storage.prototype` - `sessionStorage.__proto__ === Storage.prototype` 第二层是行为: - `length` - `key` - `getItem` - `setItem` - `removeItem` - `clear` 第三层是状态: - 初始 key/value - 业务 cookie 同步 - 站点侧 token / sign / deviceId 缓存 ### 8.3 各仓库的价值 `qxVm` 很适合做通用模板: - `length` 直接按 `Object.keys(this).length` - 方法都集中在 `Storage.prototype` `catvm` 的优点是直观: - 用一个内存对象做状态存储 - 适合做最小可用版本 `node-sandbox` 负责把实例链接对: - `localStorage` / `sessionStorage` 接回 `Storage.prototype` `boda_jsEnv` 最适合处理“预置状态”: - 浏览器侧读取真实 `localStorage` - 导出到配置 - Node 侧初始化时灌回去 ### 8.4 未来 skill 的设计建议 把 `Storage` 拆成两个层面: - `storage-shape` - 原型链 - 方法 - 描述符 - native 保护 - `storage-state` - 真实 key/value - cookie 联动 - 站点初始化快照 这样 skill 才能区分: - “补接口” - “补业务状态” ## 9. 指纹模块 ### 9.1 最有参考价值的来源 - `node-sandbox/util/cover_function.js` - `node-sandbox/util/globalMy.js` - `qxVm/qxVm_sanbox/env/DOM/WebGLRenderingContext.js` - `boda_jsEnv/bodaEnv/tools/toolsFunc.js` - `boda_jsEnv/bodaEnv/config/changeDom.js` ### 9.2 当前最值得学的对象 - `canvas` - `WebGL` - `screen` - `Battery` - `navigator.device*` ### 9.3 两条主流思路 第一条是“固定指纹返回值”。 `node-sandbox`: - `HTMLCanvasElement.getContext('webgl')` 时返回 `WebGLRenderingContext` 风格对象 - 把 `toDataURL` 指向预置的 WebGL 指纹 base64 第二条是“指纹种子池 + 随机取样”。 `boda_jsEnv.toolsFunc.initEnvFingerPrint()`: - 维护 `WebGLRenderingContext_getParameter_37446` 候选池 - 维护 `BatteryManager_level` - 维护 `canvas` / `webgl` base64 - 维护一组 `screen/window` 尺寸组合 这条路线更适合 skill,因为它天然支持配置化。 ### 9.4 `qxVm` 的价值 `qxVm` 不一定提供最真实的完整指纹,但它提供了“对象模块化承载方式”: - `WebGLRenderingContext` 单独成文件 - `getParameter(37445/37446)` 这类关键点可以单独覆写 这很适合 future skill 生成“只修一个检测点”的补丁。 ### 9.5 未来 skill 应该怎么拆 - `fingerprint-canvas` - `toDataURL` - `getImageData` - 2D 绘图结果 - `fingerprint-webgl` - `getParameter` - `getExtension` - `getSupportedExtensions` - `getShaderPrecisionFormat` - `fingerprint-screen` - `screen.width/height` - `availWidth/availHeight` - `window.inner/outer*` - `fingerprint-battery` - `getBattery` - Battery 对象值 ### 9.6 关键原则 不要把指纹模块做成“纯随机”。 更合理的是: - 同一会话内稳定 - 各字段彼此一致 - 与 `navigator.platform`、`screen`、`UA` 尽量匹配 否则反而会制造新的指纹冲突。 ## 10. Proxy 吐环境模块 ### 10.1 最有参考价值的来源 - `qxVm/qxVm_sanbox/env/TOOLS/vm_proxy.js` - `catvm/CatVm2/tools/vm_proxy.js` - `sdenv/browser/chrome/document.js` - `v_jstools` 的浏览器侧注入思路 ### 10.2 为什么这是补环境流程的入口 你前面提到的一点非常关键: - 一般是根据 Proxy 吐出来的值是否为 `undefined` 来判断是否需要补环境 这和现有框架经验是完全一致的。 补环境真正高效的流程,不是先列全对象,而是: 1. 让页面跑起来 2. 用 Proxy / Hook 吐访问路径 3. 定位 `undefined`、异常、非法调用、原型不匹配 4. 只补最短缺口 ### 10.3 各仓库的区别 `catvm` 是最小版: - 只做 `get` / `set` - 适合教学、理解“吐环境”的本质 `qxVm` 是工程版: - `get` - `set` - `has` - `deleteProperty` - `getOwnPropertyDescriptor` - `defineProperty` - `getPrototypeOf` - `setPrototypeOf` - `preventExtensions` - `isExtensible` - `ownKeys` - 并且会继续代理返回的对象和函数 这才是真正适合 future skill 沉淀的路线。 ### 10.4 future skill 应该输出的日志类型 建议统一分成四类: - `missing` - 访问得到 `undefined` - `shape` - 原型链、`instanceof`、`ownKeys`、描述符异常 - `invoke` - `Illegal invocation` - `TypeError` - 参数签名异常 - `fingerprint` - `canvas` - `WebGL` - `battery` - `plugins` ### 10.5 未来 skill 里最值得加的能力 - 路径聚合 - 把高频访问路径折叠统计 - 自动分层建议 - 判断这是缺值、缺 getter、缺原型、缺描述符、缺特殊对象 - 补环境优先级排序 - 先补能让主流程继续执行的缺口 - 回归验证 - 同一路径第二次访问是否仍为 `undefined` ## 11. 横切能力:必须专门预留的特殊模块 这些不属于单一对象模块,但 future skill 必须预留接口。 ### 11.1 Node 环境检测 至少要考虑: - `process` - `global` - `Buffer` - `require` - `module` - Node 特有堆栈痕迹 从当前仓库经验看,真实做法不是只删几个全局,而是: - 调整执行容器 - 清洗错误堆栈 - 避免把 Node 痕迹透给页面逻辑 `node-sandbox` 的 `Utils.Error_get_stack` 很值得吸收,它不是只改一行字符串,而是按规则删除运行时栈帧。 ### 11.2 `document.all` 这是特殊对象,不建议在 future skill 里伪装成“普通 JS 模块可完全搞定”。 更合理的设计是: - 明确标记为特殊能力 - 标注需要: - native addon - V8 API - 或降级兼容策略 ### 11.3 异常与堆栈检测 未来 skill 应至少支持: - 栈帧过滤 - 文件名伪装 - `evalmachine.` 替换 - 去掉内部包装函数痕迹 ### 11.4 `cookie` / `localStorage` / `sessionStorage` 业务态 很多页面不是只检查对象形状,而是直接读业务值。 因此 future skill 不能只生成: - `Storage.prototype.getItem` 还要支持: - 从浏览器导出真实状态 - 灌入 Node 环境 - 保持 cookie 与 storage 的站点态一致 ## 12. 更接近 skill 的补环境流程 结合这些仓库,比较合理的流程不是“补全浏览器”,而是“按缺口驱动”: ### 阶段 1:采集真实环境 - 从真实浏览器导出: - `navigator` - `screen` - `location` - `document` - `localStorage` - `sessionStorage` - `cookie` - 指纹样本 推荐吸收: - `boda_jsEnv/config/changeDom.js` - `v_jstools` 的页面侧注入思想 ### 阶段 2:建立对象骨架 - 先建构造函数 - 再建 prototype - 再建实例 - 再挂全局 - 再补 `Symbol.toStringTag` - 再补 constructor / prototype 描述符 推荐吸收: - `qxVm` - `catvm` - `node-sandbox` ### 阶段 3:统一 native 与描述符保护 - 统一保护函数 - 统一保护 getter/setter - 统一修正 `name` / `length` - 统一处理 `toString.toString` 推荐吸收: - `qxVm/vm_protection.js` - `catvm/vm_safefunction.js` ### 阶段 4:接入 Proxy 吐环境 - 从主对象开始代理: - `window` - `document` - `navigator` - `location` - `localStorage` - `sessionStorage` - 记录: - `undefined` - `Illegal invocation` - `descriptor mismatch` - `prototype mismatch` 推荐吸收: - `qxVm/vm_proxy.js` ### 阶段 5:按缺口补丁 补丁优先级建议: 1. 缺对象导致主流程中断 2. getter / setter 非法调用 3. 描述符异常 4. 指纹异常 5. 特殊对象,如 `document.all` ### 阶段 6:回归验证 - 同一条调用链重跑 - 看是否从: - `undefined` - `TypeError` - `Illegal invocation` - 栈暴露 变成可继续执行 ## 13. 未来 skill 最值得优先实现的代码块 如果后续正式写 skill,最应该先沉淀的不是“某站专用补环境脚本”,而是下面这些通用块: 1. `prototype-builder` - 构造函数、原型、实例、全局挂载 2. `native-protector` - `Function.prototype.toString` - getter / setter / 方法 native 化 - `toString.toString` 3. `descriptor-guard` - `constructor` - `prototype` - `Symbol.toStringTag` - 访问器/方法描述符 4. `navigator-plugin-graph` - `plugins` - `mimeTypes` - `enabledPlugin` 5. `document-special` - `cookie` - `document.all` 6. `storage-state-loader` - cookie / localStorage / sessionStorage 导入 7. `fingerprint-seed` - `canvas` - `WebGL` - `battery` - `screen` 8. `proxy-observer` - 访问路径聚合 - 缺口诊断 - 回归验证 ## 14. 实现路线建议 如果只从当前 GitHub 仓库里提炼可落地的 skill 设计路线,那么未来补环境 skill 的最佳组合不是单选,而是拼装: - 原型链和底层特殊对象:以 `node-sandbox` 为最高参考 - native 保护、描述符保护、Proxy 工程化:以 `qxVm` 为最高参考 - 教学化流程表达:以 `catvm` 为最高参考 - 指纹配置池与浏览器侧采集:以 `boda_jsEnv` 为最高参考 - `document.all` addon / jsdom 定点增强:以 `sdenv` 为最高参考 - 浏览器侧注入与自动采集工具链:以 `v_jstools` 为补充参考 换句话说,这一节存在于 skill 中的目的,是帮助后续实现时快速决定“该参考谁来写哪一层能力”,避免: - 把 skill 写成一堆站点专用补丁 - 把底层能力和采集能力混在一起 - 在 `document.all`、native 保护、Proxy、指纹模块上反复走弯路 更直接的执行建议是: - 用 `catvm` 的流程说明来教怎么做 - 用 `qxVm` 的工程结构来组织代码 - 用 `node-sandbox` 的底层经验补最难的检测点 - 用 `boda_jsEnv` 的采集和指纹配置来喂数据 - 用 `sdenv` 处理 `document.all` 这类特殊对象 它不会直接改变补环境运行结果,但会直接影响: - 模块拆分方式 - 参考代码的优先级 - 后续补环境能力是否能长期复用 这才更接近“可复用补环境 skill”,而不是“又一份站点脚本”。