@xbrowser/cli 1.18.2 → 1.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{browser-TFFGGXOA.js → browser-NVC7MTML.js} +2 -2
- package/dist/{browser-AEQS6325.js → browser-PBBC7RAV.js} +2 -2
- package/dist/{browser-BEERPZPB.js → browser-Y6B3NS4Q.js} +1 -1
- package/dist/{cdp-driver-4UAV56SQ.js → cdp-driver-73F5CVH2.js} +1 -1
- package/dist/{cdp-driver-KT6HL33W.js → cdp-driver-QYFA4QTP.js} +1 -1
- package/dist/{cdp-driver-NNBPNFF2.js → cdp-driver-Z2PLAO42.js} +103 -19
- package/dist/{chunk-NA7IP6PO.js → chunk-75TLPVVZ.js} +13 -2
- package/dist/{chunk-MTNVT2T7.js → chunk-76EKW7YJ.js} +13 -2
- package/dist/{chunk-LL37GYPP.js → chunk-FDU5BCPV.js} +103 -19
- package/dist/{chunk-7SDM4EA4.js → chunk-T2KUVO4H.js} +115 -20
- package/dist/{chunk-DVKIL7H6.js → chunk-UYEJ66VX.js} +103 -19
- package/dist/cli.js +724 -14
- package/dist/daemon-main.js +732 -17
- package/dist/index.js +725 -15
- package/dist/{session-replayer-J4GXUULG.js → session-replayer-SBYJXQZZ.js} +1 -1
- package/package.json +1 -1
|
@@ -146,8 +146,17 @@ function buildStealthInitScript() {
|
|
|
146
146
|
" if(!e)return f.call(this,e);",
|
|
147
147
|
" return f.call(this,new Proxy(e,{get:function(k,p){",
|
|
148
148
|
' if(p==="sourceCapabilities")return fc;',
|
|
149
|
-
|
|
150
|
-
|
|
149
|
+
// S124 修正(d67 攻防):恒 true 是"过度伪装"——站点自己的合成事件
|
|
150
|
+
// 也变 true → 直接判定浏览器被篡改(比检测自动化更严重的指纹暴露)。
|
|
151
|
+
// 正确策略:透传原始值(CDP Input 事件本来就是 true,JS 合成本来是 false)
|
|
152
|
+
// + paste 定向伪装(合成的 paste 在真实场景都是 trusted 的键盘操作产物)
|
|
153
|
+
' if(p==="isTrusted"){',
|
|
154
|
+
' var orig=Reflect.get(k,"isTrusted");',
|
|
155
|
+
" if(orig===true)return true;",
|
|
156
|
+
' if(k.type==="paste")return true;',
|
|
157
|
+
" return orig;",
|
|
158
|
+
" }",
|
|
159
|
+
' if((p==="clientX"||p==="clientY")&&k.type==="click"&&Number.isInteger(k[p])){',
|
|
151
160
|
" var _f=((k.timeStamp||Date.now())%89)/89*0.7+0.15;",
|
|
152
161
|
" return k[p]+_f;",
|
|
153
162
|
" }",
|
|
@@ -157,6 +166,30 @@ function buildStealthInitScript() {
|
|
|
157
166
|
" return o.call(this,t,w,op);",
|
|
158
167
|
" };",
|
|
159
168
|
" EventTarget.prototype.addEventListener=_ael;",
|
|
169
|
+
// window.event isTrusted 伪装(S123):AEL 参数包装收不到 window.event
|
|
170
|
+
// (它是原始 Event 对象的隐式引用)。定义 getter 拦截读取。
|
|
171
|
+
" try{",
|
|
172
|
+
' var _weDesc=Object.getOwnPropertyDescriptor(Window.prototype,"event");',
|
|
173
|
+
" if(_weDesc&&_weDesc.get){",
|
|
174
|
+
" var _origWeGet=_weDesc.get;",
|
|
175
|
+
' Object.defineProperty(Window.prototype,"event",{',
|
|
176
|
+
" configurable:true,",
|
|
177
|
+
" get:function(){",
|
|
178
|
+
" var ev=_origWeGet.call(this);",
|
|
179
|
+
" if(!ev)return ev;",
|
|
180
|
+
" return new Proxy(ev,{get:function(k,p){",
|
|
181
|
+
' if(p==="isTrusted"){',
|
|
182
|
+
' var orig=Reflect.get(k,"isTrusted");',
|
|
183
|
+
" if(orig===true)return true;",
|
|
184
|
+
' if(k.type==="paste")return true;',
|
|
185
|
+
" return orig;",
|
|
186
|
+
" }",
|
|
187
|
+
' var v=Reflect.get(k,p);return typeof v==="function"?v.bind(k):v;',
|
|
188
|
+
" }});",
|
|
189
|
+
" }",
|
|
190
|
+
" });",
|
|
191
|
+
" }",
|
|
192
|
+
" }catch(e){}",
|
|
160
193
|
// 2. Screen override (prototype-level, not instance-level)
|
|
161
194
|
" var _gw=function(){return 1728};",
|
|
162
195
|
" var _gh=function(){return 1117};",
|
|
@@ -165,9 +198,42 @@ function buildStealthInitScript() {
|
|
|
165
198
|
' Object.defineProperty(Screen.prototype,"height",{get:_gh,configurable:true});',
|
|
166
199
|
' Object.defineProperty(Screen.prototype,"availWidth",{get:_gw,configurable:true});',
|
|
167
200
|
' Object.defineProperty(Screen.prototype,"availHeight",{get:_gah,configurable:true});',
|
|
201
|
+
// S174: UA Headless 标记清洗——headless launch 的 UA 含 HeadlessChrome/xxx
|
|
202
|
+
// (最高频检测面),同步清洗 userAgent/appVersion/userAgentData(brands+高熵值)。
|
|
203
|
+
// 原型+实例双层覆写(S172 模式)。
|
|
204
|
+
" try{",
|
|
205
|
+
' var fixUA=function(ua){return String(ua).replace("HeadlessChrome","Chrome");};',
|
|
206
|
+
' var fixBrands=function(list){return (list||[]).map(function(b){return (b.brand&&b.brand.indexOf("Headless")!==-1)?Object.assign({},b,{brand:b.brand.replace("HeadlessChrome","Chrome")}):b;});};',
|
|
207
|
+
' var _nuad=Object.getOwnPropertyDescriptor(Navigator.prototype,"userAgentData");',
|
|
208
|
+
" var wrapUAD=function(uad){",
|
|
209
|
+
" if(!uad||uad.__xbUA)return uad;",
|
|
210
|
+
" try{",
|
|
211
|
+
' Object.defineProperty(uad,"brands",{get:function(){return fixBrands(_nuad.get.call(navigator).brands);},configurable:true});',
|
|
212
|
+
" var _ghev=uad.getHighEntropyValues.bind(uad);",
|
|
213
|
+
" uad.getHighEntropyValues=function(hints){return _ghev(hints).then(function(v){",
|
|
214
|
+
' var o={};for(var k in v){o[k]=(typeof v[k]==="string")?fixUA(v[k]):(Array.isArray(v[k])?fixBrands(v[k]):v[k]);}',
|
|
215
|
+
" return o;});};",
|
|
216
|
+
" uad.__xbUA=true;",
|
|
217
|
+
" }catch(e){}",
|
|
218
|
+
" return uad;",
|
|
219
|
+
" };",
|
|
220
|
+
' var _nua=Object.getOwnPropertyDescriptor(Navigator.prototype,"userAgent");',
|
|
221
|
+
" if(_nua&&_nua.get){",
|
|
222
|
+
" var _nuag=function(){return fixUA(_nua.get.call(this));};",
|
|
223
|
+
' _nuag.toString=function(){return "function get userAgent() { [native code] }";};',
|
|
224
|
+
' Object.defineProperty(Navigator.prototype,"userAgent",{get:_nuag,configurable:true});',
|
|
225
|
+
" }",
|
|
226
|
+
' Object.defineProperty(navigator,"userAgent",{get:function(){return fixUA(_nua?_nua.get.call(this):"");},configurable:true});',
|
|
227
|
+
" if(_nuad&&_nuad.get){",
|
|
228
|
+
" var _nuadg=function(){return wrapUAD(_nuad.get.call(this));};",
|
|
229
|
+
' _nuadg.toString=function(){return "function get userAgentData() { [native code] }";};',
|
|
230
|
+
' Object.defineProperty(Navigator.prototype,"userAgentData",{get:_nuadg,configurable:true});',
|
|
231
|
+
" }",
|
|
232
|
+
" }catch(e){}",
|
|
168
233
|
// hasFocus 与 visibilityState 联动(d29):恒 true 在 hidden 标签下暴露
|
|
169
234
|
// —— 真实浏览器失焦/后台时 hasFocus=false
|
|
170
|
-
|
|
235
|
+
// S172: 原型层覆写——实例赋值可被 Document.prototype.hasFocus.call(document) 逃逸
|
|
236
|
+
' Document.prototype.hasFocus=function(){return document.visibilityState==="visible";};',
|
|
171
237
|
// 4. Canvas/WebGL fingerprint: per-session stable noise (d20).
|
|
172
238
|
// Headless software raster differs subtly from Chrome GPU raster —
|
|
173
239
|
// toDataURL hashes fingerprint the rasterizer. Inject a stable
|
|
@@ -230,26 +296,25 @@ function buildStealthInitScript() {
|
|
|
230
296
|
" }catch(e){}",
|
|
231
297
|
// 3b. Font metrics + speechSynthesis + battery (d22): headless tells
|
|
232
298
|
" try{",
|
|
233
|
-
" var _mt=CanvasRenderingContext2D.prototype.measureText;",
|
|
234
299
|
' var _tmw=Object.getOwnPropertyDescriptor(TextMetrics.prototype,"width");',
|
|
235
|
-
"
|
|
236
|
-
"
|
|
237
|
-
'
|
|
238
|
-
"
|
|
239
|
-
" };",
|
|
300
|
+
" if(_tmw&&_tmw.get){",
|
|
301
|
+
" // S172: \u539F\u578B\u5C42\u8986\u5199\u2014\u2014\u5B9E\u4F8B(m)\u8986\u5199\u53EF\u88AB TextMetrics.prototype.width getter \u9003\u9038",
|
|
302
|
+
' Object.defineProperty(TextMetrics.prototype,"width",{get:function(){return _tmw.get.call(this)+_dx*0.01;},configurable:true});',
|
|
303
|
+
" }",
|
|
240
304
|
" }catch(e){}",
|
|
241
305
|
" try{",
|
|
242
306
|
' var _fakeVoices=["Alex","Daniel","Karen","Moira","Ralph","Samantha","Ting-Ting","Mei-Jia","Sinji","Yunda"];',
|
|
243
|
-
|
|
244
|
-
"
|
|
245
|
-
"
|
|
307
|
+
// S172: 原型层覆写——实例赋值可被 SpeechSynthesis.prototype.getVoices.call() 逃逸
|
|
308
|
+
" var _sv=SpeechSynthesis.prototype.getVoices;",
|
|
309
|
+
" SpeechSynthesis.prototype.getVoices=function(){",
|
|
310
|
+
" var real=_sv.call(this);",
|
|
246
311
|
" if(real&&real.length>50)return real;",
|
|
247
312
|
// 注意阈值从 0 改 50:iframe 原生约 10 个 —— 原阈值下 iframe 返回原生 10 个
|
|
248
313
|
// 而不是伪装 180(d33 暴露)。>50 = 主文档伪装列表已生效,其余返回伪装。
|
|
249
314
|
' return _fakeVoices.map(function(n,i){return {name:n,lang:i<2?"en-US":i<6?"en-GB":"zh-TW",localService:true,default:i===0,voiceURI:n};});',
|
|
250
315
|
" };",
|
|
251
316
|
// d33 修复:iframe 的 speechSynthesis 是独立实例(主文档 hook 不可达)——
|
|
252
|
-
// 轮询同源 iframe,对其 contentWindow
|
|
317
|
+
// 轮询同源 iframe,对其 contentWindow 的原型同样 patch(S172: 原型层,防逃逸)
|
|
253
318
|
" try{",
|
|
254
319
|
" var _piv=function(){",
|
|
255
320
|
' var frs=document.querySelectorAll("iframe");',
|
|
@@ -257,8 +322,8 @@ function buildStealthInitScript() {
|
|
|
257
322
|
" var cw=frs[i].contentWindow;",
|
|
258
323
|
" if(!cw||!cw.speechSynthesis||cw.speechSynthesis.__xbP)continue;",
|
|
259
324
|
" cw.speechSynthesis.__xbP=true;",
|
|
260
|
-
" var _sv2=cw.
|
|
261
|
-
" cw.
|
|
325
|
+
" var _sv2=cw.SpeechSynthesis.prototype.getVoices;",
|
|
326
|
+
" cw.SpeechSynthesis.prototype.getVoices=function(){var r=_sv2.call(this);if(r&&r.length>50)return r;",
|
|
262
327
|
' return _fakeVoices.map(function(n,j){return{name:n,lang:j<2?"en-US":j<6?"en-GB":"zh-TW",localService:true,default:j===0,voiceURI:n};});};',
|
|
263
328
|
" }catch(e2){}}",
|
|
264
329
|
" };",
|
|
@@ -293,10 +358,21 @@ function buildStealthInitScript() {
|
|
|
293
358
|
// 3d-2. performance.now precision clamp (d34): full-precision timestamps
|
|
294
359
|
// (11 decimals) differ from site-isolation-clamped real Chrome (~100μs).
|
|
295
360
|
" try{",
|
|
296
|
-
" var _pn=
|
|
297
|
-
"
|
|
361
|
+
" var _pn=Performance.prototype.now;",
|
|
362
|
+
" // S172: \u539F\u578B\u5C42\u8986\u5199\u2014\u2014\u5B9E\u4F8B\u8D4B\u503C\u53EF\u88AB Performance.prototype.now.call(performance) \u9003\u9038",
|
|
363
|
+
" Performance.prototype.now=function(){return Math.round(_pn.call(this)*10)/10;};",
|
|
298
364
|
' var _pto=Object.getOwnPropertyDescriptor(Performance.prototype,"timeOrigin");',
|
|
299
|
-
|
|
365
|
+
" if(_pto&&_pto.get){",
|
|
366
|
+
" // S173: timeOrigin \u771F\u4F2A\u88C5\u2014\u2014\u5360\u4F4D\u5C42\uFF08\u8FD4\u56DE\u771F\u503C\uFF09\u5347\u7EA7\u4E3A\u4F1A\u8BDD\u7EA7\u968F\u673A\u504F\u79FB\u3002",
|
|
367
|
+
" // \u9632 cross-page \u5BFC\u822A\u65F6\u523B\u5173\u8054\uFF1A\u4E0D\u540C\u7AD9\u70B9\u7528 timeOrigin \u7CBE\u786E\u5BF9\u9F50\u7528\u6237\u884C\u4E3A\u6863\u6848\u3002",
|
|
368
|
+
" // \u504F\u79FB\u7531\u672C\u9875 _seed \u6D3E\u751F\uFF08\u9875\u5185\u81EA\u6D3D\uFF0Cnow()+\u504F\u79FB\u4E00\u81F4\uFF09\uFF0C\u8DE8\u9875\u968F\u673A\uFF08\u4E0D\u53EF\u5173\u8054\uFF09\u3002",
|
|
369
|
+
" var _toff=Math.floor((_seed/2147483647)*600000-300000);",
|
|
370
|
+
" var _tog=function(){return _pto.get.call(performance)+_toff;};",
|
|
371
|
+
' _tog.toString=function(){return "function get timeOrigin() { [native code] }";};',
|
|
372
|
+
" // S172 \u5BA1\u8BA1\uFF1A\u5B9E\u4F8B\u8986\u5199\u53EF\u88AB\u539F\u578B getter \u9003\u9038\u2014\u2014\u539F\u578B\u5C42\u8986\u5199",
|
|
373
|
+
' Object.defineProperty(Performance.prototype,"timeOrigin",{get:_tog,configurable:true});',
|
|
374
|
+
' Object.defineProperty(performance,"timeOrigin",{get:_tog,configurable:true});',
|
|
375
|
+
" }",
|
|
300
376
|
" }catch(e){}",
|
|
301
377
|
// 3f. getCoalescedEvents 合成(d47):真实鼠标 125Hz 采样被浏览器按帧合并,
|
|
302
378
|
// 快速移动时 pointermove.getCoalescedEvents() 返回 2~6 个事件(群内
|
|
@@ -410,11 +486,19 @@ function buildStealthInitScript() {
|
|
|
410
486
|
' if(this===AnalyserNode.prototype.getFloatFrequencyData)return"function getFloatFrequencyData() { [native code] }";',
|
|
411
487
|
' if(this===AudioBuffer.prototype.getChannelData)return"function getChannelData() { [native code] }";',
|
|
412
488
|
' if(this===_gceH)return"function getCoalescedEvents() { [native code] }";',
|
|
489
|
+
' if(this===Document.prototype.hasFocus)return"function hasFocus() { [native code] }";',
|
|
490
|
+
' if(this===Performance.prototype.now)return"function now() { [native code] }";',
|
|
491
|
+
' if(this===SpeechSynthesis.prototype.getVoices)return"function getVoices() { [native code] }";',
|
|
413
492
|
" return _ts.call(this);",
|
|
414
493
|
" };",
|
|
415
494
|
// 4. onclick prototype hijack (dual-stream consistency)
|
|
416
495
|
" var _ba=function(k,p){",
|
|
417
|
-
' if(p==="isTrusted")
|
|
496
|
+
' if(p==="isTrusted"){',
|
|
497
|
+
' var orig=Reflect.get(k,"isTrusted");',
|
|
498
|
+
" if(orig===true)return true;",
|
|
499
|
+
' if(k.type==="paste")return true;',
|
|
500
|
+
" return orig;",
|
|
501
|
+
" }",
|
|
418
502
|
' if((p==="clientX"||p==="clientY")&&k.type==="click"&&Number.isInteger(k[p])&&k.isTrusted===true){',
|
|
419
503
|
" var _f=((k.timeStamp||Date.now())%89)/89*0.7+0.15;return k[p]+_f;",
|
|
420
504
|
" }",
|
|
@@ -5373,7 +5457,9 @@ async function resolveCDPEndpoint(raw) {
|
|
|
5373
5457
|
}
|
|
5374
5458
|
if (raw.startsWith("http://") || raw.startsWith("https://")) {
|
|
5375
5459
|
try {
|
|
5376
|
-
const
|
|
5460
|
+
const u = new URL(raw);
|
|
5461
|
+
u.pathname = (u.pathname.replace(/\/+$/, "") || "") + "/json/version";
|
|
5462
|
+
const httpResp = await fetchNoProxy(u.toString());
|
|
5377
5463
|
const data = await httpResp.json();
|
|
5378
5464
|
if (!data.webSocketDebuggerUrl) {
|
|
5379
5465
|
throw new Error(`Could not discover CDP endpoint from ${raw}`);
|
|
@@ -5511,9 +5597,17 @@ function discoverChromiumPath() {
|
|
|
5511
5597
|
}
|
|
5512
5598
|
return void 0;
|
|
5513
5599
|
}
|
|
5600
|
+
var _browserPool = /* @__PURE__ */ new Map();
|
|
5514
5601
|
async function createBrowser(options) {
|
|
5515
5602
|
if (options?.cdpEndpoint) {
|
|
5516
5603
|
const realEndpoint = await resolveCDPEndpoint(options.cdpEndpoint);
|
|
5604
|
+
if (!options.intercept) {
|
|
5605
|
+
const pooled = _browserPool.get(realEndpoint);
|
|
5606
|
+
if (pooled && !pooled.disconnected) {
|
|
5607
|
+
return pooled;
|
|
5608
|
+
}
|
|
5609
|
+
if (pooled) _browserPool.delete(realEndpoint);
|
|
5610
|
+
}
|
|
5517
5611
|
if (options.intercept) {
|
|
5518
5612
|
const config = typeof options.intercept === "object" ? { ...options.intercept, cdpEndpoint: realEndpoint } : { cdpEndpoint: realEndpoint };
|
|
5519
5613
|
_sharedCdpProxy = new CDPInterceptorProxy(config);
|
|
@@ -5526,6 +5620,7 @@ async function createBrowser(options) {
|
|
|
5526
5620
|
await browser2.discoverContexts().catch((err) => {
|
|
5527
5621
|
console.error(`[browser] discoverContexts failed: ${errMsg(err)}`);
|
|
5528
5622
|
});
|
|
5623
|
+
if (!options.intercept) _browserPool.set(realEndpoint, browser2);
|
|
5529
5624
|
return browser2;
|
|
5530
5625
|
}
|
|
5531
5626
|
const executablePath = options?.executablePath || process.env.XBROWSER_CHROMIUM_PATH || discoverChromiumPath();
|
|
@@ -144,8 +144,17 @@ function buildStealthInitScript() {
|
|
|
144
144
|
" if(!e)return f.call(this,e);",
|
|
145
145
|
" return f.call(this,new Proxy(e,{get:function(k,p){",
|
|
146
146
|
' if(p==="sourceCapabilities")return fc;',
|
|
147
|
-
|
|
148
|
-
|
|
147
|
+
// S124 修正(d67 攻防):恒 true 是"过度伪装"——站点自己的合成事件
|
|
148
|
+
// 也变 true → 直接判定浏览器被篡改(比检测自动化更严重的指纹暴露)。
|
|
149
|
+
// 正确策略:透传原始值(CDP Input 事件本来就是 true,JS 合成本来是 false)
|
|
150
|
+
// + paste 定向伪装(合成的 paste 在真实场景都是 trusted 的键盘操作产物)
|
|
151
|
+
' if(p==="isTrusted"){',
|
|
152
|
+
' var orig=Reflect.get(k,"isTrusted");',
|
|
153
|
+
" if(orig===true)return true;",
|
|
154
|
+
' if(k.type==="paste")return true;',
|
|
155
|
+
" return orig;",
|
|
156
|
+
" }",
|
|
157
|
+
' if((p==="clientX"||p==="clientY")&&k.type==="click"&&Number.isInteger(k[p])){',
|
|
149
158
|
" var _f=((k.timeStamp||Date.now())%89)/89*0.7+0.15;",
|
|
150
159
|
" return k[p]+_f;",
|
|
151
160
|
" }",
|
|
@@ -155,6 +164,30 @@ function buildStealthInitScript() {
|
|
|
155
164
|
" return o.call(this,t,w,op);",
|
|
156
165
|
" };",
|
|
157
166
|
" EventTarget.prototype.addEventListener=_ael;",
|
|
167
|
+
// window.event isTrusted 伪装(S123):AEL 参数包装收不到 window.event
|
|
168
|
+
// (它是原始 Event 对象的隐式引用)。定义 getter 拦截读取。
|
|
169
|
+
" try{",
|
|
170
|
+
' var _weDesc=Object.getOwnPropertyDescriptor(Window.prototype,"event");',
|
|
171
|
+
" if(_weDesc&&_weDesc.get){",
|
|
172
|
+
" var _origWeGet=_weDesc.get;",
|
|
173
|
+
' Object.defineProperty(Window.prototype,"event",{',
|
|
174
|
+
" configurable:true,",
|
|
175
|
+
" get:function(){",
|
|
176
|
+
" var ev=_origWeGet.call(this);",
|
|
177
|
+
" if(!ev)return ev;",
|
|
178
|
+
" return new Proxy(ev,{get:function(k,p){",
|
|
179
|
+
' if(p==="isTrusted"){',
|
|
180
|
+
' var orig=Reflect.get(k,"isTrusted");',
|
|
181
|
+
" if(orig===true)return true;",
|
|
182
|
+
' if(k.type==="paste")return true;',
|
|
183
|
+
" return orig;",
|
|
184
|
+
" }",
|
|
185
|
+
' var v=Reflect.get(k,p);return typeof v==="function"?v.bind(k):v;',
|
|
186
|
+
" }});",
|
|
187
|
+
" }",
|
|
188
|
+
" });",
|
|
189
|
+
" }",
|
|
190
|
+
" }catch(e){}",
|
|
158
191
|
// 2. Screen override (prototype-level, not instance-level)
|
|
159
192
|
" var _gw=function(){return 1728};",
|
|
160
193
|
" var _gh=function(){return 1117};",
|
|
@@ -163,9 +196,42 @@ function buildStealthInitScript() {
|
|
|
163
196
|
' Object.defineProperty(Screen.prototype,"height",{get:_gh,configurable:true});',
|
|
164
197
|
' Object.defineProperty(Screen.prototype,"availWidth",{get:_gw,configurable:true});',
|
|
165
198
|
' Object.defineProperty(Screen.prototype,"availHeight",{get:_gah,configurable:true});',
|
|
199
|
+
// S174: UA Headless 标记清洗——headless launch 的 UA 含 HeadlessChrome/xxx
|
|
200
|
+
// (最高频检测面),同步清洗 userAgent/appVersion/userAgentData(brands+高熵值)。
|
|
201
|
+
// 原型+实例双层覆写(S172 模式)。
|
|
202
|
+
" try{",
|
|
203
|
+
' var fixUA=function(ua){return String(ua).replace("HeadlessChrome","Chrome");};',
|
|
204
|
+
' var fixBrands=function(list){return (list||[]).map(function(b){return (b.brand&&b.brand.indexOf("Headless")!==-1)?Object.assign({},b,{brand:b.brand.replace("HeadlessChrome","Chrome")}):b;});};',
|
|
205
|
+
' var _nuad=Object.getOwnPropertyDescriptor(Navigator.prototype,"userAgentData");',
|
|
206
|
+
" var wrapUAD=function(uad){",
|
|
207
|
+
" if(!uad||uad.__xbUA)return uad;",
|
|
208
|
+
" try{",
|
|
209
|
+
' Object.defineProperty(uad,"brands",{get:function(){return fixBrands(_nuad.get.call(navigator).brands);},configurable:true});',
|
|
210
|
+
" var _ghev=uad.getHighEntropyValues.bind(uad);",
|
|
211
|
+
" uad.getHighEntropyValues=function(hints){return _ghev(hints).then(function(v){",
|
|
212
|
+
' var o={};for(var k in v){o[k]=(typeof v[k]==="string")?fixUA(v[k]):(Array.isArray(v[k])?fixBrands(v[k]):v[k]);}',
|
|
213
|
+
" return o;});};",
|
|
214
|
+
" uad.__xbUA=true;",
|
|
215
|
+
" }catch(e){}",
|
|
216
|
+
" return uad;",
|
|
217
|
+
" };",
|
|
218
|
+
' var _nua=Object.getOwnPropertyDescriptor(Navigator.prototype,"userAgent");',
|
|
219
|
+
" if(_nua&&_nua.get){",
|
|
220
|
+
" var _nuag=function(){return fixUA(_nua.get.call(this));};",
|
|
221
|
+
' _nuag.toString=function(){return "function get userAgent() { [native code] }";};',
|
|
222
|
+
' Object.defineProperty(Navigator.prototype,"userAgent",{get:_nuag,configurable:true});',
|
|
223
|
+
" }",
|
|
224
|
+
' Object.defineProperty(navigator,"userAgent",{get:function(){return fixUA(_nua?_nua.get.call(this):"");},configurable:true});',
|
|
225
|
+
" if(_nuad&&_nuad.get){",
|
|
226
|
+
" var _nuadg=function(){return wrapUAD(_nuad.get.call(this));};",
|
|
227
|
+
' _nuadg.toString=function(){return "function get userAgentData() { [native code] }";};',
|
|
228
|
+
' Object.defineProperty(Navigator.prototype,"userAgentData",{get:_nuadg,configurable:true});',
|
|
229
|
+
" }",
|
|
230
|
+
" }catch(e){}",
|
|
166
231
|
// hasFocus 与 visibilityState 联动(d29):恒 true 在 hidden 标签下暴露
|
|
167
232
|
// —— 真实浏览器失焦/后台时 hasFocus=false
|
|
168
|
-
|
|
233
|
+
// S172: 原型层覆写——实例赋值可被 Document.prototype.hasFocus.call(document) 逃逸
|
|
234
|
+
' Document.prototype.hasFocus=function(){return document.visibilityState==="visible";};',
|
|
169
235
|
// 4. Canvas/WebGL fingerprint: per-session stable noise (d20).
|
|
170
236
|
// Headless software raster differs subtly from Chrome GPU raster —
|
|
171
237
|
// toDataURL hashes fingerprint the rasterizer. Inject a stable
|
|
@@ -228,26 +294,25 @@ function buildStealthInitScript() {
|
|
|
228
294
|
" }catch(e){}",
|
|
229
295
|
// 3b. Font metrics + speechSynthesis + battery (d22): headless tells
|
|
230
296
|
" try{",
|
|
231
|
-
" var _mt=CanvasRenderingContext2D.prototype.measureText;",
|
|
232
297
|
' var _tmw=Object.getOwnPropertyDescriptor(TextMetrics.prototype,"width");',
|
|
233
|
-
"
|
|
234
|
-
"
|
|
235
|
-
'
|
|
236
|
-
"
|
|
237
|
-
" };",
|
|
298
|
+
" if(_tmw&&_tmw.get){",
|
|
299
|
+
" // S172: \u539F\u578B\u5C42\u8986\u5199\u2014\u2014\u5B9E\u4F8B(m)\u8986\u5199\u53EF\u88AB TextMetrics.prototype.width getter \u9003\u9038",
|
|
300
|
+
' Object.defineProperty(TextMetrics.prototype,"width",{get:function(){return _tmw.get.call(this)+_dx*0.01;},configurable:true});',
|
|
301
|
+
" }",
|
|
238
302
|
" }catch(e){}",
|
|
239
303
|
" try{",
|
|
240
304
|
' var _fakeVoices=["Alex","Daniel","Karen","Moira","Ralph","Samantha","Ting-Ting","Mei-Jia","Sinji","Yunda"];',
|
|
241
|
-
|
|
242
|
-
"
|
|
243
|
-
"
|
|
305
|
+
// S172: 原型层覆写——实例赋值可被 SpeechSynthesis.prototype.getVoices.call() 逃逸
|
|
306
|
+
" var _sv=SpeechSynthesis.prototype.getVoices;",
|
|
307
|
+
" SpeechSynthesis.prototype.getVoices=function(){",
|
|
308
|
+
" var real=_sv.call(this);",
|
|
244
309
|
" if(real&&real.length>50)return real;",
|
|
245
310
|
// 注意阈值从 0 改 50:iframe 原生约 10 个 —— 原阈值下 iframe 返回原生 10 个
|
|
246
311
|
// 而不是伪装 180(d33 暴露)。>50 = 主文档伪装列表已生效,其余返回伪装。
|
|
247
312
|
' return _fakeVoices.map(function(n,i){return {name:n,lang:i<2?"en-US":i<6?"en-GB":"zh-TW",localService:true,default:i===0,voiceURI:n};});',
|
|
248
313
|
" };",
|
|
249
314
|
// d33 修复:iframe 的 speechSynthesis 是独立实例(主文档 hook 不可达)——
|
|
250
|
-
// 轮询同源 iframe,对其 contentWindow
|
|
315
|
+
// 轮询同源 iframe,对其 contentWindow 的原型同样 patch(S172: 原型层,防逃逸)
|
|
251
316
|
" try{",
|
|
252
317
|
" var _piv=function(){",
|
|
253
318
|
' var frs=document.querySelectorAll("iframe");',
|
|
@@ -255,8 +320,8 @@ function buildStealthInitScript() {
|
|
|
255
320
|
" var cw=frs[i].contentWindow;",
|
|
256
321
|
" if(!cw||!cw.speechSynthesis||cw.speechSynthesis.__xbP)continue;",
|
|
257
322
|
" cw.speechSynthesis.__xbP=true;",
|
|
258
|
-
" var _sv2=cw.
|
|
259
|
-
" cw.
|
|
323
|
+
" var _sv2=cw.SpeechSynthesis.prototype.getVoices;",
|
|
324
|
+
" cw.SpeechSynthesis.prototype.getVoices=function(){var r=_sv2.call(this);if(r&&r.length>50)return r;",
|
|
260
325
|
' return _fakeVoices.map(function(n,j){return{name:n,lang:j<2?"en-US":j<6?"en-GB":"zh-TW",localService:true,default:j===0,voiceURI:n};});};',
|
|
261
326
|
" }catch(e2){}}",
|
|
262
327
|
" };",
|
|
@@ -291,10 +356,21 @@ function buildStealthInitScript() {
|
|
|
291
356
|
// 3d-2. performance.now precision clamp (d34): full-precision timestamps
|
|
292
357
|
// (11 decimals) differ from site-isolation-clamped real Chrome (~100μs).
|
|
293
358
|
" try{",
|
|
294
|
-
" var _pn=
|
|
295
|
-
"
|
|
359
|
+
" var _pn=Performance.prototype.now;",
|
|
360
|
+
" // S172: \u539F\u578B\u5C42\u8986\u5199\u2014\u2014\u5B9E\u4F8B\u8D4B\u503C\u53EF\u88AB Performance.prototype.now.call(performance) \u9003\u9038",
|
|
361
|
+
" Performance.prototype.now=function(){return Math.round(_pn.call(this)*10)/10;};",
|
|
296
362
|
' var _pto=Object.getOwnPropertyDescriptor(Performance.prototype,"timeOrigin");',
|
|
297
|
-
|
|
363
|
+
" if(_pto&&_pto.get){",
|
|
364
|
+
" // S173: timeOrigin \u771F\u4F2A\u88C5\u2014\u2014\u5360\u4F4D\u5C42\uFF08\u8FD4\u56DE\u771F\u503C\uFF09\u5347\u7EA7\u4E3A\u4F1A\u8BDD\u7EA7\u968F\u673A\u504F\u79FB\u3002",
|
|
365
|
+
" // \u9632 cross-page \u5BFC\u822A\u65F6\u523B\u5173\u8054\uFF1A\u4E0D\u540C\u7AD9\u70B9\u7528 timeOrigin \u7CBE\u786E\u5BF9\u9F50\u7528\u6237\u884C\u4E3A\u6863\u6848\u3002",
|
|
366
|
+
" // \u504F\u79FB\u7531\u672C\u9875 _seed \u6D3E\u751F\uFF08\u9875\u5185\u81EA\u6D3D\uFF0Cnow()+\u504F\u79FB\u4E00\u81F4\uFF09\uFF0C\u8DE8\u9875\u968F\u673A\uFF08\u4E0D\u53EF\u5173\u8054\uFF09\u3002",
|
|
367
|
+
" var _toff=Math.floor((_seed/2147483647)*600000-300000);",
|
|
368
|
+
" var _tog=function(){return _pto.get.call(performance)+_toff;};",
|
|
369
|
+
' _tog.toString=function(){return "function get timeOrigin() { [native code] }";};',
|
|
370
|
+
" // S172 \u5BA1\u8BA1\uFF1A\u5B9E\u4F8B\u8986\u5199\u53EF\u88AB\u539F\u578B getter \u9003\u9038\u2014\u2014\u539F\u578B\u5C42\u8986\u5199",
|
|
371
|
+
' Object.defineProperty(Performance.prototype,"timeOrigin",{get:_tog,configurable:true});',
|
|
372
|
+
' Object.defineProperty(performance,"timeOrigin",{get:_tog,configurable:true});',
|
|
373
|
+
" }",
|
|
298
374
|
" }catch(e){}",
|
|
299
375
|
// 3f. getCoalescedEvents 合成(d47):真实鼠标 125Hz 采样被浏览器按帧合并,
|
|
300
376
|
// 快速移动时 pointermove.getCoalescedEvents() 返回 2~6 个事件(群内
|
|
@@ -408,11 +484,19 @@ function buildStealthInitScript() {
|
|
|
408
484
|
' if(this===AnalyserNode.prototype.getFloatFrequencyData)return"function getFloatFrequencyData() { [native code] }";',
|
|
409
485
|
' if(this===AudioBuffer.prototype.getChannelData)return"function getChannelData() { [native code] }";',
|
|
410
486
|
' if(this===_gceH)return"function getCoalescedEvents() { [native code] }";',
|
|
487
|
+
' if(this===Document.prototype.hasFocus)return"function hasFocus() { [native code] }";',
|
|
488
|
+
' if(this===Performance.prototype.now)return"function now() { [native code] }";',
|
|
489
|
+
' if(this===SpeechSynthesis.prototype.getVoices)return"function getVoices() { [native code] }";',
|
|
411
490
|
" return _ts.call(this);",
|
|
412
491
|
" };",
|
|
413
492
|
// 4. onclick prototype hijack (dual-stream consistency)
|
|
414
493
|
" var _ba=function(k,p){",
|
|
415
|
-
' if(p==="isTrusted")
|
|
494
|
+
' if(p==="isTrusted"){',
|
|
495
|
+
' var orig=Reflect.get(k,"isTrusted");',
|
|
496
|
+
" if(orig===true)return true;",
|
|
497
|
+
' if(k.type==="paste")return true;',
|
|
498
|
+
" return orig;",
|
|
499
|
+
" }",
|
|
416
500
|
' if((p==="clientX"||p==="clientY")&&k.type==="click"&&Number.isInteger(k[p])&&k.isTrusted===true){',
|
|
417
501
|
" var _f=((k.timeStamp||Date.now())%89)/89*0.7+0.15;return k[p]+_f;",
|
|
418
502
|
" }",
|