news 2026/4/16 17:25:50

【khbox补环境-3】原型链与 Illegal Invocation 保护机制

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【khbox补环境-3】原型链与 Illegal Invocation 保护机制

khbox 的项目已放gitee, https://gitee.com/sugarysp/khbox_pro ,欢迎各位大佬使用测试。

目前完成的有

  • 调用链追踪
  • document.all
  • 原型保护以及toString
  • c层调用链日志保存
  • illegal 保护机制
  • ps:可能有bug

待做:

  • 异步
  • console.log 业务代码保存日志

目录

  • 核心挑战
  • 原型链继承体系
  • Function.prototype.toString 保护
  • Illegal Invocation 保护机制
  • 测试验证
  • 总结


核心挑战

1. 原型链继承

浏览器中的 DOM 对象有着复杂的继承关系:

Document → Node → EventTarget → Object Navigator → Object Window → EventTarget → Object

检测手段:

// 网站检测代码Object.getPrototypeOf(Document.prototype).constructor.name==='Node'// 必须为 true'addEventListener'inEventTarget.prototype// 必须为 true

2. Native Code 伪装

真实浏览器的原生方法调用toString()会返回[native code]

// 真实浏览器navigator.javaEnabled.toString()// "function javaEnabled() { [native code] }"// 普通 JS 实现functionjavaEnabled(){returnfalse;}javaEnabled.toString()// "function javaEnabled() { return false; }" ❌ 暴露了

3. Illegal Invocation 保护

浏览器原生方法强制检查this的合法性:

// 真实浏览器constuaGetter=Object.getOwnPropertyDescriptor(Navigator.prototype,'userAgent').get;uaGetter.call(window);// TypeError: Illegal invocation// 普通 JS 实现functionget_userAgent(){returnthis._ua;}get_userAgent.call(window);// 可以执行 ❌ 行为不一致

原型链继承体系

实现方式

KhBox 通过在 sandbox 中暴露构造函数,结合 JSDOM 的原型链实现正确的继承关系:

// main.js - 构建 sandboxconstsandbox={// 暴露实例(经过 KhBox Proxy 包装)document:proxyDocument,navigator:proxyNavigator,window:proxyWindow,// 暴露构造函数(直接引用 JSDOM 的)Document:dom.window.Document,Navigator:dom.window.Navigator,Window:dom.window.Window,Node:dom.window.Node,EventTarget:dom.window.EventTarget,// ...};

验证结果

// 测试原型链Document.prototype.constructor.name// "Document"Object.getPrototypeOf(Document.prototype).constructor.name// "Node"Object.getPrototypeOf(Node.prototype).constructor.name// "EventTarget"// 测试属性定义位置'cookie'inDocument.prototype// true'addEventListener'inEventTarget.prototype// true

Function.prototype.toString 保护

技术原理

利用 V8 的Private Symbol机制,为函数对象存储自定义的 toString 返回值:

// native_protection.ccLocal<Function>CreateNativeWrapper(...){// 创建原生函数包装器Local<FunctionTemplate>tpl=FunctionTemplate::New(isolate,callback);Local<Function>nativeFunc=tpl->GetFunction(context).ToLocalChecked();// 生成 [native code] 字符串std::string native_code;if(strcmp(method_type,"get")==0){native_code="function get "+name+"() { [native code] }";}else{native_code="function "+name+"() { [native code] }";}// 🔥 关键:用 Private Symbol 存储nativeFunc->SetPrivate(context,GetNativeStringSymbol(isolate),String::NewFromUtf8(isolate,native_code.c_str()).ToLocalChecked());returnnativeFunc;}

Private Symbol 的优势

  • 不可枚举:Object.keys()无法获取
  • 不可访问:JavaScript 层无法读取或修改
  • V8 内部支持:Function.prototype.toString会优先读取此 Symbol

验证效果

navigator.javaEnabled.toString()// "function javaEnabled() { [native code] }" ✅String(navigator.javaEnabled)// "function javaEnabled() { [native code] }" ✅

Illegal Invocation 保护机制

什么是 Illegal Invocation?

当调用浏览器原生方法时,如果this不是预期的对象类型,会抛出TypeError: Illegal invocation

// 合法调用navigator.userAgent// ✅// 非法调用constuaGetter=Object.getOwnPropertyDescriptor(Navigator.prototype,'userAgent').get;uaGetter.call(window)// ❌ TypeError: Illegal invocation

实现架构

KhBox 通过三层检查机制实现 Illegal Invocation 保护:

第一层:焊死到 Prototype 上
// toolFuncs.jslazyLoader:function(receiver,target,prop,classNameFromC){// ...// 🔥 焊死到 Constructor.prototype 而不是实例上constwindow=khBox.memory.jsdom.window;constprotoTarget=window[protoOwner]?.prototype||receiver;return{impl:khBox.envFuncs[implKey],target:protoTarget,// Navigator.prototypeclassName:protoOwner,// "Navigator"// ...};}

为什么要焊死到 Prototype?

// 如果焊死到实例上:navigator.userAgent// ✅ 可以访问Navigator.prototype.userAgent// ❌ 拿不到我们的 wrapper// 焊死到 Prototype 上:navigator.userAgent// ✅ 继承自 prototypeNavigator.prototype.userAgent// ✅ 直接获取到我们的 wrapperObject.getOwnPropertyDescriptor(Navigator.prototype,'userAgent').get// ✅ 受保护
第二层:className 权威来源

使用 JSON 配置中的类名而不是 JSDOM 的constructor.name,因为jsdom某些可能不够全,优先用自己脱下来的环境:

// watcher.cc - Watch 函数voidWatch(constv8::FunctionCallbackInfo<Value>&args){// 优先使用传入的 className(来自 JSON 配置)constchar*class_name=nullptr;if(args.Length()>=2&&args[1]->IsString()){String::Utf8Valuecn(isolate,args[1]);class_name_str=*cn;class_name=class_name_str.c_str();}// 存储到 Proxy 的 InternalFieldLocal<Object>proxy=CreateWatcherProxy(isolate,target,source_path,class_name);// ...}
// main.js - 调用时明确指定类名constproxyNavigator=addon.watch(navigator,'Navigator');constproxyWindow=addon.watch(window,'Window');

降级策略:

// toolFuncs.jsconstclassName=classNameFromC// 优先:JSON 配置||target[Symbol.toStringTag]// 降级 1||target.constructor?.name// 降级 2:JSDOM||"Unknown";

测试验证

测试脚本

完整的测试代码位于khbox_vm/pages/demo_proto/input.js

// Part 1: 原型链测试console.log('Document.prototype.constructor.name:',Document.prototype.constructor.name);console.log('Document 的父类:',Object.getPrototypeOf(Document.prototype).constructor.name);// Part 2: toString 保护console.log(navigator.javaEnabled.toString());// "function javaEnabled() { [native code] }"// Part 3: Illegal InvocationconstuaGetter=Object.getOwnPropertyDescriptor(Navigator.prototype,'userAgent').get;// 正常调用console.log(navigator.userAgent);// ✅ 成功// 非法调用uaGetter.call(window);// ❌ TypeError: Illegal invocation

运行结果

$nodemain.js## Part 1: 原型链继承测试✅ Document.prototype.constructor.name: Document ✅ Document.prototype 的父类: Node ✅ Node.prototype 的父类: EventTarget## Part 2: toString 保护机制测试✅ navigator.javaEnabled.toString():functionjavaEnabled(){[native code]}## Part 3: Illegal Invocation 保护测试---3.1正常调用 --- ✅ navigator.userAgent: Mozilla/5.0(Windows NT10.0;Win64;x64)... ✅ navigator.javaEnabled():false---3.2非法调用 ---[Illegal Invocation Check]Expected:'Navigator', Actual:'Window'✅ 抛出 Illegal Invocation: Illegal invocation

总结

KhBox 框架通过以下机制实现了完整的浏览器行为模拟:

1. 原型链继承

  • 在 sandbox 中暴露 JSDOM 的构造函数
  • 保持正确的prototype链关系
  • 属性定义位置符合浏览器标准

2. Native Code 伪装

  • 使用 V8 Private Symbol 存储自定义 toString
  • Function.prototype.toString返回[native code]
  • JavaScript 层无法篡改或检测

3. Illegal Invocation 保护

  • 焊死到Constructor.prototype而不是实例
  • className 优先使用 JSON 配置(权威来源)
  • 运行时严格检查this的类型
  • 类型不匹配时抛出TypeError: Illegal invocation


更多文章,敬请关注gzh:零基础爬虫第一天天

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/16 12:08:00

ResNet18二分类神器:1小时1块,轻松搞定男女图像识别

ResNet18二分类神器&#xff1a;1小时1块&#xff0c;轻松搞定男女图像识别 1. 为什么选择ResNet18做性别识别&#xff1f; 作为APP开发者&#xff0c;当你需要为应用增加性别识别功能时&#xff0c;ResNet18是个理想的起点。这个经典的深度学习模型有三大优势&#xff1a; …

作者头像 李华
网站建设 2026/4/15 19:29:52

无需Token验证!用MiDaS镜像实现高精度单目深度感知

无需Token验证&#xff01;用MiDaS镜像实现高精度单目深度感知 概述&#xff1a;让AI“看见”三维世界 在计算机视觉领域&#xff0c;单目深度估计&#xff08;Monocular Depth Estimation, MDE&#xff09; 是一项极具挑战又充满潜力的技术。它旨在仅通过一张2D图像&#xf…

作者头像 李华
网站建设 2026/4/15 20:50:58

无需Token!基于MiDaS的CPU友好型深度估计实践

无需Token&#xff01;基于MiDaS的CPU友好型深度估计实践 &#x1f310; 技术背景&#xff1a;从2D图像中“看见”3D空间 在计算机视觉领域&#xff0c;单目深度估计&#xff08;Monocular Depth Estimation&#xff09; 是一项极具挑战性的任务——仅凭一张普通2D照片&#xf…

作者头像 李华
网站建设 2026/4/16 12:03:25

深度估计入门必看|AI单目深度估计-MiDaS镜像全解析

深度估计入门必看&#xff5c;AI单目深度估计-MiDaS镜像全解析 &#x1f310; 技术背景&#xff1a;从2D图像到3D空间感知的跨越 在计算机视觉领域&#xff0c;单目深度估计&#xff08;Monocular Depth Estimation&#xff09;是一项极具挑战性的任务&#xff1a;仅凭一张普…

作者头像 李华
网站建设 2026/4/16 12:02:59

智能万能抠图Rembg:体育用品去背景案例

智能万能抠图Rembg&#xff1a;体育用品去背景案例 1. 引言 1.1 业务场景描述 在电商、广告设计和内容创作领域&#xff0c;图像去背景是一项高频且关键的预处理任务。尤其对于体育用品这类产品——如篮球、跑鞋、运动护具等——其表面常带有复杂纹理、反光材质或细小结构&a…

作者头像 李华