22 KiB
GitHub 代码级补环境设计笔记
这份笔记不是“仓库推荐清单”,而是把几套 GitHub 补环境框架拆成更接近未来 skill 设计稿的模块说明。
目标只有一个:为后续构建“JS 逆向补环境 skill”沉淀可复用的代码结构、流程和取舍标准。
1. 这份笔记解决什么问题
围绕下面八个模块做代码级拆解:
- 原型链模块
- native
toString模块 - 描述符模块
navigator模块document模块Storage模块- 指纹模块
- Proxy 吐环境模块
同时把这些横切难点一起纳入设计视角:
- Node 环境检测
document.all不可检测对象- 报错异常与堆栈清洗
toString.toString二次检测- getter / setter / 方法描述符保护
- 函数
name/length对齐 webdriver/plugins/mimeTypes/getBatterycanvas/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 路线、navigatorgetter native 化、定向浏览器对象修补
v_jstools- 更像“前置采集工具链”
- 更适合用于网页分析、注入、导出环境,而不是直接当补环境框架骨架
3. 原型链模块
3.1 最有参考价值的来源
node-sandbox/util/init_env.jsqxVm/qxVm_sanbox/env/*catvm/__bak/step.jsboda_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.prototypenavigator.plugins、navigator.mimeTypes、location、Storage都明确接回对应原型
qxVm 的路线更偏“模块化对象工厂”:
- 每个对象一个文件
- 每个文件内都区分构造函数、属性映射、函数映射、保护逻辑
- 适合未来 skill 输出“补某个对象模块”
catvm 的教学价值非常高:
- 明确提出先看真浏览器原型链,再决定属性该放原型还是实例
- 这很适合写进 future skill 的流程说明
3.4 应该沉淀到 future skill 的结论
- skill 必须先做“对象拓扑建模”,再做属性填充值
- skill 输出不能只是一段平铺赋值脚本,至少要区分:
- 构造函数层
- prototype 层
- instance 层
window/globalThis/self/top挂载层
- skill 需要有“复杂图对象模板”:
PluginArrayMimeTypeArrayStorageLocationNavigatorDocument
4. native toString 模块
4.1 最有参考价值的来源
qxVm/qxVm_sanbox/env/TOOLS/vm_protection.jscatvm/CatVm2/tools/vm_safefunction.jssdenv/browser/chrome/navigator.jsnode-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
- 保护 getter 的
protectSetter(target, key)- 保护 setter 的
toString
- 保护 setter 的
protectFunctionToStringSelf()- 专门处理
toString.toString
- 专门处理
repairFunctionMeta(fn, expectedName, expectedLength)- 修正
name、length
- 修正
4.5 这里的关键判断
如果 future skill 没有统一的 native 保护层,那么后面补:
navigator.getBatterydocument.cookie的 getter/setterlocation.reloadStorage.prototype.getItemWebSocketWorker
这些对象时,几乎都会重复暴露。
也就是说,toString 保护层必须是底座,不是附加项。
5. 描述符模块
5.1 最有参考价值的来源
qxVm/qxVm_sanbox/env/TOOLS/vm_protection.jsnode-sandbox/README.mdboda_jsEnv/bodaEnv/tools/globalThis.jsboda_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 必须具备的能力
- 读取目标浏览器对象的描述符快照
- 输出“实例描述符层”和“原型描述符层”
- 区分:
- 数据属性
- 访问器属性
- 方法属性
- 自动补:
configurableenumerablewritable- getter / setter 引用
constructorSymbol.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.jsnode-sandbox/util/cover_function.jsqxVm/qxVm_sanbox/env/BOM/Navigator.jssdenv/browser/chrome/navigator.jsboda_jsEnv/bodaEnv/env/Navigator.jsboda_jsEnv/bodaEnv/config/changeDom.js
6.2 这一模块的真实难点
不是把 userAgent 填上就结束,而是要同时处理:
webdriveruserAgentplatformvendorlanguagespluginsmimeTypesuserAgentDataconnectionwebkitPersistentStoragewebkitTemporaryStoragegetBattery
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-basicuserAgent、appVersion、platform、vendor、language
navigator-automationwebdriverpermissions- 可能的 headless 标志
navigator-plugin-graphpluginsmimeTypes- 双向引用关系
navigator-devicehardwareConcurrencydeviceMemorymaxTouchPointspdfViewerEnabled
navigator-async-capabilitygetBatterystorageconnectionuserAgentData
6.5 对你当前目标最重要的结论
未来 skill 不能把 navigator 当成一个单文件补完任务,而要把它当成“最复杂的检测入口”。
优先级应当是:
webdriverplugins/mimeTypesuserAgent/platform/vendor/languagesgetBatteryuserAgentDataconnection
7. document 模块
7.1 最有参考价值的来源
sdenv/browser/chrome/document.jssdenv/test/documentAll.test.jsnode-sandbox/util/init_env.jsqxVm/qxVm_sanbox/env/DOM/Document.jsqxVm/qxVm_sanbox/env/DOM/document_.jsboda_jsEnv/bodaEnv/env/Document.js
7.2 这个模块里最关键的是 document.all
这是补环境里必须单独对待的对象。
从现有仓库看,有两条路线:
第一条是 sdenv 的 native addon 路线:
- 直接通过
getDocumentAll()生成对象 - 测试里明确覆盖:
da == undefined为trueda === undefined为falsetypeof 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-basicURLreferrerdocumentURIreadyStatevisibilityState
document-cookiecookiegetter/setter- 同步
jsonCookie
document-querygetElementByIdquerySelectorcreateElementcreateExpression
document-specialallscriptsplugins- 其他站点敏感接口
7.5 skill 应该吸收的流程点
- 先从 Proxy 吐环境里看哪个
document.xxx是undefined - 再确认它是真缺对象、缺值、缺 getter,还是缺原型
- 如果是
document.all,应立刻标为特殊分支,而不是继续普通补值
8. Storage 模块
8.1 最有参考价值的来源
qxVm/qxVm_sanbox/env/BOM/Storage.jscatvm/CatVm2/browser/Storage.jsnode-sandbox/util/init_env.jsboda_jsEnv/bodaEnv/env/Storage.jsboda_jsEnv/bodaEnv/config/configFormChrome.js
8.2 这一模块看似简单,实际有三个层面
第一层是对象链:
localStorage.__proto__ === Storage.prototypesessionStorage.__proto__ === Storage.prototype
第二层是行为:
lengthkeygetItemsetItemremoveItemclear
第三层是状态:
- 初始 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.jsnode-sandbox/util/globalMy.jsqxVm/qxVm_sanbox/env/DOM/WebGLRenderingContext.jsboda_jsEnv/bodaEnv/tools/toolsFunc.jsboda_jsEnv/bodaEnv/config/changeDom.js
9.2 当前最值得学的对象
canvasWebGLscreenBatterynavigator.device*
9.3 两条主流思路
第一条是“固定指纹返回值”。
node-sandbox:
HTMLCanvasElement.getContext('webgl')时返回WebGLRenderingContext风格对象- 把
toDataURL指向预置的 WebGL 指纹 base64
第二条是“指纹种子池 + 随机取样”。
boda_jsEnv.toolsFunc.initEnvFingerPrint():
- 维护
WebGLRenderingContext_getParameter_37446候选池 - 维护
BatteryManager_level - 维护
canvas/webglbase64 - 维护一组
screen/window尺寸组合
这条路线更适合 skill,因为它天然支持配置化。
9.4 qxVm 的价值
qxVm 不一定提供最真实的完整指纹,但它提供了“对象模块化承载方式”:
WebGLRenderingContext单独成文件getParameter(37445/37446)这类关键点可以单独覆写
这很适合 future skill 生成“只修一个检测点”的补丁。
9.5 未来 skill 应该怎么拆
fingerprint-canvastoDataURLgetImageData- 2D 绘图结果
fingerprint-webglgetParametergetExtensiongetSupportedExtensionsgetShaderPrecisionFormat
fingerprint-screenscreen.width/heightavailWidth/availHeightwindow.inner/outer*
fingerprint-batterygetBattery- Battery 对象值
9.6 关键原则
不要把指纹模块做成“纯随机”。
更合理的是:
- 同一会话内稳定
- 各字段彼此一致
- 与
navigator.platform、screen、UA尽量匹配
否则反而会制造新的指纹冲突。
10. Proxy 吐环境模块
10.1 最有参考价值的来源
qxVm/qxVm_sanbox/env/TOOLS/vm_proxy.jscatvm/CatVm2/tools/vm_proxy.jssdenv/browser/chrome/document.jsv_jstools的浏览器侧注入思路
10.2 为什么这是补环境流程的入口
你前面提到的一点非常关键:
- 一般是根据 Proxy 吐出来的值是否为
undefined来判断是否需要补环境
这和现有框架经验是完全一致的。
补环境真正高效的流程,不是先列全对象,而是:
- 让页面跑起来
- 用 Proxy / Hook 吐访问路径
- 定位
undefined、异常、非法调用、原型不匹配 - 只补最短缺口
10.3 各仓库的区别
catvm 是最小版:
- 只做
get/set - 适合教学、理解“吐环境”的本质
qxVm 是工程版:
getsethasdeletePropertygetOwnPropertyDescriptordefinePropertygetPrototypeOfsetPrototypeOfpreventExtensionsisExtensibleownKeys- 并且会继续代理返回的对象和函数
这才是真正适合 future skill 沉淀的路线。
10.4 future skill 应该输出的日志类型
建议统一分成四类:
missing- 访问得到
undefined
- 访问得到
shape- 原型链、
instanceof、ownKeys、描述符异常
- 原型链、
invokeIllegal invocationTypeError- 参数签名异常
fingerprintcanvasWebGLbatteryplugins
10.5 未来 skill 里最值得加的能力
- 路径聚合
- 把高频访问路径折叠统计
- 自动分层建议
- 判断这是缺值、缺 getter、缺原型、缺描述符、缺特殊对象
- 补环境优先级排序
- 先补能让主流程继续执行的缺口
- 回归验证
- 同一路径第二次访问是否仍为
undefined
- 同一路径第二次访问是否仍为
11. 横切能力:必须专门预留的特殊模块
这些不属于单一对象模块,但 future skill 必须预留接口。
11.1 Node 环境检测
至少要考虑:
processglobalBufferrequiremodule- Node 特有堆栈痕迹
从当前仓库经验看,真实做法不是只删几个全局,而是:
- 调整执行容器
- 清洗错误堆栈
- 避免把 Node 痕迹透给页面逻辑
node-sandbox 的 Utils.Error_get_stack 很值得吸收,它不是只改一行字符串,而是按规则删除运行时栈帧。
11.2 document.all
这是特殊对象,不建议在 future skill 里伪装成“普通 JS 模块可完全搞定”。
更合理的设计是:
- 明确标记为特殊能力
- 标注需要:
- native addon
- V8 API
- 或降级兼容策略
11.3 异常与堆栈检测
未来 skill 应至少支持:
- 栈帧过滤
- 文件名伪装
evalmachine.<anonymous>替换- 去掉内部包装函数痕迹
11.4 cookie / localStorage / sessionStorage 业务态
很多页面不是只检查对象形状,而是直接读业务值。
因此 future skill 不能只生成:
Storage.prototype.getItem
还要支持:
- 从浏览器导出真实状态
- 灌入 Node 环境
- 保持 cookie 与 storage 的站点态一致
12. 更接近 skill 的补环境流程
结合这些仓库,比较合理的流程不是“补全浏览器”,而是“按缺口驱动”:
阶段 1:采集真实环境
- 从真实浏览器导出:
navigatorscreenlocationdocumentlocalStoragesessionStoragecookie- 指纹样本
推荐吸收:
boda_jsEnv/config/changeDom.jsv_jstools的页面侧注入思想
阶段 2:建立对象骨架
- 先建构造函数
- 再建 prototype
- 再建实例
- 再挂全局
- 再补
Symbol.toStringTag - 再补 constructor / prototype 描述符
推荐吸收:
qxVmcatvmnode-sandbox
阶段 3:统一 native 与描述符保护
- 统一保护函数
- 统一保护 getter/setter
- 统一修正
name/length - 统一处理
toString.toString
推荐吸收:
qxVm/vm_protection.jscatvm/vm_safefunction.js
阶段 4:接入 Proxy 吐环境
- 从主对象开始代理:
windowdocumentnavigatorlocationlocalStoragesessionStorage
- 记录:
undefinedIllegal invocationdescriptor mismatchprototype mismatch
推荐吸收:
qxVm/vm_proxy.js
阶段 5:按缺口补丁
补丁优先级建议:
- 缺对象导致主流程中断
- getter / setter 非法调用
- 描述符异常
- 指纹异常
- 特殊对象,如
document.all
阶段 6:回归验证
- 同一条调用链重跑
- 看是否从:
undefinedTypeErrorIllegal invocation- 栈暴露
变成可继续执行
13. 未来 skill 最值得优先实现的代码块
如果后续正式写 skill,最应该先沉淀的不是“某站专用补环境脚本”,而是下面这些通用块:
prototype-builder- 构造函数、原型、实例、全局挂载
native-protectorFunction.prototype.toString- getter / setter / 方法 native 化
toString.toString
descriptor-guardconstructorprototypeSymbol.toStringTag- 访问器/方法描述符
navigator-plugin-graphpluginsmimeTypesenabledPlugin
document-specialcookiedocument.all
storage-state-loader- cookie / localStorage / sessionStorage 导入
fingerprint-seedcanvasWebGLbatteryscreen
proxy-observer- 访问路径聚合
- 缺口诊断
- 回归验证
14. 实现路线建议
如果只从当前 GitHub 仓库里提炼可落地的 skill 设计路线,那么未来补环境 skill 的最佳组合不是单选,而是拼装:
- 原型链和底层特殊对象:以
node-sandbox为最高参考 - native 保护、描述符保护、Proxy 工程化:以
qxVm为最高参考 - 教学化流程表达:以
catvm为最高参考 - 指纹配置池与浏览器侧采集:以
boda_jsEnv为最高参考 document.alladdon / jsdom 定点增强:以sdenv为最高参考- 浏览器侧注入与自动采集工具链:以
v_jstools为补充参考
换句话说,这一节存在于 skill 中的目的,是帮助后续实现时快速决定“该参考谁来写哪一层能力”,避免:
- 把 skill 写成一堆站点专用补丁
- 把底层能力和采集能力混在一起
- 在
document.all、native 保护、Proxy、指纹模块上反复走弯路
更直接的执行建议是:
- 用
catvm的流程说明来教怎么做 - 用
qxVm的工程结构来组织代码 - 用
node-sandbox的底层经验补最难的检测点 - 用
boda_jsEnv的采集和指纹配置来喂数据 - 用
sdenv处理document.all这类特殊对象
它不会直接改变补环境运行结果,但会直接影响:
- 模块拆分方式
- 参考代码的优先级
- 后续补环境能力是否能长期复用
这才更接近“可复用补环境 skill”,而不是“又一份站点脚本”。