防克隆 / 防自部署跳转机制是商业站(如 toolonline.net)用 obfuscator.io 混淆前端时植入的多层自我保护,核心是 domainLock(域名锁):检测当前域名是否属于白名单,否则把页面重定向回原站。这类逻辑的字符串被加密在字符串数组里,直接 grep "toolonline.net" 永远搜不到,必须先解码。

domainLock(域名锁)——"域名统一检查"的真相

机制(从 format-html.js 反混淆后还原):

// _0x4d0156 = document.domain || location.hostname
// _0x458122  = 允许域名列表(加密串按字符类 .replace 解码后 split(";"))
for (每个允许域名 d) {
  if (hostname 以 d 结尾) { allow = true; break; }   // 后缀匹配
}
if (!allow) {
  // 动态拼出 "https://toolonline.net",然后:
  document.location = "https://toolonline.net";       // 跳回原站
}
  • 允许域名:toolonline.net(后缀匹配,localhost 不匹配 → 必跳)
  • 跳转 URL:https://toolonline.net
  • 字符串是加密的,例如原始串 htSvTtIps:B//AtoWcolSbWgoQMnline.mnReNtWaSPAcDgrNu... 用字符类正则 .replace(/[rOLAX...]/g,"") 解码才得到 URL。所以"解密"不是解码数组,而是 .replace(字符类,"") 这一步。

出现位置

  • common.js:域名锁藏在 _0x52cc81() 函数里(函数名只是混淆标识符)
  • 96 个工具脚本:各自有一个 _0x21a116() 式的独立函数(同一版本 obfuscator.io 生成,结构确定性一致)
  • 内联脚本、纯资源页面通常没有

其余保护层

层位置作用
debugProtection同域名锁函数动态构造 debugger 字符串 + 定时器陷阱,防调试
preventCheatcommon.js屏蔽 F12 / 右键 / 复制 / 选中;冻结 setTimeout/setInterval 定义
cookie 域名锁common.js setToken/removeToken非 localhost 时 cookie domain 写死 toolonline.net,自部署后登录态失效
防 iframecommon.js init()window.top !== window.self 时强制 top.location 跳走
后端依赖全局 webApiSiteHost/staticSiteHostAPI/静态资源指向原站域名

变体与坑(实战记录)

  1. 声明关键字变体:域名锁块的变量声明不一定是 var,也可能是 const/let。strip 正则写成 if(!x){var \w+=new RegExp(...) 会漏掉 const 版本(实战中 HistoryDialog.js、digital-huarong-road.js 等 5 个文件因此漏网,页面仍跳转)。正则要写 (?:var|const|let)\s+\w+=。
  2. 延迟跳转变体:部分"排行榜游戏"页(2048、n-back、数字/序列记忆)有延迟 2-3 秒的硬编码跳转 location.href = "https://toolonline.net/",埋在 javascript-obfuscator 高配变体(_0x5e98a8={hex:hex} 控制流扁平化 + 多参解码器)里,webcrack 也解不出导航语句。本质是登录/排行榜会话检查:未登录 + 非原域 → 延时跳回首页。这类属于后端鉴权依赖,前端无法彻底剥离。
  3. 诊断技巧:延迟跳转用 Network.requestWillBeSent 的 initiator 栈归因不可靠;用 Playwright 反向隔离(stub 其它脚本、只放行一个真实文件)能准确找到跳转所在文件。

万能零改动方案:欺骗 document.domain(已实测)

所有跳转都是 document.domain || location.hostname 后缀匹配 toolonline.net。location.hostname 不可篡改,但 Document.prototype.domain 的 getter 是可覆写的。注入一段守卫,让 document.domain 恒返回 toolonline.net,域名检查全部通过——任何域名/localhost 都能跑,完全不用 hosts 别名,包括反混淆不了的复杂变体(javascript-obfuscator 高配排行榜游戏)。

// 注入到每个页面 <head> 最前(先于站点脚本运行)
<script>try{Object.defineProperty(Document.prototype,"domain",{get:function(){return "toolonline.net"},configurable:true});}catch(e){}</script>

实测:2048 / n-back / 序列记忆 / 数字记忆 / 对称积木 5 个"排行榜游戏"页在纯 localhost 下全部不再跳转(此前必须靠 hosts 别名)。注意:Location.prototype.href 和 hostname 在 Chromium 里都不在原型上(各自实例的非可配置属性),无法覆写,所以打 href/hostname 主意的路子都不通。

hosts 别名 = 零代码完整方案(已实测验证)

所有跳转(域名锁 + 延迟登录检查)都是 hostname 条件触发(不匹配 toolonline.net 才跳)。所以把自托管站点跑在 toolonline.net 后缀的主机名上,任何代码都不用改就能通过全部检查,cookie 域名逻辑也自洽。

  • 本地验证:127.0.0.1 toolonline.net 加入 hosts,访问 http://toolonline.net:8080/
  • 无需改 hosts 的测试法:Chromium 启动参数 --host-resolver-rules=MAP toolonline.net 127.0.0.1,Playwright 里直接访问 http://toolonline.net:8091/...
  • 实测:alias 域名下 17/17 页面全通过(含 2048/n-back 等此前跳转的页面),唯一外部请求只剩评论 API

中和方式(三种,按侵入性排序)

  1. hosts 别名(零代码改动):在 hosts 加 127.0.0.1 toolonline.net,用 http://toolonline.net:8080 访问。因为检查是后缀匹配,本地自托管直接通过,cookie 域名逻辑也自洽。适合本地学习/复现。
  2. 删域名锁块(真正部署):删除 if(!X){... document.location=<url>;} 这个确定性结构(正则 /if\(!\w+\)\{var \w+=new RegExp\([^,\n]+,\s*['"]g['"]\)[^}]*?\w+\[\w+\]\[\w+\]=\w+;\}/g 全站批量命中)。项目脚本:toolonline.net/_scripts/strip_domainlock.py。
  3. 拆调用点(脆弱):只注释掉 _0x52cc81(); 这类调用。函数名随站点更新会变,且工具脚本的调用点容易被漏掉。

相关


本文归档于个人知识库 · 领域:CS & AI · 模块:04 Project_CS_AI/Web