Created
March 31, 2025 17:02
-
-
Save slayer/e7aa2781d1e23b82fab3b73b0c1b40cb to your computer and use it in GitHub Desktop.
deobfuscated minFraud https://device.maxmind.com/js/device.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(() => { | |
// Async function helper | |
var createAsyncPromise = (context, args, asyncFn) => new Promise((resolve, reject) => { | |
var handleNext = result => { | |
try { | |
handleResult(asyncFn.next(result)) | |
} catch (error) { | |
reject(error) | |
} | |
}; | |
var handleError = error => { | |
try { | |
handleResult(asyncFn.throw(error)) | |
} catch (innerError) { | |
reject(innerError) | |
} | |
}; | |
var handleResult = result => result.done ? resolve(result.value) : Promise.resolve(result.value).then(handleNext, handleError); | |
handleResult((asyncFn = asyncFn.apply(context, args)).next()) | |
}); | |
// Global configuration object | |
var config = window.__mmapiws || {}; | |
/** | |
* MurmurHash3 implementation for fingerprint generation | |
*/ | |
function generateHash(input, seed) { | |
let remainder = input.length & 3; | |
let mainLength = input.length - remainder; | |
let hash = seed; | |
let tempHash; | |
const c1 = 3432918353; | |
const c2 = 461845907; | |
let position = 0; | |
let value; | |
// Process the input in 4-byte chunks | |
for (; position < mainLength;) { | |
value = input.charCodeAt(position) & 255 | | |
(input.charCodeAt(++position) & 255) << 8 | | |
(input.charCodeAt(++position) & 255) << 16 | | |
(input.charCodeAt(++position) & 255) << 24; | |
++position; | |
value = (value & 65535) * c1 + (((value >>> 16) * c1 & 65535) << 16) & 4294967295; | |
value = value << 15 | value >>> 17; | |
value = (value & 65535) * c2 + (((value >>> 16) * c2 & 65535) << 16) & 4294967295; | |
hash ^= value; | |
hash = hash << 13 | hash >>> 19; | |
tempHash = (hash & 65535) * 5 + (((hash >>> 16) * 5 & 65535) << 16) & 4294967295; | |
hash = (tempHash & 65535) + 27492 + (((tempHash >>> 16) + 58964 & 65535) << 16); | |
} | |
// Process remaining bytes | |
switch (value = 0, remainder) { | |
case 3: | |
value ^= (input.charCodeAt(position + 2) & 255) << 16; | |
case 2: | |
value ^= (input.charCodeAt(position + 1) & 255) << 8; | |
case 1: | |
value ^= input.charCodeAt(position) & 255; | |
value = (value & 65535) * c1 + (((value >>> 16) * c1 & 65535) << 16) & 4294967295; | |
value = value << 15 | value >>> 17; | |
value = (value & 65535) * c2 + (((value >>> 16) * c2 & 65535) << 16) & 4294967295; | |
hash ^= value; | |
} | |
// Finalization | |
hash ^= input.length; | |
hash ^= hash >>> 16; | |
hash = (hash & 65535) * 2246822507 + (((hash >>> 16) * 2246822507 & 65535) << 16) & 4294967295; | |
hash ^= hash >>> 13; | |
hash = (hash & 65535) * 3266489909 + (((hash >>> 16) * 3266489909 & 65535) << 16) & 4294967295; | |
hash ^= hash >>> 16; | |
return hash >>> 0; | |
} | |
/** | |
* Creates a promise that resolves after specified delay | |
*/ | |
function delay(ms) { | |
return new Promise(resolve => setTimeout(resolve, ms)); | |
} | |
/** | |
* Checks if the current window is in an iframe | |
*/ | |
function isInIframe() { | |
try { | |
return window.top.location.origin !== window.self.location.origin; | |
} catch (error) { | |
return true; | |
} | |
} | |
/** | |
* Creates a 2D canvas fingerprint | |
*/ | |
function getCanvasFingerprint() { | |
let canvas, context; | |
try { | |
canvas = document.createElement("canvas"); | |
context = canvas.getContext("2d"); | |
} catch (error) {} | |
if (!context) return null; | |
// Draw complex shapes and text to generate a unique fingerprint | |
context.fillStyle = "red"; | |
context.fillRect(30, 10, 200, 100); | |
context.strokeStyle = "#1a3bc1"; | |
context.lineWidth = 6; | |
context.lineCap = "round"; | |
context.arc(50, 50, 20, 0, Math.PI, false); | |
context.stroke(); | |
context.fillStyle = "#42e1a2"; | |
context.font = "15.4px 'Arial'"; | |
context.textBaseline = "alphabetic"; | |
context.fillText("PR flacks quiz gym: TV DJ box when? ☠", 15, 60); | |
context.shadowOffsetX = 1; | |
context.shadowOffsetY = 2; | |
context.shadowColor = "white"; | |
context.fillStyle = "rgba(0, 0, 200, 0.5)"; | |
context.font = "60px 'Not a real font'"; | |
context.fillText("No骗", 40, 80); | |
// Generate a hash from the canvas data URL | |
return generateHash(canvas.toDataURL(), 0); | |
} | |
/** | |
* Detects supported media codecs | |
*/ | |
function getMediaCodecs() { | |
let videoCodecs = [ | |
'video/mp4; codecs="avc1.42c00d"', | |
'video/ogg; codecs="theora"', | |
'video/webm; codecs="vorbis,vp8"', | |
'video/webm; codecs="vorbis,vp9"', | |
'video/mp2t; codecs="avc1.42E01E,mp4a.40.2"' | |
]; | |
return { | |
audio: testCodecSupport([ | |
"audio/mpeg", | |
'audio/mp4; codecs="mp4a.40.2"', | |
'audio/ogg; codecs="vorbis"', | |
'audio/ogg; codecs="opus"', | |
'audio/webm; codecs="vorbis"', | |
'audio/wav; codecs="1"' | |
]), | |
video: testCodecSupport(videoCodecs) | |
}; | |
} | |
/** | |
* Tests which codecs are supported | |
*/ | |
function testCodecSupport(codecs) { | |
let results = {}; | |
for (let i = 0; i < codecs.length; i++) { | |
let codec = codecs[i]; | |
if (window.MediaSource) { | |
results[codec] = window.MediaSource.isTypeSupported(codec); | |
} | |
} | |
return results; | |
} | |
/** | |
* Gets system colors | |
*/ | |
function getSystemColors() { | |
let colorValues = {}; | |
let canvas, context; | |
try { | |
canvas = document.createElement("canvas"); | |
context = canvas.getContext("2d"); | |
} catch (error) {} | |
if (!context) return colorValues; | |
let colorNames = [ | |
"ActiveBorder", "ActiveCaption", "AppWorkspace", "Background", | |
"ButtonFace", "ButtonHighlight", "ButtonShadow", "ButtonText", | |
"CaptionText", "GrayText", "Highlight", "HighlightText", | |
"InactiveBorder", "InactiveCaption", "InactiveCaptionText", | |
"InfoBackground", "InfoText", "Menu", "MenuText", "Scrollbar", | |
"ThreeDDarkShadow", "ThreeDFace", "ThreeDHighlight", "ThreeDLightShadow", | |
"ThreeDShadow", "Window", "WindowFrame", "WindowText" | |
]; | |
for (let i = 0; i < colorNames.length; i++) { | |
context.fillStyle = colorNames[i]; | |
colorValues[colorNames[i]] = context.fillStyle; | |
} | |
return colorValues; | |
} | |
// CSS styles to test for rendering fingerprint | |
var cssTestStyles = [{ | |
width: "150px", | |
height: "100px", | |
border: "2px solid rgba(0,150,255,0.91)", | |
color: "rgba(255,255,255,1)", | |
background: "rgb(255,255,255)", | |
position: "absolute", | |
transform: "skewY(29.1755628deg) rotate3d(11.000000007, 90, 0.100000000000009, 60000000000033.0000000006deg)" | |
}, /* More complex CSS styles... */]; | |
/** | |
* Gets CSS rendering fingerprint | |
*/ | |
function getCSSFingerprint() { | |
return createAsyncPromise(this, null, function* () { | |
try { | |
let iframe = document.createElement("iframe"); | |
iframe.style.visibility = "hidden"; | |
document.body.appendChild(iframe); | |
return new Promise((resolve, reject) => { | |
iframe.onload = function () { | |
// Create document in iframe and test CSS rendering | |
// ... [implementation] ... | |
}; | |
iframe.src = "about:blank"; | |
}); | |
} catch (error) { | |
return null; | |
} | |
}); | |
} | |
// Common fonts to detect | |
var commonFonts = [ | |
"Abadi MT Condensed Light", "Adobe Fangsong Std", "Adobe Hebrew", | |
/* Many more fonts... */ | |
]; | |
/** | |
* Detects installed fonts | |
*/ | |
function detectInstalledFonts() { | |
let canvasResult = detectFontsUsingCanvas(); | |
return canvasResult !== null ? canvasResult : detectFontsUsingSpanElements(); | |
} | |
/** | |
* Detects fonts using canvas measurements | |
*/ | |
function detectFontsUsingCanvas() { | |
let canvas, context; | |
try { | |
canvas = document.createElement("canvas"); | |
context = canvas.getContext("2d"); | |
} catch (error) {} | |
if (!context) return null; | |
let baseFonts = ["sans-serif", "serif", "monospace"]; | |
let baseWidths = []; | |
let fontSize = "72px"; | |
let testText = "mmmmmmmmmmlli"; | |
// Measure text with base fonts | |
for (let i = 0; i < baseFonts.length; i++) { | |
context.font = fontSize + ' "' + baseFonts[i] + '"'; | |
baseWidths[i] = context.measureText(testText); | |
} | |
// Test each font by comparing measurements | |
let detectedFonts = []; | |
for (let i = 0; i < commonFonts.length; i++) { | |
let font = commonFonts[i]; | |
let fontDetected = false; | |
for (let j = 0; j < baseWidths.length; j++) { | |
context.font = fontSize + ' "' + font + '", "' + baseFonts[j] + '"'; | |
let width = context.measureText(testText); | |
if (width.width !== baseWidths[j].width || | |
width.fontBoundingBoxAscent !== baseWidths[j].fontBoundingBoxAscent) { | |
fontDetected = true; | |
break; | |
} | |
} | |
if (fontDetected) { | |
detectedFonts.push(font); | |
} | |
} | |
return detectedFonts; | |
} | |
/** | |
* Font detector class using DOM elements | |
*/ | |
function FontDetector() { | |
let baseFonts = ["monospace", "sans-serif", "serif"]; | |
let baseWidths = []; | |
let testText = "mmmmmmmmmmlli"; | |
let fontSize = "72px"; | |
let body = document.body; | |
let testSpan = document.createElement("span"); | |
let spanStyle = testSpan.style; | |
spanStyle.fontSize = fontSize; | |
spanStyle.visibility = "hidden"; | |
testSpan.innerText = testText; | |
body.appendChild(testSpan); | |
let measureFont = function (fontFamily) { | |
spanStyle.fontFamily = fontFamily; | |
return { | |
height: testSpan.offsetHeight, | |
width: testSpan.offsetWidth | |
}; | |
}; | |
// Get base measurements | |
for (let i = 0; i < baseFonts.length; i++) { | |
baseWidths[i] = measureFont(baseFonts[i]); | |
} | |
this.detect = function (font) { | |
// Compare with base fonts to detect if font is installed | |
for (let i = 0; i < baseWidths.length; i++) { | |
let dimensions = measureFont('"' + font + '",' + baseFonts[i]); | |
let reference = baseWidths[i]; | |
if (dimensions.height !== reference.height || dimensions.width !== reference.width) { | |
return true; | |
} | |
} | |
return false; | |
}; | |
this.finish = function () { | |
body.removeChild(testSpan); | |
}; | |
} | |
// Math functions and test values for fingerprinting | |
var mathFunctions = [ | |
Math.acos, Math.acosh, Math.asin, Math.atan, Math.atanh, | |
Math.cos, Math.cosh, Math.exp, Math.expm1, Math.fround, | |
Math.log, Math.log10, Math.sin, Math.sinh, Math.sqrt, | |
Math.tan, Math.tanh | |
]; | |
var mathTestValues = [ | |
-1, 0, 1, Math.E, Math.PI, Math.SQRT2, 1e300, -1e300, 1e-300, -1e-300 | |
]; | |
/** | |
* Tests math functions for fingerprinting | |
*/ | |
function getMathFingerprint() { | |
let results = {}; | |
for (let func of mathFunctions) { | |
if (func) { | |
let funcResults = results[func.name] = {}; | |
for (let testValue of mathTestValues) { | |
let result = func(testValue); | |
if (!Number.isNaN(result)) { | |
funcResults[testValue] = formatInfinityValues(result); | |
} | |
} | |
} | |
} | |
return results; | |
} | |
/** | |
* Formats infinity values for consistent representation | |
*/ | |
function formatInfinityValues(value) { | |
return value == Number.NEGATIVE_INFINITY ? "-Infinity" : | |
value == Number.POSITIVE_INFINITY ? "Infinity" : value; | |
} | |
// List of browser plugins to check for | |
var knownPlugins = [ | |
"4game", "AdblockPlugin", "AdobeExManCCDetect", | |
/* Many more plugin names... */ | |
]; | |
/** | |
* Collects browser plugin information | |
*/ | |
function getPluginData() { | |
let pluginList = []; | |
let detectedPlugins = {}; | |
// Get installed plugins | |
for (let i = 0; i < navigator.plugins.length; i++) { | |
let plugin = navigator.plugins[i]; | |
// Skip Chrome's built-in Flash | |
if (plugin.name === "Shockwave Flash" && navigator.userAgent.indexOf("Chrome") > -1) { | |
continue; | |
} | |
detectedPlugins[plugin.name] = 1; | |
pluginList.push(formatPluginData(plugin)); | |
} | |
// Try to detect additional plugins | |
for (let i = 0; i < knownPlugins.length; i++) { | |
let pluginName = knownPlugins[i]; | |
if (!detectedPlugins[pluginName]) { | |
let plugin = navigator.plugins[pluginName]; | |
if (plugin) { | |
pluginList.push(formatPluginData(plugin)); | |
} | |
} | |
} | |
return pluginList; | |
} | |
/** | |
* Formats plugin data into a structured object | |
*/ | |
function formatPluginData(plugin) { | |
let pluginInfo = { | |
name: plugin.name, | |
filename: plugin.filename.toLowerCase(), | |
description: plugin.description | |
}; | |
if (typeof plugin.version != "undefined") { | |
pluginInfo.version = plugin.version; | |
} | |
pluginInfo.mimeTypes = []; | |
for (let i = 0; i < plugin.length; i++) { | |
let mimeType = plugin[i]; | |
pluginInfo.mimeTypes.push({ | |
description: mimeType.description, | |
suffixes: mimeType.suffixes, | |
type: mimeType.type | |
}); | |
} | |
return pluginInfo; | |
} | |
/** | |
* Collects navigator object properties | |
*/ | |
function getNavigatorInfo() { | |
return createAsyncPromise(this, null, function* () { | |
let navigatorProps = serializeObject(navigator); | |
let nav = navigator; | |
let connection = nav.connection || nav.mozConnection || nav.webkitConnection; | |
if (connection) { | |
navigatorProps.connection = serializeObject(connection); | |
} | |
// Collect gamepads | |
if ("getGamepads" in navigator) { | |
try { | |
navigatorProps.gamepads = Array.from( | |
navigator.getGamepads() | |
.filter(gp => !!gp) | |
.map(gp => gp.id) | |
); | |
} catch (error) {} | |
} | |
// Collect keyboard layout if available | |
if (!isInIframe() && nav.keyboard && nav.keyboard.getLayoutMap) { | |
navigatorProps.keyboard = navigatorProps.keyboard || {}; | |
navigatorProps.keyboard.layoutMap = Array.from( | |
(yield nav.keyboard.getLayoutMap()).entries() | |
); | |
} | |
// Collect media devices info | |
if (navigator.mediaDevices) { | |
if (navigator.mediaDevices.getSupportedConstraints) { | |
navigatorProps.mediaDevices = navigatorProps.mediaDevices || {}; | |
navigatorProps.mediaDevices.supportedConstraints = | |
navigator.mediaDevices.getSupportedConstraints(); | |
} | |
if (navigator.mediaDevices.enumerateDevices) { | |
navigatorProps.mediaDevices = navigatorProps.mediaDevices || {}; | |
navigatorProps.mediaDevices.enumerateDevices = | |
(yield navigator.mediaDevices.enumerateDevices()).map(device => device.kind); | |
} | |
} | |
return navigatorProps; | |
}); | |
} | |
/** | |
* Collects screen properties | |
*/ | |
function getScreenInfo() { | |
let screenProps = {}; | |
let propNames = []; | |
for (let prop in window.screen) { | |
if (typeof window.screen[prop] != "object") { | |
screenProps[prop] = window.screen[prop]; | |
} | |
propNames.push(prop); | |
} | |
screenProps.enumerationOrder = propNames; | |
return screenProps; | |
} | |
/** | |
* Creates a plain object with all enumerable properties | |
*/ | |
function serializeObject(obj) { | |
let propNames = []; | |
let result = {}; | |
for (let prop in obj) { | |
if (typeof obj[prop] != "object" || isArray(obj[prop])) { | |
result[prop] = obj[prop]; | |
} | |
propNames.push(prop); | |
} | |
result.enumerationOrder = propNames; | |
return result; | |
} | |
/** | |
* Checks if object is an array | |
*/ | |
function isArray(obj) { | |
return Object.prototype.toString.call(obj) === "[object Array]"; | |
} | |
/** | |
* Collects speech synthesis capabilities | |
*/ | |
function getSpeechSynthesisInfo() { | |
return createAsyncPromise(this, null, function* () { | |
if (!window.speechSynthesis) return null; | |
try { | |
return { | |
voices: (yield getVoicesList()).slice(0, 20).map(voice => serializeObject(voice)) | |
}; | |
} catch (error) { | |
return null; | |
} | |
}); | |
} | |
/** | |
* Gets available speech synthesis voices | |
*/ | |
function getVoicesList() { | |
return createAsyncPromise(this, null, function* () { | |
let voices = speechSynthesis.getVoices(); | |
if (voices.length) { | |
return voices; | |
} | |
// Wait for voices to load if none are available | |
yield Promise.race([ | |
new Promise(resolve => | |
speechSynthesis.addEventListener("voiceschanged", resolve, { once: true }) | |
), | |
delay(50) | |
]); | |
return speechSynthesis.getVoices(); | |
}); | |
} | |
/** | |
* Collects WebGL capabilities and generates fingerprint | |
*/ | |
function getWebGLInfo() { | |
let canvas = document.createElement("canvas"); | |
let context; | |
let webglInfo = {}; | |
try { | |
context = canvas.getContext("webgl") || canvas.getContext("experimental-webgl"); | |
} catch (error) {} | |
if (!context) return webglInfo; | |
try { | |
webglInfo.extensions = context.getSupportedExtensions(); | |
} catch (error) {} | |
try { | |
if (!config.disableWebglHash) { | |
webglInfo.hash = generateWebGLHash(context, canvas); | |
} | |
} catch (error) {} | |
return webglInfo; | |
} | |
/** | |
* Generates a hash based on WebGL rendering | |
*/ | |
function generateWebGLHash(gl, canvas) { | |
// GLSL shader program for fingerprinting | |
let vertexShader = "attribute vec2 attrVertex; varying vec2 varyinTexCoordinate; uniform vec2 uniformOffset; void main() { varyinTexCoordinate = attrVertex + uniformOffset; gl_Position = vec4(attrVertex, 0, 1); }"; | |
let fragmentShader = "precision mediump float; varying vec2 varyinTexCoordinate; void main() { gl_FragColor = vec4(varyinTexCoordinate, 0, 1); }"; | |
// Create buffer with vertex data | |
let buffer = gl.createBuffer(); | |
gl.bindBuffer(gl.ARRAY_BUFFER, buffer); | |
let vertices = new Float32Array([-.2, -.9, 0, .4, -.26, 0, 0, .732134444, 0]); | |
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW); | |
let vertSize = 3; | |
let numVertices = 3; | |
// Create and compile shader program | |
let program = gl.createProgram(); | |
let vShader = gl.createShader(gl.VERTEX_SHADER); | |
gl.shaderSource(vShader, vertexShader); | |
gl.compileShader(vShader); | |
let fShader = gl.createShader(gl.FRAGMENT_SHADER); | |
gl.shaderSource(fShader, fragmentShader); | |
gl.compileShader(fShader); | |
gl.attachShader(program, vShader); | |
gl.attachShader(program, fShader); | |
gl.linkProgram(program); | |
gl.useProgram(program); | |
// Set up attributes and uniforms | |
let vertexAttrib = gl.getAttribLocation(program, "attrVertex"); | |
let offsetUniform = gl.getUniformLocation(program, "uniformOffset"); | |
gl.enableVertexAttribArray(0); | |
gl.vertexAttribPointer(vertexAttrib, vertSize, gl.FLOAT, false, 0, 0); | |
gl.uniform2f(offsetUniform, 1, 1); | |
// Draw the shape to the canvas | |
gl.drawArrays(gl.TRIANGLE_STRIP, 0, numVertices); | |
// Create a hash from the resulting image | |
return generateHash(canvas.toDataURL(), 0); | |
} | |
/** | |
* Main fingerprinting function | |
*/ | |
function collectFingerprint() { | |
return createAsyncPromise(this, null, function* () { | |
let currentDate = new Date(); | |
let systemColors; | |
try { | |
systemColors = getSystemColors(); | |
} catch (error) {} | |
// Collect all fingerprint data | |
let fingerprintData = { | |
accountId: getAccountIdentifier(), | |
applePay: checkApplePaySupport(), | |
canvas: { | |
"2dHash": getCanvasFingerprint(), | |
webgl: getWebGLInfo() | |
}, | |
codecs: getMediaCodecs(), | |
deviceTime: currentDate.getTime() / 1000, | |
documentUrl: document.URL, | |
fonts: { | |
css: detectInstalledFonts() | |
}, | |
hasGPC: hasGlobalPrivacyControl(), | |
heapSizeLimit: getJSHeapLimit(), | |
math: getMathFingerprint(), | |
navigator: yield getNavigatorInfo(), | |
plugins: getPluginData(), | |
screen: getScreenInfo(), | |
speechSynthesis: yield getSpeechSynthesisInfo(), | |
systemColors: systemColors, | |
timezoneOffset: currentDate.getTimezoneOffset(), | |
touchInput: hasTouchSupport(), | |
rects: yield getCSSFingerprint() | |
}; | |
// Add battery info if available | |
if (navigator.getBattery) { | |
let battery = yield navigator.getBattery(); | |
fingerprintData.battery = { | |
charging: battery.charging, | |
chargingTime: battery.chargingTime, | |
dischargingTime: battery.dischargingTime, | |
level: battery.level | |
}; | |
} | |
return fingerprintData; | |
}); | |
} | |
/** | |
* Checks for Apple Pay support | |
*/ | |
function checkApplePaySupport() { | |
if (isInIframe()) return null; | |
let applePaySession = window.ApplePaySession; | |
return applePaySession ? { | |
canMakePayments: applePaySession.canMakePayments() | |
} : null; | |
} | |
/** | |
* Checks if Global Privacy Control is enabled | |
*/ | |
function hasGlobalPrivacyControl() { | |
return !!navigator.globalPrivacyControl; | |
} | |
/** | |
* Gets JavaScript heap size limit | |
*/ | |
function getJSHeapLimit() { | |
let memory = performance.memory; | |
return memory ? memory.jsHeapSizeLimit : 0; | |
} | |
/** | |
* Checks if device has touch support | |
*/ | |
function hasTouchSupport() { | |
try { | |
return navigator.maxTouchPoints > 0 || | |
"ontouchstart" in window || | |
window.matchMedia("(-webkit-touch-enabled),(-moz-touch-enabled),(-o-touch-enabled),(-ms-touch-enabled)").matches; | |
} catch (error) { | |
return false; | |
} | |
} | |
/** | |
* Gets account identifier | |
*/ | |
function getAccountIdentifier() { | |
return typeof window.survey_height_follow != "undefined" ? window.survey_height_follow : | |
typeof window.maxmind_user_id != "undefined" ? window.maxmind_user_id : | |
config.accountId; | |
} | |
// Main execution - start fingerprinting when document is ready | |
waitForDocumentBody().catch(error => console.error(error)); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment