diff --git a/.idea/MyGitHub.iml b/.idea/MyGitHub.iml new file mode 100644 index 0000000..bc01ef9 --- /dev/null +++ b/.idea/MyGitHub.iml @@ -0,0 +1,11 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..b9ae7a7 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,42 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..dff591d --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..7ffde74 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/reverse-life/__init__.py b/reverse-life/__init__.py new file mode 100644 index 0000000..46f72d2 --- /dev/null +++ b/reverse-life/__init__.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# @Time : 2024/2/22 20:38 +# @Name : __init__.py.py +# @Author : yanlee diff --git a/reverse-life/demos/run_reverse.py b/reverse-life/demos/run_reverse.py new file mode 100644 index 0000000..7b2b55a --- /dev/null +++ b/reverse-life/demos/run_reverse.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# @Time : 2024/4/17 01:12 +# @Name : sxfae.py +# @Author : yanlee + +from loguru import logger +from faker import Faker +import requests +import base64 +from hashlib import md5 +from Crypto.Cipher import AES, PKCS1_v1_5 +from Crypto.PublicKey import RSA + +# 配置日志记录器 +logger.add("debug.log", rotation="1 week") + + +def generate_md5_signature(data): + """为提供的数据生成MD5签名。""" + return md5(data.encode()).hexdigest() + + +def zero_pad(data): + """应用零填充确保数据长度是AES块大小的倍数。""" + return data + b"\0" * (AES.block_size - len(data) % AES.block_size) + + +def encrypt_aes(key, iv, text): + """使用AES加密和CBC模式加密文本。""" + try: + padded_text = zero_pad(text.encode('utf-8')) + cipher = AES.new(key.encode('utf-8'), AES.MODE_CBC, iv.encode('utf-8')) + return base64.b64encode(cipher.encrypt(padded_text)).decode() + except Exception as e: + logger.error(f"加密文本失败: {e}") + raise + + +def encrypt_rsa(public_key, key): + """使用RSA公钥加密AES密钥。""" + try: + rsa_key = RSA.importKey(public_key) + cipher = PKCS1_v1_5.new(rsa_key) + return base64.b64encode(cipher.encrypt(key.encode())).decode() + except Exception as e: + logger.error(f"使用RSA加密失败: {e}") + raise + + +def generate_headers(): + """生成请求用的HTTP头部,包括动态用户代理。""" + return { + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'zh-CN,zh;q=0.9', + 'Cache-Control': 'no-cache', + 'Connection': 'keep-alive', + 'Content-Type': 'application/json;charset=UTF-8', + 'Origin': 'https://jjbl.sxfae.com', + 'Pragma': 'no-cache', + 'Referer': 'https://jjbl.sxfae.com/marketingIndex', + 'User-Agent': Faker().chrome() + } + + +def fetch_rsa_public_key(url, headers): + """给定URL获取RSA公钥。""" + try: + response = requests.post(url, headers=headers, json={}) + response.raise_for_status() # 对4XX或5XX错误抛出异常 + rsa_public_key = response.json()["data"]["publicKey"] + return rsa_public_key + except requests.exceptions.RequestException as e: + logger.error(f"获取RSA公钥失败: {e}") + raise + + +def prepare_encrypted_payload(page, rsa_public_key): + """准备带有签名数据的加密参数进行传输。""" + try: + ciphertext = f'{{"page": {page},"size": 10}}' + md5_signature = generate_md5_signature(ciphertext) + + key = 'XyrWHOmkaZEyRWHu' + iv = "szazgM3zOYCCHWih" + encrypted_data = encrypt_aes(key, iv, ciphertext) + + rsa_key = f"key_{key}|iv_{iv}" + encrypted_rsa_key = encrypt_rsa(rsa_public_key, rsa_key) + + return { + 'sign': md5_signature, + 'data': encrypted_data, + 'rsaKey': encrypted_rsa_key + } + except Exception as e: + logger.error(f"准备加密参数失败: {e}") + raise + + +if __name__ == '__main__': + try: + headers = generate_headers() + rsa_public_key = fetch_rsa_public_key('https://jjbl.sxfae.com/sxfaeApi/000002', headers) + current_page = 1 + json_data = prepare_encrypted_payload(current_page, rsa_public_key) + response = requests.post('https://jjbl.sxfae.com/sxfaeApi/801014', headers=headers, json=json_data) + print(response.json()) + except Exception as e: + logger.error(f"主执行块中发生错误: {e}") diff --git a/reverse-life/pjstealth/__init__.py b/reverse-life/pjstealth/__init__.py new file mode 100644 index 0000000..51c61f4 --- /dev/null +++ b/reverse-life/pjstealth/__init__.py @@ -0,0 +1,3 @@ +from .pjstealth import stealth_sync +from .pjstealth import stealth_async +from .stealth import StealthConfig diff --git a/reverse-life/pjstealth/env_data.py b/reverse-life/pjstealth/env_data.py new file mode 100644 index 0000000..4ccfd24 --- /dev/null +++ b/reverse-life/pjstealth/env_data.py @@ -0,0 +1,283 @@ +import random + +css_info = { + "activeborder": random.choice(["rgb(118, 118, 118)", "rgb(128, 128, 128)", "rgb(109, 109, 109)"]), + "activetext": random.choice(["rgb(255, 102, 0)", "rgb(100, 102, 204)"]), + "graytext": random.choice(["rgb(128, 104, 128)", "rgb(244, 109, 109)"]), + "highlight": random.choice(["rgb(185, 213, 255)", "rgb(120, 109, 255)"]), + "highlighttext": random.choice(["rgb(0, 0, 0)", "rgb(220, 220, 220)"]), + "linktext": random.choice(["rgb(0, 230, 230)", "rgb(0, 120, 120)"]), + "threeddarkshadow": random.choice(["rgb(118, 118, 118)", "rgb(35, 35, 35)"]), + "threedface": random.choice(["rgb(210, 210, 210)", "rgb(240, 240, 240)"]), + "threedhighlight": random.choice(["rgb(55, 55, 55)", "rgb(108, 108, 118)"]), + "threedlightshadow": random.choice(["rgb(118, 118, 118)", "rgb(80, 80, 80)"]), + "threedshadow": random.choice(["rgb(118, 118, 118)", "rgb(49, 49, 49)"]), + "visitedtext": random.choice(["rgb(85, 55, 139)", "rgb(0, 120, 204)"]), + "windowframe": random.choice(["rgb(118, 118, 118)", "rgb(55, 55, 55)"]) +} + +font_names = [ + "Andale Mono", + "Arial", + "Arial Black", + "Arial Hebrew", + "Arial MT", + "Arial Narrow", + "Arial Rounded MT Bold", + "Arial Unicode MS", + "Bitstream Vera Sans Mono", + "Book Antiqua", + "Bookman Old Style", + "Calibri", + "Cambria", + "Cambria Math", + "Century", + "Century Gothic", + "Century Schoolbook", + "Comic Sans", + "Comic Sans MS", + "Consolas", + "Courier", + "Courier New", + "Geneva", + "Georgia", + "Helvetica", + "Helvetica Neue", + "Impact", + "Lucida Bright", + "Lucida Calligraphy", + "Lucida Console", + "Lucida Fax", + "LUCIDA GRANDE", + "Lucida Handwriting", + "Lucida Sans", + "Lucida Sans Typewriter", + "Lucida Sans Unicode", + "Microsoft Sans Serif", + "Monaco", + "Monotype Corsiva", + "MS Gothic", + "MS Outlook", + "MS PGothic", + "MS Reference Sans Serif", + "MS Sans Serif", + "MS Serif", + "MYRIAD", + "MYRIAD PRO", + "Palatino", + "Palatino Linotype", + "Segoe Print", + "Segoe Script", + "Segoe UI", + "Segoe UI Light", + "Segoe UI Semibold", + "Segoe UI Symbol", + "Tahoma", + "Times", + "Times New Roman", + "Times New Roman PS", + "Trebuchet MS", + "Verdana", + "Wingdings", + "Wingdings 2", + "Wingdings 3", + "Bradley Hand", + "Bradley Hand ITC", + "Bremen Bd BT", + "Britannic Bold", + "Broadway", + "Browallia New", + "BrowalliaUPC", + "Brush Script MT", + "Californian FB", + "Calisto MT", + "Calligrapher", + "Candara", + "CaslonOpnface BT", + "Castellar", + "Centaur", + "Cezanne", + "CG Omega", + "CG Times", + "Chalkboard", + "Chalkboard SE", + "Chalkduster", + "Charlesworth", + "Charter Bd BT", + "Charter BT", + "Chaucer", + "ChelthmITC Bk BT", + "Chiller", + "Clarendon", + "Clarendon Condensed", + "CloisterBlack BT", + "Cochin", + "Colonna MT", + "Constantia", + "Cooper Black", + "Copperplate", + "Copperplate Gothic", + "Copperplate Gothic Bold", + "Copperplate Gothic Light", + "CopperplGoth Bd BT", + "Corbel", + "Cordia New", + "CordiaUPC", + "Cornerstone", + "Coronet", + "Cuckoo", + "Curlz MT", + "DaunPenh", + "Dauphin", + "David", + "DB LCD Temp", + "DELICIOUS", + "Denmark", + "DFKai-SB", + "Didot", + "DilleniaUPC", + "DIN", + "DokChampa", + "Dotum", + "DotumChe", + "Ebrima", + "Edwardian Script ITC", + "Elephant", + "English 111 Vivace BT", + "Engravers MT", + "EngraversGothic BT", + "Eras Bold ITC", + "Eras Demi ITC", + "Eras Light ITC", + "Eras Medium ITC", + "EucrosiaUPC", + "Futura Md BT", + "Futura ZBlk BT", + "FuturaBlack BT", + "Gabriola", + "Galliard BT", + "Gautami", + "Geeza Pro", + "Geometr231 BT", + "Geometr231 Hv BT", + "Geometr231 Lt BT", + "GeoSlab 703 Lt BT", + "GeoSlab 703 XBd BT", + "Gigi", + "Gill Sans", + "Gill Sans MT", + "Gill Sans MT Condensed", + "Gill Sans MT Ext Condensed Bold", + "Gill Sans Ultra Bold", + "Gill Sans Ultra Bold Condensed", + "Gisha", + "Harlow Solid Italic", + "Harrington", + "Heather", + "Heiti SC", + "Heiti TC", + "HELV", + "Herald", + "High Tower Text", + "Hiragino Kaku Gothic ProN", + "Hiragino Mincho ProN", + "Hoefler Text", + "Humanst 521 Cn BT", + "Humanst521 BT", + "Humanst521 Lt BT", + "Imprint MT Shadow", + "Incised901 Bd BT", + "Incised901 BT", + "Incised901 Lt BT", + "INCONSOLATA", + "Informal Roman", + "Informal011 BT", + "INTERSTATE", + "IrisUPC", + "Iskoola Pota", + "JasmineUPC", + "Jazz LET", + "Jenson", + "Jester", + "Jokerman", + "Juice ITC", + "Kabel Bk BT", + "Kabel Ult BT", + "Kailasa", + "Microsoft PhagsPa", + "Microsoft Tai Le", + "Microsoft Uighur", + "Microsoft YaHei", + "Microsoft Yi Baiti", + "MingLiU" +] +fonts_info = {} +for tmp_font_name in font_names: + fonts_info[tmp_font_name] = random.choice(font_names) + +windows_webgl = [ + ['Google Inc.(NVIDIA)', 'ANGLE (NVIDIA, NVIDIA GeForce RTX 3080 Direct3D11 vs_5_0 ps_5_0, D3D11)'], + ['Google Inc.(NVIDIA)', 'ANGLE (NVIDIA, NVIDIA GeForce RTX 3080 Ti Direct3D11 vs_5_0 ps_5_0, D3D11)'], + ['Google Inc.(NVIDIA)', 'ANGLE (NVIDIA, NVIDIA GeForce RTX 3090 Direct3D11 vs_5_0 ps_5_0, D3D11)'], + ['Google Inc.(NVIDIA)', 'ANGLE (NVIDIA, NVIDIA GeForce RTX 3070 Ti Direct3D12 vs_5_0 ps_5_0, D3D12)'], + ['Google Inc.(NVIDIA)', 'ANGLE (NVIDIA, NVIDIA GeForce RTX 3060 Ti Direct3D12 vs_5_0 ps_5_0, D3D12)'], + ['Google Inc.(NVIDIA)', 'ANGLE (NVIDIA, NVIDIA GeForce RTX 3060 Direct3D12 vs_5_0 ps_5_0, D3D12)'], + ['Google Inc.(NVIDIA)', 'ANGLE (NVIDIA, NVIDIA GeForce RTX 2080 Ti Direct3D12 vs_5_0 ps_5_0, D3D12)'], + ['Google Inc.(NVIDIA)', 'ANGLE (NVIDIA, NVIDIA GeForce RTX 2070 Super Direct3D12 vs_5_0 ps_5_0, D3D12)'], + ['Google Inc.(NVIDIA)', 'ANGLE (NVIDIA, NVIDIA GeForce RTX 2080 Super Direct3D12 vs_5_0 ps_5_0, D3D12)'], + ['Google Inc.(NVIDIA)', 'ANGLE (NVIDIA, NVIDIA GeForce RTX 2060 Super Direct3D12 vs_5_0 ps_5_0, D3D12)'], +] +mac_webgl = [ + ["Google Inc. (Apple)", "ANGLE (Apple, Apple M1, OpenGL 4.1)"], + ["Google Inc. (ATI Technologies Inc.)", + "ANGLE (ATI Technologies Inc., AMD Radeon Pro 5300M OpenGL Engine, OpenGL 4.1)"], + ["Google Inc. (Intel Inc.)", "ANGLE (Intel Inc., Intel(R) Iris(TM) Plus Graphics 655, OpenGL 4.1)"], + ["Google Inc. (Apple)", "ANGLE (Apple, Apple M2, OpenGL 4.1)"] +] + +webrtc = True +headless_check = True + +canvasfeature = { + "height": random.randint(-5, 5), + "width": random.randint(-5, 5), + "r": random.randint(-5, 5), + "g": random.randint(-5, 5), + "b": random.randint(-5, 5), + "a": random.randint(-5, 5) +} + +videofeature = { + "start_index": 265, + "random_value": random.random() +} + +clientrectfeature = False +navigator_hardware_concurrency = random.choice([8, 4, 16]) +env_data = { + "Win32": { + "webgl_infos": random.choice(windows_webgl), + "sys_platform": "Windows" + }, + "MacIntel": { + "webgl_infos": mac_webgl, + "sys_platform": "macOS" + }, + "Linux x86_64": { + "webgl_infos": random.choice(windows_webgl), + "sys_platform": "Windows" + }, + "webrtc": webrtc, + "headless_check": headless_check, + "canvasfeature": canvasfeature, + "videofeature": videofeature, + "clientrectfeature": clientrectfeature, + "languages": ['en-US', 'en'], + "language": 'en-US', + "navigator_hardware_concurrency": navigator_hardware_concurrency, + "device_memory": navigator_hardware_concurrency, + "is_mobile": False, + "fontsfeature": fonts_info, + "cssfeature": css_info, + "screen_color_depth": random.choice([16, 24, 30]) +} diff --git a/reverse-life/pjstealth/feature/chrome.app.js b/reverse-life/pjstealth/feature/chrome.app.js new file mode 100644 index 0000000..7a73929 --- /dev/null +++ b/reverse-life/pjstealth/feature/chrome.app.js @@ -0,0 +1,71 @@ +if (!window.chrome) { + // Use the exact property descriptor found in headful Chrome + // fetch it via `Object.getOwnPropertyDescriptor(window, 'chrome')` + Object.defineProperty(window, 'chrome', { + writable: true, + enumerable: true, + configurable: false, // note! + value: {} // We'll extend that later + }) +} + +// app in window.chrome means we're running headful and don't need to mock anything +if (!('app' in window.chrome)) { + const makeError = { + ErrorInInvocation: fn => { + const err = new TypeError(`Error in invocation of app.${fn}()`) + return utils.stripErrorWithAnchor( + err, + `at ${fn} (eval at ` + ) + } + } + +// There's a some static data in that property which doesn't seem to change, +// we should periodically check for updates: `JSON.stringify(window.app, null, 2)` + const APP_STATIC_DATA = JSON.parse( + ` +{ + "isInstalled": false, + "InstallState": { + "DISABLED": "disabled", + "INSTALLED": "installed", + "NOT_INSTALLED": "not_installed" + }, + "RunningState": { + "CANNOT_RUN": "cannot_run", + "READY_TO_RUN": "ready_to_run", + "RUNNING": "running" + } +} + `.trim() + ) + + window.chrome.app = { + ...APP_STATIC_DATA, + + get isInstalled() { + return false + }, + + getDetails: function getDetails() { + if (arguments.length) { + throw makeError.ErrorInInvocation(`getDetails`) + } + return null + }, + getIsInstalled: function getDetails() { + if (arguments.length) { + throw makeError.ErrorInInvocation(`getIsInstalled`) + } + return false + }, + runningState: function getDetails() { + if (arguments.length) { + throw makeError.ErrorInInvocation(`runningState`) + } + return 'cannot_run' + } + } + utils.patchToStringNested(window.chrome.app) +} \ No newline at end of file diff --git a/reverse-life/pjstealth/feature/chrome.canvasfeature.js b/reverse-life/pjstealth/feature/chrome.canvasfeature.js new file mode 100644 index 0000000..c1e5dad --- /dev/null +++ b/reverse-life/pjstealth/feature/chrome.canvasfeature.js @@ -0,0 +1,35 @@ +const getImageData = CanvasRenderingContext2D.prototype.getImageData; +// +var noisify = function (canvas, context) { + if (context) { + const shift = { + 'r': Math.floor(Math.random() * 10) - 5, + 'g': Math.floor(Math.random() * 10) - 5, + 'b': Math.floor(Math.random() * 10) - 5, + 'a': Math.floor(Math.random() * 10) - 5 + }; + // + const width = canvas.width; + const height = canvas.height; + if (width && height) { + const imageData = getImageData.apply(context, [0, 0, width, height]); + for (let i = 0; i < height; i++) { + for (let j = 0; j < width; j++) { + const n = ((i * (width * 4)) + (j * 4)); + imageData.data[n + 0] = imageData.data[n + 0] + opts.canvasfeature.r; + imageData.data[n + 1] = imageData.data[n + 1] + opts.canvasfeature.g; + imageData.data[n + 2] = imageData.data[n + 2] + opts.canvasfeature.b; + imageData.data[n + 3] = imageData.data[n + 3] + opts.canvasfeature.a; + } + } + // + context.putImageData(imageData, 0, 0); + } + } +}; +Object.defineProperty(CanvasRenderingContext2D.prototype, "getImageData", { + "value": function () { + noisify(this.canvas, this); + return getImageData.apply(this, arguments); + } +}); diff --git a/reverse-life/pjstealth/feature/chrome.canvasfeature2.js b/reverse-life/pjstealth/feature/chrome.canvasfeature2.js new file mode 100644 index 0000000..bd96362 --- /dev/null +++ b/reverse-life/pjstealth/feature/chrome.canvasfeature2.js @@ -0,0 +1,11 @@ +var getImageData2 = HTMLCanvasElement.prototype.toDataURL; + +HTMLCanvasElement.prototype.toDataURL = function () { + // console.log("当前data"); + // console.log(this.width); + // console.log(this.height); + this.width = this.width + opts.canvasfeature.width; + this.height = this.height + opts.canvasfeature.height; + return getImageData2.apply(this); +}; + diff --git a/reverse-life/pjstealth/feature/chrome.clientrectfeature.js b/reverse-life/pjstealth/feature/chrome.clientrectfeature.js new file mode 100644 index 0000000..26c0416 --- /dev/null +++ b/reverse-life/pjstealth/feature/chrome.clientrectfeature.js @@ -0,0 +1,57 @@ +var rand = { + "noise": function () { + var SIGN = Math.random() < Math.random() ? -1 : 1; + return Math.floor(Math.random() + SIGN * Math.random()); + }, + "sign": function () { + const tmp = [-1, -1, -1, -1, -1, -1, +1, -1, -1, -1]; + const index = Math.floor(Math.random() * tmp.length); + return tmp[index]; + }, + 'get': function (e, fun, name, index) { + let d + try { + if (typeof index == "undefined") { + d = Math.floor(e[fun]()[name]); + } else { + d = Math.floor(e[fun]()[index][name]); + } + + } catch (e) { + console.log(e) + } + const valid = d && rand.sign() === 1; + const result = valid ? d + rand.noise() : d; + return result; + }, + 'value': function (d) { + + const valid = d && rand.sign() === 1; + const result = valid ? d + rand.noise() : d; + return result; + } +}; +Object.defineProperty(HTMLElement.prototype, "getBoundingClientRect", { + value() { + let rects = this.getClientRects()[0]; + console.log(rects); + let _rects = JSON.parse(JSON.stringify(rects)); + let _json = {}; + for (let k in _rects) { + let d = rand.value(_rects[k]); + // console.log(k,d) + _json[k] = d; + Object.defineProperty(rects.__proto__, k, { + get() { + return d + } + }) + } + Object.defineProperty(rects.__proto__, "toJSON", { + value() { + return _json + } + }); + return rects; + }, +}); diff --git a/reverse-life/pjstealth/feature/chrome.csi.js b/reverse-life/pjstealth/feature/chrome.csi.js new file mode 100644 index 0000000..388e39f --- /dev/null +++ b/reverse-life/pjstealth/feature/chrome.csi.js @@ -0,0 +1,27 @@ +if (!window.chrome) { + // Use the exact property descriptor found in headful Chrome + // fetch it via `Object.getOwnPropertyDescriptor(window, 'chrome')` + Object.defineProperty(window, 'chrome', { + writable: true, + enumerable: true, + configurable: false, // note! + value: {} // We'll extend that later + }) +} + +// Check if we're running headful and don't need to mock anything +// Check that the Navigation Timing API v1 is available, we need that +if (!('csi' in window.chrome) && (window.performance || window.performance.timing)) { + const {csi_timing} = window.performance + + log.info('loading chrome.csi.js') + window.chrome.csi = function () { + return { + onloadT: csi_timing.domContentLoadedEventEnd, + startE: csi_timing.navigationStart, + pageT: Date.now() - csi_timing.navigationStart, + tran: 15 // Transition type or something + } + } + utils.patchToString(window.chrome.csi) +} \ No newline at end of file diff --git a/reverse-life/pjstealth/feature/chrome.cssfeature.js b/reverse-life/pjstealth/feature/chrome.cssfeature.js new file mode 100644 index 0000000..1fdd189 --- /dev/null +++ b/reverse-life/pjstealth/feature/chrome.cssfeature.js @@ -0,0 +1,10 @@ +var obj_get_computed_style = getComputedStyle; +getComputedStyle = function (data) { + for (const key in opts.cssfeature) { + if (data.style.backgroundColor === key) { + data.style.backgroundColor = opts.cssfeature[key]; + break; + } + } + return obj_get_computed_style(data); +}; \ No newline at end of file diff --git a/reverse-life/pjstealth/feature/chrome.fontsfeature.js b/reverse-life/pjstealth/feature/chrome.fontsfeature.js new file mode 100644 index 0000000..c03d1c2 --- /dev/null +++ b/reverse-life/pjstealth/feature/chrome.fontsfeature.js @@ -0,0 +1,93 @@ +var rand = { + "noise": function () { + var SIGN = Math.random() < Math.random() ? -1 : 1; + return Math.floor(Math.random() + SIGN * Math.random()); + }, + "sign": function () { + const tmp = [-1, -1, -1, -1, -1, -1, +1, -1, -1, -1, -1, 1, -1, -1, -1, 1, -1, -1, -1]; + const index = Math.floor(Math.random() * tmp.length); + return tmp[index]; + } +}; +var hookFonts = function (originFamily) { + if (originFamily.includes("monospace")) { + let tmpFontFamily = originFamily.replace("monospace", "").replace(",", "").trim(); + if (opts.fontsfeature.hasOwnProperty(tmpFontFamily)) { + originFamily = opts.fontsfeature[tmpFontFamily] + ", " + "monospace"; + } + } + + if (originFamily.includes("sans-serif")) { + let tmpFontFamily = originFamily.replace("sans-serif", "").replace(",", "").trim(); + if (opts.fontsfeature.hasOwnProperty(tmpFontFamily)) { + originFamily = opts.fontsfeature[tmpFontFamily] + ", " + "sans-serif"; + } + } + + if (originFamily.includes("serif")) { + let tmpFontFamily = originFamily.replace("serif", "").replace(",", "").trim(); + if (opts.fontsfeature.hasOwnProperty(tmpFontFamily)) { + originFamily = opts.fontsfeature[tmpFontFamily] + ", " + "serif"; + } + } + return originFamily; +} + +Object.defineProperty(HTMLElement.prototype, "offsetHeight", { + get() { + + this.style.fontFamily = hookFonts(this.style.fontFamily); + let height = 500; + try { + height = Math.floor(this.getBoundingClientRect().height); + } catch (e) { + height = Math.floor(this.clientHeight) + } + return height; + } +}); + +Object.defineProperty(HTMLElement.prototype, "offsetWidth", { + get() { + // opts.fonts_start = opts.fonts_start + 1; + // if (opts.fonts_start < opts.fontsfeature.change_index.length && opts.fontsfeature.change_index[opts.fonts_start] !== 0) { + // this.style.fontFamily = opts.fontsfeature.width[opts.fonts_start]; + // } + this.style.fontFamily = hookFonts(this.style.fontFamily); + + let width = 500; + try { + width = Math.floor(this.getBoundingClientRect().width); + } catch (e) { + width = Math.floor(this.clientWidth) + } + return width; + } +}); +// Object.defineProperty(HTMLElement.prototype, "offsetHeight", { +// get() { +// let height = 500; +// try { +// height = Math.floor(this.getBoundingClientRect().height); +// } catch (e) { +// height = Math.floor(this.clientHeight) +// } +// const valid = height && rand.sign() === 1; +// const result = valid ? height + rand.noise() : height; +// return result; +// } +// }); +// +// Object.defineProperty(HTMLElement.prototype, "offsetWidth", { +// get() { +// let width = 500; +// try { +// width = Math.floor(this.getBoundingClientRect().width); +// } catch (e) { +// width = Math.floor(this.clientWidth) +// } +// const valid = width && rand.sign() === 1; +// const result = valid ? width + rand.noise() : width; +// return result; +// } +// }); \ No newline at end of file diff --git a/reverse-life/pjstealth/feature/chrome.hairline.js b/reverse-life/pjstealth/feature/chrome.hairline.js new file mode 100644 index 0000000..4a4a633 --- /dev/null +++ b/reverse-life/pjstealth/feature/chrome.hairline.js @@ -0,0 +1,14 @@ +// https://intoli.com/blog/making-chrome-headless-undetectable/ +// store the existing descriptor +const elementDescriptor = Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'offsetHeight'); + +// redefine the property with a patched descriptor +Object.defineProperty(HTMLDivElement.prototype, 'offsetHeight', { + ...elementDescriptor, + get: function() { + if (this.id === 'modernizr') { + return 1; + } + return elementDescriptor.get.apply(this); + }, +}); \ No newline at end of file diff --git a/reverse-life/pjstealth/feature/chrome.load.times.js b/reverse-life/pjstealth/feature/chrome.load.times.js new file mode 100644 index 0000000..8135af4 --- /dev/null +++ b/reverse-life/pjstealth/feature/chrome.load.times.js @@ -0,0 +1,123 @@ +if (!window.chrome) { + // Use the exact property descriptor found in headful Chrome + // fetch it via `Object.getOwnPropertyDescriptor(window, 'chrome')` + Object.defineProperty(window, 'chrome', { + writable: true, + enumerable: true, + configurable: false, // note! + value: {} // We'll extend that later + }) +} + +// That means we're running headful and don't need to mock anything +if ('loadTimes' in window.chrome) { + console.log("处于有头模式中....") +} else { + +// Check that the Navigation Timing API v1 + v2 is available, we need that + if ( + window.performance || + window.performance.timing || + window.PerformancePaintTiming + ) { + + const {performance} = window + + // Some stuff is not available on about:blank as it requires a navigation to occur, + // let's harden the code to not fail then: + const ntEntryFallback = { + nextHopProtocol: 'h2', + type: 'other' + } + + // The API exposes some funky info regarding the connection + const protocolInfo = { + get connectionInfo() { + const ntEntry = + performance.getEntriesByType('navigation')[0] || ntEntryFallback + return ntEntry.nextHopProtocol + }, + get npnNegotiatedProtocol() { + // NPN is deprecated in favor of ALPN, but this implementation returns the + // HTTP/2 or HTTP2+QUIC/39 requests negotiated via ALPN. + const ntEntry = + performance.getEntriesByType('navigation')[0] || ntEntryFallback + return ['h2', 'hq'].includes(ntEntry.nextHopProtocol) + ? ntEntry.nextHopProtocol + : 'unknown' + }, + get navigationType() { + const ntEntry = + performance.getEntriesByType('navigation')[0] || ntEntryFallback + return ntEntry.type + }, + get wasAlternateProtocolAvailable() { + // The Alternate-Protocol header is deprecated in favor of Alt-Svc + // (https://www.mnot.net/blog/2016/03/09/alt-svc), so technically this + // should always return false. + return false + }, + get wasFetchedViaSpdy() { + // SPDY is deprecated in favor of HTTP/2, but this implementation returns + // true for HTTP/2 or HTTP2+QUIC/39 as well. + const ntEntry = + performance.getEntriesByType('navigation')[0] || ntEntryFallback + return ['h2', 'hq'].includes(ntEntry.nextHopProtocol) + }, + get wasNpnNegotiated() { + // NPN is deprecated in favor of ALPN, but this implementation returns true + // for HTTP/2 or HTTP2+QUIC/39 requests negotiated via ALPN. + const ntEntry = + performance.getEntriesByType('navigation')[0] || ntEntryFallback + return ['h2', 'hq'].includes(ntEntry.nextHopProtocol) + } + } + + const {timing} = window.performance + +// Truncate number to specific number of decimals, most of the `loadTimes` stuff has 3 + function toFixed(num, fixed) { + var re = new RegExp('^-?\\d+(?:.\\d{0,' + (fixed || -1) + '})?') + return num.toString().match(re)[0] + } + + const timingInfo = { + get firstPaintAfterLoadTime() { + // This was never actually implemented and always returns 0. + return 0 + }, + get requestTime() { + return timing.navigationStart / 1000 + }, + get startLoadTime() { + return timing.navigationStart / 1000 + }, + get commitLoadTime() { + return timing.responseStart / 1000 + }, + get finishDocumentLoadTime() { + return timing.domContentLoadedEventEnd / 1000 + }, + get finishLoadTime() { + return timing.loadEventEnd / 1000 + }, + get firstPaintTime() { + const fpEntry = performance.getEntriesByType('paint')[0] || { + startTime: timing.loadEventEnd / 1000 // Fallback if no navigation occured (`about:blank`) + } + return toFixed( + (fpEntry.startTime + performance.timeOrigin) / 1000, + 3 + ) + } + } + + window.chrome.loadTimes = function () { + return { + ...protocolInfo, + ...timingInfo + } + } + utils.patchToString(window.chrome.loadTimes) + } +} \ No newline at end of file diff --git a/reverse-life/pjstealth/feature/chrome.mouseevent.js b/reverse-life/pjstealth/feature/chrome.mouseevent.js new file mode 100644 index 0000000..5e57562 --- /dev/null +++ b/reverse-life/pjstealth/feature/chrome.mouseevent.js @@ -0,0 +1,42 @@ +// const originalDispatchEvent = EventTarget.prototype.dispatchEvent; +// +// // 重定义 dispatchEvent 方法 +// Object.defineProperty(EventTarget.prototype, 'dispatchEvent', { +// value: function (event) { +// console.log("--------"); +// // 在这里检查是否是 mousedown 事件 +// if (event.type === 'mousedown' || event.type === 'mouseup') { +// // 在这里修改 detail 值 +// event.detail = opts.mouse_event.detail; // 设置自定义的 detail 值 +// } +// +// // 调用原始的 dispatchEvent 方法 +// return originalDispatchEvent.apply(this, arguments); +// }, +// configurable: true, +// writable: true +// }); + +// Object.defineProperty(MouseEvent.prototype, "detail", { +// get() { +// if (this.type === 'mousedown') { +// return opts.mouse_event.detail; +// } else { +// console.log(this.type); +// return this.detail; +// } +// } +// }); + +// Object.defineProperty(MouseEvent.prototype, "button", { +// get() { +// return opts.mouse_event.button; +// } +// }); +// +// Object.defineProperty(MouseEvent.prototype, "buttons", { +// get() { +// return opts.mouse_event.buttons; +// } +// }); + diff --git a/reverse-life/pjstealth/feature/chrome.runtime.js b/reverse-life/pjstealth/feature/chrome.runtime.js new file mode 100644 index 0000000..b47b37e --- /dev/null +++ b/reverse-life/pjstealth/feature/chrome.runtime.js @@ -0,0 +1,262 @@ +const STATIC_DATA = { + "OnInstalledReason": { + "CHROME_UPDATE": "chrome_update", + "INSTALL": "install", + "SHARED_MODULE_UPDATE": "shared_module_update", + "UPDATE": "update" + }, + "OnRestartRequiredReason": { + "APP_UPDATE": "app_update", + "OS_UPDATE": "os_update", + "PERIODIC": "periodic" + }, + "PlatformArch": { + "ARM": "arm", + "ARM64": "arm64", + "MIPS": "mips", + "MIPS64": "mips64", + "X86_32": "x86-32", + "X86_64": "x86-64" + }, + "PlatformNaclArch": { + "ARM": "arm", + "MIPS": "mips", + "MIPS64": "mips64", + "X86_32": "x86-32", + "X86_64": "x86-64" + }, + "PlatformOs": { + "ANDROID": "android", + "CROS": "cros", + "LINUX": "linux", + "MAC": "mac", + "OPENBSD": "openbsd", + "WIN": "win" + }, + "RequestUpdateCheckStatus": { + "NO_UPDATE": "no_update", + "THROTTLED": "throttled", + "UPDATE_AVAILABLE": "update_available" + } +} + +if (!window.chrome) { + // Use the exact property descriptor found in headful Chrome + // fetch it via `Object.getOwnPropertyDescriptor(window, 'chrome')` + Object.defineProperty(window, 'chrome', { + writable: true, + enumerable: true, + configurable: false, // note! + value: {} // We'll extend that later + }) +} + +// That means we're running headfull and don't need to mock anything +const existsAlready = 'runtime' in window.chrome +// `chrome.runtime` is only exposed on secure origins +const isNotSecure = !window.location.protocol.startsWith('https') +if (!(existsAlready || (isNotSecure && !opts.runOnInsecureOrigins))) { + window.chrome.runtime = { + // There's a bunch of static data in that property which doesn't seem to change, + // we should periodically check for updates: `JSON.stringify(window.chrome.runtime, null, 2)` + ...STATIC_DATA, + // `chrome.runtime.id` is extension related and returns undefined in Chrome + get id() { + return undefined + }, + // These two require more sophisticated mocks + connect: null, + sendMessage: null + } + + const makeCustomRuntimeErrors = (preamble, method, extensionId) => ({ + NoMatchingSignature: new TypeError( + preamble + `No matching signature.` + ), + MustSpecifyExtensionID: new TypeError( + preamble + + `${method} called from a webpage must specify an Extension ID (string) for its first argument.` + ), + InvalidExtensionID: new TypeError( + preamble + `Invalid extension id: '${extensionId}'` + ) + }) + + // Valid Extension IDs are 32 characters in length and use the letter `a` to `p`: + // https://source.chromium.org/chromium/chromium/src/+/master:components/crx_file/id_util.cc;drc=14a055ccb17e8c8d5d437fe080faba4c6f07beac;l=90 + const isValidExtensionID = str => + str.length === 32 && str.toLowerCase().match(/^[a-p]+$/) + + /** Mock `chrome.runtime.sendMessage` */ + const sendMessageHandler = { + apply: function (target, ctx, args) { + const [extensionId, options, responseCallback] = args || [] + + // Define custom errors + const errorPreamble = `Error in invocation of runtime.sendMessage(optional string extensionId, any message, optional object options, optional function responseCallback): ` + const Errors = makeCustomRuntimeErrors( + errorPreamble, + `chrome.runtime.sendMessage()`, + extensionId + ) + + // Check if the call signature looks ok + const noArguments = args.length === 0 + const tooManyArguments = args.length > 4 + const incorrectOptions = options && typeof options !== 'object' + const incorrectResponseCallback = + responseCallback && typeof responseCallback !== 'function' + if ( + noArguments || + tooManyArguments || + incorrectOptions || + incorrectResponseCallback + ) { + throw Errors.NoMatchingSignature + } + + // At least 2 arguments are required before we even validate the extension ID + if (args.length < 2) { + throw Errors.MustSpecifyExtensionID + } + + // Now let's make sure we got a string as extension ID + if (typeof extensionId !== 'string') { + throw Errors.NoMatchingSignature + } + + if (!isValidExtensionID(extensionId)) { + throw Errors.InvalidExtensionID + } + + return undefined // Normal behavior + } + } + utils.mockWithProxy( + window.chrome.runtime, + 'sendMessage', + function sendMessage() { + }, + sendMessageHandler + ) + + /** + * Mock `chrome.runtime.connect` + * + * @see https://developer.chrome.com/apps/runtime#method-connect + */ + const connectHandler = { + apply: function (target, ctx, args) { + const [extensionId, connectInfo] = args || [] + + // Define custom errors + const errorPreamble = `Error in invocation of runtime.connect(optional string extensionId, optional object connectInfo): ` + const Errors = makeCustomRuntimeErrors( + errorPreamble, + `chrome.runtime.connect()`, + extensionId + ) + + // Behavior differs a bit from sendMessage: + const noArguments = args.length === 0 + const emptyStringArgument = args.length === 1 && extensionId === '' + if (noArguments || emptyStringArgument) { + throw Errors.MustSpecifyExtensionID + } + + const tooManyArguments = args.length > 2 + const incorrectConnectInfoType = + connectInfo && typeof connectInfo !== 'object' + + if (tooManyArguments || incorrectConnectInfoType) { + throw Errors.NoMatchingSignature + } + + const extensionIdIsString = typeof extensionId === 'string' + if (extensionIdIsString && extensionId === '') { + throw Errors.MustSpecifyExtensionID + } + if (extensionIdIsString && !isValidExtensionID(extensionId)) { + throw Errors.InvalidExtensionID + } + + // There's another edge-case here: extensionId is optional so we might find a connectInfo object as first param, which we need to validate + const validateConnectInfo = ci => { + // More than a first param connectInfo as been provided + if (args.length > 1) { + throw Errors.NoMatchingSignature + } + // An empty connectInfo has been provided + if (Object.keys(ci).length === 0) { + throw Errors.MustSpecifyExtensionID + } + // Loop over all connectInfo props an check them + Object.entries(ci).forEach(([k, v]) => { + const isExpected = ['name', 'includeTlsChannelId'].includes(k) + if (!isExpected) { + throw new TypeError( + errorPreamble + `Unexpected property: '${k}'.` + ) + } + const MismatchError = (propName, expected, found) => + TypeError( + errorPreamble + + `Error at property '${propName}': Invalid type: expected ${expected}, found ${found}.` + ) + if (k === 'name' && typeof v !== 'string') { + throw MismatchError(k, 'string', typeof v) + } + if (k === 'includeTlsChannelId' && typeof v !== 'boolean') { + throw MismatchError(k, 'boolean', typeof v) + } + }) + } + if (typeof extensionId === 'object') { + validateConnectInfo(extensionId) + throw Errors.MustSpecifyExtensionID + } + + // Unfortunately even when the connect fails Chrome will return an object with methods we need to mock as well + return utils.patchToStringNested(makeConnectResponse()) + } + } + utils.mockWithProxy( + window.chrome.runtime, + 'connect', + function connect() { + }, + connectHandler + ) + + function makeConnectResponse() { + const onSomething = () => ({ + addListener: function addListener() { + }, + dispatch: function dispatch() { + }, + hasListener: function hasListener() { + }, + hasListeners: function hasListeners() { + return false + }, + removeListener: function removeListener() { + } + }) + + const response = { + name: '', + sender: undefined, + disconnect: function disconnect() { + }, + onDisconnect: onSomething(), + onMessage: onSomething(), + postMessage: function postMessage() { + if (!arguments.length) { + throw new TypeError(`Insufficient number of arguments.`) + } + throw new Error(`Attempting to use a disconnected port object`) + } + } + return response + } +} diff --git a/reverse-life/pjstealth/feature/chrome.screen.colordepth.js b/reverse-life/pjstealth/feature/chrome.screen.colordepth.js new file mode 100644 index 0000000..6f4b6d3 --- /dev/null +++ b/reverse-life/pjstealth/feature/chrome.screen.colordepth.js @@ -0,0 +1,10 @@ +const screen_color_depth = window.screen.colorDepth; +Object.defineProperty(Object.getPrototypeOf(screen), 'colorDepth', { + get: function () { + return opts.screen_color_depth || screen_color_depth; + } +}); + +Object.defineProperty(Object.getPrototypeOf(screen), 'pixelDepth', { + get: () => opts.screen_color_depth || screen_color_depth +}); diff --git a/reverse-life/pjstealth/feature/chrome.videofeature.js b/reverse-life/pjstealth/feature/chrome.videofeature.js new file mode 100644 index 0000000..f8b3254 --- /dev/null +++ b/reverse-life/pjstealth/feature/chrome.videofeature.js @@ -0,0 +1,11 @@ +const getChannelData = AudioBuffer.prototype.getChannelData; +Object.defineProperty(AudioBuffer.prototype, "getChannelData", { + "value": function () { + const results_1 = getChannelData.apply(this, arguments); + window.top.postMessage("audiocontext-fingerprint-defender-alert", '*'); + for (var i = opts.videofeature.start_index; i < results_1.length; i += 100) { + results_1[i] = results_1[i] + opts.videofeature.random_value; + } + return results_1; + } +}); diff --git a/reverse-life/pjstealth/feature/chrome.webdriver.js b/reverse-life/pjstealth/feature/chrome.webdriver.js new file mode 100644 index 0000000..d612e86 --- /dev/null +++ b/reverse-life/pjstealth/feature/chrome.webdriver.js @@ -0,0 +1,3 @@ +Object.defineProperty(Object.getPrototypeOf(navigator), 'webdriver', { + get: () => false +}); diff --git a/reverse-life/pjstealth/feature/chrome.webrtc.js b/reverse-life/pjstealth/feature/chrome.webrtc.js new file mode 100644 index 0000000..094b756 --- /dev/null +++ b/reverse-life/pjstealth/feature/chrome.webrtc.js @@ -0,0 +1,6 @@ +var test_rpc_peer_connection = window.RTCPeerConnection; +window.RTCPeerConnection = function (a1, a2) { +}; +var test_rpc_peer_webkit = window.webkitRTCPeerConnection; +window.webkitRTCPeerConnection = function (a1, a2) { +}; \ No newline at end of file diff --git a/reverse-life/pjstealth/feature/generate.magic.arrays.js b/reverse-life/pjstealth/feature/generate.magic.arrays.js new file mode 100644 index 0000000..b9c2811 --- /dev/null +++ b/reverse-life/pjstealth/feature/generate.magic.arrays.js @@ -0,0 +1,142 @@ +generateFunctionMocks = ( + proto, + itemMainProp, + dataArray +) => ({ + item: utils.createProxy(proto.item, { + apply(target, ctx, args) { + if (!args.length) { + throw new TypeError( + `Failed to execute 'item' on '${ + proto[Symbol.toStringTag] + }': 1 argument required, but only 0 present.` + ) + } + // Special behavior alert: + // - Vanilla tries to cast strings to Numbers (only integers!) and use them as property index lookup + // - If anything else than an integer (including as string) is provided it will return the first entry + const isInteger = args[0] && Number.isInteger(Number(args[0])) // Cast potential string to number first, then check for integer + // Note: Vanilla never returns `undefined` + return (isInteger ? dataArray[Number(args[0])] : dataArray[0]) || null + } + }), + /** Returns the MimeType object with the specified name. */ + namedItem: utils.createProxy(proto.namedItem, { + apply(target, ctx, args) { + if (!args.length) { + throw new TypeError( + `Failed to execute 'namedItem' on '${ + proto[Symbol.toStringTag] + }': 1 argument required, but only 0 present.` + ) + } + return dataArray.find(mt => mt[itemMainProp] === args[0]) || null // Not `undefined`! + } + }), + /** Does nothing and shall return nothing */ + refresh: proto.refresh + ? utils.createProxy(proto.refresh, { + apply(target, ctx, args) { + return undefined + } + }) + : undefined +}) + +function generateMagicArray( + dataArray = [], + proto = MimeTypeArray.prototype, + itemProto = MimeType.prototype, + itemMainProp = 'type' +) { + // Quick helper to set props with the same descriptors vanilla is using + const defineProp = (obj, prop, value) => + Object.defineProperty(obj, prop, { + value, + writable: false, + enumerable: false, // Important for mimeTypes & plugins: `JSON.stringify(navigator.mimeTypes)` + configurable: false + }) + + // Loop over our fake data and construct items + const makeItem = data => { + const item = {} + for (const prop of Object.keys(data)) { + if (prop.startsWith('__')) { + continue + } + defineProp(item, prop, data[prop]) + } + // navigator.plugins[i].length should always be 1 + if (itemProto === Plugin.prototype) { + defineProp(item, 'length', 1) + } + // We need to spoof a specific `MimeType` or `Plugin` object + return Object.create(itemProto, Object.getOwnPropertyDescriptors(item)) + } + + const magicArray = [] + + // Loop through our fake data and use that to create convincing entities + dataArray.forEach(data => { + magicArray.push(makeItem(data)) + }) + + // Add direct property access based on types (e.g. `obj['application/pdf']`) afterwards + magicArray.forEach(entry => { + defineProp(magicArray, entry[itemMainProp], entry) + }) + + // This is the best way to fake the type to make sure this is false: `Array.isArray(navigator.mimeTypes)` + const magicArrayObj = Object.create(proto, { + ...Object.getOwnPropertyDescriptors(magicArray), + + // There's one ugly quirk we unfortunately need to take care of: + // The `MimeTypeArray` prototype has an enumerable `length` property, + // but headful Chrome will still skip it when running `Object.getOwnPropertyNames(navigator.mimeTypes)`. + // To strip it we need to make it first `configurable` and can then overlay a Proxy with an `ownKeys` trap. + length: { + value: magicArray.length, + writable: false, + enumerable: false, + configurable: true // Important to be able to use the ownKeys trap in a Proxy to strip `length` + } + }) + + // Generate our functional function mocks :-) + const functionMocks = generateFunctionMocks( + proto, + itemMainProp, + magicArray + ) + + // Override custom object with proxy + return new Proxy(magicArrayObj, { + get(target, key = '') { + // Redirect function calls to our custom proxied versions mocking the vanilla behavior + if (key === 'item') { + return functionMocks.item + } + if (key === 'namedItem') { + return functionMocks.namedItem + } + if (proto === PluginArray.prototype && key === 'refresh') { + return functionMocks.refresh + } + // Everything else can pass through as normal + return utils.cache.Reflect.get(...arguments) + }, + ownKeys(target) { + // There are a couple of quirks where the original property demonstrates "magical" behavior that makes no sense + // This can be witnessed when calling `Object.getOwnPropertyNames(navigator.mimeTypes)` and the absense of `length` + // My guess is that it has to do with the recent change of not allowing data enumeration and this being implemented weirdly + // For that reason we just completely fake the available property names based on our data to match what regular Chrome is doing + // Specific issues when not patching this: `length` property is available, direct `types` props (e.g. `obj['application/pdf']`) are missing + const keys = [] + const typeProps = magicArray.map(mt => mt[itemMainProp]) + typeProps.forEach((_, i) => keys.push(`${i}`)) + typeProps.forEach(propName => keys.push(propName)) + return keys + } + }) +} \ No newline at end of file diff --git a/reverse-life/pjstealth/feature/hookfuc.headless.js b/reverse-life/pjstealth/feature/hookfuc.headless.js new file mode 100644 index 0000000..eab8d57 --- /dev/null +++ b/reverse-life/pjstealth/feature/hookfuc.headless.js @@ -0,0 +1,18 @@ +var obj_get_own_p_n = Object.getOwnPropertyNames; +Object.getOwnPropertyNames = function (u) { + if (u === window) { + return ["Object", "Function", "Array", "Number", "parseFloat", "parseInt", "Infinity", "NaN", "undefined", "Boolean", "String", "Symbol", "Date", "Promise", "RegExp", "Error", "AggregateError", "EvalError", "RangeError", "ReferenceError", "SyntaxError", "TypeError", "URIError", "globalThis", "JSON", "Math", "Intl", "ArrayBuffer", "Uint8Array", "Int8Array", "Uint16Array", "Int16Array", "Uint32Array", "Int32Array", "Float32Array", "Float64Array", "Uint8ClampedArray", "BigUint64Array", "BigInt64Array", "DataView", "Map", "BigInt", "Set", "WeakMap", "WeakSet", "Proxy", "Reflect", "FinalizationRegistry", "WeakRef", "decodeURI", "decodeURIComponent", "encodeURI", "encodeURIComponent", "escape", "unescape", "eval", "isFinite", "isNaN", "console", "Option", "Image", "Audio", "webkitURL", "webkitRTCPeerConnection", "webkitMediaStream", "WebKitMutationObserver", "WebKitCSSMatrix", "XSLTProcessor", "XPathResult", "XPathExpression", "XPathEvaluator", "XMLSerializer", "XMLHttpRequestUpload", "XMLHttpRequestEventTarget", "XMLHttpRequest", "XMLDocument", "WritableStreamDefaultWriter", "WritableStreamDefaultController", "WritableStream", "Worker", "Window", "WheelEvent", "WebSocket", "WebGLVertexArrayObject", "WebGLUniformLocation", "WebGLTransformFeedback", "WebGLTexture", "WebGLSync", "WebGLShaderPrecisionFormat", "WebGLShader", "WebGLSampler", "WebGLRenderingContext", "WebGLRenderbuffer", "WebGLQuery", "WebGLProgram", "WebGLFramebuffer", "WebGLContextEvent", "WebGLBuffer", "WebGLActiveInfo", "WebGL2RenderingContext", "WaveShaperNode", "VisualViewport", "VirtualKeyboardGeometryChangeEvent", "ValidityState", "VTTCue", "UserActivation", "URLSearchParams", "URLPattern", "URL", "UIEvent", "TrustedTypePolicyFactory", "TrustedTypePolicy", "TrustedScriptURL", "TrustedScript", "TrustedHTML", "TreeWalker", "TransitionEvent", "TransformStreamDefaultController", "TransformStream", "TrackEvent", "TouchList", "TouchEvent", "Touch", "TimeRanges", "TextTrackList", "TextTrackCueList", "TextTrackCue", "TextTrack", "TextMetrics", "TextEvent", "TextEncoderStream", "TextEncoder", "TextDecoderStream", "TextDecoder", "Text", "TaskSignal", "TaskPriorityChangeEvent", "TaskController", "TaskAttributionTiming", "SyncManager", "SubmitEvent", "StyleSheetList", "StyleSheet", "StylePropertyMapReadOnly", "StylePropertyMap", "StorageEvent", "Storage", "StereoPannerNode", "StaticRange", "ShadowRoot", "Selection", "SecurityPolicyViolationEvent", "ScriptProcessorNode", "ScreenOrientation", "Screen", "Scheduling", "Scheduler", "SVGViewElement", "SVGUseElement", "SVGUnitTypes", "SVGTransformList", "SVGTransform", "SVGTitleElement", "SVGTextPositioningElement", "SVGTextPathElement", "SVGTextElement", "SVGTextContentElement", "SVGTSpanElement", "SVGSymbolElement", "SVGSwitchElement", "SVGStyleElement", "SVGStringList", "SVGStopElement", "SVGSetElement", "SVGScriptElement", "SVGSVGElement", "SVGRectElement", "SVGRect", "SVGRadialGradientElement", "SVGPreserveAspectRatio", "SVGPolylineElement", "SVGPolygonElement", "SVGPointList", "SVGPoint", "SVGPatternElement", "SVGPathElement", "SVGNumberList", "SVGNumber", "SVGMetadataElement", "SVGMatrix", "SVGMaskElement", "SVGMarkerElement", "SVGMPathElement", "SVGLinearGradientElement", "SVGLineElement", "SVGLengthList", "SVGLength", "SVGImageElement", "SVGGraphicsElement", "SVGGradientElement", "SVGGeometryElement", "SVGGElement", "SVGForeignObjectElement", "SVGFilterElement", "SVGFETurbulenceElement", "SVGFETileElement", "SVGFESpotLightElement", "SVGFESpecularLightingElement", "SVGFEPointLightElement", "SVGFEOffsetElement", "SVGFEMorphologyElement", "SVGFEMergeNodeElement", "SVGFEMergeElement", "SVGFEImageElement", "SVGFEGaussianBlurElement", "SVGFEFuncRElement", "SVGFEFuncGElement", "SVGFEFuncBElement", "SVGFEFuncAElement", "SVGFEFloodElement", "SVGFEDropShadowElement", "SVGFEDistantLightElement", "SVGFEDisplacementMapElement", "SVGFEDiffuseLightingElement", "SVGFEConvolveMatrixElement", "SVGFECompositeElement", "SVGFEComponentTransferElement", "SVGFEColorMatrixElement", "SVGFEBlendElement", "SVGEllipseElement", "SVGElement", "SVGDescElement", "SVGDefsElement", "SVGComponentTransferFunctionElement", "SVGClipPathElement", "SVGCircleElement", "SVGAnimationElement", "SVGAnimatedTransformList", "SVGAnimatedString", "SVGAnimatedRect", "SVGAnimatedPreserveAspectRatio", "SVGAnimatedNumberList", "SVGAnimatedNumber", "SVGAnimatedLengthList", "SVGAnimatedLength", "SVGAnimatedInteger", "SVGAnimatedEnumeration", "SVGAnimatedBoolean", "SVGAnimatedAngle", "SVGAnimateTransformElement", "SVGAnimateMotionElement", "SVGAnimateElement", "SVGAngle", "SVGAElement", "Response", "ResizeObserverSize", "ResizeObserverEntry", "ResizeObserver", "Request", "ReportingObserver", "ReadableStreamDefaultReader", "ReadableStreamDefaultController", "ReadableStreamBYOBRequest", "ReadableStreamBYOBReader", "ReadableStream", "ReadableByteStreamController", "Range", "RadioNodeList", "RTCTrackEvent", "RTCStatsReport", "RTCSessionDescription", "RTCSctpTransport", "RTCRtpTransceiver", "RTCRtpSender", "RTCRtpReceiver", "RTCPeerConnectionIceEvent", "RTCPeerConnectionIceErrorEvent", "RTCPeerConnection", "RTCIceTransport", "RTCIceCandidate", "RTCErrorEvent", "RTCError", "RTCEncodedVideoFrame", "RTCEncodedAudioFrame", "RTCDtlsTransport", "RTCDataChannelEvent", "RTCDataChannel", "RTCDTMFToneChangeEvent", "RTCDTMFSender", "RTCCertificate", "PromiseRejectionEvent", "ProgressEvent", "Profiler", "ProcessingInstruction", "PopStateEvent", "PointerEvent", "PluginArray", "Plugin", "PeriodicWave", "PerformanceTiming", "PerformanceServerTiming", "PerformanceResourceTiming", "PerformancePaintTiming", "PerformanceObserverEntryList", "PerformanceObserver", "PerformanceNavigationTiming", "PerformanceNavigation", "PerformanceMeasure", "PerformanceMark", "PerformanceLongTaskTiming", "PerformanceEventTiming", "PerformanceEntry", "PerformanceElementTiming", "Performance", "Path2D", "PannerNode", "PageTransitionEvent", "OverconstrainedError", "OscillatorNode", "OffscreenCanvasRenderingContext2D", "OffscreenCanvas", "OfflineAudioContext", "OfflineAudioCompletionEvent", "NodeList", "NodeIterator", "NodeFilter", "Node", "NetworkInformation", "Navigator", "NamedNodeMap", "MutationRecord", "MutationObserver", "MutationEvent", "MouseEvent", "MimeTypeArray", "MimeType", "MessagePort", "MessageEvent", "MessageChannel", "MediaStreamTrackProcessor", "MediaStreamTrackEvent", "MediaStreamEvent", "MediaStreamAudioSourceNode", "MediaStreamAudioDestinationNode", "MediaStream", "MediaRecorder", "MediaQueryListEvent", "MediaQueryList", "MediaList", "MediaError", "MediaEncryptedEvent", "MediaElementAudioSourceNode", "MediaCapabilities", "Location", "LayoutShiftAttribution", "LayoutShift", "LargestContentfulPaint", "KeyframeEffect", "KeyboardEvent", "IntersectionObserverEntry", "IntersectionObserver", "InputEvent", "InputDeviceInfo", "InputDeviceCapabilities", "ImageData", "ImageCapture", "ImageBitmapRenderingContext", "ImageBitmap", "IdleDeadline", "IIRFilterNode", "IDBVersionChangeEvent", "IDBTransaction", "IDBRequest", "IDBOpenDBRequest", "IDBObjectStore", "IDBKeyRange", "IDBIndex", "IDBFactory", "IDBDatabase", "IDBCursorWithValue", "IDBCursor", "History", "Headers", "HashChangeEvent", "HTMLVideoElement", "HTMLUnknownElement", "HTMLUListElement", "HTMLTrackElement", "HTMLTitleElement", "HTMLTimeElement", "HTMLTextAreaElement", "HTMLTemplateElement", "HTMLTableSectionElement", "HTMLTableRowElement", "HTMLTableElement", "HTMLTableColElement", "HTMLTableCellElement", "HTMLTableCaptionElement", "HTMLStyleElement", "HTMLSpanElement", "HTMLSourceElement", "HTMLSlotElement", "HTMLSelectElement", "HTMLScriptElement", "HTMLQuoteElement", "HTMLProgressElement", "HTMLPreElement", "HTMLPictureElement", "HTMLParamElement", "HTMLParagraphElement", "HTMLOutputElement", "HTMLOptionsCollection", "HTMLOptionElement", "HTMLOptGroupElement", "HTMLObjectElement", "HTMLOListElement", "HTMLModElement", "HTMLMeterElement", "HTMLMetaElement", "HTMLMenuElement", "HTMLMediaElement", "HTMLMarqueeElement", "HTMLMapElement", "HTMLLinkElement", "HTMLLegendElement", "HTMLLabelElement", "HTMLLIElement", "HTMLInputElement", "HTMLImageElement", "HTMLIFrameElement", "HTMLHtmlElement", "HTMLHeadingElement", "HTMLHeadElement", "HTMLHRElement", "HTMLFrameSetElement", "HTMLFrameElement", "HTMLFormElement", "HTMLFormControlsCollection", "HTMLFontElement", "HTMLFieldSetElement", "HTMLEmbedElement", "HTMLElement", "HTMLDocument", "HTMLDivElement", "HTMLDirectoryElement", "HTMLDialogElement", "HTMLDetailsElement", "HTMLDataListElement", "HTMLDataElement", "HTMLDListElement", "HTMLCollection", "HTMLCanvasElement", "HTMLButtonElement", "HTMLBodyElement", "HTMLBaseElement", "HTMLBRElement", "HTMLAudioElement", "HTMLAreaElement", "HTMLAnchorElement", "HTMLAllCollection", "GeolocationPositionError", "GeolocationPosition", "GeolocationCoordinates", "Geolocation", "GamepadHapticActuator", "GamepadEvent", "GamepadButton", "Gamepad", "GainNode", "FormDataEvent", "FormData", "FontFaceSetLoadEvent", "FontFace", "FocusEvent", "FileReader", "FileList", "File", "FeaturePolicy", "External", "EventTarget", "EventSource", "EventCounts", "Event", "ErrorEvent", "ElementInternals", "Element", "DynamicsCompressorNode", "DragEvent", "DocumentType", "DocumentFragment", "Document", "DelayNode", "DecompressionStream", "DataTransferItemList", "DataTransferItem", "DataTransfer", "DOMTokenList", "DOMStringMap", "DOMStringList", "DOMRectReadOnly", "DOMRectList", "DOMRect", "DOMQuad", "DOMPointReadOnly", "DOMPoint", "DOMParser", "DOMMatrixReadOnly", "DOMMatrix", "DOMImplementation", "DOMException", "DOMError", "CustomStateSet", "CustomEvent", "CustomElementRegistry", "Crypto", "CountQueuingStrategy", "ConvolverNode", "ConstantSourceNode", "CompressionStream", "CompositionEvent", "Comment", "CloseEvent", "ClipboardEvent", "CharacterData", "ChannelSplitterNode", "ChannelMergerNode", "CanvasRenderingContext2D", "CanvasPattern", "CanvasGradient", "CanvasFilter", "CanvasCaptureMediaStreamTrack", "CSSVariableReferenceValue", "CSSUnparsedValue", "CSSUnitValue", "CSSTranslate", "CSSTransformValue", "CSSTransformComponent", "CSSSupportsRule", "CSSStyleValue", "CSSStyleSheet", "CSSStyleRule", "CSSStyleDeclaration", "CSSSkewY", "CSSSkewX", "CSSSkew", "CSSScale", "CSSRuleList", "CSSRule", "CSSRotate", "CSSPropertyRule", "CSSPositionValue", "CSSPerspective", "CSSPageRule", "CSSNumericValue", "CSSNumericArray", "CSSNamespaceRule", "CSSMediaRule", "CSSMatrixComponent", "CSSMathValue", "CSSMathSum", "CSSMathProduct", "CSSMathNegate", "CSSMathMin", "CSSMathMax", "CSSMathInvert", "CSSMathClamp", "CSSLayerStatementRule", "CSSLayerBlockRule", "CSSKeywordValue", "CSSKeyframesRule", "CSSKeyframeRule", "CSSImportRule", "CSSImageValue", "CSSGroupingRule", "CSSFontFaceRule", "CSSCounterStyleRule", "CSSConditionRule", "CSS", "CDATASection", "ByteLengthQueuingStrategy", "BroadcastChannel", "BlobEvent", "Blob", "BiquadFilterNode", "BeforeUnloadEvent", "BeforeInstallPromptEvent", "BaseAudioContext", "BarProp", "AudioWorkletNode", "AudioScheduledSourceNode", "AudioProcessingEvent", "AudioParamMap", "AudioParam", "AudioNode", "AudioListener", "AudioDestinationNode", "AudioContext", "AudioBufferSourceNode", "AudioBuffer", "Attr", "AnimationEvent", "AnimationEffect", "Animation", "AnalyserNode", "AbstractRange", "AbortSignal", "AbortController", "window", "self", "document", "name", "location", "customElements", "history", "locationbar", "menubar", "personalbar", "scrollbars", "statusbar", "toolbar", "status", "closed", "frames", "length", "top", "opener", "parent", "frameElement", "navigator", "origin", "external", "screen", "innerWidth", "innerHeight", "scrollX", "pageXOffset", "scrollY", "pageYOffset", "visualViewport", "screenX", "screenY", "outerWidth", "outerHeight", "devicePixelRatio", "event", "clientInformation", "offscreenBuffering", "screenLeft", "screenTop", "styleMedia", "onsearch", "isSecureContext", "trustedTypes", "performance", "onappinstalled", "onbeforeinstallprompt", "crypto", "indexedDB", "sessionStorage", "localStorage", "onbeforexrselect", "onabort", "onbeforeinput", "onblur", "oncancel", "oncanplay", "oncanplaythrough", "onchange", "onclick", "onclose", "oncontextlost", "oncontextmenu", "oncontextrestored", "oncuechange", "ondblclick", "ondrag", "ondragend", "ondragenter", "ondragleave", "ondragover", "ondragstart", "ondrop", "ondurationchange", "onemptied", "onended", "onerror", "onfocus", "onformdata", "oninput", "oninvalid", "onkeydown", "onkeypress", "onkeyup", "onload", "onloadeddata", "onloadedmetadata", "onloadstart", "onmousedown", "onmouseenter", "onmouseleave", "onmousemove", "onmouseout", "onmouseover", "onmouseup", "onmousewheel", "onpause", "onplay", "onplaying", "onprogress", "onratechange", "onreset", "onresize", "onscroll", "onsecuritypolicyviolation", "onseeked", "onseeking", "onselect", "onslotchange", "onstalled", "onsubmit", "onsuspend", "ontimeupdate", "ontoggle", "onvolumechange", "onwaiting", "onwebkitanimationend", "onwebkitanimationiteration", "onwebkitanimationstart", "onwebkittransitionend", "onwheel", "onauxclick", "ongotpointercapture", "onlostpointercapture", "onpointerdown", "onpointermove", "onpointerrawupdate", "onpointerup", "onpointercancel", "onpointerover", "onpointerout", "onpointerenter", "onpointerleave", "onselectstart", "onselectionchange", "onanimationend", "onanimationiteration", "onanimationstart", "ontransitionrun", "ontransitionstart", "ontransitionend", "ontransitioncancel", "onafterprint", "onbeforeprint", "onbeforeunload", "onhashchange", "onlanguagechange", "onmessage", "onmessageerror", "onoffline", "ononline", "onpagehide", "onpageshow", "onpopstate", "onrejectionhandled", "onstorage", "onunhandledrejection", "onunload", "crossOriginIsolated", "scheduler", "alert", "atob", "blur", "btoa", "cancelAnimationFrame", "cancelIdleCallback", "captureEvents", "clearInterval", "clearTimeout", "close", "confirm", "createImageBitmap", "fetch", "find", "focus", "getComputedStyle", "getSelection", "matchMedia", "moveBy", "moveTo", "open", "postMessage", "print", "prompt", "queueMicrotask", "releaseEvents", "reportError", "requestAnimationFrame", "requestIdleCallback", "resizeBy", "resizeTo", "scroll", "scrollBy", "scrollTo", "setInterval", "setTimeout", "stop", "structuredClone", "webkitCancelAnimationFrame", "webkitRequestAnimationFrame", "Atomics", "chrome", "WebAssembly", "caches", "cookieStore", "ondevicemotion", "ondeviceorientation", "ondeviceorientationabsolute", "launchQueue", "onbeforematch", "AbsoluteOrientationSensor", "Accelerometer", "AudioWorklet", "BatteryManager", "Cache", "CacheStorage", "Clipboard", "ClipboardItem", "CookieChangeEvent", "CookieStore", "CookieStoreManager", "Credential", "CredentialsContainer", "CryptoKey", "DeviceMotionEvent", "DeviceMotionEventAcceleration", "DeviceMotionEventRotationRate", "DeviceOrientationEvent", "FederatedCredential", "GravitySensor", "Gyroscope", "Keyboard", "KeyboardLayoutMap", "LinearAccelerationSensor", "Lock", "LockManager", "MIDIAccess", "MIDIConnectionEvent", "MIDIInput", "MIDIInputMap", "MIDIMessageEvent", "MIDIOutput", "MIDIOutputMap", "MIDIPort", "MediaDeviceInfo", "MediaDevices", "MediaKeyMessageEvent", "MediaKeySession", "MediaKeyStatusMap", "MediaKeySystemAccess", "MediaKeys", "NavigationPreloadManager", "NavigatorManagedData", "OrientationSensor", "PasswordCredential", "RelativeOrientationSensor", "ScreenDetailed", "ScreenDetails", "Sensor", "SensorErrorEvent", "ServiceWorker", "ServiceWorkerContainer", "ServiceWorkerRegistration", "StorageManager", "SubtleCrypto", "VirtualKeyboard", "WebTransport", "WebTransportBidirectionalStream", "WebTransportDatagramDuplexStream", "WebTransportError", "Worklet", "XRDOMOverlayState", "XRLayer", "XRWebGLBinding", "AudioData", "EncodedAudioChunk", "EncodedVideoChunk", "ImageTrack", "ImageTrackList", "VideoColorSpace", "VideoFrame", "AudioDecoder", "AudioEncoder", "ImageDecoder", "VideoDecoder", "VideoEncoder", "AuthenticatorAssertionResponse", "AuthenticatorAttestationResponse", "AuthenticatorResponse", "PublicKeyCredential", "CaptureController", "EyeDropper", "FileSystemDirectoryHandle", "FileSystemFileHandle", "FileSystemHandle", "FileSystemWritableFileStream", "FontData", "FragmentDirective", "HID", "HIDConnectionEvent", "HIDDevice", "HIDInputReportEvent", "IdentityCredential", "IdleDetector", "LaunchParams", "LaunchQueue", "OTPCredential", "PaymentAddress", "PaymentRequest", "PaymentResponse", "PaymentMethodChangeEvent", "Presentation", "PresentationAvailability", "PresentationConnection", "PresentationConnectionAvailableEvent", "PresentationConnectionCloseEvent", "PresentationConnectionList", "PresentationReceiver", "PresentationRequest", "Sanitizer", "Serial", "SerialPort", "USB", "USBAlternateInterface", "USBConfiguration", "USBConnectionEvent", "USBDevice", "USBEndpoint", "USBInTransferResult", "USBInterface", "USBIsochronousInTransferPacket", "USBIsochronousInTransferResult", "USBIsochronousOutTransferPacket", "USBIsochronousOutTransferResult", "USBOutTransferResult", "WakeLock", "WakeLockSentinel", "WindowControlsOverlay", "WindowControlsOverlayGeometryChangeEvent", "XRAnchor", "XRAnchorSet", "XRBoundedReferenceSpace", "XRFrame", "XRInputSource", "XRInputSourceArray", "XRInputSourceEvent", "XRInputSourcesChangeEvent", "XRPose", "XRReferenceSpace", "XRReferenceSpaceEvent", "XRRenderState", "XRRigidTransform", "XRSession", "XRSessionEvent", "XRSpace", "XRSystem", "XRView", "XRViewerPose", "XRViewport", "XRWebGLLayer", "XRCPUDepthInformation", "XRDepthInformation", "XRWebGLDepthInformation", "XRCamera", "XRHitTestResult", "XRHitTestSource", "XRRay", "XRTransientInputHitTestResult", "XRTransientInputHitTestSource", "XRLightEstimate", "XRLightProbe", "getScreenDetails", "queryLocalFonts", "showDirectoryPicker", "showOpenFilePicker", "showSaveFilePicker", "originAgentCluster", "navigation", "webkitStorageInfo", "speechSynthesis", "oncontentvisibilityautostatechange", "AnimationPlaybackEvent", "AnimationTimeline", "CSSAnimation", "CSSTransition", "DocumentTimeline", "BackgroundFetchManager", "BackgroundFetchRecord", "BackgroundFetchRegistration", "BrowserCaptureMediaStreamTrack", "CropTarget", "CSSContainerRule", "CSSFontPaletteValuesRule", "ContentVisibilityAutoStateChangeEvent", "DelegatedInkTrailPresenter", "Ink", "Highlight", "HighlightRegistry", "MathMLElement", "MediaMetadata", "MediaSession", "MediaSource", "SourceBuffer", "SourceBufferList", "MediaSourceHandle", "MediaStreamTrack", "MediaStreamTrackGenerator", "NavigateEvent", "Navigation", "NavigationCurrentEntryChangeEvent", "NavigationDestination", "NavigationHistoryEntry", "NavigationTransition", "NavigatorUAData", "Notification", "PaymentInstruments", "PaymentManager", "PaymentRequestUpdateEvent", "PeriodicSyncManager", "PermissionStatus", "Permissions", "PictureInPictureEvent", "PictureInPictureWindow", "PushManager", "PushSubscription", "PushSubscriptionOptions", "RemotePlayback", "SharedWorker", "SpeechSynthesisErrorEvent", "SpeechSynthesisEvent", "SpeechSynthesisUtterance", "VideoPlaybackQuality", "webkitSpeechGrammar", "webkitSpeechGrammarList", "webkitSpeechRecognition", "webkitSpeechRecognitionError", "webkitSpeechRecognitionEvent", "openDatabase", "webkitRequestFileSystem", "webkitResolveLocalFileSystemURL", "__PageContext__", "_plt", "__tti", "webVitals", "__SEOINITED__", "leoConfig", "__FALLBACK_STATIC__", "__CMT_AMPLIFY_RATE__", "__ERROR_SAMPLE_RATE__", "__CDN_IMG__", "__PRIVACY_CONFIG__", "initInlineLogger", "pmmAppInfo", "__RESET_ERROR_LISTENER__", "funWebWidgets", "_SPLIT_REQUIRE_FLAG_", "__InitialLanguage__", "__InitialI18nStore__", "__InitialI18nStoreLoaded__", "__DOC_SOURCE__", "__CUI_IMAGE_FAST_SHOW_SCRIPT__", "__realFsImgSrcs", "__fsImgTotal", "__fsImgItems", "__fsImgSrcs", "extraI18nStore", "lang", "ns", "__ExtraI18nStore__", "__SSR__", "__CHUNK_DATA__", "rawData", "__MONITOR_INFOS__", "webpackChunkmobile_bg_web_home", "__funWebWidgets", "webpackChunkbg_fun_web_widgets", "__core-js_shared__", "core", "__mobxInstanceCount", "__mobxGlobals", "regeneratorRuntime", "pinnotification", "protobuf", "__pmmPagePath", "gtmLogger", "dataLayer", "__INITIAL_PROPS__", "__layout_expConfig__", "__FRONTEND_PERF_DATA__", "google_tag_manager", "google_tag_data", "dir", "dirxml", "profile", "profileEnd", "clear", "table", "keys", "values", "debug", "undebug", "monitor", "unmonitor", "inspect", "copy", "queryObjects", "$_", "$0", "$1", "$2", "$3", "$4", "getEventListeners", "getAccessibleName", "getAccessibleRole", "monitorEvents", "unmonitorEvents", "$", "$$", "$x"] + } else if (u === Permissions.prototype) { + return [] + } else if (u === WebGLRenderingContext.prototype) { + return [] + } else if (u === WebGL2RenderingContext.prototype) { + return [] + } else if (u === AudioBuffer.prototype) { + return [] + } else if (u === MouseEvent.prototype) { + return [] + } else { + return obj_get_own_p_n(u) + } +}; \ No newline at end of file diff --git a/reverse-life/pjstealth/feature/iframe.contentWindow.js b/reverse-life/pjstealth/feature/iframe.contentWindow.js new file mode 100644 index 0000000..9bdae19 --- /dev/null +++ b/reverse-life/pjstealth/feature/iframe.contentWindow.js @@ -0,0 +1,97 @@ +try { + // Adds a contentWindow proxy to the provided iframe element + const addContentWindowProxy = iframe => { + const contentWindowProxy = { + get(target, key) { + // Now to the interesting part: + // We actually make this thing behave like a regular iframe window, + // by intercepting calls to e.g. `.self` and redirect it to the correct thing. :) + // That makes it possible for these assertions to be correct: + // iframe.contentWindow.self === window.top // must be false + if (key === 'self') { + return this + } + // iframe.contentWindow.frameElement === iframe // must be true + if (key === 'frameElement') { + return iframe + } + return Reflect.get(target, key) + } + } + + if (!iframe.contentWindow) { + const proxy = new Proxy(window, contentWindowProxy) + Object.defineProperty(iframe, 'contentWindow', { + get() { + return proxy + }, + set(newValue) { + return newValue // contentWindow is immutable + }, + enumerable: true, + configurable: false + }) + } + } + + // Handles iframe element creation, augments `srcdoc` property so we can intercept further + const handleIframeCreation = (target, thisArg, args) => { + const iframe = target.apply(thisArg, args) + + // We need to keep the originals around + const _iframe = iframe + const _srcdoc = _iframe.srcdoc + + // Add hook for the srcdoc property + // We need to be very surgical here to not break other iframes by accident + Object.defineProperty(iframe, 'srcdoc', { + configurable: true, // Important, so we can reset this later + get: function () { + return _iframe.srcdoc + }, + set: function (newValue) { + addContentWindowProxy(this) + // Reset property, the hook is only needed once + Object.defineProperty(iframe, 'srcdoc', { + configurable: false, + writable: false, + value: _srcdoc + }) + _iframe.srcdoc = newValue + } + }) + return iframe + } + + // Adds a hook to intercept iframe creation events + const addIframeCreationSniffer = () => { + /* global document */ + const createElementHandler = { + // Make toString() native + get(target, key) { + return Reflect.get(target, key) + }, + apply: function (target, thisArg, args) { + const isIframe = + args && args.length && `${args[0]}`.toLowerCase() === 'iframe' + if (!isIframe) { + // Everything as usual + return target.apply(thisArg, args) + } else { + return handleIframeCreation(target, thisArg, args) + } + } + } + // All this just due to iframes with srcdoc bug + utils.replaceWithProxy( + document, + 'createElement', + createElementHandler + ) + } + + // Let's go + addIframeCreationSniffer() +} catch (err) { + // console.warn(err) +} \ No newline at end of file diff --git a/reverse-life/pjstealth/feature/media.codecs.js b/reverse-life/pjstealth/feature/media.codecs.js new file mode 100644 index 0000000..7761032 --- /dev/null +++ b/reverse-life/pjstealth/feature/media.codecs.js @@ -0,0 +1,63 @@ +/** + * Input might look funky, we need to normalize it so e.g. whitespace isn't an issue for our spoofing. + * + * @example + * video/webm; codecs="vp8, vorbis" + * video/mp4; codecs="avc1.42E01E" + * audio/x-m4a; + * audio/ogg; codecs="vorbis" + * @param {String} arg + */ +const parseInput = arg => { + const [mime, codecStr] = arg.trim().split(';') + let codecs = [] + if (codecStr && codecStr.includes('codecs="')) { + codecs = codecStr + .trim() + .replace(`codecs="`, '') + .replace(`"`, '') + .trim() + .split(',') + .filter(x => !!x) + .map(x => x.trim()) + } + return { + mime, + codecStr, + codecs + } +} + +const canPlayType = { + // Intercept certain requests + apply: function (target, ctx, args) { + if (!args || !args.length) { + return target.apply(ctx, args) + } + const {mime, codecs} = parseInput(args[0]) + // This specific mp4 codec is missing in Chromium + if (mime === 'video/mp4') { + if (codecs.includes('avc1.42E01E')) { + return 'probably' + } + } + // This mimetype is only supported if no codecs are specified + if (mime === 'audio/x-m4a' && !codecs.length) { + return 'maybe' + } + + // This mimetype is only supported if no codecs are specified + if (mime === 'audio/aac' && !codecs.length) { + return 'probably' + } + // Everything else as usual + return target.apply(ctx, args) + } +} + +/* global HTMLMediaElement */ +utils.replaceWithProxy( + HTMLMediaElement.prototype, + 'canPlayType', + canPlayType +) \ No newline at end of file diff --git a/reverse-life/pjstealth/feature/navigator.appVersion.js b/reverse-life/pjstealth/feature/navigator.appVersion.js new file mode 100644 index 0000000..15af3c3 --- /dev/null +++ b/reverse-life/pjstealth/feature/navigator.appVersion.js @@ -0,0 +1,3 @@ +Object.defineProperty(navigator, 'appVersion', { + get: () => opts.navigator_app_version +}); diff --git a/reverse-life/pjstealth/feature/navigator.deviceMemory.js b/reverse-life/pjstealth/feature/navigator.deviceMemory.js new file mode 100644 index 0000000..b947ee4 --- /dev/null +++ b/reverse-life/pjstealth/feature/navigator.deviceMemory.js @@ -0,0 +1,8 @@ +const patchNavigator2 = (name, value) => + utils.replaceProperty(Object.getPrototypeOf(navigator), name, { + get() { + return value + } + }); + +patchNavigator2('deviceMemory', opts.device_memory || 4); \ No newline at end of file diff --git a/reverse-life/pjstealth/feature/navigator.hardwareConcurrency.js b/reverse-life/pjstealth/feature/navigator.hardwareConcurrency.js new file mode 100644 index 0000000..1208b54 --- /dev/null +++ b/reverse-life/pjstealth/feature/navigator.hardwareConcurrency.js @@ -0,0 +1,8 @@ +const patchNavigator3 = (name, value) => + utils.replaceProperty(Object.getPrototypeOf(navigator), name, { + get() { + return value + } + }) + +patchNavigator3('hardwareConcurrency', opts.navigator_hardware_concurrency || 4); \ No newline at end of file diff --git a/reverse-life/pjstealth/feature/navigator.language.js b/reverse-life/pjstealth/feature/navigator.language.js new file mode 100644 index 0000000..360f0c1 --- /dev/null +++ b/reverse-life/pjstealth/feature/navigator.language.js @@ -0,0 +1,3 @@ +Object.defineProperty(Object.getPrototypeOf(navigator), 'language', { + get: () => opts.language +}); diff --git a/reverse-life/pjstealth/feature/navigator.languages.js b/reverse-life/pjstealth/feature/navigator.languages.js new file mode 100644 index 0000000..e59284b --- /dev/null +++ b/reverse-life/pjstealth/feature/navigator.languages.js @@ -0,0 +1,3 @@ +Object.defineProperty(Object.getPrototypeOf(navigator), 'languages', { + get: () => opts.languages || ['en-US', 'en'] +}); diff --git a/reverse-life/pjstealth/feature/navigator.permissions.js b/reverse-life/pjstealth/feature/navigator.permissions.js new file mode 100644 index 0000000..1d90531 --- /dev/null +++ b/reverse-life/pjstealth/feature/navigator.permissions.js @@ -0,0 +1,19 @@ +const handler = { + apply: function (target, ctx, args) { + const param = (args || [])[0] + + if (param && param.name && param.name === 'notifications') { + const result = {state: Notification.permission} + Object.setPrototypeOf(result, PermissionStatus.prototype) + return Promise.resolve(result) + } + + return utils.cache.Reflect.apply(...arguments) + } +} + +utils.replaceWithProxy( + window.navigator.permissions.__proto__, // eslint-disable-line no-proto + 'query', + handler +) diff --git a/reverse-life/pjstealth/feature/navigator.platform.js b/reverse-life/pjstealth/feature/navigator.platform.js new file mode 100644 index 0000000..1398696 --- /dev/null +++ b/reverse-life/pjstealth/feature/navigator.platform.js @@ -0,0 +1,5 @@ +if (opts.navigator_platform) { + Object.defineProperty(Object.getPrototypeOf(navigator), 'platform', { + get: () => opts.navigator_platform, + }) +} \ No newline at end of file diff --git a/reverse-life/pjstealth/feature/navigator.plugins.js b/reverse-life/pjstealth/feature/navigator.plugins.js new file mode 100644 index 0000000..117aeb6 --- /dev/null +++ b/reverse-life/pjstealth/feature/navigator.plugins.js @@ -0,0 +1,92 @@ +data = { + "mimeTypes": [ + { + "type": "application/pdf", + "suffixes": "pdf", + "description": "", + "__pluginName": "Chrome PDF Viewer" + }, + { + "type": "application/x-google-chrome-pdf", + "suffixes": "pdf", + "description": "Portable Document Format", + "__pluginName": "Chrome PDF Plugin" + }, + { + "type": "application/x-nacl", + "suffixes": "", + "description": "Native Client Executable", + "__pluginName": "Native Client" + }, + { + "type": "application/x-pnacl", + "suffixes": "", + "description": "Portable Native Client Executable", + "__pluginName": "Native Client" + } + ], + "plugins": [ + { + "name": "Chrome PDF Plugin", + "filename": "internal-pdf-viewer", + "description": "Portable Document Format", + "__mimeTypes": ["application/x-google-chrome-pdf"] + }, + { + "name": "Chrome PDF Viewer", + "filename": "mhjfbmdgcfjbbpaeojofohoefgiehjai", + "description": "", + "__mimeTypes": ["application/pdf"] + }, + { + "name": "Native Client", + "filename": "internal-nacl-plugin", + "description": "", + "__mimeTypes": ["application/x-nacl", "application/x-pnacl"] + } + ] +} + + +// That means we're running headful +const hasPlugins = 'plugins' in navigator && navigator.plugins.length +if (!(hasPlugins)) { + + const mimeTypes = generateMagicArray( + data.mimeTypes, + MimeTypeArray.prototype, + MimeType.prototype, + 'type' + ) + const plugins = generateMagicArray( + data.plugins, + PluginArray.prototype, + Plugin.prototype, + 'name' + ) + + // Plugin and MimeType cross-reference each other, let's do that now + // Note: We're looping through `data.plugins` here, not the generated `plugins` + for (const pluginData of data.plugins) { + pluginData.__mimeTypes.forEach((type, index) => { + plugins[pluginData.name][index] = mimeTypes[type] + plugins[type] = mimeTypes[type] + Object.defineProperty(mimeTypes[type], 'enabledPlugin', { + value: JSON.parse(JSON.stringify(plugins[pluginData.name])), + writable: false, + enumerable: false, // Important: `JSON.stringify(navigator.plugins)` + configurable: false + }) + }) + } + + const patchNavigator = (name, value) => + utils.replaceProperty(Object.getPrototypeOf(navigator), name, { + get() { + return value + } + }) + + patchNavigator('mimeTypes', mimeTypes) + patchNavigator('plugins', plugins) +} \ No newline at end of file diff --git a/reverse-life/pjstealth/feature/navigator.userAgent.js b/reverse-life/pjstealth/feature/navigator.userAgent.js new file mode 100644 index 0000000..343150f --- /dev/null +++ b/reverse-life/pjstealth/feature/navigator.userAgent.js @@ -0,0 +1,5 @@ +// replace Headless references in default useragent +const current_ua = navigator.userAgent; +Object.defineProperty(Object.getPrototypeOf(navigator), 'userAgent', { + get: () => opts.navigator_user_agent || current_ua.replace('HeadlessChrome/', 'Chrome/') +}) diff --git a/reverse-life/pjstealth/feature/navigator.userAgentData.js b/reverse-life/pjstealth/feature/navigator.userAgentData.js new file mode 100644 index 0000000..97c4329 --- /dev/null +++ b/reverse-life/pjstealth/feature/navigator.userAgentData.js @@ -0,0 +1,5 @@ +if (opts.user_agent_data) { + Object.defineProperty(Object.getPrototypeOf(navigator), 'userAgentData', { + get: () => opts.user_agent_data, + }) +} \ No newline at end of file diff --git a/reverse-life/pjstealth/feature/navigator.vendor.js b/reverse-life/pjstealth/feature/navigator.vendor.js new file mode 100644 index 0000000..1349b67 --- /dev/null +++ b/reverse-life/pjstealth/feature/navigator.vendor.js @@ -0,0 +1,3 @@ +Object.defineProperty(Object.getPrototypeOf(navigator), 'vendor', { + get: () => opts.navigator_vendor || 'Google Inc.', +}) diff --git a/reverse-life/pjstealth/feature/utils.js b/reverse-life/pjstealth/feature/utils.js new file mode 100644 index 0000000..5e26ae6 --- /dev/null +++ b/reverse-life/pjstealth/feature/utils.js @@ -0,0 +1,456 @@ +/** + * A set of shared utility functions specifically for the purpose of modifying native browser APIs without leaving traces. + * + * Meant to be passed down in puppeteer and used in the context of the page (everything in here runs in NodeJS as well as a browser). + * + * Note: If for whatever reason you need to use this outside of `puppeteer-extra`: + * Just remove the `module.exports` statement at the very bottom, the rest can be copy pasted into any browser context. + * + * Alternatively take a look at the `extract-stealth-evasions` package to create a finished bundle which includes these utilities. + * + */ +const utils = {} + +/** + * Wraps a JS Proxy Handler and strips it's presence from error stacks, in case the traps throw. + * + * The presence of a JS Proxy can be revealed as it shows up in error stack traces. + * + * @param {object} handler - The JS Proxy handler to wrap + */ +utils.stripProxyFromErrors = (handler = {}) => { + const newHandler = {} + // We wrap each trap in the handler in a try/catch and modify the error stack if they throw + const traps = Object.getOwnPropertyNames(handler) + traps.forEach(trap => { + newHandler[trap] = function() { + try { + // Forward the call to the defined proxy handler + return handler[trap].apply(this, arguments || []) + } catch (err) { + // Stack traces differ per browser, we only support chromium based ones currently + if (!err || !err.stack || !err.stack.includes(`at `)) { + throw err + } + + // When something throws within one of our traps the Proxy will show up in error stacks + // An earlier implementation of this code would simply strip lines with a blacklist, + // but it makes sense to be more surgical here and only remove lines related to our Proxy. + // We try to use a known "anchor" line for that and strip it with everything above it. + // If the anchor line cannot be found for some reason we fall back to our blacklist approach. + + const stripWithBlacklist = stack => { + const blacklist = [ + `at Reflect.${trap} `, // e.g. Reflect.get or Reflect.apply + `at Object.${trap} `, // e.g. Object.get or Object.apply + `at Object.newHandler. [as ${trap}] ` // caused by this very wrapper :-) + ] + return ( + err.stack + .split('\n') + // Always remove the first (file) line in the stack (guaranteed to be our proxy) + .filter((line, index) => index !== 1) + // Check if the line starts with one of our blacklisted strings + .filter(line => !blacklist.some(bl => line.trim().startsWith(bl))) + .join('\n') + ) + } + + const stripWithAnchor = stack => { + const stackArr = stack.split('\n') + const anchor = `at Object.newHandler. [as ${trap}] ` // Known first Proxy line in chromium + const anchorIndex = stackArr.findIndex(line => + line.trim().startsWith(anchor) + ) + if (anchorIndex === -1) { + return false // 404, anchor not found + } + // Strip everything from the top until we reach the anchor line + // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`) + stackArr.splice(1, anchorIndex) + return stackArr.join('\n') + } + + // Try using the anchor method, fallback to blacklist if necessary + err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack) + + throw err // Re-throw our now sanitized error + } + } + }) + return newHandler +} + +/** + * Strip error lines from stack traces until (and including) a known line the stack. + * + * @param {object} err - The error to sanitize + * @param {string} anchor - The string the anchor line starts with + */ +utils.stripErrorWithAnchor = (err, anchor) => { + const stackArr = err.stack.split('\n') + const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor)) + if (anchorIndex === -1) { + return err // 404, anchor not found + } + // Strip everything from the top until we reach the anchor line (remove anchor line as well) + // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`) + stackArr.splice(1, anchorIndex) + err.stack = stackArr.join('\n') + return err +} + +/** + * Replace the property of an object in a stealthy way. + * + * Note: You also want to work on the prototype of an object most often, + * as you'd otherwise leave traces (e.g. showing up in Object.getOwnPropertyNames(obj)). + * + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty + * + * @example + * replaceProperty(WebGLRenderingContext.prototype, 'getParameter', { value: "alice" }) + * // or + * replaceProperty(Object.getPrototypeOf(navigator), 'languages', { get: () => ['en-US', 'en'] }) + * + * @param {object} obj - The object which has the property to replace + * @param {string} propName - The property name to replace + * @param {object} descriptorOverrides - e.g. { value: "alice" } + */ +utils.replaceProperty = (obj, propName, descriptorOverrides = {}) => { + return Object.defineProperty(obj, propName, { + // Copy over the existing descriptors (writable, enumerable, configurable, etc) + ...(Object.getOwnPropertyDescriptor(obj, propName) || {}), + // Add our overrides (e.g. value, get()) + ...descriptorOverrides + }) +} + +/** + * Preload a cache of function copies and data. + * + * For a determined enough observer it would be possible to overwrite and sniff usage of functions + * we use in our internal Proxies, to combat that we use a cached copy of those functions. + * + * This is evaluated once per execution context (e.g. window) + */ +utils.preloadCache = () => { + if (utils.cache) { + return + } + utils.cache = { + // Used in our proxies + Reflect: { + get: Reflect.get.bind(Reflect), + apply: Reflect.apply.bind(Reflect) + }, + // Used in `makeNativeString` + nativeToStringStr: Function.toString + '' // => `function toString() { [native code] }` + } +} + +/** + * Utility function to generate a cross-browser `toString` result representing native code. + * + * There's small differences: Chromium uses a single line, whereas FF & Webkit uses multiline strings. + * To future-proof this we use an existing native toString result as the basis. + * + * The only advantage we have over the other team is that our JS runs first, hence we cache the result + * of the native toString result once, so they cannot spoof it afterwards and reveal that we're using it. + * + * Note: Whenever we add a `Function.prototype.toString` proxy we should preload the cache before, + * by executing `utils.preloadCache()` before the proxy is applied (so we don't cause recursive lookups). + * + * @example + * makeNativeString('foobar') // => `function foobar() { [native code] }` + * + * @param {string} [name] - Optional function name + */ +utils.makeNativeString = (name = '') => { + // Cache (per-window) the original native toString or use that if available + utils.preloadCache() + return utils.cache.nativeToStringStr.replace('toString', name || '') +} + +/** + * Helper function to modify the `toString()` result of the provided object. + * + * Note: Use `utils.redirectToString` instead when possible. + * + * There's a quirk in JS Proxies that will cause the `toString()` result to differ from the vanilla Object. + * If no string is provided we will generate a `[native code]` thing based on the name of the property object. + * + * @example + * patchToString(WebGLRenderingContext.prototype.getParameter, 'function getParameter() { [native code] }') + * + * @param {object} obj - The object for which to modify the `toString()` representation + * @param {string} str - Optional string used as a return value + */ +utils.patchToString = (obj, str = '') => { + utils.preloadCache() + + const toStringProxy = new Proxy(Function.prototype.toString, { + apply: function(target, ctx) { + // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + ""` + if (ctx === Function.prototype.toString) { + return utils.makeNativeString('toString') + } + // `toString` targeted at our proxied Object detected + if (ctx === obj) { + // We either return the optional string verbatim or derive the most desired result automatically + return str || utils.makeNativeString(obj.name) + } + // Check if the toString protype of the context is the same as the global prototype, + // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test case + const hasSameProto = Object.getPrototypeOf( + Function.prototype.toString + ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins + if (!hasSameProto) { + // Pass the call on to the local Function.prototype.toString instead + return ctx.toString() + } + return target.call(ctx) + } + }) + utils.replaceProperty(Function.prototype, 'toString', { + value: toStringProxy + }) +} + +/** + * Make all nested functions of an object native. + * + * @param {object} obj + */ +utils.patchToStringNested = (obj = {}) => { + return utils.execRecursively(obj, ['function'], utils.patchToString) +} + +/** + * Redirect toString requests from one object to another. + * + * @param {object} proxyObj - The object that toString will be called on + * @param {object} originalObj - The object which toString result we wan to return + */ +utils.redirectToString = (proxyObj, originalObj) => { + utils.preloadCache() + + const toStringProxy = new Proxy(Function.prototype.toString, { + apply: function(target, ctx) { + // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + ""` + if (ctx === Function.prototype.toString) { + return utils.makeNativeString('toString') + } + + // `toString` targeted at our proxied Object detected + if (ctx === proxyObj) { + const fallback = () => + originalObj && originalObj.name + ? utils.makeNativeString(originalObj.name) + : utils.makeNativeString(proxyObj.name) + + // Return the toString representation of our original object if possible + return originalObj + '' || fallback() + } + + // Check if the toString protype of the context is the same as the global prototype, + // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test case + const hasSameProto = Object.getPrototypeOf( + Function.prototype.toString + ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins + if (!hasSameProto) { + // Pass the call on to the local Function.prototype.toString instead + return ctx.toString() + } + + return target.call(ctx) + } + }) + utils.replaceProperty(Function.prototype, 'toString', { + value: toStringProxy + }) +} + +/** + * All-in-one method to replace a property with a JS Proxy using the provided Proxy handler with traps. + * + * Will stealthify these aspects (strip error stack traces, redirect toString, etc). + * Note: This is meant to modify native Browser APIs and works best with prototype objects. + * + * @example + * replaceWithProxy(WebGLRenderingContext.prototype, 'getParameter', proxyHandler) + * + * @param {object} obj - The object which has the property to replace + * @param {string} propName - The name of the property to replace + * @param {object} handler - The JS Proxy handler to use + */ +utils.replaceWithProxy = (obj, propName, handler) => { + utils.preloadCache() + const originalObj = obj[propName] + const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler)) + + utils.replaceProperty(obj, propName, { value: proxyObj }) + utils.redirectToString(proxyObj, originalObj) + + return true +} + +/** + * All-in-one method to mock a non-existing property with a JS Proxy using the provided Proxy handler with traps. + * + * Will stealthify these aspects (strip error stack traces, redirect toString, etc). + * + * @example + * mockWithProxy(chrome.runtime, 'sendMessage', function sendMessage() {}, proxyHandler) + * + * @param {object} obj - The object which has the property to replace + * @param {string} propName - The name of the property to replace or create + * @param {object} pseudoTarget - The JS Proxy target to use as a basis + * @param {object} handler - The JS Proxy handler to use + */ +utils.mockWithProxy = (obj, propName, pseudoTarget, handler) => { + utils.preloadCache() + const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler)) + + utils.replaceProperty(obj, propName, { value: proxyObj }) + utils.patchToString(proxyObj) + + return true +} + +/** + * All-in-one method to create a new JS Proxy with stealth tweaks. + * + * This is meant to be used whenever we need a JS Proxy but don't want to replace or mock an existing known property. + * + * Will stealthify certain aspects of the Proxy (strip error stack traces, redirect toString, etc). + * + * @example + * createProxy(navigator.mimeTypes.__proto__.namedItem, proxyHandler) // => Proxy + * + * @param {object} pseudoTarget - The JS Proxy target to use as a basis + * @param {object} handler - The JS Proxy handler to use + */ +utils.createProxy = (pseudoTarget, handler) => { + utils.preloadCache() + const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler)) + utils.patchToString(proxyObj) + + return proxyObj +} + +/** + * Helper function to split a full path to an Object into the first part and property. + * + * @example + * splitObjPath(`HTMLMediaElement.prototype.canPlayType`) + * // => {objName: "HTMLMediaElement.prototype", propName: "canPlayType"} + * + * @param {string} objPath - The full path to an object as dot notation string + */ +utils.splitObjPath = objPath => ({ + // Remove last dot entry (property) ==> `HTMLMediaElement.prototype` + objName: objPath + .split('.') + .slice(0, -1) + .join('.'), + // Extract last dot entry ==> `canPlayType` + propName: objPath.split('.').slice(-1)[0] +}) + +/** + * Convenience method to replace a property with a JS Proxy using the provided objPath. + * + * Supports a full path (dot notation) to the object as string here, in case that makes it easier. + * + * @example + * replaceObjPathWithProxy('WebGLRenderingContext.prototype.getParameter', proxyHandler) + * + * @param {string} objPath - The full path to an object (dot notation string) to replace + * @param {object} handler - The JS Proxy handler to use + */ +utils.replaceObjPathWithProxy = (objPath, handler) => { + const { objName, propName } = utils.splitObjPath(objPath) + const obj = eval(objName) // eslint-disable-line no-eval + return utils.replaceWithProxy(obj, propName, handler) +} + +/** + * Traverse nested properties of an object recursively and apply the given function on a whitelist of value types. + * + * @param {object} obj + * @param {array} typeFilter - e.g. `['function']` + * @param {Function} fn - e.g. `utils.patchToString` + */ +utils.execRecursively = (obj = {}, typeFilter = [], fn) => { + function recurse(obj) { + for (const key in obj) { + if (obj[key] === undefined) { + continue + } + if (obj[key] && typeof obj[key] === 'object') { + recurse(obj[key]) + } else { + if (obj[key] && typeFilter.includes(typeof obj[key])) { + fn.call(this, obj[key]) + } + } + } + } + recurse(obj) + return obj +} + +/** + * Everything we run through e.g. `page.evaluate` runs in the browser context, not the NodeJS one. + * That means we cannot just use reference variables and functions from outside code, we need to pass everything as a parameter. + * + * Unfortunately the data we can pass is only allowed to be of primitive types, regular functions don't survive the built-in serialization process. + * This utility function will take an object with functions and stringify them, so we can pass them down unharmed as strings. + * + * We use this to pass down our utility functions as well as any other functions (to be able to split up code better). + * + * @see utils.materializeFns + * + * @param {object} fnObj - An object containing functions as properties + */ +utils.stringifyFns = (fnObj = { hello: () => 'world' }) => { + // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are fine + // https://github.com/feross/fromentries + function fromEntries(iterable) { + return [...iterable].reduce((obj, [key, val]) => { + obj[key] = val + return obj + }, {}) + } + return (Object.fromEntries || fromEntries)( + Object.entries(fnObj) + .filter(([key, value]) => typeof value === 'function') + .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-eval + ) +} + +/** + * Utility function to reverse the process of `utils.stringifyFns`. + * Will materialize an object with stringified functions (supports classic and fat arrow functions). + * + * @param {object} fnStrObj - An object containing stringified functions as properties + */ +utils.materializeFns = (fnStrObj = { hello: "() => 'world'" }) => { + return Object.fromEntries( + Object.entries(fnStrObj).map(([key, value]) => { + if (value.startsWith('function')) { + // some trickery is needed to make oldschool functions work :-) + return [key, eval(`() => ${value}`)()] // eslint-disable-line no-eval + } else { + // arrow functions just work + return [key, eval(value)] // eslint-disable-line no-eval + } + }) + ) +} + +// -- +// Stuff starting below this line is NodeJS specific. +// -- +// module.exports = utils \ No newline at end of file diff --git a/reverse-life/pjstealth/feature/webgl.vendor.js b/reverse-life/pjstealth/feature/webgl.vendor.js new file mode 100644 index 0000000..6b585a9 --- /dev/null +++ b/reverse-life/pjstealth/feature/webgl.vendor.js @@ -0,0 +1,25 @@ +console.log(opts) +const getParameterProxyHandler = { + apply: function (target, ctx, args) { + const param = (args || [])[0] + // UNMASKED_VENDOR_WEBGL + if (param === 37445) { + return opts.webgl_vendor || 'Intel Inc.' // default in headless: Google Inc. + } + // UNMASKED_RENDERER_WEBGL + if (param === 37446) { + return opts.webgl_renderer || 'Intel Iris OpenGL Engine' // default in headless: Google SwiftShader + } + return utils.cache.Reflect.apply(target, ctx, args) + } +} + +// There's more than one WebGL rendering context +// https://developer.mozilla.org/en-US/docs/Web/API/WebGL2RenderingContext#Browser_compatibility +// To find out the original values here: Object.getOwnPropertyDescriptors(WebGLRenderingContext.prototype.getParameter) +const addProxy = (obj, propName) => { + utils.replaceWithProxy(obj, propName, getParameterProxyHandler) +} +// For whatever weird reason loops don't play nice with Object.defineProperty, here's the next best thing: +addProxy(WebGLRenderingContext.prototype, 'getParameter') +addProxy(WebGL2RenderingContext.prototype, 'getParameter') \ No newline at end of file diff --git a/reverse-life/pjstealth/feature/window.outerdimensions.js b/reverse-life/pjstealth/feature/window.outerdimensions.js new file mode 100644 index 0000000..e9ef868 --- /dev/null +++ b/reverse-life/pjstealth/feature/window.outerdimensions.js @@ -0,0 +1,12 @@ +'use strict' + +try { + if (!!window.outerWidth && !!window.outerHeight) { + const windowFrame = 85 // probably OS and WM dependent + window.outerWidth = window.innerWidth + console.log(`current window outer height ${window.outerHeight}`) + window.outerHeight = window.innerHeight + windowFrame + console.log(`new window outer height ${window.outerHeight}`) + } +} catch (err) { +} diff --git a/reverse-life/pjstealth/js/chrome.app.js b/reverse-life/pjstealth/js/chrome.app.js new file mode 100644 index 0000000..7a73929 --- /dev/null +++ b/reverse-life/pjstealth/js/chrome.app.js @@ -0,0 +1,71 @@ +if (!window.chrome) { + // Use the exact property descriptor found in headful Chrome + // fetch it via `Object.getOwnPropertyDescriptor(window, 'chrome')` + Object.defineProperty(window, 'chrome', { + writable: true, + enumerable: true, + configurable: false, // note! + value: {} // We'll extend that later + }) +} + +// app in window.chrome means we're running headful and don't need to mock anything +if (!('app' in window.chrome)) { + const makeError = { + ErrorInInvocation: fn => { + const err = new TypeError(`Error in invocation of app.${fn}()`) + return utils.stripErrorWithAnchor( + err, + `at ${fn} (eval at ` + ) + } + } + +// There's a some static data in that property which doesn't seem to change, +// we should periodically check for updates: `JSON.stringify(window.app, null, 2)` + const APP_STATIC_DATA = JSON.parse( + ` +{ + "isInstalled": false, + "InstallState": { + "DISABLED": "disabled", + "INSTALLED": "installed", + "NOT_INSTALLED": "not_installed" + }, + "RunningState": { + "CANNOT_RUN": "cannot_run", + "READY_TO_RUN": "ready_to_run", + "RUNNING": "running" + } +} + `.trim() + ) + + window.chrome.app = { + ...APP_STATIC_DATA, + + get isInstalled() { + return false + }, + + getDetails: function getDetails() { + if (arguments.length) { + throw makeError.ErrorInInvocation(`getDetails`) + } + return null + }, + getIsInstalled: function getDetails() { + if (arguments.length) { + throw makeError.ErrorInInvocation(`getIsInstalled`) + } + return false + }, + runningState: function getDetails() { + if (arguments.length) { + throw makeError.ErrorInInvocation(`runningState`) + } + return 'cannot_run' + } + } + utils.patchToStringNested(window.chrome.app) +} \ No newline at end of file diff --git a/reverse-life/pjstealth/js/chrome.canvasfeature.js b/reverse-life/pjstealth/js/chrome.canvasfeature.js new file mode 100644 index 0000000..a8c8826 --- /dev/null +++ b/reverse-life/pjstealth/js/chrome.canvasfeature.js @@ -0,0 +1,35 @@ +const getImageData = CanvasRenderingContext2D.prototype.getImageData; +// +var noisify = function (canvas, context) { + if (context) { + const shift = { + 'r': Math.floor(Math.random() * 10) - 5, + 'g': Math.floor(Math.random() * 10) - 5, + 'b': Math.floor(Math.random() * 10) - 5, + 'a': Math.floor(Math.random() * 10) - 5 + }; + // + const width = canvas.width; + const height = canvas.height; + if (width && height) { + const imageData = getImageData.apply(context, [0, 0, width, height]); + for (let i = 0; i < height; i++) { + for (let j = 0; j < width; j++) { + const n = ((i * (width * 4)) + (j * 4)); + imageData.data[n + 0] = imageData.data[n + 0] + shift.r; + imageData.data[n + 1] = imageData.data[n + 1] + shift.g; + imageData.data[n + 2] = imageData.data[n + 2] + shift.b; + imageData.data[n + 3] = imageData.data[n + 3] + shift.a; + } + } + // + context.putImageData(imageData, 0, 0); + } + } +}; +Object.defineProperty(CanvasRenderingContext2D.prototype, "getImageData", { + "value": function () { + noisify(this.canvas, this); + return getImageData.apply(this, arguments); + } +}); diff --git a/reverse-life/pjstealth/js/chrome.canvasfeature2.js b/reverse-life/pjstealth/js/chrome.canvasfeature2.js new file mode 100644 index 0000000..a01c31b --- /dev/null +++ b/reverse-life/pjstealth/js/chrome.canvasfeature2.js @@ -0,0 +1,18 @@ +const getRandomIntInclusive = function (min, max) { + min = Math.ceil(min); + max = Math.floor(max); + return Math.floor(Math.random() * (max - min + 1) + min); +}; + +var getImageData2 = HTMLCanvasElement.prototype.toDataURL; +// + +HTMLCanvasElement.prototype.toDataURL = function () { + // console.log("当前data"); + // console.log(this.width); + // console.log(this.height); + this.width = this.width + getRandomIntInclusive(-5, 5); + this.height = this.height + getRandomIntInclusive(-5, 5); + return getImageData2.apply(this); +}; + diff --git a/reverse-life/pjstealth/js/chrome.clientrectfeature.js b/reverse-life/pjstealth/js/chrome.clientrectfeature.js new file mode 100644 index 0000000..b6cd9da --- /dev/null +++ b/reverse-life/pjstealth/js/chrome.clientrectfeature.js @@ -0,0 +1,57 @@ +var rand = { + "noise": function () { + var SIGN = Math.random() < Math.random() ? -1 : 1; + return Math.floor(Math.random() + SIGN * Math.random()); + }, + "sign": function () { + const tmp = [-1, -1, -1, -1, -1, -1, +1, -1, -1, -1]; + const index = Math.floor(Math.random() * tmp.length); + return tmp[index]; + }, + 'get': function (e, fun, name, index) { + let d + try { + if (typeof index == "undefined") { + d = Math.floor(e[fun]()[name]); + } else { + d = Math.floor(e[fun]()[index][name]); + } + + } catch (e) { + console.log(e) + } + const valid = d && rand.sign() === 1; + const result = valid ? d + rand.noise() : d; + return result; + }, + 'value': function (d) { + + const valid = d && rand.sign() === 1; + const result = valid ? d + rand.noise() : d; + return result; + } +}; +Object.defineProperty(HTMLElement.prototype, "getBoundingClientRect", { + value() { + let rects = this.getClientRects()[0]; + console.log(rects) + let _rects = JSON.parse(JSON.stringify(rects)); + let _json = {} + for (let k in _rects) { + let d = rand.value(_rects[k]) + // console.log(k,d) + _json[k] = d + Object.defineProperty(rects.__proto__, k, { + get() { + return d + } + }) + } + Object.defineProperty(rects.__proto__, "toJSON", { + value() { + return _json + } + }) + return rects; + }, +}); diff --git a/reverse-life/pjstealth/js/chrome.csi.js b/reverse-life/pjstealth/js/chrome.csi.js new file mode 100644 index 0000000..388e39f --- /dev/null +++ b/reverse-life/pjstealth/js/chrome.csi.js @@ -0,0 +1,27 @@ +if (!window.chrome) { + // Use the exact property descriptor found in headful Chrome + // fetch it via `Object.getOwnPropertyDescriptor(window, 'chrome')` + Object.defineProperty(window, 'chrome', { + writable: true, + enumerable: true, + configurable: false, // note! + value: {} // We'll extend that later + }) +} + +// Check if we're running headful and don't need to mock anything +// Check that the Navigation Timing API v1 is available, we need that +if (!('csi' in window.chrome) && (window.performance || window.performance.timing)) { + const {csi_timing} = window.performance + + log.info('loading chrome.csi.js') + window.chrome.csi = function () { + return { + onloadT: csi_timing.domContentLoadedEventEnd, + startE: csi_timing.navigationStart, + pageT: Date.now() - csi_timing.navigationStart, + tran: 15 // Transition type or something + } + } + utils.patchToString(window.chrome.csi) +} \ No newline at end of file diff --git a/reverse-life/pjstealth/js/chrome.cssfeature.js b/reverse-life/pjstealth/js/chrome.cssfeature.js new file mode 100644 index 0000000..0c9174d --- /dev/null +++ b/reverse-life/pjstealth/js/chrome.cssfeature.js @@ -0,0 +1,62 @@ +var obj_get_computed_style = getComputedStyle; +getComputedStyle = function(data){ + if (data.style.backgroundColor == "activeborder"){ + var tmp = ["rgb(118, 118, 118)", "rgb(0, 0, 0)"] + data.style.backgroundColor = tmp[Math.floor(Math.random() * tmp.length)]; + } + if (data.style.backgroundColor == "activetext"){ + var tmp = ["rgb(255, 0, 0)", "rgb(0, 102, 204)"] + data.style.backgroundColor = tmp[Math.floor(Math.random() * tmp.length)]; + } + if (data.style.backgroundColor == "graytext"){ + var tmp = ["rgb(128, 128, 128)", "rgb(109, 109, 109)"] + data.style.backgroundColor = tmp[Math.floor(Math.random() * tmp.length)]; + } + if (data.style.backgroundColor == "graytext"){ + var tmp = ["rgb(128, 128, 128)", "rgb(109, 109, 109)"] + data.style.backgroundColor = tmp[Math.floor(Math.random() * tmp.length)]; + } + if (data.style.backgroundColor == "highlight"){ + var tmp = ["rgb(181, 213, 255)", "rgb(51, 153, 255)"] + data.style.backgroundColor = tmp[Math.floor(Math.random() * tmp.length)]; + } + if (data.style.backgroundColor == "highlighttext"){ + var tmp = ["rgb(0, 0, 0)", "rgb(255, 255, 255)"] + data.style.backgroundColor = tmp[Math.floor(Math.random() * tmp.length)]; + } + if (data.style.backgroundColor == "linktext"){ + var tmp = ["rgb(0, 0, 238)", "rgb(0, 102, 204)"] + data.style.backgroundColor = tmp[Math.floor(Math.random() * tmp.length)]; + } + if (data.style.backgroundColor == "threeddarkshadow"){ + var tmp = ["rgb(118, 118, 118)", "rgb(0, 0, 0)"] + data.style.backgroundColor = tmp[Math.floor(Math.random() * tmp.length)]; + } + if (data.style.backgroundColor == "threedface"){ + var tmp = ["rgb(239, 239, 239)", "rgb(240, 240, 240)"] + data.style.backgroundColor = tmp[Math.floor(Math.random() * tmp.length)]; + } + if (data.style.backgroundColor == "threedhighlight"){ + var tmp = ["rgb(0, 0, 0)", "rgb(118, 118, 118)"] + data.style.backgroundColor = tmp[Math.floor(Math.random() * tmp.length)]; + } + if (data.style.backgroundColor == "threedlightshadow"){ + var tmp = ["rgb(118, 118, 118)", "rgb(0, 0, 0)"] + data.style.backgroundColor = tmp[Math.floor(Math.random() * tmp.length)]; + } + if (data.style.backgroundColor == "threedshadow"){ + var tmp = ["rgb(118, 118, 118)", "rgb(0, 0, 0)"] + data.style.backgroundColor = tmp[Math.floor(Math.random() * tmp.length)]; + } + if (data.style.backgroundColor == "visitedtext"){ + var tmp = ["rgb(85, 26, 139)", "rgb(0, 102, 204)"] + data.style.backgroundColor = tmp[Math.floor(Math.random() * tmp.length)]; + } + if (data.style.backgroundColor == "windowframe"){ + var tmp = ["rgb(118, 118, 118)", "rgb(0, 0, 0)"] + data.style.backgroundColor = tmp[Math.floor(Math.random() * tmp.length)]; + } + + var test_n = obj_get_computed_style(data) + return test_n +} \ No newline at end of file diff --git a/reverse-life/pjstealth/js/chrome.fontsfeature.js b/reverse-life/pjstealth/js/chrome.fontsfeature.js new file mode 100644 index 0000000..923fab5 --- /dev/null +++ b/reverse-life/pjstealth/js/chrome.fontsfeature.js @@ -0,0 +1,38 @@ +var rand = { + "noise": function () { + var SIGN = Math.random() < Math.random() ? -1 : 1; + return Math.floor(Math.random() + SIGN * Math.random()); + }, + "sign": function () { + const tmp = [-1, -1, -1, -1, -1, -1, +1, -1, -1, -1, -1, 1, -1, -1, -1, 1, -1, -1, -1]; + const index = Math.floor(Math.random() * tmp.length); + return tmp[index]; + } +}; +Object.defineProperty(HTMLElement.prototype, "offsetHeight", { + get() { + let height = 500; + try { + height = Math.floor(this.getBoundingClientRect().height); + } catch (e) { + height = Math.floor(this.clientHeight) + } + const valid = height && rand.sign() === 1; + const result = valid ? height + rand.noise() : height; + return result; + } +}); + +Object.defineProperty(HTMLElement.prototype, "offsetWidth", { + get() { + let width = 500; + try { + width = Math.floor(this.getBoundingClientRect().width); + } catch (e) { + width = Math.floor(this.clientWidth) + } + const valid = width && rand.sign() === 1; + const result = valid ? width + rand.noise() : width; + return result; + } +}); diff --git a/reverse-life/pjstealth/js/chrome.hairline.js b/reverse-life/pjstealth/js/chrome.hairline.js new file mode 100644 index 0000000..4a4a633 --- /dev/null +++ b/reverse-life/pjstealth/js/chrome.hairline.js @@ -0,0 +1,14 @@ +// https://intoli.com/blog/making-chrome-headless-undetectable/ +// store the existing descriptor +const elementDescriptor = Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'offsetHeight'); + +// redefine the property with a patched descriptor +Object.defineProperty(HTMLDivElement.prototype, 'offsetHeight', { + ...elementDescriptor, + get: function() { + if (this.id === 'modernizr') { + return 1; + } + return elementDescriptor.get.apply(this); + }, +}); \ No newline at end of file diff --git a/reverse-life/pjstealth/js/chrome.load.times.js b/reverse-life/pjstealth/js/chrome.load.times.js new file mode 100644 index 0000000..daf1ba0 --- /dev/null +++ b/reverse-life/pjstealth/js/chrome.load.times.js @@ -0,0 +1,123 @@ +if (!window.chrome) { + // Use the exact property descriptor found in headful Chrome + // fetch it via `Object.getOwnPropertyDescriptor(window, 'chrome')` + Object.defineProperty(window, 'chrome', { + writable: true, + enumerable: true, + configurable: false, // note! + value: {} // We'll extend that later + }) +} + +// That means we're running headful and don't need to mock anything +if ('loadTimes' in window.chrome) { + console.log("处于有头模式中....") +} else { + // Check that the Navigation Timing API v1 + v2 is available, we need that + if ( + window.performance || + window.performance.timing || + window.PerformancePaintTiming + ) { + + const {performance} = window + + // Some stuff is not available on about:blank as it requires a navigation to occur, + // let's harden the code to not fail then: + const ntEntryFallback = { + nextHopProtocol: 'h2', + type: 'other' + } + + // The API exposes some funky info regarding the connection + const protocolInfo = { + get connectionInfo() { + const ntEntry = + performance.getEntriesByType('navigation')[0] || ntEntryFallback + return ntEntry.nextHopProtocol + }, + get npnNegotiatedProtocol() { + // NPN is deprecated in favor of ALPN, but this implementation returns the + // HTTP/2 or HTTP2+QUIC/39 requests negotiated via ALPN. + const ntEntry = + performance.getEntriesByType('navigation')[0] || ntEntryFallback + return ['h2', 'hq'].includes(ntEntry.nextHopProtocol) + ? ntEntry.nextHopProtocol + : 'unknown' + }, + get navigationType() { + const ntEntry = + performance.getEntriesByType('navigation')[0] || ntEntryFallback + return ntEntry.type + }, + get wasAlternateProtocolAvailable() { + // The Alternate-Protocol header is deprecated in favor of Alt-Svc + // (https://www.mnot.net/blog/2016/03/09/alt-svc), so technically this + // should always return false. + return false + }, + get wasFetchedViaSpdy() { + // SPDY is deprecated in favor of HTTP/2, but this implementation returns + // true for HTTP/2 or HTTP2+QUIC/39 as well. + const ntEntry = + performance.getEntriesByType('navigation')[0] || ntEntryFallback + return ['h2', 'hq'].includes(ntEntry.nextHopProtocol) + }, + get wasNpnNegotiated() { + // NPN is deprecated in favor of ALPN, but this implementation returns true + // for HTTP/2 or HTTP2+QUIC/39 requests negotiated via ALPN. + const ntEntry = + performance.getEntriesByType('navigation')[0] || ntEntryFallback + return ['h2', 'hq'].includes(ntEntry.nextHopProtocol) + } + } + + const {timing} = window.performance + +// Truncate number to specific number of decimals, most of the `loadTimes` stuff has 3 + function toFixed(num, fixed) { + var re = new RegExp('^-?\\d+(?:.\\d{0,' + (fixed || -1) + '})?') + return num.toString().match(re)[0] + } + + const timingInfo = { + get firstPaintAfterLoadTime() { + // This was never actually implemented and always returns 0. + return 0 + }, + get requestTime() { + return timing.navigationStart / 1000 + }, + get startLoadTime() { + return timing.navigationStart / 1000 + }, + get commitLoadTime() { + return timing.responseStart / 1000 + }, + get finishDocumentLoadTime() { + return timing.domContentLoadedEventEnd / 1000 + }, + get finishLoadTime() { + return timing.loadEventEnd / 1000 + }, + get firstPaintTime() { + const fpEntry = performance.getEntriesByType('paint')[0] || { + startTime: timing.loadEventEnd / 1000 // Fallback if no navigation occured (`about:blank`) + } + return toFixed( + (fpEntry.startTime + performance.timeOrigin) / 1000, + 3 + ) + } + } + + window.chrome.loadTimes = function () { + return { + ...protocolInfo, + ...timingInfo + } + } + utils.patchToString(window.chrome.loadTimes) + } +} + diff --git a/reverse-life/pjstealth/js/chrome.runtime.js b/reverse-life/pjstealth/js/chrome.runtime.js new file mode 100644 index 0000000..b47b37e --- /dev/null +++ b/reverse-life/pjstealth/js/chrome.runtime.js @@ -0,0 +1,262 @@ +const STATIC_DATA = { + "OnInstalledReason": { + "CHROME_UPDATE": "chrome_update", + "INSTALL": "install", + "SHARED_MODULE_UPDATE": "shared_module_update", + "UPDATE": "update" + }, + "OnRestartRequiredReason": { + "APP_UPDATE": "app_update", + "OS_UPDATE": "os_update", + "PERIODIC": "periodic" + }, + "PlatformArch": { + "ARM": "arm", + "ARM64": "arm64", + "MIPS": "mips", + "MIPS64": "mips64", + "X86_32": "x86-32", + "X86_64": "x86-64" + }, + "PlatformNaclArch": { + "ARM": "arm", + "MIPS": "mips", + "MIPS64": "mips64", + "X86_32": "x86-32", + "X86_64": "x86-64" + }, + "PlatformOs": { + "ANDROID": "android", + "CROS": "cros", + "LINUX": "linux", + "MAC": "mac", + "OPENBSD": "openbsd", + "WIN": "win" + }, + "RequestUpdateCheckStatus": { + "NO_UPDATE": "no_update", + "THROTTLED": "throttled", + "UPDATE_AVAILABLE": "update_available" + } +} + +if (!window.chrome) { + // Use the exact property descriptor found in headful Chrome + // fetch it via `Object.getOwnPropertyDescriptor(window, 'chrome')` + Object.defineProperty(window, 'chrome', { + writable: true, + enumerable: true, + configurable: false, // note! + value: {} // We'll extend that later + }) +} + +// That means we're running headfull and don't need to mock anything +const existsAlready = 'runtime' in window.chrome +// `chrome.runtime` is only exposed on secure origins +const isNotSecure = !window.location.protocol.startsWith('https') +if (!(existsAlready || (isNotSecure && !opts.runOnInsecureOrigins))) { + window.chrome.runtime = { + // There's a bunch of static data in that property which doesn't seem to change, + // we should periodically check for updates: `JSON.stringify(window.chrome.runtime, null, 2)` + ...STATIC_DATA, + // `chrome.runtime.id` is extension related and returns undefined in Chrome + get id() { + return undefined + }, + // These two require more sophisticated mocks + connect: null, + sendMessage: null + } + + const makeCustomRuntimeErrors = (preamble, method, extensionId) => ({ + NoMatchingSignature: new TypeError( + preamble + `No matching signature.` + ), + MustSpecifyExtensionID: new TypeError( + preamble + + `${method} called from a webpage must specify an Extension ID (string) for its first argument.` + ), + InvalidExtensionID: new TypeError( + preamble + `Invalid extension id: '${extensionId}'` + ) + }) + + // Valid Extension IDs are 32 characters in length and use the letter `a` to `p`: + // https://source.chromium.org/chromium/chromium/src/+/master:components/crx_file/id_util.cc;drc=14a055ccb17e8c8d5d437fe080faba4c6f07beac;l=90 + const isValidExtensionID = str => + str.length === 32 && str.toLowerCase().match(/^[a-p]+$/) + + /** Mock `chrome.runtime.sendMessage` */ + const sendMessageHandler = { + apply: function (target, ctx, args) { + const [extensionId, options, responseCallback] = args || [] + + // Define custom errors + const errorPreamble = `Error in invocation of runtime.sendMessage(optional string extensionId, any message, optional object options, optional function responseCallback): ` + const Errors = makeCustomRuntimeErrors( + errorPreamble, + `chrome.runtime.sendMessage()`, + extensionId + ) + + // Check if the call signature looks ok + const noArguments = args.length === 0 + const tooManyArguments = args.length > 4 + const incorrectOptions = options && typeof options !== 'object' + const incorrectResponseCallback = + responseCallback && typeof responseCallback !== 'function' + if ( + noArguments || + tooManyArguments || + incorrectOptions || + incorrectResponseCallback + ) { + throw Errors.NoMatchingSignature + } + + // At least 2 arguments are required before we even validate the extension ID + if (args.length < 2) { + throw Errors.MustSpecifyExtensionID + } + + // Now let's make sure we got a string as extension ID + if (typeof extensionId !== 'string') { + throw Errors.NoMatchingSignature + } + + if (!isValidExtensionID(extensionId)) { + throw Errors.InvalidExtensionID + } + + return undefined // Normal behavior + } + } + utils.mockWithProxy( + window.chrome.runtime, + 'sendMessage', + function sendMessage() { + }, + sendMessageHandler + ) + + /** + * Mock `chrome.runtime.connect` + * + * @see https://developer.chrome.com/apps/runtime#method-connect + */ + const connectHandler = { + apply: function (target, ctx, args) { + const [extensionId, connectInfo] = args || [] + + // Define custom errors + const errorPreamble = `Error in invocation of runtime.connect(optional string extensionId, optional object connectInfo): ` + const Errors = makeCustomRuntimeErrors( + errorPreamble, + `chrome.runtime.connect()`, + extensionId + ) + + // Behavior differs a bit from sendMessage: + const noArguments = args.length === 0 + const emptyStringArgument = args.length === 1 && extensionId === '' + if (noArguments || emptyStringArgument) { + throw Errors.MustSpecifyExtensionID + } + + const tooManyArguments = args.length > 2 + const incorrectConnectInfoType = + connectInfo && typeof connectInfo !== 'object' + + if (tooManyArguments || incorrectConnectInfoType) { + throw Errors.NoMatchingSignature + } + + const extensionIdIsString = typeof extensionId === 'string' + if (extensionIdIsString && extensionId === '') { + throw Errors.MustSpecifyExtensionID + } + if (extensionIdIsString && !isValidExtensionID(extensionId)) { + throw Errors.InvalidExtensionID + } + + // There's another edge-case here: extensionId is optional so we might find a connectInfo object as first param, which we need to validate + const validateConnectInfo = ci => { + // More than a first param connectInfo as been provided + if (args.length > 1) { + throw Errors.NoMatchingSignature + } + // An empty connectInfo has been provided + if (Object.keys(ci).length === 0) { + throw Errors.MustSpecifyExtensionID + } + // Loop over all connectInfo props an check them + Object.entries(ci).forEach(([k, v]) => { + const isExpected = ['name', 'includeTlsChannelId'].includes(k) + if (!isExpected) { + throw new TypeError( + errorPreamble + `Unexpected property: '${k}'.` + ) + } + const MismatchError = (propName, expected, found) => + TypeError( + errorPreamble + + `Error at property '${propName}': Invalid type: expected ${expected}, found ${found}.` + ) + if (k === 'name' && typeof v !== 'string') { + throw MismatchError(k, 'string', typeof v) + } + if (k === 'includeTlsChannelId' && typeof v !== 'boolean') { + throw MismatchError(k, 'boolean', typeof v) + } + }) + } + if (typeof extensionId === 'object') { + validateConnectInfo(extensionId) + throw Errors.MustSpecifyExtensionID + } + + // Unfortunately even when the connect fails Chrome will return an object with methods we need to mock as well + return utils.patchToStringNested(makeConnectResponse()) + } + } + utils.mockWithProxy( + window.chrome.runtime, + 'connect', + function connect() { + }, + connectHandler + ) + + function makeConnectResponse() { + const onSomething = () => ({ + addListener: function addListener() { + }, + dispatch: function dispatch() { + }, + hasListener: function hasListener() { + }, + hasListeners: function hasListeners() { + return false + }, + removeListener: function removeListener() { + } + }) + + const response = { + name: '', + sender: undefined, + disconnect: function disconnect() { + }, + onDisconnect: onSomething(), + onMessage: onSomething(), + postMessage: function postMessage() { + if (!arguments.length) { + throw new TypeError(`Insufficient number of arguments.`) + } + throw new Error(`Attempting to use a disconnected port object`) + } + } + return response + } +} diff --git a/reverse-life/pjstealth/js/chrome.videofeature.js b/reverse-life/pjstealth/js/chrome.videofeature.js new file mode 100644 index 0000000..f10cc87 --- /dev/null +++ b/reverse-life/pjstealth/js/chrome.videofeature.js @@ -0,0 +1,16 @@ +const getChannelData = AudioBuffer.prototype.getChannelData; +Object.defineProperty(AudioBuffer.prototype, "getChannelData", { + "value": function () { + const results_1 = getChannelData.apply(this, arguments); + // if (context.BUFFER !== results_1) { + // context.BUFFER = results_1; + window.top.postMessage("audiocontext-fingerprint-defender-alert", '*'); + for (var i = 0; i < results_1.length; i += 100) { + let index = Math.floor(Math.random() * i); + results_1[index] = results_1[index] + Math.random() * 0.0000001; + // } + } + // + return results_1; + } +}); diff --git a/reverse-life/pjstealth/js/chrome.webrtc.js b/reverse-life/pjstealth/js/chrome.webrtc.js new file mode 100644 index 0000000..4888b78 --- /dev/null +++ b/reverse-life/pjstealth/js/chrome.webrtc.js @@ -0,0 +1,6 @@ +var test_rpc_peer_connection = window.RTCPeerConnection; +window.RTCPeerConnection = function (a1, a2) { +} +var test_rpc_peer_webkit = window.webkitRTCPeerConnection; +window.webkitRTCPeerConnection = function (a1, a2) { +} \ No newline at end of file diff --git a/reverse-life/pjstealth/js/generate.magic.arrays.js b/reverse-life/pjstealth/js/generate.magic.arrays.js new file mode 100644 index 0000000..b9c2811 --- /dev/null +++ b/reverse-life/pjstealth/js/generate.magic.arrays.js @@ -0,0 +1,142 @@ +generateFunctionMocks = ( + proto, + itemMainProp, + dataArray +) => ({ + item: utils.createProxy(proto.item, { + apply(target, ctx, args) { + if (!args.length) { + throw new TypeError( + `Failed to execute 'item' on '${ + proto[Symbol.toStringTag] + }': 1 argument required, but only 0 present.` + ) + } + // Special behavior alert: + // - Vanilla tries to cast strings to Numbers (only integers!) and use them as property index lookup + // - If anything else than an integer (including as string) is provided it will return the first entry + const isInteger = args[0] && Number.isInteger(Number(args[0])) // Cast potential string to number first, then check for integer + // Note: Vanilla never returns `undefined` + return (isInteger ? dataArray[Number(args[0])] : dataArray[0]) || null + } + }), + /** Returns the MimeType object with the specified name. */ + namedItem: utils.createProxy(proto.namedItem, { + apply(target, ctx, args) { + if (!args.length) { + throw new TypeError( + `Failed to execute 'namedItem' on '${ + proto[Symbol.toStringTag] + }': 1 argument required, but only 0 present.` + ) + } + return dataArray.find(mt => mt[itemMainProp] === args[0]) || null // Not `undefined`! + } + }), + /** Does nothing and shall return nothing */ + refresh: proto.refresh + ? utils.createProxy(proto.refresh, { + apply(target, ctx, args) { + return undefined + } + }) + : undefined +}) + +function generateMagicArray( + dataArray = [], + proto = MimeTypeArray.prototype, + itemProto = MimeType.prototype, + itemMainProp = 'type' +) { + // Quick helper to set props with the same descriptors vanilla is using + const defineProp = (obj, prop, value) => + Object.defineProperty(obj, prop, { + value, + writable: false, + enumerable: false, // Important for mimeTypes & plugins: `JSON.stringify(navigator.mimeTypes)` + configurable: false + }) + + // Loop over our fake data and construct items + const makeItem = data => { + const item = {} + for (const prop of Object.keys(data)) { + if (prop.startsWith('__')) { + continue + } + defineProp(item, prop, data[prop]) + } + // navigator.plugins[i].length should always be 1 + if (itemProto === Plugin.prototype) { + defineProp(item, 'length', 1) + } + // We need to spoof a specific `MimeType` or `Plugin` object + return Object.create(itemProto, Object.getOwnPropertyDescriptors(item)) + } + + const magicArray = [] + + // Loop through our fake data and use that to create convincing entities + dataArray.forEach(data => { + magicArray.push(makeItem(data)) + }) + + // Add direct property access based on types (e.g. `obj['application/pdf']`) afterwards + magicArray.forEach(entry => { + defineProp(magicArray, entry[itemMainProp], entry) + }) + + // This is the best way to fake the type to make sure this is false: `Array.isArray(navigator.mimeTypes)` + const magicArrayObj = Object.create(proto, { + ...Object.getOwnPropertyDescriptors(magicArray), + + // There's one ugly quirk we unfortunately need to take care of: + // The `MimeTypeArray` prototype has an enumerable `length` property, + // but headful Chrome will still skip it when running `Object.getOwnPropertyNames(navigator.mimeTypes)`. + // To strip it we need to make it first `configurable` and can then overlay a Proxy with an `ownKeys` trap. + length: { + value: magicArray.length, + writable: false, + enumerable: false, + configurable: true // Important to be able to use the ownKeys trap in a Proxy to strip `length` + } + }) + + // Generate our functional function mocks :-) + const functionMocks = generateFunctionMocks( + proto, + itemMainProp, + magicArray + ) + + // Override custom object with proxy + return new Proxy(magicArrayObj, { + get(target, key = '') { + // Redirect function calls to our custom proxied versions mocking the vanilla behavior + if (key === 'item') { + return functionMocks.item + } + if (key === 'namedItem') { + return functionMocks.namedItem + } + if (proto === PluginArray.prototype && key === 'refresh') { + return functionMocks.refresh + } + // Everything else can pass through as normal + return utils.cache.Reflect.get(...arguments) + }, + ownKeys(target) { + // There are a couple of quirks where the original property demonstrates "magical" behavior that makes no sense + // This can be witnessed when calling `Object.getOwnPropertyNames(navigator.mimeTypes)` and the absense of `length` + // My guess is that it has to do with the recent change of not allowing data enumeration and this being implemented weirdly + // For that reason we just completely fake the available property names based on our data to match what regular Chrome is doing + // Specific issues when not patching this: `length` property is available, direct `types` props (e.g. `obj['application/pdf']`) are missing + const keys = [] + const typeProps = magicArray.map(mt => mt[itemMainProp]) + typeProps.forEach((_, i) => keys.push(`${i}`)) + typeProps.forEach(propName => keys.push(propName)) + return keys + } + }) +} \ No newline at end of file diff --git a/reverse-life/pjstealth/js/hookfuc.headless.js b/reverse-life/pjstealth/js/hookfuc.headless.js new file mode 100644 index 0000000..eb7f539 --- /dev/null +++ b/reverse-life/pjstealth/js/hookfuc.headless.js @@ -0,0 +1,18 @@ +var obj_get_own_p_n = Object.getOwnPropertyNames; +Object.getOwnPropertyNames = function(u) { + if (u === window) { + return ["Object", "Function", "Array", "Number", "parseFloat", "parseInt", "Infinity", "NaN", "undefined", "Boolean", "String", "Symbol", "Date", "Promise", "RegExp", "Error", "AggregateError", "EvalError", "RangeError", "ReferenceError", "SyntaxError", "TypeError", "URIError", "globalThis", "JSON", "Math", "Intl", "ArrayBuffer", "Uint8Array", "Int8Array", "Uint16Array", "Int16Array", "Uint32Array", "Int32Array", "Float32Array", "Float64Array", "Uint8ClampedArray", "BigUint64Array", "BigInt64Array", "DataView", "Map", "BigInt", "Set", "WeakMap", "WeakSet", "Proxy", "Reflect", "FinalizationRegistry", "WeakRef", "decodeURI", "decodeURIComponent", "encodeURI", "encodeURIComponent", "escape", "unescape", "eval", "isFinite", "isNaN", "console", "Option", "Image", "Audio", "webkitURL", "webkitRTCPeerConnection", "webkitMediaStream", "WebKitMutationObserver", "WebKitCSSMatrix", "XSLTProcessor", "XPathResult", "XPathExpression", "XPathEvaluator", "XMLSerializer", "XMLHttpRequestUpload", "XMLHttpRequestEventTarget", "XMLHttpRequest", "XMLDocument", "WritableStreamDefaultWriter", "WritableStreamDefaultController", "WritableStream", "Worker", "Window", "WheelEvent", "WebSocket", "WebGLVertexArrayObject", "WebGLUniformLocation", "WebGLTransformFeedback", "WebGLTexture", "WebGLSync", "WebGLShaderPrecisionFormat", "WebGLShader", "WebGLSampler", "WebGLRenderingContext", "WebGLRenderbuffer", "WebGLQuery", "WebGLProgram", "WebGLFramebuffer", "WebGLContextEvent", "WebGLBuffer", "WebGLActiveInfo", "WebGL2RenderingContext", "WaveShaperNode", "VisualViewport", "VirtualKeyboardGeometryChangeEvent", "ValidityState", "VTTCue", "UserActivation", "URLSearchParams", "URLPattern", "URL", "UIEvent", "TrustedTypePolicyFactory", "TrustedTypePolicy", "TrustedScriptURL", "TrustedScript", "TrustedHTML", "TreeWalker", "TransitionEvent", "TransformStreamDefaultController", "TransformStream", "TrackEvent", "TouchList", "TouchEvent", "Touch", "TimeRanges", "TextTrackList", "TextTrackCueList", "TextTrackCue", "TextTrack", "TextMetrics", "TextEvent", "TextEncoderStream", "TextEncoder", "TextDecoderStream", "TextDecoder", "Text", "TaskSignal", "TaskPriorityChangeEvent", "TaskController", "TaskAttributionTiming", "SyncManager", "SubmitEvent", "StyleSheetList", "StyleSheet", "StylePropertyMapReadOnly", "StylePropertyMap", "StorageEvent", "Storage", "StereoPannerNode", "StaticRange", "ShadowRoot", "Selection", "SecurityPolicyViolationEvent", "ScriptProcessorNode", "ScreenOrientation", "Screen", "Scheduling", "Scheduler", "SVGViewElement", "SVGUseElement", "SVGUnitTypes", "SVGTransformList", "SVGTransform", "SVGTitleElement", "SVGTextPositioningElement", "SVGTextPathElement", "SVGTextElement", "SVGTextContentElement", "SVGTSpanElement", "SVGSymbolElement", "SVGSwitchElement", "SVGStyleElement", "SVGStringList", "SVGStopElement", "SVGSetElement", "SVGScriptElement", "SVGSVGElement", "SVGRectElement", "SVGRect", "SVGRadialGradientElement", "SVGPreserveAspectRatio", "SVGPolylineElement", "SVGPolygonElement", "SVGPointList", "SVGPoint", "SVGPatternElement", "SVGPathElement", "SVGNumberList", "SVGNumber", "SVGMetadataElement", "SVGMatrix", "SVGMaskElement", "SVGMarkerElement", "SVGMPathElement", "SVGLinearGradientElement", "SVGLineElement", "SVGLengthList", "SVGLength", "SVGImageElement", "SVGGraphicsElement", "SVGGradientElement", "SVGGeometryElement", "SVGGElement", "SVGForeignObjectElement", "SVGFilterElement", "SVGFETurbulenceElement", "SVGFETileElement", "SVGFESpotLightElement", "SVGFESpecularLightingElement", "SVGFEPointLightElement", "SVGFEOffsetElement", "SVGFEMorphologyElement", "SVGFEMergeNodeElement", "SVGFEMergeElement", "SVGFEImageElement", "SVGFEGaussianBlurElement", "SVGFEFuncRElement", "SVGFEFuncGElement", "SVGFEFuncBElement", "SVGFEFuncAElement", "SVGFEFloodElement", "SVGFEDropShadowElement", "SVGFEDistantLightElement", "SVGFEDisplacementMapElement", "SVGFEDiffuseLightingElement", "SVGFEConvolveMatrixElement", "SVGFECompositeElement", "SVGFEComponentTransferElement", "SVGFEColorMatrixElement", "SVGFEBlendElement", "SVGEllipseElement", "SVGElement", "SVGDescElement", "SVGDefsElement", "SVGComponentTransferFunctionElement", "SVGClipPathElement", "SVGCircleElement", "SVGAnimationElement", "SVGAnimatedTransformList", "SVGAnimatedString", "SVGAnimatedRect", "SVGAnimatedPreserveAspectRatio", "SVGAnimatedNumberList", "SVGAnimatedNumber", "SVGAnimatedLengthList", "SVGAnimatedLength", "SVGAnimatedInteger", "SVGAnimatedEnumeration", "SVGAnimatedBoolean", "SVGAnimatedAngle", "SVGAnimateTransformElement", "SVGAnimateMotionElement", "SVGAnimateElement", "SVGAngle", "SVGAElement", "Response", "ResizeObserverSize", "ResizeObserverEntry", "ResizeObserver", "Request", "ReportingObserver", "ReadableStreamDefaultReader", "ReadableStreamDefaultController", "ReadableStreamBYOBRequest", "ReadableStreamBYOBReader", "ReadableStream", "ReadableByteStreamController", "Range", "RadioNodeList", "RTCTrackEvent", "RTCStatsReport", "RTCSessionDescription", "RTCSctpTransport", "RTCRtpTransceiver", "RTCRtpSender", "RTCRtpReceiver", "RTCPeerConnectionIceEvent", "RTCPeerConnectionIceErrorEvent", "RTCPeerConnection", "RTCIceTransport", "RTCIceCandidate", "RTCErrorEvent", "RTCError", "RTCEncodedVideoFrame", "RTCEncodedAudioFrame", "RTCDtlsTransport", "RTCDataChannelEvent", "RTCDataChannel", "RTCDTMFToneChangeEvent", "RTCDTMFSender", "RTCCertificate", "PromiseRejectionEvent", "ProgressEvent", "Profiler", "ProcessingInstruction", "PopStateEvent", "PointerEvent", "PluginArray", "Plugin", "PeriodicWave", "PerformanceTiming", "PerformanceServerTiming", "PerformanceResourceTiming", "PerformancePaintTiming", "PerformanceObserverEntryList", "PerformanceObserver", "PerformanceNavigationTiming", "PerformanceNavigation", "PerformanceMeasure", "PerformanceMark", "PerformanceLongTaskTiming", "PerformanceEventTiming", "PerformanceEntry", "PerformanceElementTiming", "Performance", "Path2D", "PannerNode", "PageTransitionEvent", "OverconstrainedError", "OscillatorNode", "OffscreenCanvasRenderingContext2D", "OffscreenCanvas", "OfflineAudioContext", "OfflineAudioCompletionEvent", "NodeList", "NodeIterator", "NodeFilter", "Node", "NetworkInformation", "Navigator", "NamedNodeMap", "MutationRecord", "MutationObserver", "MutationEvent", "MouseEvent", "MimeTypeArray", "MimeType", "MessagePort", "MessageEvent", "MessageChannel", "MediaStreamTrackProcessor", "MediaStreamTrackEvent", "MediaStreamEvent", "MediaStreamAudioSourceNode", "MediaStreamAudioDestinationNode", "MediaStream", "MediaRecorder", "MediaQueryListEvent", "MediaQueryList", "MediaList", "MediaError", "MediaEncryptedEvent", "MediaElementAudioSourceNode", "MediaCapabilities", "Location", "LayoutShiftAttribution", "LayoutShift", "LargestContentfulPaint", "KeyframeEffect", "KeyboardEvent", "IntersectionObserverEntry", "IntersectionObserver", "InputEvent", "InputDeviceInfo", "InputDeviceCapabilities", "ImageData", "ImageCapture", "ImageBitmapRenderingContext", "ImageBitmap", "IdleDeadline", "IIRFilterNode", "IDBVersionChangeEvent", "IDBTransaction", "IDBRequest", "IDBOpenDBRequest", "IDBObjectStore", "IDBKeyRange", "IDBIndex", "IDBFactory", "IDBDatabase", "IDBCursorWithValue", "IDBCursor", "History", "Headers", "HashChangeEvent", "HTMLVideoElement", "HTMLUnknownElement", "HTMLUListElement", "HTMLTrackElement", "HTMLTitleElement", "HTMLTimeElement", "HTMLTextAreaElement", "HTMLTemplateElement", "HTMLTableSectionElement", "HTMLTableRowElement", "HTMLTableElement", "HTMLTableColElement", "HTMLTableCellElement", "HTMLTableCaptionElement", "HTMLStyleElement", "HTMLSpanElement", "HTMLSourceElement", "HTMLSlotElement", "HTMLSelectElement", "HTMLScriptElement", "HTMLQuoteElement", "HTMLProgressElement", "HTMLPreElement", "HTMLPictureElement", "HTMLParamElement", "HTMLParagraphElement", "HTMLOutputElement", "HTMLOptionsCollection", "HTMLOptionElement", "HTMLOptGroupElement", "HTMLObjectElement", "HTMLOListElement", "HTMLModElement", "HTMLMeterElement", "HTMLMetaElement", "HTMLMenuElement", "HTMLMediaElement", "HTMLMarqueeElement", "HTMLMapElement", "HTMLLinkElement", "HTMLLegendElement", "HTMLLabelElement", "HTMLLIElement", "HTMLInputElement", "HTMLImageElement", "HTMLIFrameElement", "HTMLHtmlElement", "HTMLHeadingElement", "HTMLHeadElement", "HTMLHRElement", "HTMLFrameSetElement", "HTMLFrameElement", "HTMLFormElement", "HTMLFormControlsCollection", "HTMLFontElement", "HTMLFieldSetElement", "HTMLEmbedElement", "HTMLElement", "HTMLDocument", "HTMLDivElement", "HTMLDirectoryElement", "HTMLDialogElement", "HTMLDetailsElement", "HTMLDataListElement", "HTMLDataElement", "HTMLDListElement", "HTMLCollection", "HTMLCanvasElement", "HTMLButtonElement", "HTMLBodyElement", "HTMLBaseElement", "HTMLBRElement", "HTMLAudioElement", "HTMLAreaElement", "HTMLAnchorElement", "HTMLAllCollection", "GeolocationPositionError", "GeolocationPosition", "GeolocationCoordinates", "Geolocation", "GamepadHapticActuator", "GamepadEvent", "GamepadButton", "Gamepad", "GainNode", "FormDataEvent", "FormData", "FontFaceSetLoadEvent", "FontFace", "FocusEvent", "FileReader", "FileList", "File", "FeaturePolicy", "External", "EventTarget", "EventSource", "EventCounts", "Event", "ErrorEvent", "ElementInternals", "Element", "DynamicsCompressorNode", "DragEvent", "DocumentType", "DocumentFragment", "Document", "DelayNode", "DecompressionStream", "DataTransferItemList", "DataTransferItem", "DataTransfer", "DOMTokenList", "DOMStringMap", "DOMStringList", "DOMRectReadOnly", "DOMRectList", "DOMRect", "DOMQuad", "DOMPointReadOnly", "DOMPoint", "DOMParser", "DOMMatrixReadOnly", "DOMMatrix", "DOMImplementation", "DOMException", "DOMError", "CustomStateSet", "CustomEvent", "CustomElementRegistry", "Crypto", "CountQueuingStrategy", "ConvolverNode", "ConstantSourceNode", "CompressionStream", "CompositionEvent", "Comment", "CloseEvent", "ClipboardEvent", "CharacterData", "ChannelSplitterNode", "ChannelMergerNode", "CanvasRenderingContext2D", "CanvasPattern", "CanvasGradient", "CanvasFilter", "CanvasCaptureMediaStreamTrack", "CSSVariableReferenceValue", "CSSUnparsedValue", "CSSUnitValue", "CSSTranslate", "CSSTransformValue", "CSSTransformComponent", "CSSSupportsRule", "CSSStyleValue", "CSSStyleSheet", "CSSStyleRule", "CSSStyleDeclaration", "CSSSkewY", "CSSSkewX", "CSSSkew", "CSSScale", "CSSRuleList", "CSSRule", "CSSRotate", "CSSPropertyRule", "CSSPositionValue", "CSSPerspective", "CSSPageRule", "CSSNumericValue", "CSSNumericArray", "CSSNamespaceRule", "CSSMediaRule", "CSSMatrixComponent", "CSSMathValue", "CSSMathSum", "CSSMathProduct", "CSSMathNegate", "CSSMathMin", "CSSMathMax", "CSSMathInvert", "CSSMathClamp", "CSSLayerStatementRule", "CSSLayerBlockRule", "CSSKeywordValue", "CSSKeyframesRule", "CSSKeyframeRule", "CSSImportRule", "CSSImageValue", "CSSGroupingRule", "CSSFontFaceRule", "CSSCounterStyleRule", "CSSConditionRule", "CSS", "CDATASection", "ByteLengthQueuingStrategy", "BroadcastChannel", "BlobEvent", "Blob", "BiquadFilterNode", "BeforeUnloadEvent", "BeforeInstallPromptEvent", "BaseAudioContext", "BarProp", "AudioWorkletNode", "AudioScheduledSourceNode", "AudioProcessingEvent", "AudioParamMap", "AudioParam", "AudioNode", "AudioListener", "AudioDestinationNode", "AudioContext", "AudioBufferSourceNode", "AudioBuffer", "Attr", "AnimationEvent", "AnimationEffect", "Animation", "AnalyserNode", "AbstractRange", "AbortSignal", "AbortController", "window", "self", "document", "name", "location", "customElements", "history", "locationbar", "menubar", "personalbar", "scrollbars", "statusbar", "toolbar", "status", "closed", "frames", "length", "top", "opener", "parent", "frameElement", "navigator", "origin", "external", "screen", "innerWidth", "innerHeight", "scrollX", "pageXOffset", "scrollY", "pageYOffset", "visualViewport", "screenX", "screenY", "outerWidth", "outerHeight", "devicePixelRatio", "event", "clientInformation", "offscreenBuffering", "screenLeft", "screenTop", "styleMedia", "onsearch", "isSecureContext", "trustedTypes", "performance", "onappinstalled", "onbeforeinstallprompt", "crypto", "indexedDB", "sessionStorage", "localStorage", "onbeforexrselect", "onabort", "onbeforeinput", "onblur", "oncancel", "oncanplay", "oncanplaythrough", "onchange", "onclick", "onclose", "oncontextlost", "oncontextmenu", "oncontextrestored", "oncuechange", "ondblclick", "ondrag", "ondragend", "ondragenter", "ondragleave", "ondragover", "ondragstart", "ondrop", "ondurationchange", "onemptied", "onended", "onerror", "onfocus", "onformdata", "oninput", "oninvalid", "onkeydown", "onkeypress", "onkeyup", "onload", "onloadeddata", "onloadedmetadata", "onloadstart", "onmousedown", "onmouseenter", "onmouseleave", "onmousemove", "onmouseout", "onmouseover", "onmouseup", "onmousewheel", "onpause", "onplay", "onplaying", "onprogress", "onratechange", "onreset", "onresize", "onscroll", "onsecuritypolicyviolation", "onseeked", "onseeking", "onselect", "onslotchange", "onstalled", "onsubmit", "onsuspend", "ontimeupdate", "ontoggle", "onvolumechange", "onwaiting", "onwebkitanimationend", "onwebkitanimationiteration", "onwebkitanimationstart", "onwebkittransitionend", "onwheel", "onauxclick", "ongotpointercapture", "onlostpointercapture", "onpointerdown", "onpointermove", "onpointerrawupdate", "onpointerup", "onpointercancel", "onpointerover", "onpointerout", "onpointerenter", "onpointerleave", "onselectstart", "onselectionchange", "onanimationend", "onanimationiteration", "onanimationstart", "ontransitionrun", "ontransitionstart", "ontransitionend", "ontransitioncancel", "onafterprint", "onbeforeprint", "onbeforeunload", "onhashchange", "onlanguagechange", "onmessage", "onmessageerror", "onoffline", "ononline", "onpagehide", "onpageshow", "onpopstate", "onrejectionhandled", "onstorage", "onunhandledrejection", "onunload", "crossOriginIsolated", "scheduler", "alert", "atob", "blur", "btoa", "cancelAnimationFrame", "cancelIdleCallback", "captureEvents", "clearInterval", "clearTimeout", "close", "confirm", "createImageBitmap", "fetch", "find", "focus", "getComputedStyle", "getSelection", "matchMedia", "moveBy", "moveTo", "open", "postMessage", "print", "prompt", "queueMicrotask", "releaseEvents", "reportError", "requestAnimationFrame", "requestIdleCallback", "resizeBy", "resizeTo", "scroll", "scrollBy", "scrollTo", "setInterval", "setTimeout", "stop", "structuredClone", "webkitCancelAnimationFrame", "webkitRequestAnimationFrame", "Atomics", "chrome", "WebAssembly", "caches", "cookieStore", "ondevicemotion", "ondeviceorientation", "ondeviceorientationabsolute", "launchQueue", "onbeforematch", "AbsoluteOrientationSensor", "Accelerometer", "AudioWorklet", "BatteryManager", "Cache", "CacheStorage", "Clipboard", "ClipboardItem", "CookieChangeEvent", "CookieStore", "CookieStoreManager", "Credential", "CredentialsContainer", "CryptoKey", "DeviceMotionEvent", "DeviceMotionEventAcceleration", "DeviceMotionEventRotationRate", "DeviceOrientationEvent", "FederatedCredential", "GravitySensor", "Gyroscope", "Keyboard", "KeyboardLayoutMap", "LinearAccelerationSensor", "Lock", "LockManager", "MIDIAccess", "MIDIConnectionEvent", "MIDIInput", "MIDIInputMap", "MIDIMessageEvent", "MIDIOutput", "MIDIOutputMap", "MIDIPort", "MediaDeviceInfo", "MediaDevices", "MediaKeyMessageEvent", "MediaKeySession", "MediaKeyStatusMap", "MediaKeySystemAccess", "MediaKeys", "NavigationPreloadManager", "NavigatorManagedData", "OrientationSensor", "PasswordCredential", "RelativeOrientationSensor", "ScreenDetailed", "ScreenDetails", "Sensor", "SensorErrorEvent", "ServiceWorker", "ServiceWorkerContainer", "ServiceWorkerRegistration", "StorageManager", "SubtleCrypto", "VirtualKeyboard", "WebTransport", "WebTransportBidirectionalStream", "WebTransportDatagramDuplexStream", "WebTransportError", "Worklet", "XRDOMOverlayState", "XRLayer", "XRWebGLBinding", "AudioData", "EncodedAudioChunk", "EncodedVideoChunk", "ImageTrack", "ImageTrackList", "VideoColorSpace", "VideoFrame", "AudioDecoder", "AudioEncoder", "ImageDecoder", "VideoDecoder", "VideoEncoder", "AuthenticatorAssertionResponse", "AuthenticatorAttestationResponse", "AuthenticatorResponse", "PublicKeyCredential", "CaptureController", "EyeDropper", "FileSystemDirectoryHandle", "FileSystemFileHandle", "FileSystemHandle", "FileSystemWritableFileStream", "FontData", "FragmentDirective", "HID", "HIDConnectionEvent", "HIDDevice", "HIDInputReportEvent", "IdentityCredential", "IdleDetector", "LaunchParams", "LaunchQueue", "OTPCredential", "PaymentAddress", "PaymentRequest", "PaymentResponse", "PaymentMethodChangeEvent", "Presentation", "PresentationAvailability", "PresentationConnection", "PresentationConnectionAvailableEvent", "PresentationConnectionCloseEvent", "PresentationConnectionList", "PresentationReceiver", "PresentationRequest", "Sanitizer", "Serial", "SerialPort", "USB", "USBAlternateInterface", "USBConfiguration", "USBConnectionEvent", "USBDevice", "USBEndpoint", "USBInTransferResult", "USBInterface", "USBIsochronousInTransferPacket", "USBIsochronousInTransferResult", "USBIsochronousOutTransferPacket", "USBIsochronousOutTransferResult", "USBOutTransferResult", "WakeLock", "WakeLockSentinel", "WindowControlsOverlay", "WindowControlsOverlayGeometryChangeEvent", "XRAnchor", "XRAnchorSet", "XRBoundedReferenceSpace", "XRFrame", "XRInputSource", "XRInputSourceArray", "XRInputSourceEvent", "XRInputSourcesChangeEvent", "XRPose", "XRReferenceSpace", "XRReferenceSpaceEvent", "XRRenderState", "XRRigidTransform", "XRSession", "XRSessionEvent", "XRSpace", "XRSystem", "XRView", "XRViewerPose", "XRViewport", "XRWebGLLayer", "XRCPUDepthInformation", "XRDepthInformation", "XRWebGLDepthInformation", "XRCamera", "XRHitTestResult", "XRHitTestSource", "XRRay", "XRTransientInputHitTestResult", "XRTransientInputHitTestSource", "XRLightEstimate", "XRLightProbe", "getScreenDetails", "queryLocalFonts", "showDirectoryPicker", "showOpenFilePicker", "showSaveFilePicker", "originAgentCluster", "navigation", "webkitStorageInfo", "speechSynthesis", "oncontentvisibilityautostatechange", "AnimationPlaybackEvent", "AnimationTimeline", "CSSAnimation", "CSSTransition", "DocumentTimeline", "BackgroundFetchManager", "BackgroundFetchRecord", "BackgroundFetchRegistration", "BrowserCaptureMediaStreamTrack", "CropTarget", "CSSContainerRule", "CSSFontPaletteValuesRule", "ContentVisibilityAutoStateChangeEvent", "DelegatedInkTrailPresenter", "Ink", "Highlight", "HighlightRegistry", "MathMLElement", "MediaMetadata", "MediaSession", "MediaSource", "SourceBuffer", "SourceBufferList", "MediaSourceHandle", "MediaStreamTrack", "MediaStreamTrackGenerator", "NavigateEvent", "Navigation", "NavigationCurrentEntryChangeEvent", "NavigationDestination", "NavigationHistoryEntry", "NavigationTransition", "NavigatorUAData", "Notification", "PaymentInstruments", "PaymentManager", "PaymentRequestUpdateEvent", "PeriodicSyncManager", "PermissionStatus", "Permissions", "PictureInPictureEvent", "PictureInPictureWindow", "PushManager", "PushSubscription", "PushSubscriptionOptions", "RemotePlayback", "SharedWorker", "SpeechSynthesisErrorEvent", "SpeechSynthesisEvent", "SpeechSynthesisUtterance", "VideoPlaybackQuality", "webkitSpeechGrammar", "webkitSpeechGrammarList", "webkitSpeechRecognition", "webkitSpeechRecognitionError", "webkitSpeechRecognitionEvent", "openDatabase", "webkitRequestFileSystem", "webkitResolveLocalFileSystemURL", "__PageContext__", "_plt", "__tti", "webVitals", "__SEOINITED__", "leoConfig", "__FALLBACK_STATIC__", "__CMT_AMPLIFY_RATE__", "__ERROR_SAMPLE_RATE__", "__CDN_IMG__", "__PRIVACY_CONFIG__", "initInlineLogger", "pmmAppInfo", "__RESET_ERROR_LISTENER__", "funWebWidgets", "_SPLIT_REQUIRE_FLAG_", "__InitialLanguage__", "__InitialI18nStore__", "__InitialI18nStoreLoaded__", "__DOC_SOURCE__", "__CUI_IMAGE_FAST_SHOW_SCRIPT__", "__realFsImgSrcs", "__fsImgTotal", "__fsImgItems", "__fsImgSrcs", "extraI18nStore", "lang", "ns", "__ExtraI18nStore__", "__SSR__", "__CHUNK_DATA__", "rawData", "__MONITOR_INFOS__", "webpackChunkmobile_bg_web_home", "__funWebWidgets", "webpackChunkbg_fun_web_widgets", "__core-js_shared__", "core", "__mobxInstanceCount", "__mobxGlobals", "regeneratorRuntime", "pinnotification", "protobuf", "__pmmPagePath", "gtmLogger", "dataLayer", "__INITIAL_PROPS__", "__layout_expConfig__", "__FRONTEND_PERF_DATA__", "google_tag_manager", "google_tag_data", "dir", "dirxml", "profile", "profileEnd", "clear", "table", "keys", "values", "debug", "undebug", "monitor", "unmonitor", "inspect", "copy", "queryObjects", "$_", "$0", "$1", "$2", "$3", "$4", "getEventListeners", "getAccessibleName", "getAccessibleRole", "monitorEvents", "unmonitorEvents", "$", "$$", "$x"] + }else if(u===Permissions.prototype){ + return [] + } + else if(u===WebGLRenderingContext.prototype) + { + return [] + } + else if(u===WebGL2RenderingContext.prototype){ + return [] + } + else { + return obj_get_own_p_n(u) + } +} \ No newline at end of file diff --git a/reverse-life/pjstealth/js/iframe.contentWindow.js b/reverse-life/pjstealth/js/iframe.contentWindow.js new file mode 100644 index 0000000..9bdae19 --- /dev/null +++ b/reverse-life/pjstealth/js/iframe.contentWindow.js @@ -0,0 +1,97 @@ +try { + // Adds a contentWindow proxy to the provided iframe element + const addContentWindowProxy = iframe => { + const contentWindowProxy = { + get(target, key) { + // Now to the interesting part: + // We actually make this thing behave like a regular iframe window, + // by intercepting calls to e.g. `.self` and redirect it to the correct thing. :) + // That makes it possible for these assertions to be correct: + // iframe.contentWindow.self === window.top // must be false + if (key === 'self') { + return this + } + // iframe.contentWindow.frameElement === iframe // must be true + if (key === 'frameElement') { + return iframe + } + return Reflect.get(target, key) + } + } + + if (!iframe.contentWindow) { + const proxy = new Proxy(window, contentWindowProxy) + Object.defineProperty(iframe, 'contentWindow', { + get() { + return proxy + }, + set(newValue) { + return newValue // contentWindow is immutable + }, + enumerable: true, + configurable: false + }) + } + } + + // Handles iframe element creation, augments `srcdoc` property so we can intercept further + const handleIframeCreation = (target, thisArg, args) => { + const iframe = target.apply(thisArg, args) + + // We need to keep the originals around + const _iframe = iframe + const _srcdoc = _iframe.srcdoc + + // Add hook for the srcdoc property + // We need to be very surgical here to not break other iframes by accident + Object.defineProperty(iframe, 'srcdoc', { + configurable: true, // Important, so we can reset this later + get: function () { + return _iframe.srcdoc + }, + set: function (newValue) { + addContentWindowProxy(this) + // Reset property, the hook is only needed once + Object.defineProperty(iframe, 'srcdoc', { + configurable: false, + writable: false, + value: _srcdoc + }) + _iframe.srcdoc = newValue + } + }) + return iframe + } + + // Adds a hook to intercept iframe creation events + const addIframeCreationSniffer = () => { + /* global document */ + const createElementHandler = { + // Make toString() native + get(target, key) { + return Reflect.get(target, key) + }, + apply: function (target, thisArg, args) { + const isIframe = + args && args.length && `${args[0]}`.toLowerCase() === 'iframe' + if (!isIframe) { + // Everything as usual + return target.apply(thisArg, args) + } else { + return handleIframeCreation(target, thisArg, args) + } + } + } + // All this just due to iframes with srcdoc bug + utils.replaceWithProxy( + document, + 'createElement', + createElementHandler + ) + } + + // Let's go + addIframeCreationSniffer() +} catch (err) { + // console.warn(err) +} \ No newline at end of file diff --git a/reverse-life/pjstealth/js/media.codecs.js b/reverse-life/pjstealth/js/media.codecs.js new file mode 100644 index 0000000..7761032 --- /dev/null +++ b/reverse-life/pjstealth/js/media.codecs.js @@ -0,0 +1,63 @@ +/** + * Input might look funky, we need to normalize it so e.g. whitespace isn't an issue for our spoofing. + * + * @example + * video/webm; codecs="vp8, vorbis" + * video/mp4; codecs="avc1.42E01E" + * audio/x-m4a; + * audio/ogg; codecs="vorbis" + * @param {String} arg + */ +const parseInput = arg => { + const [mime, codecStr] = arg.trim().split(';') + let codecs = [] + if (codecStr && codecStr.includes('codecs="')) { + codecs = codecStr + .trim() + .replace(`codecs="`, '') + .replace(`"`, '') + .trim() + .split(',') + .filter(x => !!x) + .map(x => x.trim()) + } + return { + mime, + codecStr, + codecs + } +} + +const canPlayType = { + // Intercept certain requests + apply: function (target, ctx, args) { + if (!args || !args.length) { + return target.apply(ctx, args) + } + const {mime, codecs} = parseInput(args[0]) + // This specific mp4 codec is missing in Chromium + if (mime === 'video/mp4') { + if (codecs.includes('avc1.42E01E')) { + return 'probably' + } + } + // This mimetype is only supported if no codecs are specified + if (mime === 'audio/x-m4a' && !codecs.length) { + return 'maybe' + } + + // This mimetype is only supported if no codecs are specified + if (mime === 'audio/aac' && !codecs.length) { + return 'probably' + } + // Everything else as usual + return target.apply(ctx, args) + } +} + +/* global HTMLMediaElement */ +utils.replaceWithProxy( + HTMLMediaElement.prototype, + 'canPlayType', + canPlayType +) \ No newline at end of file diff --git a/reverse-life/pjstealth/js/navigator.appVersion.js b/reverse-life/pjstealth/js/navigator.appVersion.js new file mode 100644 index 0000000..15af3c3 --- /dev/null +++ b/reverse-life/pjstealth/js/navigator.appVersion.js @@ -0,0 +1,3 @@ +Object.defineProperty(navigator, 'appVersion', { + get: () => opts.navigator_app_version +}); diff --git a/reverse-life/pjstealth/js/navigator.deviceMemory.js b/reverse-life/pjstealth/js/navigator.deviceMemory.js new file mode 100644 index 0000000..b947ee4 --- /dev/null +++ b/reverse-life/pjstealth/js/navigator.deviceMemory.js @@ -0,0 +1,8 @@ +const patchNavigator2 = (name, value) => + utils.replaceProperty(Object.getPrototypeOf(navigator), name, { + get() { + return value + } + }); + +patchNavigator2('deviceMemory', opts.device_memory || 4); \ No newline at end of file diff --git a/reverse-life/pjstealth/js/navigator.hardwareConcurrency.js b/reverse-life/pjstealth/js/navigator.hardwareConcurrency.js new file mode 100644 index 0000000..1208b54 --- /dev/null +++ b/reverse-life/pjstealth/js/navigator.hardwareConcurrency.js @@ -0,0 +1,8 @@ +const patchNavigator3 = (name, value) => + utils.replaceProperty(Object.getPrototypeOf(navigator), name, { + get() { + return value + } + }) + +patchNavigator3('hardwareConcurrency', opts.navigator_hardware_concurrency || 4); \ No newline at end of file diff --git a/reverse-life/pjstealth/js/navigator.language.js b/reverse-life/pjstealth/js/navigator.language.js new file mode 100644 index 0000000..360f0c1 --- /dev/null +++ b/reverse-life/pjstealth/js/navigator.language.js @@ -0,0 +1,3 @@ +Object.defineProperty(Object.getPrototypeOf(navigator), 'language', { + get: () => opts.language +}); diff --git a/reverse-life/pjstealth/js/navigator.languages.js b/reverse-life/pjstealth/js/navigator.languages.js new file mode 100644 index 0000000..e59284b --- /dev/null +++ b/reverse-life/pjstealth/js/navigator.languages.js @@ -0,0 +1,3 @@ +Object.defineProperty(Object.getPrototypeOf(navigator), 'languages', { + get: () => opts.languages || ['en-US', 'en'] +}); diff --git a/reverse-life/pjstealth/js/navigator.permissions.js b/reverse-life/pjstealth/js/navigator.permissions.js new file mode 100644 index 0000000..1d90531 --- /dev/null +++ b/reverse-life/pjstealth/js/navigator.permissions.js @@ -0,0 +1,19 @@ +const handler = { + apply: function (target, ctx, args) { + const param = (args || [])[0] + + if (param && param.name && param.name === 'notifications') { + const result = {state: Notification.permission} + Object.setPrototypeOf(result, PermissionStatus.prototype) + return Promise.resolve(result) + } + + return utils.cache.Reflect.apply(...arguments) + } +} + +utils.replaceWithProxy( + window.navigator.permissions.__proto__, // eslint-disable-line no-proto + 'query', + handler +) diff --git a/reverse-life/pjstealth/js/navigator.platform.js b/reverse-life/pjstealth/js/navigator.platform.js new file mode 100644 index 0000000..f61e32f --- /dev/null +++ b/reverse-life/pjstealth/js/navigator.platform.js @@ -0,0 +1,5 @@ +if (opts.navigator_platform) { + Object.defineProperty(Object.getPrototypeOf(navigator), 'platform', { + get: () => opts.navigator_plaftorm, + }) +} \ No newline at end of file diff --git a/reverse-life/pjstealth/js/navigator.plugins.js b/reverse-life/pjstealth/js/navigator.plugins.js new file mode 100644 index 0000000..117aeb6 --- /dev/null +++ b/reverse-life/pjstealth/js/navigator.plugins.js @@ -0,0 +1,92 @@ +data = { + "mimeTypes": [ + { + "type": "application/pdf", + "suffixes": "pdf", + "description": "", + "__pluginName": "Chrome PDF Viewer" + }, + { + "type": "application/x-google-chrome-pdf", + "suffixes": "pdf", + "description": "Portable Document Format", + "__pluginName": "Chrome PDF Plugin" + }, + { + "type": "application/x-nacl", + "suffixes": "", + "description": "Native Client Executable", + "__pluginName": "Native Client" + }, + { + "type": "application/x-pnacl", + "suffixes": "", + "description": "Portable Native Client Executable", + "__pluginName": "Native Client" + } + ], + "plugins": [ + { + "name": "Chrome PDF Plugin", + "filename": "internal-pdf-viewer", + "description": "Portable Document Format", + "__mimeTypes": ["application/x-google-chrome-pdf"] + }, + { + "name": "Chrome PDF Viewer", + "filename": "mhjfbmdgcfjbbpaeojofohoefgiehjai", + "description": "", + "__mimeTypes": ["application/pdf"] + }, + { + "name": "Native Client", + "filename": "internal-nacl-plugin", + "description": "", + "__mimeTypes": ["application/x-nacl", "application/x-pnacl"] + } + ] +} + + +// That means we're running headful +const hasPlugins = 'plugins' in navigator && navigator.plugins.length +if (!(hasPlugins)) { + + const mimeTypes = generateMagicArray( + data.mimeTypes, + MimeTypeArray.prototype, + MimeType.prototype, + 'type' + ) + const plugins = generateMagicArray( + data.plugins, + PluginArray.prototype, + Plugin.prototype, + 'name' + ) + + // Plugin and MimeType cross-reference each other, let's do that now + // Note: We're looping through `data.plugins` here, not the generated `plugins` + for (const pluginData of data.plugins) { + pluginData.__mimeTypes.forEach((type, index) => { + plugins[pluginData.name][index] = mimeTypes[type] + plugins[type] = mimeTypes[type] + Object.defineProperty(mimeTypes[type], 'enabledPlugin', { + value: JSON.parse(JSON.stringify(plugins[pluginData.name])), + writable: false, + enumerable: false, // Important: `JSON.stringify(navigator.plugins)` + configurable: false + }) + }) + } + + const patchNavigator = (name, value) => + utils.replaceProperty(Object.getPrototypeOf(navigator), name, { + get() { + return value + } + }) + + patchNavigator('mimeTypes', mimeTypes) + patchNavigator('plugins', plugins) +} \ No newline at end of file diff --git a/reverse-life/pjstealth/js/navigator.userAgent.js b/reverse-life/pjstealth/js/navigator.userAgent.js new file mode 100644 index 0000000..f35f07a --- /dev/null +++ b/reverse-life/pjstealth/js/navigator.userAgent.js @@ -0,0 +1,5 @@ +// replace Headless references in default useragent +const current_ua = navigator.userAgent +Object.defineProperty(Object.getPrototypeOf(navigator), 'userAgent', { + get: () => opts.navigator_user_agent || current_ua.replace('HeadlessChrome/', 'Chrome/') +}) diff --git a/reverse-life/pjstealth/js/navigator.userAgentData.js b/reverse-life/pjstealth/js/navigator.userAgentData.js new file mode 100644 index 0000000..97c4329 --- /dev/null +++ b/reverse-life/pjstealth/js/navigator.userAgentData.js @@ -0,0 +1,5 @@ +if (opts.user_agent_data) { + Object.defineProperty(Object.getPrototypeOf(navigator), 'userAgentData', { + get: () => opts.user_agent_data, + }) +} \ No newline at end of file diff --git a/reverse-life/pjstealth/js/navigator.vendor.js b/reverse-life/pjstealth/js/navigator.vendor.js new file mode 100644 index 0000000..1349b67 --- /dev/null +++ b/reverse-life/pjstealth/js/navigator.vendor.js @@ -0,0 +1,3 @@ +Object.defineProperty(Object.getPrototypeOf(navigator), 'vendor', { + get: () => opts.navigator_vendor || 'Google Inc.', +}) diff --git a/reverse-life/pjstealth/js/utils.js b/reverse-life/pjstealth/js/utils.js new file mode 100644 index 0000000..5e26ae6 --- /dev/null +++ b/reverse-life/pjstealth/js/utils.js @@ -0,0 +1,456 @@ +/** + * A set of shared utility functions specifically for the purpose of modifying native browser APIs without leaving traces. + * + * Meant to be passed down in puppeteer and used in the context of the page (everything in here runs in NodeJS as well as a browser). + * + * Note: If for whatever reason you need to use this outside of `puppeteer-extra`: + * Just remove the `module.exports` statement at the very bottom, the rest can be copy pasted into any browser context. + * + * Alternatively take a look at the `extract-stealth-evasions` package to create a finished bundle which includes these utilities. + * + */ +const utils = {} + +/** + * Wraps a JS Proxy Handler and strips it's presence from error stacks, in case the traps throw. + * + * The presence of a JS Proxy can be revealed as it shows up in error stack traces. + * + * @param {object} handler - The JS Proxy handler to wrap + */ +utils.stripProxyFromErrors = (handler = {}) => { + const newHandler = {} + // We wrap each trap in the handler in a try/catch and modify the error stack if they throw + const traps = Object.getOwnPropertyNames(handler) + traps.forEach(trap => { + newHandler[trap] = function() { + try { + // Forward the call to the defined proxy handler + return handler[trap].apply(this, arguments || []) + } catch (err) { + // Stack traces differ per browser, we only support chromium based ones currently + if (!err || !err.stack || !err.stack.includes(`at `)) { + throw err + } + + // When something throws within one of our traps the Proxy will show up in error stacks + // An earlier implementation of this code would simply strip lines with a blacklist, + // but it makes sense to be more surgical here and only remove lines related to our Proxy. + // We try to use a known "anchor" line for that and strip it with everything above it. + // If the anchor line cannot be found for some reason we fall back to our blacklist approach. + + const stripWithBlacklist = stack => { + const blacklist = [ + `at Reflect.${trap} `, // e.g. Reflect.get or Reflect.apply + `at Object.${trap} `, // e.g. Object.get or Object.apply + `at Object.newHandler. [as ${trap}] ` // caused by this very wrapper :-) + ] + return ( + err.stack + .split('\n') + // Always remove the first (file) line in the stack (guaranteed to be our proxy) + .filter((line, index) => index !== 1) + // Check if the line starts with one of our blacklisted strings + .filter(line => !blacklist.some(bl => line.trim().startsWith(bl))) + .join('\n') + ) + } + + const stripWithAnchor = stack => { + const stackArr = stack.split('\n') + const anchor = `at Object.newHandler. [as ${trap}] ` // Known first Proxy line in chromium + const anchorIndex = stackArr.findIndex(line => + line.trim().startsWith(anchor) + ) + if (anchorIndex === -1) { + return false // 404, anchor not found + } + // Strip everything from the top until we reach the anchor line + // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`) + stackArr.splice(1, anchorIndex) + return stackArr.join('\n') + } + + // Try using the anchor method, fallback to blacklist if necessary + err.stack = stripWithAnchor(err.stack) || stripWithBlacklist(err.stack) + + throw err // Re-throw our now sanitized error + } + } + }) + return newHandler +} + +/** + * Strip error lines from stack traces until (and including) a known line the stack. + * + * @param {object} err - The error to sanitize + * @param {string} anchor - The string the anchor line starts with + */ +utils.stripErrorWithAnchor = (err, anchor) => { + const stackArr = err.stack.split('\n') + const anchorIndex = stackArr.findIndex(line => line.trim().startsWith(anchor)) + if (anchorIndex === -1) { + return err // 404, anchor not found + } + // Strip everything from the top until we reach the anchor line (remove anchor line as well) + // Note: We're keeping the 1st line (zero index) as it's unrelated (e.g. `TypeError`) + stackArr.splice(1, anchorIndex) + err.stack = stackArr.join('\n') + return err +} + +/** + * Replace the property of an object in a stealthy way. + * + * Note: You also want to work on the prototype of an object most often, + * as you'd otherwise leave traces (e.g. showing up in Object.getOwnPropertyNames(obj)). + * + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty + * + * @example + * replaceProperty(WebGLRenderingContext.prototype, 'getParameter', { value: "alice" }) + * // or + * replaceProperty(Object.getPrototypeOf(navigator), 'languages', { get: () => ['en-US', 'en'] }) + * + * @param {object} obj - The object which has the property to replace + * @param {string} propName - The property name to replace + * @param {object} descriptorOverrides - e.g. { value: "alice" } + */ +utils.replaceProperty = (obj, propName, descriptorOverrides = {}) => { + return Object.defineProperty(obj, propName, { + // Copy over the existing descriptors (writable, enumerable, configurable, etc) + ...(Object.getOwnPropertyDescriptor(obj, propName) || {}), + // Add our overrides (e.g. value, get()) + ...descriptorOverrides + }) +} + +/** + * Preload a cache of function copies and data. + * + * For a determined enough observer it would be possible to overwrite and sniff usage of functions + * we use in our internal Proxies, to combat that we use a cached copy of those functions. + * + * This is evaluated once per execution context (e.g. window) + */ +utils.preloadCache = () => { + if (utils.cache) { + return + } + utils.cache = { + // Used in our proxies + Reflect: { + get: Reflect.get.bind(Reflect), + apply: Reflect.apply.bind(Reflect) + }, + // Used in `makeNativeString` + nativeToStringStr: Function.toString + '' // => `function toString() { [native code] }` + } +} + +/** + * Utility function to generate a cross-browser `toString` result representing native code. + * + * There's small differences: Chromium uses a single line, whereas FF & Webkit uses multiline strings. + * To future-proof this we use an existing native toString result as the basis. + * + * The only advantage we have over the other team is that our JS runs first, hence we cache the result + * of the native toString result once, so they cannot spoof it afterwards and reveal that we're using it. + * + * Note: Whenever we add a `Function.prototype.toString` proxy we should preload the cache before, + * by executing `utils.preloadCache()` before the proxy is applied (so we don't cause recursive lookups). + * + * @example + * makeNativeString('foobar') // => `function foobar() { [native code] }` + * + * @param {string} [name] - Optional function name + */ +utils.makeNativeString = (name = '') => { + // Cache (per-window) the original native toString or use that if available + utils.preloadCache() + return utils.cache.nativeToStringStr.replace('toString', name || '') +} + +/** + * Helper function to modify the `toString()` result of the provided object. + * + * Note: Use `utils.redirectToString` instead when possible. + * + * There's a quirk in JS Proxies that will cause the `toString()` result to differ from the vanilla Object. + * If no string is provided we will generate a `[native code]` thing based on the name of the property object. + * + * @example + * patchToString(WebGLRenderingContext.prototype.getParameter, 'function getParameter() { [native code] }') + * + * @param {object} obj - The object for which to modify the `toString()` representation + * @param {string} str - Optional string used as a return value + */ +utils.patchToString = (obj, str = '') => { + utils.preloadCache() + + const toStringProxy = new Proxy(Function.prototype.toString, { + apply: function(target, ctx) { + // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + ""` + if (ctx === Function.prototype.toString) { + return utils.makeNativeString('toString') + } + // `toString` targeted at our proxied Object detected + if (ctx === obj) { + // We either return the optional string verbatim or derive the most desired result automatically + return str || utils.makeNativeString(obj.name) + } + // Check if the toString protype of the context is the same as the global prototype, + // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test case + const hasSameProto = Object.getPrototypeOf( + Function.prototype.toString + ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins + if (!hasSameProto) { + // Pass the call on to the local Function.prototype.toString instead + return ctx.toString() + } + return target.call(ctx) + } + }) + utils.replaceProperty(Function.prototype, 'toString', { + value: toStringProxy + }) +} + +/** + * Make all nested functions of an object native. + * + * @param {object} obj + */ +utils.patchToStringNested = (obj = {}) => { + return utils.execRecursively(obj, ['function'], utils.patchToString) +} + +/** + * Redirect toString requests from one object to another. + * + * @param {object} proxyObj - The object that toString will be called on + * @param {object} originalObj - The object which toString result we wan to return + */ +utils.redirectToString = (proxyObj, originalObj) => { + utils.preloadCache() + + const toStringProxy = new Proxy(Function.prototype.toString, { + apply: function(target, ctx) { + // This fixes e.g. `HTMLMediaElement.prototype.canPlayType.toString + ""` + if (ctx === Function.prototype.toString) { + return utils.makeNativeString('toString') + } + + // `toString` targeted at our proxied Object detected + if (ctx === proxyObj) { + const fallback = () => + originalObj && originalObj.name + ? utils.makeNativeString(originalObj.name) + : utils.makeNativeString(proxyObj.name) + + // Return the toString representation of our original object if possible + return originalObj + '' || fallback() + } + + // Check if the toString protype of the context is the same as the global prototype, + // if not indicates that we are doing a check across different windows., e.g. the iframeWithdirect` test case + const hasSameProto = Object.getPrototypeOf( + Function.prototype.toString + ).isPrototypeOf(ctx.toString) // eslint-disable-line no-prototype-builtins + if (!hasSameProto) { + // Pass the call on to the local Function.prototype.toString instead + return ctx.toString() + } + + return target.call(ctx) + } + }) + utils.replaceProperty(Function.prototype, 'toString', { + value: toStringProxy + }) +} + +/** + * All-in-one method to replace a property with a JS Proxy using the provided Proxy handler with traps. + * + * Will stealthify these aspects (strip error stack traces, redirect toString, etc). + * Note: This is meant to modify native Browser APIs and works best with prototype objects. + * + * @example + * replaceWithProxy(WebGLRenderingContext.prototype, 'getParameter', proxyHandler) + * + * @param {object} obj - The object which has the property to replace + * @param {string} propName - The name of the property to replace + * @param {object} handler - The JS Proxy handler to use + */ +utils.replaceWithProxy = (obj, propName, handler) => { + utils.preloadCache() + const originalObj = obj[propName] + const proxyObj = new Proxy(obj[propName], utils.stripProxyFromErrors(handler)) + + utils.replaceProperty(obj, propName, { value: proxyObj }) + utils.redirectToString(proxyObj, originalObj) + + return true +} + +/** + * All-in-one method to mock a non-existing property with a JS Proxy using the provided Proxy handler with traps. + * + * Will stealthify these aspects (strip error stack traces, redirect toString, etc). + * + * @example + * mockWithProxy(chrome.runtime, 'sendMessage', function sendMessage() {}, proxyHandler) + * + * @param {object} obj - The object which has the property to replace + * @param {string} propName - The name of the property to replace or create + * @param {object} pseudoTarget - The JS Proxy target to use as a basis + * @param {object} handler - The JS Proxy handler to use + */ +utils.mockWithProxy = (obj, propName, pseudoTarget, handler) => { + utils.preloadCache() + const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler)) + + utils.replaceProperty(obj, propName, { value: proxyObj }) + utils.patchToString(proxyObj) + + return true +} + +/** + * All-in-one method to create a new JS Proxy with stealth tweaks. + * + * This is meant to be used whenever we need a JS Proxy but don't want to replace or mock an existing known property. + * + * Will stealthify certain aspects of the Proxy (strip error stack traces, redirect toString, etc). + * + * @example + * createProxy(navigator.mimeTypes.__proto__.namedItem, proxyHandler) // => Proxy + * + * @param {object} pseudoTarget - The JS Proxy target to use as a basis + * @param {object} handler - The JS Proxy handler to use + */ +utils.createProxy = (pseudoTarget, handler) => { + utils.preloadCache() + const proxyObj = new Proxy(pseudoTarget, utils.stripProxyFromErrors(handler)) + utils.patchToString(proxyObj) + + return proxyObj +} + +/** + * Helper function to split a full path to an Object into the first part and property. + * + * @example + * splitObjPath(`HTMLMediaElement.prototype.canPlayType`) + * // => {objName: "HTMLMediaElement.prototype", propName: "canPlayType"} + * + * @param {string} objPath - The full path to an object as dot notation string + */ +utils.splitObjPath = objPath => ({ + // Remove last dot entry (property) ==> `HTMLMediaElement.prototype` + objName: objPath + .split('.') + .slice(0, -1) + .join('.'), + // Extract last dot entry ==> `canPlayType` + propName: objPath.split('.').slice(-1)[0] +}) + +/** + * Convenience method to replace a property with a JS Proxy using the provided objPath. + * + * Supports a full path (dot notation) to the object as string here, in case that makes it easier. + * + * @example + * replaceObjPathWithProxy('WebGLRenderingContext.prototype.getParameter', proxyHandler) + * + * @param {string} objPath - The full path to an object (dot notation string) to replace + * @param {object} handler - The JS Proxy handler to use + */ +utils.replaceObjPathWithProxy = (objPath, handler) => { + const { objName, propName } = utils.splitObjPath(objPath) + const obj = eval(objName) // eslint-disable-line no-eval + return utils.replaceWithProxy(obj, propName, handler) +} + +/** + * Traverse nested properties of an object recursively and apply the given function on a whitelist of value types. + * + * @param {object} obj + * @param {array} typeFilter - e.g. `['function']` + * @param {Function} fn - e.g. `utils.patchToString` + */ +utils.execRecursively = (obj = {}, typeFilter = [], fn) => { + function recurse(obj) { + for (const key in obj) { + if (obj[key] === undefined) { + continue + } + if (obj[key] && typeof obj[key] === 'object') { + recurse(obj[key]) + } else { + if (obj[key] && typeFilter.includes(typeof obj[key])) { + fn.call(this, obj[key]) + } + } + } + } + recurse(obj) + return obj +} + +/** + * Everything we run through e.g. `page.evaluate` runs in the browser context, not the NodeJS one. + * That means we cannot just use reference variables and functions from outside code, we need to pass everything as a parameter. + * + * Unfortunately the data we can pass is only allowed to be of primitive types, regular functions don't survive the built-in serialization process. + * This utility function will take an object with functions and stringify them, so we can pass them down unharmed as strings. + * + * We use this to pass down our utility functions as well as any other functions (to be able to split up code better). + * + * @see utils.materializeFns + * + * @param {object} fnObj - An object containing functions as properties + */ +utils.stringifyFns = (fnObj = { hello: () => 'world' }) => { + // Object.fromEntries() ponyfill (in 6 lines) - supported only in Node v12+, modern browsers are fine + // https://github.com/feross/fromentries + function fromEntries(iterable) { + return [...iterable].reduce((obj, [key, val]) => { + obj[key] = val + return obj + }, {}) + } + return (Object.fromEntries || fromEntries)( + Object.entries(fnObj) + .filter(([key, value]) => typeof value === 'function') + .map(([key, value]) => [key, value.toString()]) // eslint-disable-line no-eval + ) +} + +/** + * Utility function to reverse the process of `utils.stringifyFns`. + * Will materialize an object with stringified functions (supports classic and fat arrow functions). + * + * @param {object} fnStrObj - An object containing stringified functions as properties + */ +utils.materializeFns = (fnStrObj = { hello: "() => 'world'" }) => { + return Object.fromEntries( + Object.entries(fnStrObj).map(([key, value]) => { + if (value.startsWith('function')) { + // some trickery is needed to make oldschool functions work :-) + return [key, eval(`() => ${value}`)()] // eslint-disable-line no-eval + } else { + // arrow functions just work + return [key, eval(value)] // eslint-disable-line no-eval + } + }) + ) +} + +// -- +// Stuff starting below this line is NodeJS specific. +// -- +// module.exports = utils \ No newline at end of file diff --git a/reverse-life/pjstealth/js/webgl.vendor.js b/reverse-life/pjstealth/js/webgl.vendor.js new file mode 100644 index 0000000..6b585a9 --- /dev/null +++ b/reverse-life/pjstealth/js/webgl.vendor.js @@ -0,0 +1,25 @@ +console.log(opts) +const getParameterProxyHandler = { + apply: function (target, ctx, args) { + const param = (args || [])[0] + // UNMASKED_VENDOR_WEBGL + if (param === 37445) { + return opts.webgl_vendor || 'Intel Inc.' // default in headless: Google Inc. + } + // UNMASKED_RENDERER_WEBGL + if (param === 37446) { + return opts.webgl_renderer || 'Intel Iris OpenGL Engine' // default in headless: Google SwiftShader + } + return utils.cache.Reflect.apply(target, ctx, args) + } +} + +// There's more than one WebGL rendering context +// https://developer.mozilla.org/en-US/docs/Web/API/WebGL2RenderingContext#Browser_compatibility +// To find out the original values here: Object.getOwnPropertyDescriptors(WebGLRenderingContext.prototype.getParameter) +const addProxy = (obj, propName) => { + utils.replaceWithProxy(obj, propName, getParameterProxyHandler) +} +// For whatever weird reason loops don't play nice with Object.defineProperty, here's the next best thing: +addProxy(WebGLRenderingContext.prototype, 'getParameter') +addProxy(WebGL2RenderingContext.prototype, 'getParameter') \ No newline at end of file diff --git a/reverse-life/pjstealth/js/window.outerdimensions.js b/reverse-life/pjstealth/js/window.outerdimensions.js new file mode 100644 index 0000000..e9ef868 --- /dev/null +++ b/reverse-life/pjstealth/js/window.outerdimensions.js @@ -0,0 +1,12 @@ +'use strict' + +try { + if (!!window.outerWidth && !!window.outerHeight) { + const windowFrame = 85 // probably OS and WM dependent + window.outerWidth = window.innerWidth + console.log(`current window outer height ${window.outerHeight}`) + window.outerHeight = window.innerHeight + windowFrame + console.log(`new window outer height ${window.outerHeight}`) + } +} catch (err) { +} diff --git a/reverse-life/pjstealth/pjstealth.py b/reverse-life/pjstealth/pjstealth.py new file mode 100644 index 0000000..510a74a --- /dev/null +++ b/reverse-life/pjstealth/pjstealth.py @@ -0,0 +1,26 @@ +from playwright.async_api import Page as AsyncPage +from playwright.sync_api import Page as SyncPage +from .stealth import StealthConfig + + +def stealth_sync(page: SyncPage, config: StealthConfig = None): + """teaches synchronous playwright Page to be stealthy like a ninja!""" + navigator_user_agent: str = page.evaluate("navigator.userAgent") + navigator_platform = page.evaluate("navigator.platform") + for script in (config or StealthConfig(navigator_user_agent, navigator_platform, {})).enabled_scripts: + page.add_init_script(script) + + +async def stealth_async(page: AsyncPage, config: StealthConfig = None): + """teaches asynchronous playwright Page to be stealthy like a ninja!""" + navigator_user_agent: str = await page.evaluate("navigator.userAgent") + navigator_platform = await page.evaluate("navigator.platform") + for script in (config or StealthConfig(navigator_user_agent, navigator_platform, {})).enabled_scripts: + await page.add_init_script(script) + +# +# def stealth_sync_dri(page: ChromiumPage, config: StealthConfig = None): +# navigator_user_agent = page.run_js("return navigator.userAgent") +# navigator_platform = page.run_js("return navigator.platform") +# for script in (config or StealthConfig(navigator_user_agent, navigator_platform, {})).enabled_scripts: +# page.run_js(script) diff --git a/reverse-life/pjstealth/stealth.py b/reverse-life/pjstealth/stealth.py new file mode 100644 index 0000000..be1b59c --- /dev/null +++ b/reverse-life/pjstealth/stealth.py @@ -0,0 +1,314 @@ +# -*- coding: utf-8 -*- +import json +import random +import re +from dataclasses import dataclass +from typing import Optional, Dict +from .env_data import env_data +import pkg_resources + + +def from_file(name): + """Read script from ./js directory""" + return pkg_resources.resource_string('pjstealth', f'feature/{name}').decode() + + +SCRIPTS: Dict[str, str] = { + 'chrome_csi': from_file('chrome.csi.js'), + 'chrome_app': from_file('chrome.app.js'), + 'chrome_runtime': from_file('chrome.runtime.js'), + 'chrome_load_times': from_file('chrome.load.times.js'), + 'chrome_hairline': from_file('chrome.hairline.js'), + 'generate_magic_arrays': from_file('generate.magic.arrays.js'), + 'iframe_content_window': from_file('iframe.contentWindow.js'), + 'media_codecs': from_file('media.codecs.js'), + 'navigator_vendor': from_file('navigator.vendor.js'), + 'navigator_plugins': from_file('navigator.plugins.js'), + 'navigator_permissions': from_file('navigator.permissions.js'), + 'navigator_languages': from_file('navigator.languages.js'), + 'navigator_platform': from_file('navigator.platform.js'), + 'navigator_user_agent': from_file('navigator.userAgent.js'), + 'navigator_hardwareConcurrency': from_file('navigator.hardwareConcurrency.js'), + 'outerdimensions': from_file('window.outerdimensions.js'), + 'utils': from_file('utils.js'), + 'webdriver': from_file('chrome.webdriver.js'), + 'webgl_vendor': from_file('webgl.vendor.js'), + 'navigator_appVersion': from_file('navigator.appVersion.js'), + 'navigator_deviceMemory': from_file('navigator.deviceMemory.js'), + 'navigator_language': from_file('navigator.language.js'), + 'navigator_userAgentData': from_file('navigator.userAgentData.js'), + 'chrome_canvasfeature': from_file('chrome.canvasfeature.js'), + 'chrome_clientrectfeature': from_file('chrome.clientrectfeature.js'), + 'chrome_cssfeature': from_file('chrome.cssfeature.js'), + 'chrome_fontsfeature': from_file('chrome.fontsfeature.js'), + 'chrome_webrtc': from_file('chrome.webrtc.js'), + 'hookfuc_headless': from_file('hookfuc.headless.js'), + 'chrome_videofeature': from_file('chrome.videofeature.js'), + 'chrome_canvasfeature2': from_file('chrome.canvasfeature2.js'), + 'chrome_screen_colordepth': from_file('chrome.screen.colordepth.js'), + 'chrome_mouse_event': from_file('chrome.mouseevent.js') +} + + +@dataclass +class StealthConfig(object): + vendor: str = 'Intel Inc.' + renderer: str = 'Intel Iris OpenGL Engine' + nav_vendor: str = 'Google Inc.' + runOnInsecureOrigins: Optional[bool] = None + mouse_detail = 0 + mouse_button = 0 + mouse_buttons = 1 + + webdriver: bool = True + webgl_vendor: bool = True + navigator_plugins: bool = True + navigator_permissions: bool = True + media_codecs: bool = True + iframe_content_window: bool = True + chrome_runtime: bool = True + chrome_load_times: bool = True + chrome_csi: bool = True + chrome_app: bool = True + outerdimensions: bool = True + hairline: bool = True + + # 随机特征开启默认为false + random_feature = True + + def __init__(self, navigator_user_agent, navigator_platform, kwargs): + # 匹配user-agent + self.navigator_user_agent: str = navigator_user_agent + self.navigator_platform = navigator_platform + + self.random_feature = kwargs.get("random_feature") if kwargs.get( + "random_feature") is not None else self.random_feature + + if self.random_feature: + + self.navigator_languages = env_data.get("languages") + self.navigator_language = env_data.get("language") + # user-agent mac(m系列和非m系列), windows版对应 + self.navigator_user_agent = env_data.get("user_agent") if env_data.get( + "user_agent") is not None else self.navigator_user_agent + self.browser_version = env_data.get("browser_version") + + if self.navigator_user_agent: + if self.navigator_user_agent.lower().__contains__("(mac"): + self.navigator_platform = 'MacIntel' + + if not self.navigator_user_agent.lower().__contains__("intel"): + while True: + tmp_webgl_info = random.choice(env_data.get(self.navigator_platform).get("webgl_infos")) + if str(tmp_webgl_info).lower().__contains__("m1") or str( + tmp_webgl_info).lower().__contains__("m2"): + self.vendor = tmp_webgl_info[0] + self.renderer = tmp_webgl_info[1] + break + else: + while True: + tmp_webgl_info = random.choice(env_data.get(self.navigator_platform).get("webgl_infos")) + if str(tmp_webgl_info).lower().__contains__("m1") or str( + tmp_webgl_info).lower().__contains__("m2"): + continue + else: + self.vendor = tmp_webgl_info[0] + self.renderer = tmp_webgl_info[1] + break + + if self.navigator_user_agent.lower().__contains__("(windows"): + self.navigator_platform = 'Win32' + + self.vendor = env_data.get(self.navigator_platform).get("webgl_infos")[0] + self.renderer = env_data.get(self.navigator_platform).get("webgl_infos")[1] + + if self.navigator_user_agent.lower().__contains__("linux"): + self.navigator_platform = "Linux x86_64" + self.vendor = env_data.get(self.navigator_platform).get("webgl_infos")[0] + self.renderer = env_data.get(self.navigator_platform).get("webgl_infos")[1] + + self.browser_version = re.search(r"Chrome/(\d+)", self.navigator_user_agent).group(1) + self.navigator_platform = self.navigator_platform if self.navigator_platform is not None else random.choice( + ['MacIntel', 'Win32']) + + self.sys_platform = env_data.get(self.navigator_platform).get("sys_platform") + self.navigator_hardware_concurrency = env_data.get("navigator_hardware_concurrency") + self.device_memory = env_data.get("device_memory") + self.cssfeature = env_data.get("cssfeature") + self.fontsfeature = env_data.get("fontsfeature") + self.webrtc = env_data.get("webrtc") + self.canvasfeature = env_data.get("canvasfeature") + self.videofeature = env_data.get("videofeature") + self.clientrectfeature = env_data.get("clientrectfeature") + self.headless_check = env_data.get("headless_check") + self.is_mobile = False + self.screen_color_depth = env_data.get("screen_color_depth") + + self.navigator_languages = kwargs.get("navigator_languages") if kwargs.get( + "navigator_languages") else self.navigator_languages + self.navigator_language = kwargs.get("navigator_language") if kwargs.get( + "navigator_language") else self.navigator_language + self.navigator_platform: str = kwargs.get("navigator_platform") if kwargs.get( + "navigator_platform") else self.navigator_platform + self.navigator_user_agent = kwargs.get("user_agent") if kwargs.get( + "user_agent") is not None else self.navigator_user_agent + if self.navigator_platform is not None: + self.sys_platform = "Windows" if self.navigator_platform.startswith("W") else "macOS" + else: + self.sys_platform = None + self.navigator_hardware_concurrency = kwargs.get("navigator_hardware_concurrency") if kwargs.get( + "navigator_hardware_concurrency") else self.navigator_hardware_concurrency + self.device_memory = kwargs.get("device_memory") if kwargs.get("device_memory") else self.device_memory + self.is_mobile = kwargs.get("is_mobile") if kwargs.get("is_mobile") is not None else self.is_mobile + self.browser_version = kwargs.get("browser_version") if kwargs.get("browser_version") else self.browser_version + self.screen_color_depth = kwargs.get("screen_color_depth") if kwargs.get( + "screen_color_depth") else self.screen_color_depth + + self.vendor = kwargs.get("vendor") if kwargs.get("vendor") is not None else self.vendor + self.renderer = kwargs.get("renderer") if kwargs.get("renderer") is not None else self.renderer + self.nav_vendor = kwargs.get("nav_vendor") if kwargs.get("nav_vendor") is not None else self.nav_vendor + self.runOnInsecureOrigins = kwargs.get("runOnInsecureOrigins") if kwargs.get( + "runOnInsecureOrigins") is not None else self.runOnInsecureOrigins + self.webdriver = kwargs.get("webdriver") if kwargs.get("webdriver") is not None else self.webdriver + self.webgl_vendor = kwargs.get("webgl_vendor") if kwargs.get( + "webgl_vendor") is not None else self.webgl_vendor + self.navigator_plugins = kwargs.get("navigator_plugins") if kwargs.get( + "navigator_plugins") is not None else self.navigator_plugins + self.navigator_permissions = kwargs.get("navigator_permissions") if kwargs.get( + "navigator_permissions") is not None else self.navigator_permissions + self.media_codecs = kwargs.get("media_codecs") if kwargs.get( + "media_codecs") is not None else self.media_codecs + self.iframe_content_window = kwargs.get("iframe_content_window") if kwargs.get( + "iframe_content_window") is not None else self.iframe_content_window + self.chrome_runtime = kwargs.get("chrome_runtime") if kwargs.get( + "chrome_runtime") is not None else self.chrome_runtime + self.chrome_load_times = kwargs.get("chrome_load_times") if kwargs.get( + "chrome_load_times") is not None else self.chrome_load_times + self.chrome_csi = kwargs.get("chrome_csi") if kwargs.get("chrome_csi") is not None else self.chrome_csi + self.chrome_app = kwargs.get("chrome_app") if kwargs.get("chrome_app") is not None else self.chrome_app + self.outerdimensions = kwargs.get("outerdimensions") if kwargs.get( + "outerdimensions") is not None else self.outerdimensions + self.hairline = kwargs.get("hairline") if kwargs.get("hairline") is not None else self.hairline + self.cssfeature = kwargs.get("cssfeature") if kwargs.get("cssfeature") is not None else self.cssfeature + self.fontsfeature = kwargs.get("fontsfeature") if kwargs.get("fontsfeature") is not None else self.fontsfeature + self.webrtc = kwargs.get("webrtc") if kwargs.get("webrtc") is not None else self.webrtc + self.canvasfeature = kwargs.get("canvasfeature") if kwargs.get( + "canvasfeature") is not None else self.canvasfeature + self.videofeature = kwargs.get("videofeature") if kwargs.get("videofeature") is not None else self.videofeature + self.clientrectfeature = kwargs.get("clientrectfeature") if kwargs.get( + "clientrectfeature") is not None else self.clientrectfeature + self.headless_check = kwargs.get("headless_check") if kwargs.get( + "headless_check") is not None else self.headless_check + + self.opts = { + "languages": self.navigator_languages, + "language": self.navigator_language, + "webgl_vendor": self.vendor, + "webgl_renderer": self.renderer, + "navigator_vendor": self.nav_vendor, + "navigator_platform": self.navigator_platform, + "navigator_user_agent": self.navigator_user_agent, + "navigator_app_version": self.navigator_user_agent.replace("Mozilla/", + "") if self.navigator_user_agent else None, + "runOnInsecureOrigins": self.runOnInsecureOrigins, + "navigator_hardware_concurrency": self.navigator_hardware_concurrency, + "device_memory": self.device_memory, + "user_agent_data": { + "brands": [{"brand": "Not)A;Brand", "version": "24"}, + {"brand": "Chromium", "version": f"{self.browser_version}"}, + {"brand": "Google Chrome", "version": f"{self.browser_version}"}], "mobile": self.is_mobile, + "platform": self.sys_platform} if self.browser_version is not None and self.is_mobile is not None and self.sys_platform is not None else None, + "cssfeature": self.cssfeature, + "fontsfeature": self.fontsfeature, + "webrtc": self.webrtc, + "canvasfeature": self.canvasfeature, + "videofeature": self.videofeature, + "clientrectfeature": self.clientrectfeature, + "headless_check": self.headless_check, + "fonts_start": 0, + 'screen_color_depth': self.screen_color_depth, + "mouse_event": { + "detail": self.mouse_detail, + "button": self.mouse_button, + "buttons": self.mouse_buttons + } + } + + @property + def enabled_scripts(self): + opts = json.dumps(self.opts) + # defined options constant + yield f'const opts = {opts}' + # init utils and generate_magic_arrays helper + yield SCRIPTS['utils'] + yield SCRIPTS['generate_magic_arrays'] + yield SCRIPTS['webgl_vendor'] + + if self.chrome_app: + yield SCRIPTS['chrome_app'] + if self.chrome_runtime: + yield SCRIPTS['chrome_runtime'] + if self.chrome_load_times: + yield SCRIPTS['chrome_load_times'] + if self.chrome_csi: + yield SCRIPTS['chrome_csi'] + if self.iframe_content_window: + yield SCRIPTS['iframe_content_window'] + if self.media_codecs: + yield SCRIPTS['media_codecs'] + if self.navigator_plugins: + yield SCRIPTS['navigator_plugins'] + if self.navigator_permissions: + yield SCRIPTS['navigator_permissions'] + if self.webdriver: + yield SCRIPTS['webdriver'] + if self.outerdimensions: + yield SCRIPTS['outerdimensions'] + if self.hairline: + yield SCRIPTS['chrome_hairline'] + + if self.opts.get("navigator_languages"): + yield SCRIPTS['navigator_languages'] + + if self.opts.get("navigator_vendor"): + yield SCRIPTS['navigator_vendor'] + + if self.opts.get("navigator_platform"): + yield SCRIPTS['navigator_platform'] + if self.opts.get("navigator_user_agent"): + yield SCRIPTS['navigator_user_agent'] + yield SCRIPTS['navigator_appVersion'] + + if self.opts.get("language"): + yield SCRIPTS['navigator_language'] + + if self.opts.get("user_agent_data"): + yield SCRIPTS['navigator_userAgentData'] + + if self.opts.get("navigator_hardware_concurrency"): + yield SCRIPTS['navigator_hardwareConcurrency'] + + if self.opts.get("device_memory"): + yield SCRIPTS['navigator_deviceMemory'] + + if self.opts.get("cssfeature"): + yield SCRIPTS['chrome_cssfeature'] + if self.opts.get("fontsfeature"): + yield SCRIPTS['chrome_fontsfeature'] + if self.opts.get("webrtc"): + yield SCRIPTS['chrome_webrtc'] + if self.opts.get("headless_check"): + yield SCRIPTS['hookfuc_headless'] + + if self.opts.get("canvasfeature"): + yield SCRIPTS['chrome_canvasfeature'] + yield SCRIPTS['chrome_canvasfeature2'] + if self.opts.get("videofeature"): + yield SCRIPTS['chrome_videofeature'] + if self.opts.get("clientrectfeature"): + yield SCRIPTS['chrome_clientrectfeature'] + + if self.opts.get("screen_color_depth"): + yield SCRIPTS['chrome_screen_colordepth'] + if self.opts.get("mouse_event"): + yield SCRIPTS['chrome_mouse_event'] diff --git a/reverse_oklink.py b/reverse-life/reverse_oklink.py similarity index 100% rename from reverse_oklink.py rename to reverse-life/reverse_oklink.py diff --git a/reverse_sxfae.py b/reverse-life/reverse_sxfae.py similarity index 96% rename from reverse_sxfae.py rename to reverse-life/reverse_sxfae.py index 15e052a..7b2b55a 100644 --- a/reverse_sxfae.py +++ b/reverse-life/reverse_sxfae.py @@ -1,110 +1,110 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# @Time : 2024/4/17 01:12 -# @Name : sxfae.py -# @Author : yanlee - -from loguru import logger -from faker import Faker -import requests -import base64 -from hashlib import md5 -from Crypto.Cipher import AES, PKCS1_v1_5 -from Crypto.PublicKey import RSA - -# 配置日志记录器 -logger.add("debug.log", rotation="1 week") - - -def generate_md5_signature(data): - """为提供的数据生成MD5签名。""" - return md5(data.encode()).hexdigest() - - -def zero_pad(data): - """应用零填充确保数据长度是AES块大小的倍数。""" - return data + b"\0" * (AES.block_size - len(data) % AES.block_size) - - -def encrypt_aes(key, iv, text): - """使用AES加密和CBC模式加密文本。""" - try: - padded_text = zero_pad(text.encode('utf-8')) - cipher = AES.new(key.encode('utf-8'), AES.MODE_CBC, iv.encode('utf-8')) - return base64.b64encode(cipher.encrypt(padded_text)).decode() - except Exception as e: - logger.error(f"加密文本失败: {e}") - raise - - -def encrypt_rsa(public_key, key): - """使用RSA公钥加密AES密钥。""" - try: - rsa_key = RSA.importKey(public_key) - cipher = PKCS1_v1_5.new(rsa_key) - return base64.b64encode(cipher.encrypt(key.encode())).decode() - except Exception as e: - logger.error(f"使用RSA加密失败: {e}") - raise - - -def generate_headers(): - """生成请求用的HTTP头部,包括动态用户代理。""" - return { - 'Accept': 'application/json, text/plain, */*', - 'Accept-Language': 'zh-CN,zh;q=0.9', - 'Cache-Control': 'no-cache', - 'Connection': 'keep-alive', - 'Content-Type': 'application/json;charset=UTF-8', - 'Origin': 'https://jjbl.sxfae.com', - 'Pragma': 'no-cache', - 'Referer': 'https://jjbl.sxfae.com/marketingIndex', - 'User-Agent': Faker().chrome() - } - - -def fetch_rsa_public_key(url, headers): - """给定URL获取RSA公钥。""" - try: - response = requests.post(url, headers=headers, json={}) - response.raise_for_status() # 对4XX或5XX错误抛出异常 - rsa_public_key = response.json()["data"]["publicKey"] - return rsa_public_key - except requests.exceptions.RequestException as e: - logger.error(f"获取RSA公钥失败: {e}") - raise - - -def prepare_encrypted_payload(page, rsa_public_key): - """准备带有签名数据的加密参数进行传输。""" - try: - ciphertext = f'{{"page": {page},"size": 10}}' - md5_signature = generate_md5_signature(ciphertext) - - key = 'XyrWHOmkaZEyRWHu' - iv = "szazgM3zOYCCHWih" - encrypted_data = encrypt_aes(key, iv, ciphertext) - - rsa_key = f"key_{key}|iv_{iv}" - encrypted_rsa_key = encrypt_rsa(rsa_public_key, rsa_key) - - return { - 'sign': md5_signature, - 'data': encrypted_data, - 'rsaKey': encrypted_rsa_key - } - except Exception as e: - logger.error(f"准备加密参数失败: {e}") - raise - - -if __name__ == '__main__': - try: - headers = generate_headers() - rsa_public_key = fetch_rsa_public_key('https://jjbl.sxfae.com/sxfaeApi/000002', headers) - current_page = 1 - json_data = prepare_encrypted_payload(current_page, rsa_public_key) - response = requests.post('https://jjbl.sxfae.com/sxfaeApi/801014', headers=headers, json=json_data) - print(response.json()) - except Exception as e: - logger.error(f"主执行块中发生错误: {e}") +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# @Time : 2024/4/17 01:12 +# @Name : sxfae.py +# @Author : yanlee + +from loguru import logger +from faker import Faker +import requests +import base64 +from hashlib import md5 +from Crypto.Cipher import AES, PKCS1_v1_5 +from Crypto.PublicKey import RSA + +# 配置日志记录器 +logger.add("debug.log", rotation="1 week") + + +def generate_md5_signature(data): + """为提供的数据生成MD5签名。""" + return md5(data.encode()).hexdigest() + + +def zero_pad(data): + """应用零填充确保数据长度是AES块大小的倍数。""" + return data + b"\0" * (AES.block_size - len(data) % AES.block_size) + + +def encrypt_aes(key, iv, text): + """使用AES加密和CBC模式加密文本。""" + try: + padded_text = zero_pad(text.encode('utf-8')) + cipher = AES.new(key.encode('utf-8'), AES.MODE_CBC, iv.encode('utf-8')) + return base64.b64encode(cipher.encrypt(padded_text)).decode() + except Exception as e: + logger.error(f"加密文本失败: {e}") + raise + + +def encrypt_rsa(public_key, key): + """使用RSA公钥加密AES密钥。""" + try: + rsa_key = RSA.importKey(public_key) + cipher = PKCS1_v1_5.new(rsa_key) + return base64.b64encode(cipher.encrypt(key.encode())).decode() + except Exception as e: + logger.error(f"使用RSA加密失败: {e}") + raise + + +def generate_headers(): + """生成请求用的HTTP头部,包括动态用户代理。""" + return { + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'zh-CN,zh;q=0.9', + 'Cache-Control': 'no-cache', + 'Connection': 'keep-alive', + 'Content-Type': 'application/json;charset=UTF-8', + 'Origin': 'https://jjbl.sxfae.com', + 'Pragma': 'no-cache', + 'Referer': 'https://jjbl.sxfae.com/marketingIndex', + 'User-Agent': Faker().chrome() + } + + +def fetch_rsa_public_key(url, headers): + """给定URL获取RSA公钥。""" + try: + response = requests.post(url, headers=headers, json={}) + response.raise_for_status() # 对4XX或5XX错误抛出异常 + rsa_public_key = response.json()["data"]["publicKey"] + return rsa_public_key + except requests.exceptions.RequestException as e: + logger.error(f"获取RSA公钥失败: {e}") + raise + + +def prepare_encrypted_payload(page, rsa_public_key): + """准备带有签名数据的加密参数进行传输。""" + try: + ciphertext = f'{{"page": {page},"size": 10}}' + md5_signature = generate_md5_signature(ciphertext) + + key = 'XyrWHOmkaZEyRWHu' + iv = "szazgM3zOYCCHWih" + encrypted_data = encrypt_aes(key, iv, ciphertext) + + rsa_key = f"key_{key}|iv_{iv}" + encrypted_rsa_key = encrypt_rsa(rsa_public_key, rsa_key) + + return { + 'sign': md5_signature, + 'data': encrypted_data, + 'rsaKey': encrypted_rsa_key + } + except Exception as e: + logger.error(f"准备加密参数失败: {e}") + raise + + +if __name__ == '__main__': + try: + headers = generate_headers() + rsa_public_key = fetch_rsa_public_key('https://jjbl.sxfae.com/sxfaeApi/000002', headers) + current_page = 1 + json_data = prepare_encrypted_payload(current_page, rsa_public_key) + response = requests.post('https://jjbl.sxfae.com/sxfaeApi/801014', headers=headers, json=json_data) + print(response.json()) + except Exception as e: + logger.error(f"主执行块中发生错误: {e}") diff --git a/sign.js b/reverse-life/sign.js similarity index 100% rename from sign.js rename to reverse-life/sign.js