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