zelin-bot / src /stealth-engine.js
Z User
v5.8.5: Gemma 4, MC Wiki, MC Player, anti-hallucination, CPU optimizations
ee826ee
/**
* stealth-engine.js β€” Motor de Stealth Completo para Zelin
* ==========================================================
* Basado en investigaciΓ³n real 2025-2026:
*
* HALLAZGO CLAVE: "Blocking or randomizing canvas fingerprinting is easily detected
* because the fingerprint changes on every page load, which is itself suspicious.
* Natural fingerprints that match real device configurations are superior."
* β€” Multilogin Research, 2025
*
* HALLAZGO CLAVE 2: "BotBrowser's key advantage: Canvas, WebGL, AudioContext,
* and Fonts must be CONSISTENT across the session. Inconsistencies instantly
* expose fake fingerprints."
* β€” GeeTest Anti-Bot Research, Nov 2025
*
* ESTRATEGIA: Fingerprint consistente + realista, NO aleatorio en cada request
* β†’ Generar UNA identidad de dispositivo por sesiΓ³n
* → Mantenerla IDÉNTICA en todas las pÑginas de esa sesión
* β†’ Que corresponda a una configuraciΓ³n de hardware que PODRÍA EXISTIR
*/
import { createHash } from 'crypto';
// ── Pool de configuraciones reales de dispositivos ───────────────────────────
// ExtraΓ­das de datasets de fingerprints reales (BrowserLeaks, CreepJS stats 2025)
// CRÍTICO: cada configuración debe ser INTERNAMENTE CONSISTENTE
// (GPU vendor debe coincidir con canvas output, etc.)
const REAL_DEVICE_CONFIGS = [
{
// Windows 11 + Chrome 120 + Intel UHD 770 (configuraciΓ³n mΓ‘s comΓΊn ~18% de usuarios)
platform : 'Win32',
oscpu : 'Windows NT 10.0',
vendor : 'Google Inc.',
renderer : 'ANGLE (Intel, Intel(R) UHD Graphics 770 Direct3D11 vs_5_0 ps_5_0, D3D11)',
vendorUnmasked : 'Intel',
rendererUnmasked: 'Intel(R) UHD Graphics 770',
hardwareConcurrency: 8,
deviceMemory : 8,
maxTouchPoints : 0,
screenRes : [1920, 1080],
colorDepth : 24,
pixelRatio : 1,
timezone : 'America/Mexico_City',
language : 'es-MX',
fonts : ['Arial', 'Calibri', 'Cambria', 'Comic Sans MS', 'Consolas', 'Courier New', 'Georgia', 'Segoe UI', 'Tahoma', 'Times New Roman', 'Trebuchet MS', 'Verdana'],
plugins : ['Chrome PDF Plugin', 'Chrome PDF Viewer', 'Native Client'],
chromeVersion : '120.0.6099.130',
userAgent : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
},
{
// Windows 10 + Chrome 119 + NVIDIA RTX 3060 (gamer setup)
platform : 'Win32',
oscpu : 'Windows NT 10.0',
vendor : 'Google Inc.',
renderer : 'ANGLE (NVIDIA, NVIDIA GeForce RTX 3060 Direct3D11 vs_5_0 ps_5_0, D3D11)',
vendorUnmasked : 'NVIDIA Corporation',
rendererUnmasked: 'NVIDIA GeForce RTX 3060',
hardwareConcurrency: 12,
deviceMemory : 16,
maxTouchPoints : 0,
screenRes : [2560, 1440],
colorDepth : 24,
pixelRatio : 1,
timezone : 'America/Mexico_City',
language : 'es-MX',
fonts : ['Arial', 'Calibri', 'Cambria', 'Consolas', 'Courier New', 'Georgia', 'Impact', 'Segoe UI', 'Tahoma', 'Times New Roman', 'Verdana'],
plugins : ['Chrome PDF Plugin', 'Chrome PDF Viewer'],
chromeVersion : '119.0.6045.200',
userAgent : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36',
},
{
// macOS 14 + Chrome 120 + Apple M2
platform : 'MacIntel',
oscpu : 'Intel Mac OS X 10_15_7',
vendor : 'Google Inc.',
renderer : 'ANGLE (Apple, ANGLE Metal Renderer: Apple M2, Unspecified Version)',
vendorUnmasked : 'Apple',
rendererUnmasked: 'Apple M2',
hardwareConcurrency: 8,
deviceMemory : 8,
maxTouchPoints : 0,
screenRes : [2560, 1664],
colorDepth : 30,
pixelRatio : 2,
timezone : 'America/Mexico_City',
language : 'es-MX',
fonts : ['Arial', 'Courier New', 'Geneva', 'Georgia', 'Helvetica', 'Monaco', 'Times New Roman', 'Verdana'],
plugins : ['Chrome PDF Plugin', 'Chrome PDF Viewer'],
chromeVersion : '120.0.6099.129',
userAgent : 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
},
];
// ── Generar identidad de sesiΓ³n (una sola vez, CONSISTENTE toda la sesiΓ³n) ───
let _sessionIdentity = null;
export function generateSessionIdentity(seed = null) {
if (_sessionIdentity && !seed) return _sessionIdentity;
// Seleccionar configuraciΓ³n base (misma para toda la sesiΓ³n)
const configIndex = seed
? parseInt(createHash('md5').update(seed).digest('hex').slice(0, 2), 16) % REAL_DEVICE_CONFIGS.length
: Math.floor(Math.random() * REAL_DEVICE_CONFIGS.length);
const base = REAL_DEVICE_CONFIGS[configIndex];
// AΓ±adir variaciΓ³n SUTIL y CONSISTENTE (mismos valores toda la sesiΓ³n)
const sessionSeed = seed || Date.now().toString();
const noise = (str, range) => {
const h = parseInt(createHash('md5').update(str + sessionSeed).digest('hex').slice(0, 4), 16);
return (h % (range * 2)) - range;
};
_sessionIdentity = {
...base,
// PequeΓ±a variaciΓ³n en resoluciΓ³n de pantalla (Β±8px) β€” realista
screenRes: [
base.screenRes[0] + noise('sw', 8),
base.screenRes[1] + noise('sh', 8),
],
// VariaciΓ³n en memoria (+/- una banda) β€” realista
deviceMemory: base.deviceMemory,
// Canvas noise value β€” fijo para toda la sesiΓ³n (no cambia entre pΓ‘ginas)
canvasNoise: noise('canvas', 3) * 0.001,
audioNoise : noise('audio', 5) * 0.0001,
sessionId : sessionSeed,
};
return _sessionIdentity;
}
export function resetSessionIdentity() {
_sessionIdentity = null;
}
// ── Script de inyecciΓ³n completo ─────────────────────────────────────────────
// Se inyecta ANTES de que cualquier script de la pΓ‘gina se ejecute
export function generateStealthScript(identity) {
const id = identity || generateSessionIdentity();
return `
(function() {
'use strict';
// ── 1. Ocultar webdriver completamente ─────────────────────────────────────
Object.defineProperty(navigator, 'webdriver', { get: () => undefined, configurable: false });
delete window.navigator.__proto__.webdriver;
// ── 2. Chrome runtime y permisos (ausentes en headless real) ──────────────
if (!window.chrome) {
window.chrome = {
app: { isInstalled: false, getDetails: function() {}, getIsInstalled: function() {}, runningState: function() { return 'cannot_run'; } },
runtime: {
onMessage: { addListener: function() {}, removeListener: function() {}, hasListeners: function() { return false; } },
connect: function() { return { onMessage: { addListener: function() {} }, onDisconnect: { addListener: function() {} }, postMessage: function() {} }; },
sendMessage: function() {},
id: undefined,
},
loadTimes: function() { return { commitLoadTime: ${Date.now() / 1000 - 1.2 - Math.random()}, connectionInfo: 'http/1.1', finishDocumentLoadTime: 0, finishLoadTime: 0, firstPaintAfterLoadTime: 0, firstPaintTime: 0, navigationType: 'Other', npnNegotiatedProtocol: 'unknown', requestTime: ${Date.now() / 1000 - 1.5 - Math.random()}, startLoadTime: ${Date.now() / 1000 - 1.4 - Math.random()}, wasAlternateProtocolAvailable: false, wasFetchedViaSpdy: false, wasNpnNegotiated: false }; },
csi: function() { return { onloadT: ${Date.now() - 800}, pageT: 1500, startE: ${Date.now() - 1200}, tran: 15 }; },
};
}
// ── 3. Navigator properties β€” CONSISTENTES con el perfil ─────────────────
const navigatorProps = {
platform : '${id.platform}',
vendor : '${id.vendor}',
hardwareConcurrency: ${id.hardwareConcurrency},
deviceMemory : ${id.deviceMemory},
maxTouchPoints : ${id.maxTouchPoints},
languages : ['${id.language}', '${id.language.split('-')[0]}', 'en'],
language : '${id.language}',
plugins : { length: ${id.plugins.length} },
mimeTypes : { length: 4 },
doNotTrack : null,
};
for (const [key, value] of Object.entries(navigatorProps)) {
try {
Object.defineProperty(navigator, key, { get: () => value, configurable: true });
} catch(e) {}
}
// ── 4. Canvas fingerprint β€” ruido CONSISTENTE (mismo en cada carga) ────────
// CRÍTICO: NO aleatorio en cada llamada, sino fijo para la sesión
const CANVAS_NOISE = ${id.canvasNoise};
const _origToDataURL = HTMLCanvasElement.prototype.toDataURL;
const _origGetImageData = CanvasRenderingContext2D.prototype.getImageData;
HTMLCanvasElement.prototype.toDataURL = function(type, quality) {
const result = _origToDataURL.apply(this, arguments);
// Solo aΓ±adir ruido si parece fingerprinting (canvas pequeΓ±o)
if (this.width <= 300 && this.height <= 150) {
try {
const ctx = this.getContext('2d');
if (ctx) {
const id = ctx.getImageData(0, 0, this.width, this.height);
// Ruido imperceptible pero consistente
for (let i = 0; i < id.data.length; i += 4) {
id.data[i] = Math.max(0, Math.min(255, id.data[i] + Math.round(CANVAS_NOISE * 255)));
id.data[i+1] = Math.max(0, Math.min(255, id.data[i+1] + Math.round(CANVAS_NOISE * 127)));
}
ctx.putImageData(id, 0, 0);
}
} catch(e) {}
}
return _origToDataURL.apply(this, arguments);
};
// ── 5. WebGL fingerprint β€” valores REALES del perfil ─────────────────────
const _origGetParam = WebGLRenderingContext.prototype.getParameter;
WebGLRenderingContext.prototype.getParameter = function(parameter) {
if (parameter === 0x9245) return '${id.vendorUnmasked}'; // UNMASKED_VENDOR_WEBGL
if (parameter === 0x9246) return '${id.rendererUnmasked}'; // UNMASKED_RENDERER_WEBGL
if (parameter === 0x1F00) return '${id.vendor}'; // VENDOR
if (parameter === 0x1F01) return '${id.renderer}'; // RENDERER
return _origGetParam.apply(this, arguments);
};
// WebGL2 tambiΓ©n
try {
const _origGetParam2 = WebGL2RenderingContext.prototype.getParameter;
WebGL2RenderingContext.prototype.getParameter = function(parameter) {
if (parameter === 0x9245) return '${id.vendorUnmasked}';
if (parameter === 0x9246) return '${id.rendererUnmasked}';
if (parameter === 0x1F00) return '${id.vendor}';
if (parameter === 0x1F01) return '${id.renderer}';
return _origGetParam2.apply(this, arguments);
};
} catch(e) {}
// ── 6. Audio fingerprint β€” ruido consistente ──────────────────────────────
const AUDIO_NOISE = ${id.audioNoise};
try {
const _origGetChanData = AudioBuffer.prototype.getChannelData;
AudioBuffer.prototype.getChannelData = function(channel) {
const data = _origGetChanData.apply(this, arguments);
for (let i = 0; i < data.length; i += 100) {
data[i] += AUDIO_NOISE;
}
return data;
};
} catch(e) {}
// ── 7. Timing attacks β€” usar valores realistas ────────────────────────────
const _origNow = Date.now;
const timeOffset = ${Math.floor(Math.random() * 200) - 100};
// Solo aΓ±adir offset mΓ­nimo para no romper lΓ³gica de timers
// Date.now = function() { return _origNow() + timeOffset; }; // deshabilitado: rompe demasiado
// ── 8. Permissions API β€” igual que Chrome real ────────────────────────────
if (navigator.permissions) {
const _origQuery = navigator.permissions.query.bind(navigator.permissions);
navigator.permissions.query = async function(params) {
if (params.name === 'notifications') return { state: Notification.permission, onchange: null };
return _origQuery(params);
};
}
// ── 9. Screen properties consistentes ────────────────────────────────────
try {
Object.defineProperty(screen, 'width', { get: () => ${id.screenRes[0]}, configurable: true });
Object.defineProperty(screen, 'height', { get: () => ${id.screenRes[1]}, configurable: true });
Object.defineProperty(screen, 'availWidth', { get: () => ${id.screenRes[0]}, configurable: true });
Object.defineProperty(screen, 'availHeight', { get: () => ${id.screenRes[1] - 40}, configurable: true });
Object.defineProperty(screen, 'colorDepth', { get: () => ${id.colorDepth}, configurable: true });
Object.defineProperty(window, 'devicePixelRatio', { get: () => ${id.pixelRatio}, configurable: true });
} catch(e) {}
// ── 10. Eliminar rastros de Playwright/CDP ────────────────────────────────
try {
delete window.cdc_adoQpoasnfa76pfcZLmcfl_Array;
delete window.cdc_adoQpoasnfa76pfcZLmcfl_Promise;
delete window.cdc_adoQpoasnfa76pfcZLmcfl_Symbol;
delete window.__playwright;
delete window.__pw_manual;
delete window.__PW_inspect;
} catch(e) {}
// ── 11. iframe contentWindow stealth ─────────────────────────────────────
// Anti-bot checks en iframes (como Cloudflare Turnstile)
const _origContentWindow = Object.getOwnPropertyDescriptor(HTMLIFrameElement.prototype, 'contentWindow');
if (_origContentWindow) {
Object.defineProperty(HTMLIFrameElement.prototype, 'contentWindow', {
get: function() {
const cw = _origContentWindow.get.call(this);
if (!cw) return null;
try {
Object.defineProperty(cw.navigator, 'webdriver', { get: () => undefined, configurable: true });
} catch(e) {}
return cw;
},
configurable: true,
});
}
// ── 12. Ocultar stack traces de automation ────────────────────────────────
// Algunos sites detectan errores con stack traces de Playwright
const _origPrepareStackTrace = Error.prepareStackTrace;
Error.prepareStackTrace = function(err, stack) {
const filtered = stack.filter(frame => {
const name = frame.getFileName?.() || '';
return !name.includes('playwright') && !name.includes('puppeteer') && !name.includes('@playwright');
});
return _origPrepareStackTrace ? _origPrepareStackTrace(err, filtered) : filtered.map(f => f.toString()).join('\n');
};
window._stealthApplied = true;
window._sessionId = '${id.sessionId}';
})();
`;
}
export function getIdentityStats(identity) {
const id = identity || generateSessionIdentity();
return {
platform : id.platform,
renderer : id.rendererUnmasked,
screen : id.screenRes.join('x'),
language : id.language,
userAgent : id.userAgent.slice(0, 60) + '...',
};
}