Skip to content

Instantly share code, notes, and snippets.

@Crypto-Spartan
Created April 26, 2023 06:35
Show Gist options
  • Save Crypto-Spartan/b52f282e0f0b997f6a33c6e5fb75021b to your computer and use it in GitHub Desktop.
Save Crypto-Spartan/b52f282e0f0b997f6a33c6e5fb75021b to your computer and use it in GitHub Desktop.
Abort Browser Fingerprinting Scripts via uBlock Origin
/// Original found here: https://github.com/abrahamjuliot/ublock-origin-abf
/// abf.js
/// alias abf.js
(function() {
const sessionName = '@user__'
const hashMini = str => {
const json = `${JSON.stringify(str)}`
let i, len, hash = 0x811c9dc5
for (i = 0, len = json.length; i < len; i++) {
hash = Math.imul(31, hash) + json.charCodeAt(i) | 0
}
return ('0000000' + (hash >>> 0).toString(16)).substr(-8)
}
const computeRandomValues = () => {
const listRand = (list) => list[Math.floor(Math.random() * list.length)]
const evenRand = (min, max) =>
(Math.floor(Math.random() * ((max / 2) - min + 1)) + min) * 2
const rand = (min, max) =>
(Math.floor(Math.random() * (max - min + 1)) + min)
// Device GPU
// https://www.primegrid.com/gpu_list.php
const webglRenderer = () => {
const macRenderers = [{
gpu: 'AMD Radeon',
model: `20${listRand(['HD 7950', 'Pro 580', 'RX 570', 'RX Vega 56'])} Compute Engine`
},
{
gpu: 'NVIDIA GeForce GTX',
model: listRand(['675MX', '680'])
}
]
const renderers = [{
gpu: 'NVIDIA GeForce RTX',
model: `20${listRand([7, 8])}0${listRand(['', ' Super'])}`
},
{
gpu: 'NVIDIA GeForce GTX',
model: `10${listRand([5, 6, 7, 8])}0${listRand(['', ' Ti'])}`
},
{
gpu: 'Radeon',
model: `RX ${listRand([560, 570, 580])} Series`
}
]
const randomRenderer = (
navigator.platform == 'MacIntel' ? listRand(macRenderers) : listRand(renderers)
)
const {
gpu,
model
} = randomRenderer
const randomizedRenderer = `${gpu} ${model}`
const extension = {
37446: `ANGLE (${randomizedRenderer} vs_${rand(1, 5)}_0 ps_${rand(1, 5)}_0)`
}
return extension
}
const webglExtensionComputed = webglRenderer()
// canvasContext
const canvas = () => {
const randomRGBA = () => {
const clr = () => Math.round(Math.random() * 255)
return `rgba(${clr()},${clr()},${clr()},${Math.random().toFixed(1)})`
}
const randomFont = () => {
const fontFamily = [
'Arial', 'Courier', 'Georgia', 'Helvetica', 'Impact', 'monospace', 'Tahoma', 'Times', 'Verdana'
]
const fontSize = Math.floor((Math.random() * 100) + 12)
const rand = Math.floor(Math.random() * fontFamily.length)
return `${fontSize}px ${fontFamily[rand]}`
}
const fillStyle = randomRGBA()
const shadowColor = randomRGBA()
const strokeStyle = randomRGBA()
const font = randomFont()
const clearColor = [...Array(4)].map(() => Math.random().toFixed(1))
const randomChar = () => {
const capitalize = Math.random() < 0.5
const str = String.fromCharCode(97 + Math.floor(Math.random() * 26))
return capitalize ? str.toUpperCase() : str
}
const randomChars = [
randomChar(),
randomChar(),
randomChar(),
randomChar(),
randomChar(),
]
return {
fillStyle,
shadowColor,
strokeStyle,
font,
clearColor,
randomChars
}
}
const canvasContextComputed = canvas()
// clientRects
const clientRectsOffsetComputed = rand(10, 99)
// audioData
const channelNoise = Math.random() * 0.0000001
const frequencyNoise = Math.random() * 0.001
const audioDataComputed = {
channelNoise,
frequencyNoise
}
// create hashes
const hash = hashMini({
webglExtensionComputed,
canvasContextComputed,
clientRectsOffsetComputed,
audioDataComputed
})
// log exection
const timestamp = new Date().toLocaleTimeString()
console.log(`${timestamp}: Setting up new randomization... ${hash}`)
return {
timestamp,
hash,
webglExtensionComputed,
canvasContextComputed,
clientRectsOffsetComputed,
audioDataComputed
}
}
// set session
if (!sessionStorage.getItem(sessionName)) {
sessionStorage.setItem(sessionName, JSON.stringify(computeRandomValues()))
}
// get session
const {
timestamp,
hash,
webglExtensionComputed,
canvasContextComputed,
clientRectsOffsetComputed,
audioDataComputed
} = JSON.parse(sessionStorage.getItem(sessionName))
const sessionProtection = `uBlock Origin ABF Session: ${hash} @${timestamp}`
console.log(sessionProtection)
// webgl
function computeGetParameter(type) {
const nativeGetParameter = (
type == 'webgl2' ?
WebGL2RenderingContext.prototype.getParameter :
WebGLRenderingContext.prototype.getParameter
)
return function getParameter(x) {
return (
webglExtensionComputed === false ? nativeGetParameter.apply(this, arguments) :
webglExtensionComputed[x] ? webglExtensionComputed[x] :
nativeGetParameter.apply(this, arguments)
)
}
}
// canvas
const nativeGetContext = HTMLCanvasElement.prototype.getContext
const nativeToDataURL = HTMLCanvasElement.prototype.toDataURL
const nativeToBlob = HTMLCanvasElement.prototype.toBlob
const nativeGetImageData = CanvasRenderingContext2D.prototype.getImageData
function getContext(contextType, contextAttributes) {
this._contextType = contextType
return nativeGetContext.apply(this, arguments)
}
function randomizeContext2D(context) {
const {
fillStyle,
shadowColor,
strokeStyle,
font
} = canvasContextComputed
context.textBaseline = 'top'
context.textBaseline = 'alphabetic'
context.fillStyle = fillStyle
context.shadowColor = shadowColor
context.strokeStyle = strokeStyle
context.fillText('.', 4, 17)
context.font = font
return context
}
function randomizeContextWebgl(canvas) {
const context = nativeGetContext.apply(canvas, [canvas._contextType])
if (context) {
const {
clearColor
} = canvasContextComputed
context.clearColor(...clearColor)
}
return context
}
function randomizeWebgl(dataURI) {
const chunks = dataURI.split('+')
const lastChunk = chunks[chunks.length-1]
const [rand1, rand2, rand3, rand4, rand5] = canvasContextComputed.randomChars
const chunkRandomized = lastChunk.replace(/./g, (char, i) => {
const len = lastChunk.length
return (
i == len-1 ? rand1 :
i == len-3 ? rand2 :
i == len-5 ? rand3 :
i == len-7 ? rand4 :
i == len-9 ? rand5 :
char
)
})
chunks.splice(-1, 1, chunkRandomized)
return chunks.join('+')
}
function toDataURL() {
if (this._contextType == '2d') {
const context = nativeGetContext.apply(this, ['2d'])
randomizeContext2D(context)
return nativeToDataURL.apply(this, arguments)
}
else if (this._contextType == 'webgl' || this._contextType == 'webgl2') {
const dataURI = nativeToDataURL.apply(this, arguments)
return randomizeWebgl(dataURI)
}
return nativeToDataURL.apply(this, arguments)
}
function toBlob() {
if (this._contextType == '2d') {
const context = nativeGetContext.apply(this, ['2d'])
randomizeContext2D(context)
return nativeToBlob.apply(this, arguments)
}
else if (this._contextType == 'webgl' || this._contextType == 'webgl2') {
randomizeContextWebgl(this)
return nativeToBlob.apply(this, arguments)
}
return nativeToBlob.apply(this, arguments)
}
function getImageData() {
const context = randomizeContext2D(this)
return nativeGetImageData.apply(context, arguments)
}
// clientRects
const nativeElementGetClientRects = Element.prototype.getClientRects
const nativeElementGetBoundingClientRect = Element.prototype.getBoundingClientRect
const nativeRangeGetClientRects = Range.prototype.getClientRects
const nativeRangeGetBoundingClientRect = Range.prototype.getBoundingClientRect
const randomClient = type => {
const tryRandomNumber = (num, computedOffset) => {
const shouldLieNumber = num => {
const decimals = num && num.toString().split('.')[1]
return decimals && decimals.length > 10 ? true : false
}
if (shouldLieNumber(num)) {
const str = '' + num
const offset = '' + computedOffset
const randomizedNumber = +(
str.slice(0, -3) + offset + str.slice(-1)
)
return randomizedNumber
}
return num
}
const method = (
type == 'rangeRects' ? nativeRangeGetClientRects :
type == 'rangeBounding' ? nativeRangeGetBoundingClientRect :
type == 'elementRects' ? nativeElementGetClientRects :
type == 'elementBounding' ? nativeElementGetBoundingClientRect : ''
)
const domRectify = (client) => {
const props = ['bottom', 'height', 'left', 'right', 'top', 'width', 'x', 'y']
if (client.length) {
let i, len = client.length
for (i = 0; i < len; i++) {
client[i][props[0]] = tryRandomNumber(client[i][props[0]], clientRectsOffsetComputed)
client[i][props[1]] = tryRandomNumber(client[i][props[1]], clientRectsOffsetComputed)
client[i][props[2]] = tryRandomNumber(client[i][props[2]], clientRectsOffsetComputed)
client[i][props[3]] = tryRandomNumber(client[i][props[3]], clientRectsOffsetComputed)
client[i][props[4]] = tryRandomNumber(client[i][props[4]], clientRectsOffsetComputed)
client[i][props[5]] = tryRandomNumber(client[i][props[5]], clientRectsOffsetComputed)
client[i][props[6]] = tryRandomNumber(client[i][props[6]], clientRectsOffsetComputed)
client[i][props[7]] = tryRandomNumber(client[i][props[7]], clientRectsOffsetComputed)
}
return client
}
props.forEach(prop => {
client[prop] = tryRandomNumber(client[prop], clientRectsOffsetComputed)
})
return client
}
function getBoundingClientRect() {
const client = method.apply(this, arguments)
return domRectify(client)
}
function getClientRects() {
const client = method.apply(this, arguments)
return domRectify(client)
}
return (
type == 'rangeRects' || type == 'elementRects' ? getClientRects :
getBoundingClientRect
)
}
// audioData
const {
channelNoise,
frequencyNoise
} = audioDataComputed
const nativeGetChannelData = AudioBuffer.prototype.getChannelData
const nativeCopyFromChannel = AudioBuffer.prototype.copyFromChannel
const nativeGetByteFrequencyData = AnalyserNode.prototype.getByteFrequencyData
const nativeGetFloatFrequencyData = AnalyserNode.prototype.getFloatFrequencyData
function computePCMData(obj, args) {
const data = nativeGetChannelData.apply(obj, args)
let i, len = data ? data.length : 0
for (i = 0; i < len; i++) {
// ensure audio is within range of -1 and 1
const audio = data[i]
const noisified = audio + channelNoise
data[i] = noisified > -1 && noisified < 1 ? noisified : audio
}
obj._pcmDataComputedChannel = args[0]
obj._pcmDataComputed = data
return data
}
function getChannelData(channel) {
// if pcm data is already computed to this AudioBuffer Channel then return it
if (this._pcmDataComputed && this._pcmDataComputedChannel == channel) {
return this._pcmDataComputed
}
// else compute pcm data to this AudioBuffer Channel and return it
const data = computePCMData(this, arguments)
return data
}
function copyFromChannel(destination, channel) {
// if pcm data is not yet computed to this AudioBuffer Channel then compute it
if (!(this._pcmDataComputed && this._pcmDataComputedChannel == channel)) {
computePCMData(this, [channel])
}
// else make no changes to this AudioBuffer Channel (seeing it is already computed)
return nativeCopyFromChannel.apply(this, arguments)
}
function computeFrequencyData(data) {
let i, len = data.length
for (i = 0; i < len; i++) {
data[i] += frequencyNoise
}
return
}
function getByteFrequencyData(uint8Arr) {
nativeGetByteFrequencyData.apply(this, arguments)
computeFrequencyData(uint8Arr)
return
}
function getFloatFrequencyData(float32Arr) {
nativeGetFloatFrequencyData.apply(this, arguments)
computeFrequencyData(float32Arr)
return
}
// Detect Fingerprinting
// Property API and Fingerprint Rank
const propAPI = {
appVersion: ['Navigator.prototype.appVersion', 1],
deviceMemory: ['Navigator.prototype.deviceMemory', 3],
doNotTrack: ['Navigator.prototype.doNotTrack', 1],
hardwareConcurrency: ['Navigator.prototype.hardwareConcurrency', 3],
languages: ['Navigator.prototype.languages', 1],
maxTouchPoints: ['Navigator.prototype.maxTouchPoints', 1],
mimeTypes: ['Navigator.prototype.mimeTypes', 6],
platform: ['Navigator.prototype.platform', 1],
plugins: ['Navigator.prototype.plugins', 6],
userAgent: ['Navigator.prototype.userAgent', 1],
vendor: ['Navigator.prototype.vendor', 1],
connection: ['Navigator.prototype.connection', 1],
cookieEnabled: ['Navigator.prototype.cookieEnabled', 1],
getBattery: ['Navigator.prototype.getBattery', 1],
getGamepads: ['Navigator.prototype.getGamepads', 1],
width: ['Screen.prototype.width', 1],
height: ['Screen.prototype.height', 1],
availWidth: ['Screen.prototype.availWidth', 1],
availHeight: ['Screen.prototype.availHeight', 1],
availTop: ['Screen.prototype.availTop', 1],
availLeft: ['Screen.prototype.availLeft', 1],
colorDepth: ['Screen.prototype.colorDepth', 1],
pixelDepth: ['Screen.prototype.pixelDepth', 1],
getTimezoneOffset: ['Date.prototype.getTimezoneOffset', 1],
resolvedOptions: ['Intl.DateTimeFormat.prototype.resolvedOptions', 1],
acos: ['acos: Math.acos', 1],
acosh: ['Math.acosh', 1],
asin: ['Math.asin', 1],
asinh: ['Math.asinh', 1],
cosh: ['Math.cosh', 1],
expm1: ['Math.expm1', 1],
sinh: ['Math.sinh', 1],
enumerateDevices: ['navigator.mediaDevices.enumerateDevices', 1],
now: ['Performance.prototype.now', 1],
getBoundingClientRect: ['prototype.getBoundingClientRect', 4],
getClientRects: ['prototype.getClientRects', 4],
shaderSource: ['WebGLRenderingContext.prototype.shaderSource', 4],
getExtension: ['WebGLRenderingContext.prototype.getExtension', 4],
getParameter: ['WebGLRenderingContext.prototype.getParameter', 6],
getSupportedExtensions: ['WebGLRenderingContext.prototype.getSupportedExtensions', 4],
getContext: ['HTMLCanvasElement.prototype.getContext', 1],
toDataURL: ['HTMLCanvasElement.prototype.toDataURL', 6],
toBlob: ['HTMLCanvasElement.prototype.toBlob', 4],
getImageData: ['CanvasRenderingContext2D.prototype.getImageData', 1],
isPointInPath: ['CanvasRenderingContext2D.prototype.isPointInPath', 1],
isPointInStroke: ['CanvasRenderingContext2D.prototype.isPointInStroke', 1],
measureText: ['CanvasRenderingContext2D.prototype.measureText', 6],
getChannelData: ['AudioBuffer.prototype.getChannelData', 8],
copyFromChannel: ['AudioBuffer.prototype.copyFromChannel', 8],
getByteFrequencyData: ['AnalyserNode.prototype.getByteFrequencyData', 8],
getFloatFrequencyData: ['AnalyserNode.prototype.getFloatFrequencyData', 6],
createDataChannel: ['RTCPeerConnection.prototype.createDataChannel', 3],
createOffer: ['RTCPeerConnection.prototype.createOffer', 3],
setRemoteDescription: ['RTCPeerConnection.prototype.setRemoteDescription', 3]
}
const randomChar = () => String.fromCharCode(97 + Math.floor(Math.random() * 26))
const listRand = (list) => list[Math.floor(Math.random() * list.length)]
const letter = randomChar()
const errorStruct = {
'RangeError': {
firefox: [
'invalid array length',
'repeat count must be non-negative'
],
chrome: [
'Invalid array length',
'Invalid count value'
]
},
'ReferenceError': {
firefox: [
`${letter} is not defined`,
`assignment to undeclared variable ${letter}`,
`can't access lexical declaration ${letter} before initialization`,
'invalid assignment left-hand side'
],
chrome: [
`${letter} is not defined`,
'invalid assignment left-hand side'
]
},
'SyntaxError': {
firefox: [
'function is a reserved identifier',
'function statement requires a name',
'identifier starts immediately after numeric literal',
'illegal character',
`invalid regular expression flag ${letter}`,
'expected expression, got end of script',
`redeclaration of formal parameter ${letter}`
],
chrome: [
'Unexpected reserved word',
'Unexpected token',
'Unexpected number',
'Invalid or unexpected token',
'Invalid regular expression flags',
'Unexpected end of input',
`Identifier ${letter} has already been declared`
]
},
'TypeError': {
firefox: [
`${letter} is not a function`,
`${letter} is not iterable`,
`${letter} is null`
],
chrome: [
`${letter} is not a function`,
`${letter} is not iterable`,
`Cannot read property ${letter} of null`
]
}
}
const firefox = (navigator.userAgent.indexOf('Firefox') != -1)
const errorType = listRand(Object.keys(errorStruct))
// https://stackoverflow.com/questions/2255689/how-to-get-the-file-path-of-the-currently-executing-javascript-code
const unknownSource = '[unknown source]'
const getCurrentScript = () => {
const jsURL = /(\/.+\.(js|html|htm))/gi
const error = new Error()
const jsPath = error.stack.match(jsURL)
return jsPath ? 'https:' + jsPath[0] : unknownSource
}
const warningRank = 16 // total rank that triggers fingerprinting warning
const scripts = {}
const watch = prop => {
const url = getCurrentScript()
const propDescription = propAPI[prop][0]
const randomError = listRand(errorStruct[errorType][(firefox ? 'firefox' : 'chrome')])
const abort = (errorType, randomError) => {
return (
errorType == 'RangeError' ? new RangeError(randomError) :
errorType == 'ReferenceError' ? new ReferenceError(randomError) :
errorType == 'SyntaxError' ? new SyntaxError(randomError) :
new TypeError(randomError)
)
}
// abort creepy url if permission denied
const sessionPermission = sessionStorage.getItem(sessionName + 'permit')
const creeps = JSON.parse(sessionStorage.getItem(sessionName + 'creeps'))
if (sessionPermission == 'deny' && creeps && creeps[url]) {
console.log('aborting '+propDescription)
const { timestamp } = JSON.parse(sessionStorage.getItem(sessionName + 'error'))
const secondsPassed = (new Date() - timestamp) / 1000
if (secondsPassed > 30) {
sessionStorage.setItem(sessionName + 'error', JSON.stringify({ timestamp: +(new Date()), type: errorType, message: randomError }))
const error = abort(errorType, randomError)
throw error
}
else {
const { type, message } = JSON.parse(sessionStorage.getItem(sessionName + 'error'))
const error = abort(type, message)
throw error
}
}
// capture script
const rank = propAPI[prop][1]
const capturedScript = scripts[url]
if (!capturedScript) {
scripts[url] = {
creep: false,
rank,
reads: { [propDescription]: true }
}
}
// if new prop, add it to already captured script
else if (!capturedScript.reads[propDescription]) {
capturedScript.reads[propDescription] = true
// compute rank increase
const { reads } = capturedScript
const readsLen = Object.keys(reads).length
capturedScript.rank += (
rank == 1 && readsLen >= 8 ? 4 :
rank == 1 && readsLen >= 6 ? 3 :
rank == 1 && readsLen >= 4 ? 2 :
rank
)
// detect fingerprinting
if (!capturedScript.creep && capturedScript.rank >= warningRank) {
capturedScript.creep = true
const unknown = url == unknownSource
const reads = Object.keys(capturedScript.reads)
const readsFormatted = reads.map(prop => prop.replace(/\.prototype/, '')).join('\n')
if (!unknown) {
console.groupCollapsed(`Fingerprinting detected!`)
console.log(`Creepy script: ${url}`)
console.log(
`Detection triggered by ${reads.length} property reads:`,
'\n' + readsFormatted
)
console.log('\n\nScript Data:\n', capturedScript)
console.groupEnd()
}
const message = (confirmPermission, [url, session, reads]) => {
return '🤮 Fingerprinting detected!'
+ (confirmPermission ? ' OK to allow or Cancel to abort\n' : '\n')
+ '🛡 ' + session + '\n'
+ '💩 Creepy script: ' + url + '\n'
+ '🧐\n' + reads + '\n...' + '\n'
}
if ((creeps && !creeps[url]) || !sessionPermission) {
let permission = null
const creepyOrigin = sessionStorage.getItem(sessionName + 'creepyOrigin')
if (unknown && !creepyOrigin) {
sessionStorage.setItem(sessionName + 'creepyOrigin', true)
//const { origin } = location
//alert(message(false, [origin, sessionProtection, readsFormatted]))
}
else if (!unknown) {
permission = confirm(message(true, [url, sessionProtection, readsFormatted]))
}
if (permission) {
sessionStorage.setItem(sessionName + 'permit', 'allow')
}
else if (permission === false) {
sessionStorage.setItem(sessionName + 'permit', 'deny')
sessionStorage.setItem(sessionName + 'error', JSON.stringify({ timestamp: +(new Date()), type: errorType, message: randomError }))
if (creeps && !unknown) {
creeps[url] = true
sessionStorage.setItem(sessionName + 'creeps', JSON.stringify(creeps))
}
else if (!unknown) {
sessionStorage.setItem(sessionName + 'creeps', JSON.stringify({ [url]: true }))
}
const error = abort(errorType, randomError)
throw error
}
}
}
}
return
}
// difinify
const intlProps = {
resolvedOptions: Intl.DateTimeFormat.prototype.resolvedOptions
}
const mediaDeviceProps = {
enumerateDevices: navigator.mediaDevices.enumerateDevices
}
const apiStructs = [{
name: 'Navigator',
proto: true,
struct: {
platform: navigator.platform,
vendor: navigator.vendor,
appVersion: navigator.appVersion,
userAgent: navigator.userAgent,
maxTouchPoints: navigator.maxTouchPoints,
hardwareConcurrency: navigator.hardwareConcurrency,
deviceMemory: navigator.deviceMemory,
doNotTrack: navigator.doNotTrack,
languages: navigator.languages,
mimeTypes: navigator.mimeTypes,
plugins: navigator.plugins,
connection: navigator.connection,
cookieEnabled: navigator.cookieEnabled,
getBattery: navigator.getBattery,
getGamepads: navigator.getGamepads
}
},
{
name: 'Screen',
proto: true,
struct: {
width: screen.width,
height: screen.height,
availWidth: screen.availWidth,
availHeight: screen.availHeight,
colorDepth: screen.colorDepth,
pixelDepth: screen.pixelDepth
}
},
{
name: 'Date',
proto: true,
struct: {
getTimezoneOffset: Date.prototype.getTimezoneOffset
}
},
{
name: 'Math',
proto: false,
struct: {
acos: Math.acos,
acosh: Math.acosh,
asin: Math.asin,
asinh: Math.asinh,
cosh: Math.cosh,
expm1: Math.expm1,
sinh: Math.sinh
}
},
{
name: 'Performance',
proto: true,
struct: {
now: performance.now
}
},
{
name: 'Element',
proto: true,
struct: {
getBoundingClientRect: randomClient('elementBounding'),
getClientRects: randomClient('elementRects')
}
},
{
name: 'Range',
proto: true,
struct: {
getBoundingClientRect: randomClient('rangeBounding'),
getClientRects: randomClient('rangeRects')
}
},
{
name: 'WebGLRenderingContext',
proto: true,
struct: {
shaderSource: WebGLRenderingContext.prototype.shaderSource,
getExtension: WebGLRenderingContext.prototype.getExtension,
getParameter: computeGetParameter('webgl'),
getSupportedExtensions: WebGLRenderingContext.prototype.getSupportedExtensions
}
},
{
name: 'WebGL2RenderingContext',
proto: true,
struct: {
shaderSource: WebGL2RenderingContext.prototype.shaderSource,
getExtension: WebGL2RenderingContext.prototype.getExtension,
getParameter: computeGetParameter('webgl2'),
getSupportedExtensions: WebGL2RenderingContext.prototype.getSupportedExtensions
}
},
{
name: 'HTMLCanvasElement',
proto: true,
struct: {
getContext: getContext,
toDataURL: toDataURL,
toBlob: toBlob
}
},
{
name: 'CanvasRenderingContext2D',
proto: true,
struct: {
getImageData: getImageData,
isPointInPath: CanvasRenderingContext2D.prototype.isPointInPath,
isPointInStroke: CanvasRenderingContext2D.prototype.isPointInStroke,
measureText: CanvasRenderingContext2D.prototype.measureText
}
},
{
name: 'AudioBuffer',
proto: true,
struct: {
getChannelData: getChannelData,
copyFromChannel: copyFromChannel
}
},
{
name: 'AnalyserNode',
proto: true,
struct: {
getByteFrequencyData: getByteFrequencyData,
getFloatFrequencyData: getFloatFrequencyData
}
},
{
name: 'RTCPeerConnection',
proto: true,
struct: {
createDataChannel: RTCPeerConnection.prototype.createDataChannel,
createOffer: RTCPeerConnection.prototype.createOffer,
setRemoteDescription: RTCPeerConnection.prototype.setRemoteDescription
}
}
]
function definify(struct) {
const redefinedProps = {}
Object.keys(struct).forEach(prop => {
const fn = () => {
watch(prop)
return struct[prop]
}
Object.defineProperties(fn, {
name: {
value: `${!firefox ? 'get ' : ''}${prop}`,
configurable: true
}
})
redefinedProps[prop] = {
get: fn
}
})
return redefinedProps
}
function redefine(root) {
// Randomized
apiStructs.forEach(api => {
const {
name,
proto,
struct
} = api
try {
return Object.defineProperties(
(proto ? root[name].prototype : root[name]), definify(struct)
)
} catch (error) {
console.error(error)
}
})
// Deep calls
Object.defineProperties(root.Intl.DateTimeFormat.prototype, definify(intlProps))
Object.defineProperties(root.navigator.mediaDevices, definify(mediaDeviceProps))
// Resist lie detection
const library = {
appVersion: 'appVersion',
deviceMemory: 'deviceMemory',
doNotTrack: 'doNotTrack',
hardwareConcurrency: 'hardwareConcurrency',
languages: 'languages',
maxTouchPoints: 'maxTouchPoints',
mimeTypes: 'mimeTypes',
platform: 'platform',
plugins: 'plugins',
userAgent: 'userAgent',
vendor: 'vendor',
connection: 'connection',
cookieEnabled: 'cookieEnabled',
getBattery: 'getBattery',
getGamepads: 'getGamepads',
width: 'width',
height: 'height',
availWidth: 'availWidth',
availHeight: 'availHeight',
availTop: 'availTop',
availLeft: 'availLeft',
colorDepth: 'colorDepth',
pixelDepth: 'pixelDepth',
getTimezoneOffset: 'getTimezoneOffset',
resolvedOptions: 'resolvedOptions',
acos: 'acos',
acosh: 'acosh',
asin: 'asin',
asinh: 'asinh',
cosh: 'cosh',
expm1: 'expm1',
sinh: 'sinh',
enumerateDevices: 'enumerateDevices',
now: 'now',
getBoundingClientRect: 'getBoundingClientRect',
getClientRects: 'getClientRects',
shaderSource: 'shaderSource',
getExtension: 'getExtension',
getParameter: 'getParameter',
getSupportedExtensions: 'getSupportedExtensions',
getContext: 'getContext',
toDataURL: 'toDataURL',
toBlob: 'toBlob',
getImageData: 'getImageData',
isPointInPath: 'isPointInPath',
isPointInStroke: 'isPointInStroke',
measureText: 'measureText',
getChannelData: 'getChannelData',
copyFromChannel: 'copyFromChannel',
getByteFrequencyData: 'getByteFrequencyData',
getFloatFrequencyData: 'getFloatFrequencyData',
createDataChannel: 'createDataChannel',
createOffer: 'createOffer',
setRemoteDescription: 'setRemoteDescription',
}
// create Proxy
const {
toString
} = Function.prototype
const toStringProxy = new Proxy(toString, {
apply: (target, thisArg, args) => {
const native = name => {
return `function ${name}() {${!firefox ? ' [native code] ' : '\n [native code]\n'}}`
}
const name = thisArg.name
const propName = name.replace('get ', '')
if (thisArg === toString.toString) {
return native('toString')
}
if (propName === library[propName]) {
return native(name)
}
return target.call(thisArg, ...args)
}
})
root.Function.prototype.toString = toStringProxy
}
redefine(window)
// catch iframes on dom loaded
const domLoaded = (fn) => document.readyState != 'loading' ?
fn() : document.addEventListener('DOMContentLoaded', fn)
domLoaded(() => {
;[...document.getElementsByTagName('iframe')].forEach(frame => redefine(frame.contentWindow))
})
})()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment