233 lines
20 KiB
JavaScript
233 lines
20 KiB
JavaScript
// 离线 instrument 沙箱 v2: 全容错 Proxy 桩, 让 kwpsec(kws)+kwf 的 Jimbei VM 跑到底。
|
||
// 记录它读了哪些指纹 / 监听了哪些事件 / 采了什么触摸保真 / 有没有自动化检测。
|
||
// 决定性: 真手指相对 adb 是否存在不可伪造信号。
|
||
// 有 force/pressure/radius/coalesced/精确时序 → 行为墙 → 人工路径
|
||
// 只可合成轨迹 + 真机已真指纹 → 真手指无优势 → 号码信誉墙 → 换号
|
||
const fs = require("fs"), vm = require("vm"), path = require("path");
|
||
const ROOT = path.join(__dirname, "..");
|
||
const H5 = path.join(ROOT, "out", "h5_security_js");
|
||
const KWS = fs.readFileSync(path.join(H5, "kws-11-0.0.1-obfuscated.5e0a90af726d8a7e.js"), "utf-8");
|
||
const KWF = fs.readFileSync(path.join(H5, "kwf-0.0.2.2cee19b4b7dec496.js"), "utf-8");
|
||
|
||
const ctx = vm.createContext({
|
||
console, setTimeout, clearTimeout, setImmediate, setInterval, clearInterval, queueMicrotask,
|
||
// 注意: 不注入 Buffer/process (Node 全局) —— kwpsec 会探测它们判定 Node 环境 (自动化), 真浏览器没有。
|
||
btoa: (s) => Buffer.from(s, "binary").toString("base64"),
|
||
atob: (s) => Buffer.from(s, "base64").toString("binary"),
|
||
});
|
||
|
||
vm.runInContext(`
|
||
globalThis.__reads = {}; globalThis.__events = [];
|
||
globalThis.__canvas = []; globalThis.__audio = []; globalThis.__errs = [];
|
||
var __win; // window 自指
|
||
|
||
// 全容错日志 Proxy: 任何属性都不返回 undefined (缺的返回可调用代理), 永不崩。
|
||
// 自动化/Node 环境标记黑名单: 真浏览器/WebView 里全是 undefined。强制返回 undefined (否则代理返回 truthy → 被误判自动化)。
|
||
var MARKERS = new Set(["_phantom","callPhantom","__nightmare","phantom","_selenium","callSelenium","_Selenium_IDE_Recorder","selenium","domAutomation","domAutomationController","Buffer","process","fbejkbakrbadskfe","kwscb","webdriver","__webdriver_evaluate","__webdriver_script_function","__webdriver_script_func","__webdriver_script_fn","__webdriver_unwrapped","__selenium_evaluate","__selenium_unwrapped","__fxdriver_evaluate","__fxdriver_unwrapped","__driver_evaluate","__driver_unwrapped","spawn","_WEBDRIVER_ELEM_CACHE","ChromeDriverw","require","module","__core-js_shared__","awesomplete","callPhantom"]);
|
||
// 全容错日志 Proxy。callable=true 时代理本身是函数 (target=noop), 这样未知方法 .apply/.call/() 不崩。
|
||
// 容器(navigator/document/window)用 callable=false (typeof object, 准确)。
|
||
function mkTotal(name, real, strict, callable){
|
||
real = real || {};
|
||
var target = callable ? function(){} : {};
|
||
function sub(n){ return mkTotal(n, {}, false, true); } // 未知子值一律=可调用代理 (永远不崩)
|
||
return new Proxy(target, {
|
||
get: function(t,k){
|
||
if (typeof k === "symbol") return undefined;
|
||
if (k === "then" || k === "toJSON") return undefined;
|
||
var key = String(k);
|
||
if (__win && (key==="window"||key==="self"||key==="globalThis"||key==="top"||key==="parent"||key==="frames")) return __win;
|
||
var lk = name+"."+key; __reads[lk]=(__reads[lk]||0)+1;
|
||
if (key in real && real[key] !== undefined){
|
||
var v = real[key];
|
||
if (typeof v === "function") return v.bind(real);
|
||
if (typeof v === "object" && v !== null) return v;
|
||
return v;
|
||
}
|
||
// 内置全局 (Array/Object/Function/encodeURIComponent...) 真返回
|
||
if (key!=="window"&&key!=="self"&&key!=="globalThis"&&typeof globalThis[key]!=="undefined"){ return globalThis[key]; }
|
||
// 自动化/Node 标记: 真浏览器里 undefined, 强制返回 undefined (否则 truthy 代理被误判自动化)
|
||
if (MARKERS.has(key)){ return undefined; }
|
||
if (strict){ return undefined; } // window 精确模式保留
|
||
return sub(lk);
|
||
},
|
||
apply: callable ? function(t,thisArg,args){ var k=name+"()"; __reads[k]=(__reads[k]||0)+1; return sub(k); } : undefined,
|
||
construct: callable ? function(){ return {}; } : undefined,
|
||
has: function(){ return true; },
|
||
set: function(){ return true; },
|
||
getOwnPropertyDescriptor: function(t,k){ if(k in real) return Object.getOwnPropertyDescriptor(real,k); return {configurable:true,enumerable:false,value:undefined}; },
|
||
ownKeys: function(){ return Object.keys(real); }
|
||
});
|
||
}
|
||
|
||
// ---- 真实 canvas/webgl 实现 (关键信号要真跑) ----
|
||
function mkCanvasReal(){
|
||
return {
|
||
width:300, height:150, style:{},
|
||
getContext: function(type){
|
||
__canvas.push("getContext:"+(type||"?"));
|
||
var ctx2d = {
|
||
fillRect:function(){}, clearRect:function(){}, strokeRect:function(){},
|
||
fillText:function(t){ __canvas.push("2d.fillText:"+(t||"").slice(0,8)); },
|
||
strokeText:function(){ __canvas.push("2d.strokeText"); },
|
||
measureText:function(s){ __canvas.push("2d.measureText"); return { width:(s?String(s).length:0)*7, actualBoundingBoxAscent:8, actualBoundingBoxDescent:2 }; },
|
||
getImageData:function(x,y,w,h){ __canvas.push("2d.getImageData"); return { width:w, height:h, data: new Uint8ClampedArray((w||1)*(h||1)*4) }; },
|
||
createImageData:function(w,h){ return { width:w, height:h, data: new Uint8ClampedArray((w||1)*(h||1)*4) }; },
|
||
putImageData:function(){}, beginPath:function(){}, closePath:function(){}, moveTo:function(){}, lineTo:function(){}, arc:function(){}, rect:function(){}, fill:function(){}, stroke:function(){},
|
||
save:function(){}, restore:function(){}, translate:function(){}, rotate:function(){}, scale:function(){}, setTransform:function(){}, transform:function(){}, drawImage:function(){}, clip:function(){},
|
||
arcTo:function(){}, quadraticCurveTo:function(){}, bezierCurveTo:function(){}, isPointInPath:function(){return false;}, createLinearGradient:function(){return {addColorStop:function(){}};}, createRadialGradient:function(){return {addColorStop:function(){}};},
|
||
fillStyle:"#000", strokeStyle:"#000", font:"16px Arial", textBaseline:"alphabetic", textAlign:"start",
|
||
globalAlpha:1, lineWidth:1, lineCap:"butt", lineJoin:"miter", shadowBlur:0, shadowColor:"rgba(0,0,0,0)", shadowOffsetX:0, shadowOffsetY:0,
|
||
};
|
||
var webgl = {
|
||
getParameter:function(p){ __canvas.push("webgl.getParameter:"+p); return {"37445":"Qualcomm","37446":"Adreno (TM) 740","7936":"WebKit WebGL","7937":"WebKit WebGL","34921":16384,"33902":16384,"33901":16384,"35724":"WebGL GLSL ES 1.00 (OpenGL ES)"}[p] || "WebKit WebGL"; },
|
||
getExtension:function(n){ __canvas.push("webgl.getExtension:"+n); return n==="WEBGL_debug_renderer_info"?{UNMASKED_VENDOR_WEBGL:37445,UNMASKED_RENDERER_WEBGL:37446}:{}; },
|
||
getSupportedExtensions:function(){ return ["EXT_blend_minmax","ANGLE_instanced_arrays","WEBGL_debug_renderer_info","OES_texture_float"]; },
|
||
getShaderPrecisionFormat:function(){ return {rangeMin:127,rangeMax:127,precision:23}; },
|
||
createBuffer:function(){return{};},bindBuffer:function(){},bufferData:function(){},createShader:function(){return{};},shaderSource:function(){},compileShader:function(){},createProgram:function(){return{};},attachShader:function(){},linkProgram:function(){},useProgram:function(){},enableVertexAttribArray:function(){},vertexAttribPointer:function(){},drawArrays:function(){},
|
||
drawingBufferWidth:300, drawingBufferHeight:150, UNMASKED_VENDOR_WEBGL:37445, UNMASKED_RENDERER_WEBGL:37446,
|
||
};
|
||
return mkTotal("canvas.ctx("+(type||"")+")", type==="webgl"||type==="experimental-webgl" ? webgl : ctx2d);
|
||
},
|
||
toDataURL: function(type){ __canvas.push("toDataURL:"+(type||"png")); return "data:image/png;base64,iVBORw0KGgoAAAANSUhEUg=="; },
|
||
toBlob: function(cb){ __canvas.push("toBlob"); if(cb) setTimeout(function(){cb({size:100});},0); },
|
||
addEventListener: function(t){ __events.push("canvas:"+t); },
|
||
getBoundingClientRect: function(){ return {left:0,top:0,right:300,bottom:150,width:300,height:150,x:0,y:0}; },
|
||
captureStream: function(){ return { getTracks:function(){return [];} }; },
|
||
};
|
||
}
|
||
|
||
// ---- 真实 audio 实现 ----
|
||
function mkAudioReal(label){
|
||
var dest={connect:function(){return null;},disconnect:function(){}};
|
||
function node(){ return { connect:function(){return dest;}, disconnect:function(){}, frequency:{value:440}, type:"sine", value:1, start:function(){}, stop:function(){}, gain:{value:1}, threshold:-24, knee:30, ratio:12, attack:0.003, release:0.25, reduction:0 }; }
|
||
return {
|
||
sampleRate:44100, currentTime:0, destination:dest, state:"running", listener:{},
|
||
createOscillator:function(){ __audio.push(label+":createOscillator"); return node(); },
|
||
createAnalyser:function(){ __audio.push(label+":createAnalyser"); return node(); },
|
||
createDynamicsCompressor:function(){ __audio.push(label+":createDynamicsCompressor"); return node(); },
|
||
createGain:function(){ __audio.push(label+":createGain"); return node(); },
|
||
createBiquadFilter:function(){ return node(); }, createScriptProcessor:function(){ return node(); },
|
||
createBuffer:function(ch,len,sr){ return { getChannelData:function(){ return new Float32Array(len); }, length:len, numberOfChannels:ch, sampleRate:sr }; },
|
||
createBufferSource:function(){ var n=node(); n.buffer=null; return n; },
|
||
decodeAudioData:function(b,ok){ if(ok) ok({getChannelData:function(){return new Float32Array(100);}}); },
|
||
startRendering:function(){ __audio.push(label+":startRendering"); return Promise.resolve({ getChannelData:function(){ return new Float32Array(100); }, length:100 }); },
|
||
close:function(){ return Promise.resolve(); }, resume:function(){ return Promise.resolve(); }
|
||
};
|
||
}
|
||
|
||
// ---- navigator / screen / document / window ----
|
||
var nav = mkTotal("navigator", {
|
||
userAgent:"Mozilla/5.0 (Linux; Android 13; PJZ110) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/114.0.0.0 Mobile Safari/537.36",
|
||
appVersion:"5.0 (Linux; Android 13; PJZ110)", appName:"Netscape", appCodeName:"Mozilla",
|
||
platform:"Linux armv8l", oscpu:"Linux armv8l", product:"Gecko", productSub:"20030107", vendor:"Google Inc.", vendorSub:"",
|
||
language:"zh-CN", languages:["zh-CN","en-US"], cookieEnabled:true, doNotTrack:null, onLine:true,
|
||
webdriver:false, hardwareConcurrency:8, deviceMemory:8, maxTouchPoints:5,
|
||
plugins:{length:0}, mimeTypes:{length:0}, javaEnabled:function(){return false;},
|
||
connection:{effectiveType:"4g",rtt:50,downlink:10,saveData:false},
|
||
permissions:{query:function(){return Promise.resolve({state:"prompt"});}},
|
||
mediaDevices:{enumerateDevices:function(){return Promise.resolve([]);}},
|
||
userAgentData:{mobile:true,brands:[{brand:"Chromium",version:"114"}]},
|
||
geolocation:{getCurrentPosition:function(){},watchPosition:function(){}},
|
||
storage:{estimate:function(){return Promise.resolve({quota:1e9,usage:1e6});}},
|
||
sendBeacon:function(){return true;}
|
||
});
|
||
globalThis.navigator = nav;
|
||
|
||
globalThis.screen = mkTotal("screen", { width:1080, height:2376, availWidth:1080, availHeight:2256, availLeft:0, availTop:0, colorDepth:24, pixelDepth:24, orientation:{type:"portrait-primary",angle:0} });
|
||
|
||
var docEl={style:{},clientWidth:1080,clientHeight:2256,getBoundingClientRect:function(){return {left:0,top:0,width:1080,height:2256};},addEventListener:function(t){__events.push("docEl:"+t);}};
|
||
var bodyEl={style:{},clientWidth:1080,clientHeight:2256,appendChild:function(){},addEventListener:function(t){__events.push("body:"+t);}};
|
||
var headEl={appendChild:function(){},getElementsByTagName:function(){return [];}};
|
||
var doc = mkTotal("document", {
|
||
nodeType:9, documentElement:docEl, body:bodyEl, head:headEl, cookie:"", referrer:"https://app.m.kuaishou.com/",
|
||
URL:"https://app.m.kuaishou.com/verify/captcha.html", domain:"app.m.kuaishou.com", title:"安全验证", readyState:"complete",
|
||
visibilityState:"visible", hidden:false, hasFocus:function(){return true;},
|
||
createElement:function(tag){ if(String(tag).toLowerCase()==="canvas") return mkTotal("canvas", mkCanvasReal());
|
||
return mkTotal("el("+tag+")", { tagName:String(tag).toUpperCase(), style:{}, setAttribute:function(){}, getAttribute:function(){return null;}, appendChild:function(c){return c;}, addEventListener:function(t){__events.push("el("+tag+"):"+t);}, getBoundingClientRect:function(){return {left:0,top:0,width:100,height:20};}, width:0, height:0, innerHTML:"", textContent:"" }); },
|
||
createElementNS:function(ns,tag){ return this.createElement(tag); }, createTextNode:function(t){return {nodeValue:t};},
|
||
getElementsByTagName:function(t){ return t==="html"?[docEl]:t==="body"?[bodyEl]:t==="head"?[headEl]:[]; },
|
||
getElementById:function(){return null;}, querySelector:function(){return null;}, querySelectorAll:function(){return [];},
|
||
addEventListener:function(type,fn){ __events.push("document:"+type); if(type==="DOMContentLoaded"||type==="load"||type==="readystatechange"){ try{fn&&fn();}catch(e){__errs.push("docEvh:"+e.message);} } },
|
||
removeEventListener:function(){}, dispatchEvent:function(){return true;},
|
||
fonts:{ready:Promise.resolve(),check:function(){return true;},load:function(){return Promise.resolve([]);}},
|
||
createEvent:function(){return {initEvent:function(){}};}, queryCommandSupported:function(){return false;}, adoptNode:function(n){return n;}, importNode:function(n){return n;}
|
||
});
|
||
globalThis.document = doc;
|
||
|
||
globalThis.AudioContext=function(){ return mkTotal("AudioContext", mkAudioReal("AC")); };
|
||
globalThis.webkitAudioContext=globalThis.AudioContext;
|
||
globalThis.OfflineAudioContext=function(){ return mkTotal("OfflineAudioContext", mkAudioReal("OAC")); };
|
||
globalThis.webkitOfflineAudioContext=globalThis.OfflineAudioContext;
|
||
|
||
globalThis.MessageChannel=function(){ this.port1={postMessage:function(){},onmessage:null,close:function(){}}; this.port2=this.port1; };
|
||
globalThis.Worker=function(){ this.postMessage=function(){}; this.terminate=function(){}; };
|
||
globalThis.Blob=function(a,o){ this.type=(o&&o.type)||""; this.size=10; this.arrayBuffer=function(){return Promise.resolve(new ArrayBuffer(10));}; };
|
||
globalThis.URL=createURL();
|
||
function createURL(){ return {createObjectURL:function(){return "blob:x";},revokeObjectURL:function(){}}; }
|
||
|
||
// window 级属性: 用 mkTotal 包一个真 backing, 再绑定到 globalThis
|
||
var winBacking = {
|
||
devicePixelRatio:3, innerWidth:1080, innerHeight:2256, outerWidth:1080, outerHeight:2376,
|
||
screenX:0, screenY:0, scrollX:0, scrollY:0, pageXOffset:0, pageYOffset:0,
|
||
localStorage:{getItem:function(){return null;},setItem:function(){},removeItem:function(){}},
|
||
sessionStorage:{getItem:function(){return null;},setItem:function(){}},
|
||
indexedDB:{}, name:"", status:"", closed:false,
|
||
ontouchstart:null, ondevicemotion:null, ondeviceorientation:null,
|
||
DeviceMotionEvent:function(){}, DeviceOrientationEvent:function(){},
|
||
addEventListener:function(type,fn){ __events.push("window:"+type); if(type==="load"||type==="DOMContentLoaded"){ try{fn&&fn();}catch(e){__errs.push("winEvh:"+e.message);} } },
|
||
removeEventListener:function(){}, postMessage:function(){},
|
||
WebSocket:function(){ this.send=function(){}; this.close=function(){}; },
|
||
XMLHttpRequest:function(){ this.open=function(){}; this.send=function(){}; this.setRequestHeader=function(){}; },
|
||
fetch:function(){ return Promise.resolve({json:function(){return Promise.resolve({});},text:function(){return Promise.resolve("");},arrayBuffer:function(){return Promise.resolve(new ArrayBuffer(8));}}); },
|
||
requestAnimationFrame:function(cb){ return 1; }, cancelAnimationFrame:function(){},
|
||
history:{length:1,pushState:function(){},replaceState:function(){}},
|
||
location:{href:"https://app.m.kuaishou.com/verify/captcha.html?key=-123&type=7",protocol:"https:",host:"app.m.kuaishou.com",hostname:"app.m.kuaishou.com",pathname:"/verify/captcha.html",search:"?key=-123&type=7",hash:"",origin:"https://app.m.kuaishou.com"},
|
||
crypto:{getRandomValues:function(b){for(var i=0;i<b.length;i++)b[i]=(Math.random()*256)|0;return b;},subtle:{},randomUUID:function(){return "00000000-0000-4000-8000-000000000000";}},
|
||
performance:{now:function(){return 1234.5;},timeOrigin:1700000000000,timing:{navigationStart:1700000000000,loadEventEnd:1700000002000},navigation:{type:0,redirectCount:0},getEntries:function(){return [];},getEntriesByType:function(){return [];},getEntriesByName:function(){return [];},memory:{jsHeapSizeLimit:4294705156,totalJSHeapSize:25000000,usedJSHeapSize:15000000},mark:function(){},measure:function(){}}
|
||
};
|
||
__win = mkTotal("window", winBacking, true);
|
||
// 把 winBacking 的键也挂到 globalThis (裸标识符访问), 但经 __win 代理
|
||
["devicePixelRatio","innerWidth","innerHeight","outerWidth","outerHeight","screenX","screenY","scrollX","scrollY","pageXOffset","pageYOffset","ontouchstart","ondevicemotion","ondeviceorientation","DeviceMotionEvent","DeviceOrientationEvent","WebSocket","XMLHttpRequest","fetch","requestAnimationFrame","cancelAnimationFrame","history","location","crypto","performance","localStorage","sessionStorage","indexedDB","name","status","closed"].forEach(function(k){
|
||
Object.defineProperty(globalThis, k, { configurable:true, get:function(){ return __win[k]; }, set:function(v){ winBacking[k]=v; } });
|
||
});
|
||
globalThis.addEventListener = function(t,fn){ __win.addEventListener(t,fn); };
|
||
globalThis.removeEventListener = function(){};
|
||
globalThis.window = __win; globalThis.self = __win; globalThis.global = __win;
|
||
`, ctx, { filename: "bootstrap", timeout: 15000 });
|
||
|
||
function run(label, src) {
|
||
try { vm.runInContext(src, ctx, { filename: label, timeout: 15000 }); console.log("[" + label + "] loaded OK"); }
|
||
catch (e) {
|
||
const msg = (e && e.message) || e;
|
||
const m = (e && e.stack && e.stack.match(new RegExp(label.replace(".","\\.") + ":1:(\\d+)")));
|
||
let ctx_ = "";
|
||
if (m) { const col = +m[1]; ctx_ = "\n └─ 崩溃处源码: " + src.substring(col-80, col+60).replace(/\n/g," "); }
|
||
console.log("[" + label + "] threw: " + msg + (m? " @col "+m[1] : "") + ctx_);
|
||
ctx.__errs.push(label + ":" + msg + (m? "@col"+m[1]:""));
|
||
}
|
||
}
|
||
run("kws.js", KWS);
|
||
run("kwf.js", KWF);
|
||
|
||
setTimeout(() => {
|
||
const reads = ctx.__reads, events = ctx.__events, canvas = ctx.__canvas, audio = ctx.__audio;
|
||
const FIDELITY = ["force","pressure","radiusX","radiusY","webkitForce","mozPressure","tiltX","tiltY","coalesced","getCoalescedEvents","predicted","twist","tangentialPressure","width","height"];
|
||
const fidHit = []; for (const f of FIDELITY) for (const k in reads) if (k.toLowerCase().endsWith("."+f.toLowerCase()) || k.toLowerCase().includes("."+f.toLowerCase())) fidHit.push(k+"="+reads[k]);
|
||
const AUTO = ["webdriver","phantom","nightmare","selenium","domAutomation","callPhantom","_phantom","__nightmare","cdc_","driver","headless","puppeteter","spawn","Buffer","process"];
|
||
const autoHit = []; for (const a of AUTO) for (const k in reads) if (k.toLowerCase().includes(a.toLowerCase())) autoHit.push(k+"="+reads[k]);
|
||
const behUniq = [...new Set(events.filter(e=>/mouse|touch|pointer|click|scroll|wheel|key|focus|blur|visibility|devicemotion|orientation|resize|input|drag|transition|copy|paste/i.test(e)))].sort();
|
||
|
||
console.log("\n================ kwpsec 信号采集面报告 ================");
|
||
console.log("\n[A] 行为事件监听 ("+behUniq.length+"): " + (behUniq.join(", ") || "(无)"));
|
||
console.log("\n[B] 触摸保真字段 (有命中=真手指不可伪造→人工路径): " + (fidHit.join(", ") || "(无)"));
|
||
console.log("\n[C] 自动化检测读取: " + (autoHit.join(", ") || "(无明文命中)"));
|
||
console.log("\n[D] Canvas/WebGL 探针 ("+canvas.length+"): " + ([...new Set(canvas)].slice(0,40).join(", ") || "(无)"));
|
||
console.log("\n[E] Audio 探针 ("+audio.length+"): " + ([...new Set(audio)].slice(0,20).join(", ") || "(无)"));
|
||
console.log("\n[F] 全部属性探针 (top 60 by 次数):");
|
||
console.log(Object.entries(reads).sort((a,b)=>b[1]-a[1]).slice(0,60).map(([k,v])=>" "+k+" ×"+v).join("\n"));
|
||
console.log("\n[G] 全部事件 (unique): " + ([...new Set(events)].sort().join(", ") || "(无)"));
|
||
console.log("\n[H] 运行期错误 ("+ctx.__errs.length+"): " + (ctx.__errs.slice(0,12).join(" | ") || "(无)"));
|
||
fs.writeFileSync(path.join(ROOT,"out","kws_signal_report.json"), JSON.stringify({reads,events:[...new Set(events)],canvas:[...new Set(canvas)],audio:[...new Set(audio)],errs:ctx.__errs},null,2));
|
||
console.log("\n[saved] out/kws_signal_report.json");
|
||
}, 800);
|