├── resources └── keys.json ├── README.md ├── core ├── obfuscation │ ├── dapib.py │ ├── crypto.py │ └── proof_of_work.py ├── utilities │ ├── output.py │ └── ip_intelligence.py ├── image │ └── image_classification.py ├── mouse_movement │ └── biometrics.py ├── arkose_session │ ├── arkose_handler.py │ └── funcaptcha_session.py └── browser │ ├── arkose_bda.py │ └── fingerprint.py ├── Example_fp.json ├── administation.py ├── testfile.py ├── app.py └── bda.txt /resources/keys.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "key": "BLOXCAPTCHA-ADMIN-6FBNYL1CO61RT6897G0LRMS33BIHUCG5", 4 | "balance": 99997.75980000125 5 | } 6 | ] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Funcaptcha solver 2 | 3 | > [!IMPORTANT] 4 | > as of 25/3/2025 the xevil node used by this project is no longer active, to change it edit [this line](https://github.com/BoarIncorporated/Funcaptcha-Solver-Bloxcaptcha/blob/main/core/image/image_classification.py#L13) 5 | 6 | ~~Back up as of 28/3/2025~~ 7 | Back down as of 15/04/2025 8 | 9 | For alternatives or a new unflagged solver please contact https://t.me/Dinglenut18 (paid) 10 | 11 | To purchase high quality unused fingerprint go to https://fingerprinting.my or contact my telegram (min 10k do not try to bargain to 1k please) 12 | -------------------------------------------------------------------------------- /core/obfuscation/dapib.py: -------------------------------------------------------------------------------- 1 | from curl_cffi.requests import Session 2 | from re import search, sub 3 | from subprocess import run, PIPE 4 | from typing import List, Dict 5 | 6 | __all__ = ("DapibBreaker",) 7 | 8 | 9 | class DapibBreaker: 10 | def __init__(self, session: Session, base_url: str, secure_url: str) -> None: 11 | self.http_session: Session = session 12 | self.base_url: str = base_url 13 | self.secure_url: str = secure_url 14 | 15 | def fetch_transformed_guess(self, answer_list: List[str], call_count: int) -> str: 16 | pattern = r'([a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12})/(\d+)' 17 | match = search(pattern, self.base_url) 18 | 19 | uuid = match.group(1) 20 | number = match.group(2) 21 | 22 | if call_count == 0: 23 | self.http_session.headers["Origin"] = self.secure_url 24 | self.http_session.get(f"{self.secure_url}/params/sri/dapib/{uuid}/{number}") 25 | 26 | self.http_session.headers["Sec-Fetch-Dest"] = "script" 27 | js_code = self.http_session.get(self.base_url).text 28 | self.http_session.headers["Sec-Fetch-Dest"] = "empty" 29 | 30 | js_code = js_code.replace("(function(){const ", "function main(){const ") 31 | pattern = r'function\s+(\w+)\(answers\)' 32 | match = search(pattern, js_code) 33 | function_name = match.group(1) 34 | js_code = sub(r'try{.+', '{try{console.log(JSON.stringify(' + function_name + '(' + str(answer_list) + ')));}catch(e){}}};main();', js_code) 35 | 36 | result = run(["node", "-e", js_code], stdout=PIPE).stdout.decode("utf-8").strip("\n") 37 | return result 38 | -------------------------------------------------------------------------------- /core/utilities/output.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from threading import Lock 3 | from typing import Final 4 | 5 | 6 | LOCK: Final[Lock] = Lock() 7 | 8 | 9 | class Console: 10 | def __init__(self) -> None: 11 | pass 12 | 13 | def _print_success( 14 | self, token: str, waves: str, game_type: str, variant: str 15 | ) -> None: 16 | with LOCK: 17 | print( 18 | f"\033[96mBloxCAPTCHA\033[0m | \033[91m{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\033[0m | \033[92mSolved\033[0m | \033[94mTOKEN\033[0m\033[90m[\033[0m{token}\033[90m]\033[0m :: \033[94mWAVES\033[0m\033[90m[\033[0m{waves}\033[90m]\033[0m :: \033[94mGAME-TYPE\033[0m\033[90m[\033[0m{game_type}\033[90m]\033[0m :: \033[94mVARIANT\033[0m\033[90m[\033[0m{variant}\033[90m]\033[0m" 19 | ) 20 | 21 | def _print_failed( 22 | self, token: str, waves: str, game_type: str, variant: str 23 | ) -> None: 24 | with LOCK: 25 | print( 26 | f"\033[96mBloxCAPTCHA\033[0m | \033[91m{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\033[0m | \033[91mFailed\033[0m | \033[94mTOKEN\033[0m\033[90m[\033[0m{token}\033[90m]\033[0m :: \033[94mWAVES\033[0m\033[90m[\033[0m{waves}\033[90m]\033[0m :: \033[94mGAME-TYPE\033[0m\033[90m[\033[0m{game_type}\033[90m]\033[0m :: \033[94mVARIANT\033[0m\033[90m[\033[0m{variant}\033[90m]\033[0m" 27 | ) 28 | 29 | def _print_challenge( 30 | self, token: str, waves: str, game_type: str, variant: str 31 | ) -> None: 32 | with LOCK: 33 | print( 34 | f"\033[96mBloxCAPTCHA\033[0m | \033[91m{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\033[0m | \033[38;2;255;165;0mChallenge\033[0m | \033[94mTOKEN\033[0m\033[90m[\033[0m{token}\033[90m]\033[0m :: \033[94mWAVES\033[0m\033[90m[\033[0m{waves}\033[90m]\033[0m :: \033[94mGAME-TYPE\033[0m\033[90m[\033[0m{game_type}\033[90m]\033[0m :: \033[94mVARIANT\033[0m\033[90m[\033[0m{variant}\033[90m]\033[0m" 35 | ) 36 | -------------------------------------------------------------------------------- /core/obfuscation/crypto.py: -------------------------------------------------------------------------------- 1 | from json import loads, dumps 2 | from base64 import b64decode, b64encode 3 | from hashlib import md5 4 | from string import ascii_lowercase 5 | from random import choice 6 | from Crypto.Cipher import AES 7 | from Crypto.Util.Padding import pad, unpad 8 | from typing import Dict, Any 9 | 10 | 11 | __all__ = ("decrypt_data", "encrypt_data") 12 | 13 | 14 | def _generate_salted_key(key: str, salt: str) -> str: 15 | salted = "" 16 | dx = b"" 17 | 18 | for _ in range(3): 19 | dx = md5(dx + key.encode("utf-8") + salt.encode("utf-8")).digest() 20 | salted += dx.hex() 21 | 22 | return salted 23 | 24 | 25 | def decrypt_data(raw_data: str, key: str) -> str: 26 | data: Dict[str, str] = loads(raw_data) 27 | 28 | salt: bytes = bytes.fromhex(data["s"]) 29 | salted_key: str = _generate_salted_key(key, salt.decode("utf-8")) 30 | 31 | aes_key: bytes = bytes.fromhex(salted_key[:64]) 32 | iv: bytes = bytes.fromhex(data["iv"]) 33 | 34 | aes = AES.new(aes_key, AES.MODE_CBC, iv) 35 | 36 | cipher_text: bytes = b64decode(data["ct"]) 37 | decrypted_data: bytes = unpad(aes.decrypt(cipher_text), AES.block_size) 38 | 39 | return decrypted_data.decode("utf-8") 40 | 41 | 42 | def encrypt_data(data: str, key: str, switch_order: bool = False) -> str: 43 | salt: str = "".join(choice(ascii_lowercase) for _ in range(8)) 44 | salted_key: str = _generate_salted_key(key, salt) 45 | 46 | aes_key: bytes = bytes.fromhex(salted_key[:64]) 47 | iv: bytes = bytes.fromhex(salted_key[64:96]) 48 | 49 | aes = AES.new(aes_key, AES.MODE_CBC, iv) 50 | 51 | padded_data: bytes = pad(data.encode("utf-8"), AES.block_size) 52 | cipher_text: bytes = aes.encrypt(padded_data) 53 | 54 | encrypted_data: Dict[str, Any] = { 55 | "ct": b64encode(cipher_text).decode("utf-8"), 56 | "iv": iv.hex(), 57 | "s": salt.encode("utf-8").hex(), 58 | } 59 | 60 | if switch_order: 61 | encrypted_data = { 62 | "ct": b64encode(cipher_text).decode("utf-8"), 63 | "s": salt.encode("utf-8").hex(), 64 | "iv": iv.hex(), 65 | } 66 | 67 | return dumps(encrypted_data, separators=(",", ":")) 68 | -------------------------------------------------------------------------------- /Example_fp.json: -------------------------------------------------------------------------------- 1 | { 2 | "doNotTrack": "unknown", 3 | "colorDepth": 24, 4 | "pixelRatio": 0.8999999761581421, 5 | "screen": [ 6 | 2135, 7 | 1064 8 | ], 9 | "availScreen": [ 10 | 2135, 11 | 1064 12 | ], 13 | "platform": "Win32", 14 | "cfp": "-161538", 15 | "plugins": "", 16 | "hardwareConcurrency": 2, 17 | "webglExtensions": "ANGLE_instanced_arrays;EXT_blend_minmax;EXT_clip_control;EXT_color_buffer_half_float;EXT_depth_clamp;EXT_disjoint_timer_query;EXT_float_blend;EXT_frag_depth;EXT_polygon_offset_clamp;EXT_shader_texture_lod;EXT_texture_compression_bptc;EXT_texture_compression_rgtc;EXT_texture_filter_anisotropic;EXT_texture_mirror_clamp_to_edge;EXT_sRGB;KHR_parallel_shader_compile;OES_element_index_uint;OES_fbo_render_mipmap;OES_standard_derivatives;OES_texture_float;OES_texture_float_linear;OES_texture_half_float;OES_texture_half_float_linear;OES_vertex_array_object;WEBGL_blend_func_extended;WEBGL_color_buffer_float;WEBGL_compressed_texture_s3tc;WEBGL_compressed_texture_s3tc_srgb;WEBGL_debug_renderer_info;WEBGL_debug_shaders;WEBGL_depth_texture;WEBGL_draw_buffers;WEBGL_lose_context;WEBGL_multi_draw;WEBGL_polygon_mode", 18 | "webglRenderer": "WebKit WebGL", 19 | "webglVendor": "WebKit", 20 | "webglVersion": "WebGL 1.0 (OpenGL ES 2.0 Chromium)", 21 | "webglShadingLanguageVersion": "WebGL GLSL ES 1.0 (OpenGL ES GLSL ES 1.0 Chromium)", 22 | "webglAliasedLineWidthRange": "[1, 1]", 23 | "webglAliasedPointSizeRange": "[1, 1024]", 24 | "webglAntialiasing": "yes", 25 | "webglMaxParams": "16,32,16384,1024,16384,16384,30,16,16,4096", 26 | "webglMaxViewportDims": "[32767, 32767]", 27 | "webglUnmaskedVendor": "Google Inc.", 28 | "webglUnmaskedRenderer": "ANGLE (AMD, AMD Radeon RX 6650 XT (0x000073EF) Direct3D11 vs_5_0 ps_5_0, D3D11)", 29 | "webglVsfParams": "23,127,127,127,127,127,127,127,127", 30 | "webglVsiParams": "0,31,30,31,30,31,30,31,30", 31 | "webglFsfParams": "23,127,127,127,127,127,127,127,127", 32 | "webglFsiParams": "0,31,30,31,30,31,30,31,30", 33 | "audioCodecs": "blabla", 34 | "videoCodecs": "blabla", 35 | "audioCodecsExtended": "blabla", 36 | "videoCodecsExtended": "blabla", 37 | "cssMediaQueries": 0, 38 | "cssColorGamut": "srgb", 39 | "cssContrast": "no-preference", 40 | "cssMonochrome": false, 41 | "cssPointer": "fine", 42 | "cssGridSupport": false 43 | } 44 | 45 | -------------------------------------------------------------------------------- /administation.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import os 3 | 4 | action = input("Enter admin action: ") 5 | admin_key = "NOVAK-Ssh3OOXK5pldXmOHobhh3UZnaAE9p3q" 6 | 7 | 8 | def admin_actions() -> None: 9 | if str(action) == "add_key": 10 | key_string: str = input("Enter key string: ") 11 | balance_amount: int = input("Enter balance amount: ") 12 | data: dict = { 13 | "admin_key": admin_key, 14 | "action": str(action), 15 | "key": key_string, 16 | "balance": balance_amount, 17 | } 18 | elif str(action) == "gen_key": 19 | balance_amount: int = input("Enter balance amount: ") 20 | data: dict = { 21 | "admin_key": admin_key, 22 | "action": str(action), 23 | "balance": int(balance_amount), 24 | } 25 | 26 | elif str(action) == "remove_key": 27 | key_string: str = input("Enter key string: ") 28 | data: dict = {"admin_key": admin_key, "action": str(action), "key": key_string} 29 | elif str(action) == "set_balance": 30 | key_string: str = input("Enter key string: ") 31 | balance_amount: int = input("Enter balance amount: ") 32 | data: dict = { 33 | "admin_key": admin_key, 34 | "action": str(action), 35 | "key": key_string, 36 | "balance": balance_amount, 37 | } 38 | 39 | elif str(action) == "increase_balance": 40 | key_string: str = input("Enter key string: ") 41 | balance_amount: int = input("Enter balance amount: ") 42 | data: dict = { 43 | "admin_key": admin_key, 44 | "action": str(action), 45 | "key": key_string, 46 | "balance": balance_amount, 47 | } 48 | 49 | elif str(action) == "decrease_balance": 50 | key_string: str = input("Enter key string: ") 51 | balance_amount: int = input("Enter balance amount: ") 52 | data: dict = { 53 | "admin_key": admin_key, 54 | "action": str(action), 55 | "key": key_string, 56 | "balance": balance_amount, 57 | } 58 | 59 | response = requests.post("http://127.0.0.1:5000/admin", json=data) 60 | 61 | print(f"Response Code: {response.status_code} | JSON: {response.json()}") 62 | 63 | 64 | if __name__ == "__main__": 65 | os.system("cls" if os.name == "nt" else "clear") 66 | admin_actions() 67 | -------------------------------------------------------------------------------- /core/utilities/ip_intelligence.py: -------------------------------------------------------------------------------- 1 | import pytz 2 | from curl_cffi.requests import Session 3 | from datetime import datetime, timezone 4 | from typing import Dict 5 | 6 | __all__ = ("IpIntelligence",) 7 | 8 | class IpIntelligence: 9 | def __init__(self, session: Session) -> None: 10 | self.http_session: Session = session 11 | 12 | def fetch_ip_data(self) -> Dict[str, str | int]: 13 | response_ip_address: Dict[str, str] = self.http_session.get( 14 | "https://wtfismyip.com/json" 15 | ) 16 | response_json: Dict[str, str] = self.http_session.get( 17 | "https://api.ipfind.com/", 18 | headers={ 19 | "origin": "https://ipfind.com", 20 | "referer": "https://ipfind.com/", 21 | }, 22 | params={"ip": response_ip_address.json()["YourFuckingIPAddress"]}, 23 | ).json() 24 | time_zone: Dict[str, str] = response_json["timezone"] 25 | timezone_offset: int = self.calculate_timezone_offset(time_zone) 26 | 27 | language_code: str = response_json["languages"][0] 28 | main_language: str = ( 29 | f"{language_code}-{language_code.upper()}" 30 | if len(language_code) == 2 31 | else language_code 32 | ) 33 | 34 | language_list: str = ",".join([main_language, main_language.split("-")[0]]) 35 | 36 | accept_language: str = self.build_accept_language(language_list) 37 | 38 | data = { 39 | "timezone_offset": timezone_offset, 40 | "language": main_language, 41 | "languages": language_list, 42 | "accept_language": accept_language, 43 | } 44 | return data 45 | 46 | def calculate_timezone_offset(self, timezone_str: str) -> int: 47 | utc_now = datetime.now(timezone.utc) 48 | local_now = utc_now.astimezone(pytz.timezone(timezone_str)) 49 | 50 | utf_offset = local_now.utcoffset() 51 | if not utf_offset: 52 | return 0 53 | 54 | return int((utf_offset.total_seconds() / 60) * -1) 55 | 56 | def build_accept_language(self, languages: str) -> str: 57 | language_list: list[str] = languages.split(",") 58 | result: list[str] = [] 59 | 60 | for i, lang in enumerate(language_list): 61 | if i == 0: 62 | result.append(lang) 63 | else: 64 | q_value: float = 1.0 - (i * 0.1) 65 | result.append(f"{lang};q={q_value:.1f}") 66 | 67 | return ",".join(result) 68 | 69 | -------------------------------------------------------------------------------- /core/image/image_classification.py: -------------------------------------------------------------------------------- 1 | import time 2 | import random 3 | 4 | from curl_cffi.requests import Session 5 | from typing import Dict, List, Tuple, Optional 6 | 7 | __all__ = ["ImageClassification"] 8 | 9 | 10 | class ImageClassification: 11 | def __init__(self) -> None: 12 | self.xevil_nodes: List[Tuple[str, str]] = [ 13 | ("http://193.233.254.2:80", "bd205a35d544e2981ff852820af2a723"), 14 | ] 15 | self.api_url_create: str = "/in.php" 16 | self.api_url_get: str = "/res.php" 17 | self.request_session: Session = Session() 18 | self.request_session.timeout = 10 19 | 20 | def classify_image(self, image_data: bytes, task_description: str) -> Optional[int]: 21 | print(image_data[:32]) 22 | shuffled_nodes = self.xevil_nodes[:] 23 | random.shuffle(shuffled_nodes) 24 | for api_url, api_key in shuffled_nodes: 25 | payload: Dict[str, str] = { 26 | "key": api_key, 27 | "recaptcha": "1", 28 | "method": "base64", 29 | "body": image_data.decode(), 30 | "imginstructions": task_description, 31 | } 32 | 33 | try: 34 | response = self.request_session.post( 35 | f"{api_url}{self.api_url_create}", data=payload 36 | ) 37 | 38 | response_content: str = response.text 39 | if not response_content.startswith("OK"): 40 | continue 41 | 42 | task_id: str = response_content.split("|")[1] 43 | status_payload: Dict[str, str] = { 44 | "key": api_key, 45 | "action": "get", 46 | "id": task_id, 47 | } 48 | while True: 49 | solution_response = self.request_session.post( 50 | f"{api_url}{self.api_url_get}", data=status_payload 51 | ) 52 | solution_content = solution_response.text 53 | 54 | if solution_content.startswith("OK"): 55 | result = int(solution_content.split("|")[1]) - 1 56 | return result 57 | 58 | elif solution_content == "ERROR_CAPTCHA_UNSOLVABLE": 59 | break 60 | 61 | time.sleep(1) 62 | 63 | except Exception: 64 | return self.classify_image(image_data, task_description) 65 | 66 | return None 67 | -------------------------------------------------------------------------------- /core/mouse_movement/biometrics.py: -------------------------------------------------------------------------------- 1 | from random import randint, choice, random 2 | from typing import List, Optional 3 | 4 | __all__ = ("Biometrics",) 5 | 6 | 7 | class Biometrics: 8 | def __init__(self) -> None: 9 | self.mouse_bio: str = "" 10 | self.last_movement: Optional[List[int]] = None 11 | self.current_x: int = 0 12 | self.current_y: int = 0 13 | self.current_time: int = 0 14 | self.movement_count: int = 0 15 | 16 | self.initialize_starting_point() 17 | self.generate_mouse_bio() 18 | 19 | def initialize_starting_point(self) -> None: 20 | self.current_time = randint(1000, 3000) 21 | self.current_x, self.current_y = randint(100, 300), randint(100, 300) 22 | 23 | movement: List[int] = [self.current_time, 0, self.current_x, self.current_y] 24 | self.last_movement = movement 25 | 26 | self.mouse_bio += f"{self.current_time},0,{self.current_x},{self.current_y};" 27 | self.movement_count += 1 28 | 29 | def generate_mouse_bio(self) -> None: 30 | max_movements: int = randint(10, 149) 31 | movement_index: int = 0 32 | 33 | increase_x: bool = choice([True, False]) 34 | increase_y: bool = choice([True, False]) 35 | 36 | direction_steps_x: int = randint(1, 3) if random() <= 0.7 else randint(4, 15) 37 | direction_steps_y: int = randint(1, 3) if random() <= 0.7 else randint(4, 15) 38 | 39 | while movement_index < max_movements: 40 | self.current_time += randint(10, 60) 41 | 42 | if direction_steps_x == 0: 43 | direction_steps_x = randint(1, 5) 44 | increase_x = choice([True, False]) 45 | 46 | if direction_steps_y == 0: 47 | direction_steps_y = randint(1, 5) 48 | increase_y = choice([True, False]) 49 | 50 | if not increase_x and self.current_x - 10 > 0: 51 | self.current_x -= randint(1, 3) if random() <= 0.7 else randint(4, 15) 52 | elif increase_x and self.current_x + 10 < 500: 53 | self.current_x += randint(1, 3) if random() <= 0.7 else randint(4, 15) 54 | 55 | if not increase_y and self.current_y - 10 > 0: 56 | self.current_y -= randint(1, 3) if random() <= 0.7 else randint(4, 15) 57 | elif increase_y and self.current_y + 10 < 500: 58 | self.current_y += randint(1, 3) if random() <= 0.7 else randint(4, 15) 59 | 60 | movements_remaining: int = max_movements - movement_index 61 | 62 | if movements_remaining == 2: 63 | self.mouse_bio += ( 64 | f"{self.current_time},1,{self.current_x},{self.current_y};" 65 | ) 66 | elif movements_remaining == 1: 67 | self.mouse_bio += ( 68 | f"{self.current_time},2,{self.current_x},{self.current_y};" 69 | ) 70 | else: 71 | self.mouse_bio += ( 72 | f"{self.current_time},0,{self.current_x},{self.current_y};" 73 | ) 74 | 75 | direction_steps_x -= 1 76 | direction_steps_y -= 1 77 | 78 | movement_index += 1 79 | 80 | def retrieve_mouse_bio(self) -> str: 81 | return self.mouse_bio 82 | -------------------------------------------------------------------------------- /testfile.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Dict 2 | from curl_cffi import requests 3 | import re 4 | 5 | def solve_funcaptcha(proxy_url: str) -> str | None: 6 | session: requests.Session = requests.Session() 7 | challengeInfo: Dict[str, Any] = { 8 | "public_key": "85800716-F435-4981-864C-8B90602D10F7", 9 | "website_url": "https://www.match.com", 10 | "service_url": "https://match-api.arkoselabs.com", 11 | "capi_mode": "lightbox", 12 | "style_theme": "default", 13 | "language_enabled": False, 14 | "jsf_enabled": True, 15 | #"extra_data": {"blob": blob_data}, 16 | "ancestor_origins": ["https://www.match.com"], 17 | "tree_index": [5], 18 | "tree_structure": "[[[]],[],[],[[]],[],[],[],[],[],[],[]]", 19 | "location_h_ref": "https://www.match.com/login", 20 | } 21 | 22 | browserInfo: Dict[str, str] = { 23 | "Cookie": "", 24 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36", 25 | } 26 | 27 | payload: dict[str, Any] = { 28 | "api_key": "BLOXCAPTCHA-ADMIN-6FBNYL1CO61RT6897G0LRMS33BIHUCG5", 29 | "challenge_info": challengeInfo, 30 | "browser_info": browserInfo, 31 | "proxy": "http://25740687-res_US_sppj4m2fmer:ocfgtron@gw.cloudbypass.com:1288",#proxy_url, 32 | } 33 | 34 | solution: Dict = session.post( 35 | "http://127.0.0.1:5000/solve/FunCaptcha", json=payload, timeout=600 36 | ).json() 37 | print(solution) 38 | if "solution" in solution: 39 | return solution.get("solution", None) 40 | 41 | 42 | USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36" 43 | URL_LOGIN = "https://mobi.match.com/login" 44 | URL_API = "https://mobi.match.com/api" 45 | 46 | headers = { 47 | "User-Agent": USER_AGENT, 48 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", 49 | "Accept-Language": "en-US,en;q=0.5", 50 | "Accept-Encoding": "gzip, deflate, br", 51 | "Connection": "keep-alive", 52 | } 53 | 54 | response = requests.get(URL_LOGIN, headers=headers, proxies={"https": "http://25740687-res_US_sppj4m2fmer:ocfgtron@gw.cloudbypass.com:1288"}) 55 | csrf_token = re.search(r'"_csrf":"(.*?)"', response.text).group(1) 56 | 57 | solution = solve_funcaptcha("http://127.0.0.1:5000/solve/FunCaptcha") 58 | 59 | data = { 60 | "requests": { 61 | "g0": { 62 | "resource": "auth", 63 | "operation": "create", 64 | "params": "login", 65 | "body": { 66 | "email": "tgrube79@gmail.com", 67 | "password": "pumpkin44", 68 | "rememberMe": True, 69 | "submitted": False, 70 | "recaptchaResponse": f"a:{solution}", 71 | "reactivateUrl": "reactivateAccount" 72 | } 73 | } 74 | }, 75 | "context": { 76 | "locale": "en-US", 77 | "_csrf": csrf_token 78 | } 79 | } 80 | 81 | headers.update({ 82 | "Content-Type": "application/json", 83 | "accept": "*/*", 84 | "x-requested-with": "XMLHttpRequest", 85 | "adrum": "isAjax:true", 86 | "Referer": URL_LOGIN 87 | }) 88 | 89 | # Make the login request 90 | response = requests.post(f"{URL_API}?_csrf={csrf_token}&locale=en-US", json=data, headers=headers, proxies={"https": "http://25740687-res_US_sppj4m2fmer:ocfgtron@gw.cloudbypass.com:1288"}) 91 | 92 | print(response) 93 | print(response.text) 94 | -------------------------------------------------------------------------------- /core/arkose_session/arkose_handler.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, Optional 2 | from core.arkose_session.funcaptcha_session import FunCaptchaSolver 3 | from core.image.image_classification import ImageClassification 4 | from core.utilities.output import Console 5 | import concurrent.futures 6 | from time import sleep 7 | 8 | OUTPUT = Console() 9 | 10 | __all__ = "FunCaptchaTask" 11 | 12 | 13 | class FunCaptchaTask: 14 | def __init__( 15 | self, 16 | challenge_info: Dict[str, str], 17 | browser_info: Dict[str, str], 18 | bda_fingerprint: Dict[str, str], 19 | proxy: Optional[str] = None, 20 | ) -> None: 21 | self.proxy = proxy if proxy != "" else None 22 | self.interactor = FunCaptchaSolver( 23 | challenge_info, browser_info, bda_fingerprint, self.proxy 24 | ) 25 | 26 | def _classify_image(self, wave_index: int, old_headers: Dict[str, str]) -> int: 27 | self.interactor.http_session.headers = old_headers 28 | img = self.interactor._get_base64_image(wave_index) 29 | return ImageClassification().classify_image(img, self.interactor.variant) 30 | 31 | def _solve_challenge(self) -> Dict[str, bool]: 32 | self.interactor._get_cloudfare_cookie() 33 | self.interactor._get_funcaptcha_token() 34 | if not self.interactor.funcaptcha_token: 35 | raise ValueError( 36 | "Failed to generate FunCaptcha Token, Fingerprint/Blob related issue." 37 | ) 38 | 39 | if "sup=1" in self.interactor.funcaptcha_token: 40 | OUTPUT._print_success( 41 | self.interactor.funcaptcha_token.split("|")[0], 0, 4, "suppressed" 42 | ) 43 | return {"success": True, "solution": self.interactor.funcaptcha_token} 44 | 45 | self.interactor._get_challenge() 46 | 47 | OUTPUT._print_challenge( 48 | self.interactor.session_token, 49 | self.interactor.waves, 50 | self.interactor.game_type, 51 | self.interactor.variant 52 | ) 53 | 54 | old_headers = self.interactor.http_session.headers.copy() 55 | correct_indices = [] 56 | with concurrent.futures.ThreadPoolExecutor() as executor: 57 | futures = { 58 | executor.submit( 59 | self._classify_image, wave_index, old_headers 60 | ): wave_index 61 | for wave_index in range(self.interactor.waves) 62 | } 63 | 64 | results = {} 65 | for future in concurrent.futures.as_completed(futures): 66 | 67 | wave_index = futures[future] 68 | results[wave_index] = future.result() 69 | 70 | correct_indices = [results[wave_index] for wave_index in sorted(results)] 71 | for correct_index in correct_indices: 72 | self.interactor._set_biometrics() 73 | 74 | if self.interactor.game_type == 4: 75 | answer_response = self.interactor._submit_index_answer(correct_index) 76 | else: 77 | answer_response = self.interactor._submit_tile_answer(correct_index) 78 | 79 | solved = answer_response["solved"] 80 | 81 | if solved: 82 | OUTPUT._print_success( 83 | self.interactor.session_token, 84 | self.interactor.waves, 85 | self.interactor.game_type, 86 | self.interactor.variant 87 | ) 88 | return {"success": True, "solution": self.interactor.funcaptcha_token} 89 | 90 | OUTPUT._print_failed( 91 | self.interactor.session_token, 92 | self.interactor.waves, 93 | self.interactor.game_type, 94 | self.interactor.variant 95 | ) 96 | return {"success": False, "solution": "Failed to solve the captcha."} -------------------------------------------------------------------------------- /core/obfuscation/proof_of_work.py: -------------------------------------------------------------------------------- 1 | from hashlib import sha512, sha256 2 | from time import time 3 | from random import choices, randint 4 | from string import ascii_lowercase, digits 5 | from decimal import Decimal 6 | from typing import Final, Dict, Any 7 | 8 | __all__ = ("ProofOfWork",) 9 | 10 | 11 | class ProofOfWork: 12 | def __init__(self) -> None: 13 | self.START_TIME: Final[float] = time() - randint(20000, 50000) 14 | 15 | def generate_sha256_hash(self, input_data: str) -> str: 16 | return sha256(input_data.encode()).hexdigest() 17 | 18 | def generate_analytics_pow(self) -> Dict[str, Any]: 19 | return { 20 | "challenge": "".join(choices(ascii_lowercase + digits, k=9)), 21 | "difficulty": 2, 22 | "timeout": 2, 23 | } 24 | 25 | def solve_analytics_pow(self) -> Dict[str, Any]: 26 | state_tracker: Dict[str, Any] = { 27 | "tryEntries": [{"tryLoc": "root", "completion": {"type": "normal"}}], 28 | "prev": 0, 29 | "next": 0, 30 | "done": False, 31 | "delegate": None, 32 | "method": "next", 33 | } 34 | pow_data = self.generate_analytics_pow() 35 | 36 | challenge_str: str = pow_data["challenge"] 37 | difficulty: int = pow_data["difficulty"] 38 | timeout: int = pow_data["timeout"] 39 | 40 | attempt_count: int = 0 41 | start_offset: float = None 42 | expiration_time: float = None 43 | random_suffix: str = None 44 | hash_result: str = None 45 | exec_time: float = None 46 | time_spent: float = None 47 | average_time_per_attempt: float = None 48 | 49 | while True: 50 | state_tracker["prev"] = state_tracker["next"] 51 | if state_tracker["prev"] == 0: 52 | attempt_count = 0 53 | start_offset = time() - self.START_TIME 54 | expiration_time = start_offset + timeout 55 | if state_tracker["next"] == 3 or state_tracker["prev"] == 0: 56 | random_suffix = "".join(choices(ascii_lowercase + digits, k=15)) 57 | state_tracker["next"] = 7 58 | state_tracker["sent"] = self.generate_sha256_hash( 59 | challenge_str + random_suffix 60 | ) 61 | if state_tracker["next"] == 7: 62 | hash_result = state_tracker["sent"] 63 | attempt_count += 1 64 | if not hash_result.startswith("0" * difficulty): 65 | state_tracker["next"] = 14 66 | else: 67 | exec_time = time() - self.START_TIME 68 | time_spent = exec_time - start_offset 69 | average_time_per_attempt = time_spent / attempt_count 70 | return { 71 | "cs_": challenge_str, 72 | "ct_": str(attempt_count), 73 | "g_": random_suffix, 74 | "h_": hash_result, 75 | "pt_": str(Decimal(time_spent)), 76 | "aht_": str(Decimal(average_time_per_attempt)), 77 | } 78 | if state_tracker["next"] == 14: 79 | state_tracker["next"] = 16 80 | if state_tracker["next"] == 16: 81 | state_tracker["next"] = 3 82 | 83 | def solve_proof_of_work( 84 | self, pow_seed: str, leading_zero_count: int, session_token: str, pow_token: str 85 | ) -> Dict[str, Any]: 86 | interaction_count: int = 0 87 | initial_time: int = randint(2000, 4000) 88 | 89 | while True: 90 | interaction_count += 1 91 | random_suffix: str = "".join(choices(ascii_lowercase + digits, k=15)) 92 | hash_result: str = sha512((pow_seed + random_suffix).encode()).hexdigest() 93 | if hash_result[:leading_zero_count] == "0" * leading_zero_count: 94 | execution_time: int = initial_time - randint(1000, 1500) 95 | return { 96 | "session_token": session_token, 97 | "pow_token": pow_token, 98 | "result": random_suffix, 99 | "execution_time": round(execution_time), 100 | "iteration_count": interaction_count, 101 | "hash_rate": interaction_count / execution_time, 102 | } 103 | -------------------------------------------------------------------------------- /core/browser/arkose_bda.py: -------------------------------------------------------------------------------- 1 | from time import time 2 | from random import randint, choice 3 | from re import sub 4 | from json import dumps 5 | from base64 import b64encode 6 | from mmh3 import hash128 7 | from core.obfuscation.crypto import encrypt_data 8 | from core.browser.fingerprint import Fingerprint 9 | from typing import Union, Dict, List 10 | 11 | __all__ = ["ArkoseBDA"] 12 | 13 | 14 | class ArkoseBDA: 15 | def __init__( 16 | self, 17 | ip_info: Dict[str, str], 18 | challenge_info: Dict[str, str], 19 | browser_info: Dict[str, str], 20 | fingerprint_data: Dict[str, str], 21 | ) -> None: 22 | self.user_agent: str = browser_info["User-Agent"] 23 | self.fingerprint: Fingerprint = Fingerprint( 24 | ip_info, challenge_info, browser_info, fingerprint_data 25 | ) 26 | self.fingerprint_entries: List[str] = self._prepare_fingerprint_entries( 27 | self.fingerprint.generate_fingerprint_data() 28 | ) 29 | self.formatted_fingerprint: str = self._prepare_fingerprint( 30 | self.fingerprint.generate_fingerprint_data() 31 | ) 32 | self.enhanced_fp: List[Dict[str, str]] = self._prepare_enhanced_fp( 33 | self.fingerprint.get_enhanced_fingerprint() 34 | ) 35 | self.ife_hash_input: str = self._json_stringify( 36 | dumps(self.fingerprint_entries)[1:-1] 37 | ) 38 | 39 | def _x64hash128(self, data: str, seed: int) -> str: 40 | hashed_value: int = hash128(data, seed) 41 | return str( 42 | hex(((hashed_value & 0xFFFFFFFFFFFFFFFF) << 64) + (hashed_value >> 64)) 43 | ).removeprefix("0x") 44 | 45 | def _json_stringify(self, values: Union[str, List[str]]) -> Union[str, List[str]]: 46 | if isinstance(values, str): 47 | return ( 48 | values.replace("None", "null") 49 | .replace("True", "true") 50 | .replace("False", "false") 51 | .replace('"', "") 52 | ) 53 | return [ 54 | value.replace("None", "null") 55 | .replace("True", "true") 56 | .replace("False", "false") 57 | .replace('"', "") 58 | for value in values 59 | ] 60 | 61 | def _format_bda(self, bda: str) -> str: 62 | bda = sub( 63 | r'\{"key":"window__tree_index","value":\[(\d+,\s*\d+)\]\}', 64 | lambda match: sub(r"\s", "", match.group(0)), 65 | bda, 66 | ) 67 | bda = sub( 68 | r"(\"key\":\"navigator_connection_downlink\",\"value\":)\"(\d+\.?\d*)\"", 69 | lambda match: f"{match.group(1)}{match.group(2)}", 70 | bda, 71 | ) 72 | return bda.replace("\\\\", "\\").replace("\\u2062", "⁢").replace("\\u2063", "⁣") 73 | 74 | def _prepare_fingerprint(self, fingerprint: Dict[str, Union[str, int]]) -> str: 75 | return ";".join(str(value) for value in fingerprint.values()) 76 | 77 | def _prepare_fingerprint_entries( 78 | self, fingerprint: Dict[str, Union[str, int]] 79 | ) -> List[str]: 80 | return [f"{key}:{value}" for key, value in fingerprint.items()] 81 | 82 | def _prepare_enhanced_fp(self, enhanced_fp: Dict[str, str]) -> List[Dict[str, str]]: 83 | return [{"key": key, "value": value} for key, value in enhanced_fp.items()] 84 | 85 | def generate_bda(self) -> str: 86 | current_time: int = int(time()) 87 | rounded_time: str = str(current_time - (current_time % 21600)) 88 | base64_encoded_time: str = b64encode(str(current_time).encode("utf-8")).decode( 89 | "utf-8" 90 | ) 91 | history_length: str = str(randint(1, 8)) 92 | 93 | bda: List[Dict[str, Union[str, List[Dict[str, str]]]]] = [ 94 | {"key": "api_type", "value": "js"}, 95 | {"key": "f", "value": self._x64hash128(self.formatted_fingerprint, 0)}, 96 | {"key": "n", "value": base64_encoded_time}, 97 | { 98 | "key": "wh", 99 | "value": f"{''.join(choice('0123456789abcdef') for _ in range(32))}|72627afbfd19a741c7da1732218301ac", 100 | }, 101 | {"key": "enhanced_fp", "value": self.enhanced_fp}, 102 | {"key": "fe", "value": self.fingerprint_entries}, 103 | {"key": "ife_hash", "value": self._x64hash128(self.ife_hash_input, 38)}, 104 | { 105 | "key": "jsbd", 106 | "value": '{"HL":' 107 | + history_length 108 | + ',"NCE":true,"DT":"","NWD":"false","DMTO":1,"DOTO":1}', 109 | }, 110 | ] 111 | 112 | formatted_bda: str = self._format_bda(dumps(bda, separators=(",", ":"))) 113 | encryption_key: str = self.user_agent + rounded_time 114 | return encrypt_data(formatted_bda, encryption_key, True) -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, request, jsonify 2 | from core.arkose_session.arkose_handler import FunCaptchaTask 3 | from time import time 4 | from json import load 5 | from random import shuffle 6 | from threading import Lock 7 | from concurrent.futures import ThreadPoolExecutor 8 | from logging import NullHandler, getLogger 9 | import os 10 | import json 11 | import string 12 | import secrets 13 | import warnings 14 | 15 | warnings.filterwarnings( 16 | "ignore", 17 | category=UserWarning, 18 | module=r"curl_cffi\.requests\.session" 19 | ) 20 | 21 | 22 | app = Flask(__name__) 23 | logger = getLogger("werkzeug") 24 | logger.addHandler(NullHandler()) 25 | 26 | fingerprint_files: list[str] = [ 27 | file 28 | for file in os.listdir("fingerprints") 29 | if os.path.isfile(os.path.join("fingerprints", file)) 30 | ] 31 | shuffle(fingerprint_files) 32 | 33 | fingerprints: list[str] = fingerprint_files 34 | lock: Lock = Lock() 35 | last_updated: float = time() 36 | fingerprint_index: int = 0 37 | executor: ThreadPoolExecutor = ThreadPoolExecutor(max_workers=1000) 38 | deduction_amount: float = 0.0009 39 | 40 | with open("resources/keys.json", "r") as key_file: 41 | keys_data: list[dict[str, float | str]] = json.load(key_file) 42 | 43 | 44 | def save_keys() -> None: 45 | with open("resources/keys.json", "w") as key_file: 46 | json.dump(keys_data, key_file, indent=4) 47 | 48 | 49 | @app.route("/check_balance", methods=["POST"]) 50 | def check_balance() -> tuple: 51 | data: dict = request.get_json() 52 | 53 | if "key" in data: 54 | for key_info in keys_data: 55 | if key_info["key"] == data["key"]: 56 | return jsonify({"Balance": f"${key_info['balance']}"}), 200 57 | return jsonify({"response": "Invalid key."}), 400 58 | return jsonify({"response": "Invalid key."}), 400 59 | 60 | 61 | @app.route("/admin", methods=["POST"]) 62 | def admin() -> tuple: 63 | data: dict = request.get_json() 64 | 65 | if ( 66 | "admin_key" in data 67 | and "action" in data 68 | and data["admin_key"] == "NOVAK-Ssh3OOXK5pldXmOHobhh3UZnaAE9p3q" 69 | ): 70 | action: str = data["action"] 71 | key: str | None = data.get("key") 72 | balance: float | None = data.get("balance") 73 | 74 | if action == "gen_key" and balance is not None: 75 | 76 | def generate_secure_string(length: int = 32) -> str: 77 | alphabet: str = string.ascii_uppercase + string.digits 78 | return "".join(secrets.choice(alphabet) for _ in range(length)) 79 | 80 | generated_key: str = "BLOXCAPTCHA-" + generate_secure_string() 81 | 82 | keys_data.append({"key": generated_key, "balance": balance}) 83 | save_keys() 84 | return ( 85 | jsonify( 86 | {"response": "Key generated successfully.", "key": generated_key} 87 | ), 88 | 200, 89 | ) 90 | 91 | if action == "add_key" and key and balance is not None: 92 | keys_data.append({"key": key, "balance": balance}) 93 | save_keys() 94 | return jsonify({"response": "Key added successfully.", "key": key}), 200 95 | 96 | if action == "remove_key" and key: 97 | for key_info in keys_data: 98 | if key_info["key"] == key: 99 | keys_data.remove(key_info) 100 | save_keys() 101 | return jsonify({"response": "Key removed successfully."}), 200 102 | return jsonify({"response": "Key not found."}), 404 103 | 104 | if action == "set_balance" and key and balance is not None: 105 | for key_info in keys_data: 106 | if key_info["key"] == key: 107 | key_info["balance"] = balance 108 | save_keys() 109 | return jsonify({"response": "Balance set successfully."}), 200 110 | return jsonify({"response": "Key not found."}), 404 111 | 112 | if action == "increase_balance" and key and balance is not None: 113 | for key_info in keys_data: 114 | if key_info["key"] == key: 115 | key_info["balance"] += balance 116 | save_keys() 117 | return jsonify({"response": "Balance increased successfully."}), 200 118 | return jsonify({"response": "Key not found."}), 404 119 | 120 | if action == "decrease_balance" and key and balance is not None: 121 | for key_info in keys_data: 122 | if key_info["key"] == key: 123 | if key_info["balance"] >= balance: 124 | key_info["balance"] -= balance 125 | save_keys() 126 | return ( 127 | jsonify({"response": "Balance decreased successfully."}), 128 | 200, 129 | ) 130 | return ( 131 | jsonify({"response": "Insufficient balance to decrease."}), 132 | 400, 133 | ) 134 | return jsonify({"response": "Key not found."}), 404 135 | 136 | return jsonify({"response": "Invalid action or parameters."}), 400 137 | return jsonify({"response": "Invalid admin key or missing parameters."}), 400 138 | 139 | 140 | def process_captcha(payload: dict) -> tuple[dict, int]: 141 | global fingerprint_index, last_updated 142 | 143 | try: 144 | fingerprint_path: str = fingerprints[fingerprint_index % len(fingerprints)] 145 | with open( 146 | os.path.join("fingerprints", fingerprint_path), "r", encoding="utf-8" 147 | ) as file: 148 | fingerprint_data: dict = load(file) 149 | 150 | captcha_solver = FunCaptchaTask( 151 | payload["challenge_info"], 152 | payload["browser_info"], 153 | fingerprint_data, 154 | payload.get("proxy", ""), 155 | ) 156 | 157 | start_time: float = time() 158 | captcha_solution: str = captcha_solver._solve_challenge().get("solution", "") 159 | elapsed_time: float = round(time() - start_time, 2) 160 | 161 | if "sup=1" in captcha_solution: 162 | return { 163 | "solution": captcha_solution, 164 | "game_info": { 165 | "variant": "silent_pass", 166 | "waves": 0, 167 | "game_type": 4, 168 | "solve_time": elapsed_time, 169 | }, 170 | }, 200 171 | 172 | interactor = captcha_solver.interactor 173 | variant: str = interactor.variant 174 | waves: int = interactor.waves 175 | game_type: int = interactor.game_type 176 | 177 | if "rid" not in interactor.funcaptcha_token: 178 | with lock: 179 | if time() > last_updated: 180 | fingerprint_index += 1 181 | last_updated = time() 182 | 183 | if captcha_solution == "Failed to solve the captcha.": 184 | return {"error": "Failed to solve the captcha."}, 500 185 | 186 | return { 187 | "solution": captcha_solution, 188 | "game_info": { 189 | "variant": variant, 190 | "waves": waves, 191 | "game_type": game_type, 192 | "solve_time": elapsed_time, 193 | }, 194 | }, 200 195 | 196 | except ValueError as error: 197 | return {"error": str(error)}, 500 198 | except Exception as error: 199 | return {"error": f"Unknown / proxy error. {str(error)}"}, 500 200 | 201 | 202 | @app.route("/solve/FunCaptcha", methods=["POST"]) 203 | def fun_captcha_handler() -> tuple: 204 | payload: dict = request.get_json() 205 | 206 | if "api_key" in payload: 207 | for key_info in keys_data: 208 | if key_info["key"] == payload["api_key"]: 209 | if key_info["balance"] > 0: 210 | key_info["balance"] -= deduction_amount 211 | save_keys() 212 | 213 | required_challenge_keys: list[str] = [ 214 | "public_key", 215 | "website_url", 216 | "service_url", 217 | "capi_mode", 218 | "style_theme", 219 | "language_enabled", 220 | "jsf_enabled", 221 | "ancestor_origins", 222 | "tree_index", 223 | "tree_structure", 224 | "location_h_ref", 225 | ] 226 | required_browser_keys: list[str] = ["User-Agent"] 227 | 228 | if not all( 229 | key in payload.get("challenge_info", {}) 230 | for key in required_challenge_keys 231 | ): 232 | return ( 233 | jsonify( 234 | {"error": "Missing required challenge_info parameters."} 235 | ), 236 | 400, 237 | ) 238 | 239 | if not all( 240 | key in payload.get("browser_info", {}) 241 | for key in required_browser_keys 242 | ): 243 | return ( 244 | jsonify( 245 | {"error": "Missing required browser_info parameters."} 246 | ), 247 | 400, 248 | ) 249 | 250 | future = executor.submit(process_captcha, payload) 251 | result, status_code = future.result() 252 | if "error" in result: 253 | key_info["balance"] += deduction_amount 254 | save_keys() 255 | 256 | return jsonify(result), status_code 257 | 258 | 259 | if __name__ == "__main__": 260 | os.system("cls" if os.name == "nt" else "clear") 261 | app.run(host="0.0.0.0", port=5000, threaded=True) -------------------------------------------------------------------------------- /bda.txt: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "key": "api_type", 4 | "value": "js" 5 | }, 6 | { 7 | "key": "f", 8 | "value": "256dba2ba94edaa0c09eb76cc60cc6a2" 9 | }, 10 | { 11 | "key": "n", 12 | "value": "MTc0MjE3Nzk2MQ==" 13 | }, 14 | { 15 | "key": "wh", 16 | "value": "ffb40d5c681ad05733a1a08815eb042b|72627afbfd19a741c7da1732218301ac" 17 | }, 18 | { 19 | "key": "enhanced_fp", 20 | "value": [ 21 | { 22 | "key": "webgl_extensions", 23 | "value": "ANGLE_instanced_arrays;EXT_blend_minmax;EXT_clip_control;EXT_color_buffer_half_float;EXT_depth_clamp;EXT_disjoint_timer_query;EXT_float_blend;EXT_frag_depth;EXT_polygon_offset_clamp;EXT_shader_texture_lod;EXT_texture_compression_bptc;EXT_texture_compression_rgtc;EXT_texture_filter_anisotropic;EXT_texture_mirror_clamp_to_edge;EXT_sRGB;KHR_parallel_shader_compile;OES_element_index_uint;OES_fbo_render_mipmap;OES_standard_derivatives;OES_texture_float;OES_texture_float_linear;OES_texture_half_float;OES_texture_half_float_linear;OES_vertex_array_object;WEBGL_blend_func_extended;WEBGL_color_buffer_float;WEBGL_compressed_texture_s3tc;WEBGL_compressed_texture_s3tc_srgb;WEBGL_debug_renderer_info;WEBGL_debug_shaders;WEBGL_depth_texture;WEBGL_draw_buffers;WEBGL_lose_context;WEBGL_multi_draw;WEBGL_polygon_mode" 24 | }, 25 | { 26 | "key": "webgl_extensions_hash", 27 | "value": "7300c23f4e6fa34e534fc99c1b628588" 28 | }, 29 | { 30 | "key": "webgl_renderer", 31 | "value": "WebKit WebGL" 32 | }, 33 | { 34 | "key": "webgl_vendor", 35 | "value": "WebKit" 36 | }, 37 | { 38 | "key": "webgl_version", 39 | "value": "WebGL 1.0 (OpenGL ES 2.0 Chromium)" 40 | }, 41 | { 42 | "key": "webgl_shading_language_version", 43 | "value": "WebGL GLSL ES 1.0 (OpenGL ES GLSL ES 1.0 Chromium)" 44 | }, 45 | { 46 | "key": "webgl_aliased_line_width_range", 47 | "value": "[1, 2048]" 48 | }, 49 | { 50 | "key": "webgl_aliased_point_size_range", 51 | "value": "[1, 2048]" 52 | }, 53 | { 54 | "key": "webgl_antialiasing", 55 | "value": "yes" 56 | }, 57 | { 58 | "key": "webgl_bits", 59 | "value": "8,8,24,8,8,0" 60 | }, 61 | { 62 | "key": "webgl_max_params", 63 | "value": "16,64,16384,1024,16384,32,16384,32,16,32,1024" 64 | }, 65 | { 66 | "key": "webgl_max_viewport_dims", 67 | "value": "[16384, 16384]" 68 | }, 69 | { 70 | "key": "webgl_unmasked_vendor", 71 | "value": "Google Inc. (AMD)" 72 | }, 73 | { 74 | "key": "webgl_unmasked_renderer", 75 | "value": "ANGLE (AMD, AMD Radeon 610M (radeonsi raphael_mendocino LLVM 19.1.7), OpenGL 4.6)" 76 | }, 77 | { 78 | "key": "webgl_vsf_params", 79 | "value": "23,127,127,23,127,127,23,127,127" 80 | }, 81 | { 82 | "key": "webgl_vsi_params", 83 | "value": "0,31,30,0,31,30,0,31,30" 84 | }, 85 | { 86 | "key": "webgl_fsf_params", 87 | "value": "23,127,127,23,127,127,23,127,127" 88 | }, 89 | { 90 | "key": "webgl_fsi_params", 91 | "value": "0,31,30,0,31,30,0,31,30" 92 | }, 93 | { 94 | "key": "webgl_hash_webgl", 95 | "value": "31c1ded93ef88ca712e9f8646a27ca34" 96 | }, 97 | { 98 | "key": "user_agent_data_brands", 99 | "value": "Not A(Brand,Chromium,Google Chrome" 100 | }, 101 | { 102 | "key": "user_agent_data_mobile", 103 | "value": false 104 | }, 105 | { 106 | "key": "navigator_connection_downlink", 107 | "value": 10 108 | }, 109 | { 110 | "key": "navigator_connection_downlink_max", 111 | "value": null 112 | }, 113 | { 114 | "key": "network_info_rtt", 115 | "value": 200 116 | }, 117 | { 118 | "key": "network_info_save_data", 119 | "value": false 120 | }, 121 | { 122 | "key": "network_info_rtt_type", 123 | "value": null 124 | }, 125 | { 126 | "key": "screen_pixel_depth", 127 | "value": 24 128 | }, 129 | { 130 | "key": "navigator_device_memory", 131 | "value": 4 132 | }, 133 | { 134 | "key": "navigator_pdf_viewer_enabled", 135 | "value": true 136 | }, 137 | { 138 | "key": "navigator_languages", 139 | "value": "en-US,en" 140 | }, 141 | { 142 | "key": "window_inner_width", 143 | "value": 0 144 | }, 145 | { 146 | "key": "window_inner_height", 147 | "value": 0 148 | }, 149 | { 150 | "key": "window_outer_width", 151 | "value": 1920 152 | }, 153 | { 154 | "key": "window_outer_height", 155 | "value": 1036 156 | }, 157 | { 158 | "key": "browser_detection_firefox", 159 | "value": false 160 | }, 161 | { 162 | "key": "browser_detection_brave", 163 | "value": false 164 | }, 165 | { 166 | "key": "browser_api_checks", 167 | "value": [ 168 | "permission_status: true", 169 | "eye_dropper: true", 170 | "audio_data: true", 171 | "writable_stream: true", 172 | "css_style_rule: true", 173 | "navigator_ua: true", 174 | "barcode_detector: false", 175 | "display_names: true", 176 | "contacts_manager: false", 177 | "svg_discard_element: false", 178 | "usb: defined", 179 | "media_device: defined", 180 | "playback_quality: true" 181 | ] 182 | }, 183 | { 184 | "key": "browser_object_checks", 185 | "value": "554838a8451ac36cb977e719e9d6623c" 186 | }, 187 | { 188 | "key": "29s83ih9", 189 | "value": "68934a3e9455fa72420237eb05902327⁣" 190 | }, 191 | { 192 | "key": "audio_codecs", 193 | "value": "{\"ogg\":\"probably\",\"mp3\":\"probably\",\"wav\":\"probably\",\"m4a\":\"maybe\",\"aac\":\"probably\"}" 194 | }, 195 | { 196 | "key": "audio_codecs_extended_hash", 197 | "value": "805036349642e2569ec299baed02315b" 198 | }, 199 | { 200 | "key": "video_codecs", 201 | "value": "{\"ogg\":\"\",\"h264\":\"probably\",\"webm\":\"probably\",\"mpeg4v\":\"\",\"mpeg4a\":\"\",\"theora\":\"\"}" 202 | }, 203 | { 204 | "key": "video_codecs_extended_hash", 205 | "value": "cb2c967d0cd625019556b39c63f7d435" 206 | }, 207 | { 208 | "key": "media_query_dark_mode", 209 | "value": false 210 | }, 211 | { 212 | "key": "css_media_queries", 213 | "value": 0 214 | }, 215 | { 216 | "key": "css_color_gamut", 217 | "value": "srgb" 218 | }, 219 | { 220 | "key": "css_contrast", 221 | "value": "no-preference" 222 | }, 223 | { 224 | "key": "css_monochrome", 225 | "value": false 226 | }, 227 | { 228 | "key": "css_pointer", 229 | "value": "fine" 230 | }, 231 | { 232 | "key": "css_grid_support", 233 | "value": false 234 | }, 235 | { 236 | "key": "headless_browser_phantom", 237 | "value": false 238 | }, 239 | { 240 | "key": "headless_browser_selenium", 241 | "value": false 242 | }, 243 | { 244 | "key": "headless_browser_nightmare_js", 245 | "value": false 246 | }, 247 | { 248 | "key": "headless_browser_generic", 249 | "value": 4 250 | }, 251 | { 252 | "key": "1l2l5234ar2", 253 | "value": "1742177961110⁢" 254 | }, 255 | { 256 | "key": "document__referrer", 257 | "value": "https://www.roblox.com/" 258 | }, 259 | { 260 | "key": "window__ancestor_origins", 261 | "value": [ 262 | "https://www.roblox.com", 263 | "https://www.roblox.com" 264 | ] 265 | }, 266 | { 267 | "key": "window__tree_index", 268 | "value": [ 269 | 0, 270 | 0 271 | ] 272 | }, 273 | { 274 | "key": "window__tree_structure", 275 | "value": "[[[]]]" 276 | }, 277 | { 278 | "key": "window__location_href", 279 | "value": "https://arkoselabs.roblox.com/v2/2.11.6/enforcement.f9e933a9f186f0bdb8e44dd39534e940.html" 280 | }, 281 | { 282 | "key": "client_config__sitedata_location_href", 283 | "value": "https://www.roblox.com/arkose/iframe" 284 | }, 285 | { 286 | "key": "client_config__language", 287 | "value": null 288 | }, 289 | { 290 | "key": "client_config__surl", 291 | "value": "https://arkoselabs.roblox.com" 292 | }, 293 | { 294 | "key": "c8480e29a", 295 | "value": "82e587de59c07c4a379a6cc646c0e644⁢" 296 | }, 297 | { 298 | "key": "client_config__triggered_inline", 299 | "value": false 300 | }, 301 | { 302 | "key": "mobile_sdk__is_sdk", 303 | "value": false 304 | }, 305 | { 306 | "key": "audio_fingerprint", 307 | "value": "124.04347527516074" 308 | }, 309 | { 310 | "key": "navigator_battery_charging", 311 | "value": true 312 | }, 313 | { 314 | "key": "media_device_kinds", 315 | "value": [ 316 | "videoinput", 317 | "audiooutput" 318 | ] 319 | }, 320 | { 321 | "key": "media_devices_hash", 322 | "value": "a100118c0b7b0da99a7b6db752e59b8c" 323 | }, 324 | { 325 | "key": "navigator_permissions_hash", 326 | "value": "67419471976a14a1430378465782c62d" 327 | }, 328 | { 329 | "key": "math_fingerprint", 330 | "value": "3b2ff195f341257a6a2abbc122f4ae67" 331 | }, 332 | { 333 | "key": "supported_math_functions", 334 | "value": "e9dd4fafb44ee489f48f7c93d0f48163" 335 | }, 336 | { 337 | "key": "screen_orientation", 338 | "value": "landscape-primary" 339 | }, 340 | { 341 | "key": "rtc_peer_connection", 342 | "value": 5 343 | }, 344 | { 345 | "key": "4b4b269e68", 346 | "value": "6ef220b7-9933-46b1-8069-1815acec4a0d" 347 | }, 348 | { 349 | "key": "6a62b2a558", 350 | "value": "f9e933a9f186f0bdb8e44dd39534e940" 351 | }, 352 | { 353 | "key": "is_keyless", 354 | "value": false 355 | }, 356 | { 357 | "key": "speech_default_voice", 358 | "value": "Google Deutsch || de-DE" 359 | }, 360 | { 361 | "key": "speech_voices_hash", 362 | "value": "a0c90ca98043f0489c1e731e233b937e" 363 | }, 364 | { 365 | "key": "4ca87df3d1", 366 | "value": "Ow==" 367 | }, 368 | { 369 | "key": "867e25e5d4", 370 | "value": "Ow==" 371 | }, 372 | { 373 | "key": "d4a306884c", 374 | "value": "Ow==" 375 | } 376 | ] 377 | }, 378 | { 379 | "key": "fe", 380 | "value": [ 381 | "DNT:unknown", 382 | "L:en-US", 383 | "D:24", 384 | "PR:1", 385 | "S:1920,1080", 386 | "AS:1920,1036", 387 | "TO:0", 388 | "SS:true", 389 | "LS:true", 390 | "IDB:true", 391 | "B:false", 392 | "ODB:false", 393 | "CPUC:unknown", 394 | "PK:Linux x86_64", 395 | "CFP:-661929228", 396 | "FR:false", 397 | "FOS:false", 398 | "FB:false", 399 | "JSF:", 400 | "P:Chrome PDF Viewer,Chromium PDF Viewer,Microsoft Edge PDF Viewer,PDF Viewer,WebKit built-in PDF", 401 | "T:0,false,false", 402 | "H:8", 403 | "SWF:false" 404 | ] 405 | }, 406 | { 407 | "key": "ife_hash", 408 | "value": "b431439354d29b004e7e2aa317d5363b" 409 | }, 410 | { 411 | "key": "jsbd", 412 | "value": "{\"HL\":3,\"NCE\":true,\"DT\":\"\",\"NWD\":\"false\",\"DMTO\":1,\"DOTO\":1}" 413 | } 414 | ] -------------------------------------------------------------------------------- /core/browser/fingerprint.py: -------------------------------------------------------------------------------- 1 | from base64 import b64encode 2 | from random import choice, randint 3 | from json import dumps 4 | from mmh3 import hash128 5 | from hashlib import md5 6 | from time import time 7 | from uuid import uuid4 8 | from typing import Dict, List, Union, Any 9 | 10 | from core.mouse_movement.biometrics import Biometrics 11 | 12 | __all__ = ("Fingerprint",) 13 | 14 | 15 | class Fingerprint: 16 | def __init__( 17 | self, 18 | ip_info: Dict[str, Union[str, int, List[str]]], 19 | challenge_info: Dict[str, Union[bool, str]], 20 | browser_info: Dict[str, str], 21 | bda_fingerprint: Dict[str, Union[str, int, List[int]]], 22 | ) -> None: 23 | self.challenge_info = challenge_info 24 | #sec_ch_ua: str = browser_info["Sec-Ch-Ua"] 25 | #sec_ch_ua_split: List[str] = sec_ch_ua.split('"') 26 | #self.data_brands: str = ",".join( 27 | # [sec_ch_ua_split[1], sec_ch_ua_split[5], sec_ch_ua_split[9]] 28 | #) 29 | self.language: str = ip_info["language"] 30 | self.languages: List[str] = ip_info["languages"] 31 | self.timezone_offset: int = ip_info["timezone_offset"] 32 | self.bda_fingerprint = bda_fingerprint 33 | self.media_devices: List[Union[List[str], str]] = self._generate_media_devices() 34 | self.speech_voice: str = self._select_speech_voice() 35 | 36 | def _generate_md5_hash(self, input_string: str) -> str: 37 | return md5(input_string.encode("utf-8")).hexdigest() 38 | 39 | def _generate_x64_hash(self, input_string: str, seed: int) -> str: 40 | hashed_value: int = hash128(input_string, seed) 41 | return str( 42 | hex(((hashed_value & 0xFFFFFFFFFFFFFFFF) << 64) + (hashed_value >> 64)) 43 | ).removeprefix("0x") 44 | 45 | def _format_extended_string(self, input_string: str) -> str: 46 | return dumps(input_string)[1:-1].replace('\\"', '"').replace("\\\\", "\\") 47 | 48 | def _format_numeric_value(self, value: Union[float, int]) -> Union[float, int]: 49 | numeric_string: str = str(value) 50 | if "." in numeric_string: 51 | decimal_part: str = numeric_string.split(".")[1] 52 | if len(decimal_part) == 2 and decimal_part[1] == "0": 53 | return float(numeric_string[:-1]) 54 | if decimal_part == "0": 55 | return int(numeric_string[:-2]) 56 | return value 57 | 58 | def _compute_webgl_hash(self, fingerprint_data: Dict[str, str]) -> str: 59 | webgl_data: Dict[str, str] = { 60 | key: value 61 | for key, value in fingerprint_data.items() 62 | if key.startswith("webgl_") 63 | } 64 | return self._generate_x64_hash( 65 | ",".join([item for pair in webgl_data.items() for item in pair]), 0 66 | ) 67 | 68 | def _select_speech_voice(self) -> str: 69 | speech_voices: List[str] = [ 70 | "Google US English || en-US", 71 | "Google UK English Female || en-GB", 72 | "Google UK English Male || en-GB", 73 | "Google español || es-ES", 74 | "Google español de Estados Unidos || es-US", 75 | "Google français || fr-FR", 76 | "Google हिन्दी || hi-IN", 77 | "Google Bahasa Indonesia || id-ID", 78 | "Google italiano || it-IT", 79 | "Google 日本語 || ja-JP", 80 | "Google 한국의 || ko-KR", 81 | "Google Nederlands || nl-NL", 82 | "Google polski || pl-PL", 83 | "Google português do Brasil || pt-BR", 84 | "Google русский || ru-RU", 85 | "Google 普通话(中国大陆) || zh-CN", 86 | "Google 粤語(香港) || zh-HK", 87 | "Google 國語(臺灣) || zh-TW", 88 | "Microsoft Mark - English (United States) || en-US", 89 | "Microsoft Zira - English (United States) || en-US", 90 | "Microsoft Hazel - English (United Kingdom) || en-GB", 91 | "Microsoft Susan - English (United Kingdom) || en-GB", 92 | ] 93 | return choice(speech_voices) 94 | 95 | def _generate_media_devices(self) -> List[Union[List[str], str]]: 96 | device_types: List[str] = [ 97 | "audioinput", 98 | "audiooutput", 99 | "videoinput", 100 | "videooutput", 101 | ] 102 | selected_device_types: List[str] = [] 103 | device_info: List[Dict[str, str]] = [] 104 | 105 | for _ in range(randint(1, 3)): 106 | selected_type: str = choice(device_types) 107 | device_types.remove(selected_type) 108 | 109 | selected_device_types.append(selected_type) 110 | device_info.append({"kind": selected_type, "id": "", "group": ""}) 111 | 112 | devices_json: str = dumps(device_info, separators=(",", ":")) 113 | devices_hash: str = self._generate_md5_hash(devices_json) 114 | 115 | return [selected_device_types, devices_hash] 116 | 117 | def generate_fingerprint_data(self) -> Dict[str, Union[str, int, bool, List[str]]]: 118 | js_fonts: str = ( 119 | "Arial,Arial Black,Arial Narrow,Calibri,Cambria,Cambria Math,Comic Sans MS,Consolas," 120 | "Courier,Courier New,Georgia,Helvetica,Impact,Lucida Console,Lucida Sans Unicode," 121 | "Microsoft Sans Serif,MS Gothic,MS PGothic,MS Sans Serif,MS Serif,Palatino Linotype," 122 | "Segoe Print,Segoe Script,Segoe UI,Segoe UI Light,Segoe UI Semibold,Segoe UI Symbol," 123 | "Tahoma,Times,Times New Roman,Trebuchet MS,Verdana,Wingdings" 124 | ) 125 | 126 | fingerprint_data: Dict[str, Union[str, int, bool, List[str]]] = { 127 | "DNT": "unknown", 128 | "L": self.language, 129 | "D": self.bda_fingerprint["colorDepth"], 130 | "PR": self.bda_fingerprint["pixelRatio"], 131 | "S": ",".join(map(str, self.bda_fingerprint["screen"])), 132 | "AS": ",".join(map(str, self.bda_fingerprint["availScreen"])), 133 | "TO": self.timezone_offset, 134 | "SS": True, 135 | "LS": True, 136 | "IDB": True, 137 | "B": False, 138 | "ODB": False, 139 | "CPUC": "unknown", 140 | "PK": self.bda_fingerprint["platform"], 141 | "CFP": self.bda_fingerprint["cfp"], 142 | "FR": False, 143 | "FOS": False, 144 | "FB": False, 145 | "JSF": js_fonts if self.challenge_info["jsf_enabled"] else "", 146 | "P": ( 147 | "Chrome PDF Viewer,Chromium PDF Viewer,Microsoft Edge PDF Viewer,PDF Viewer," 148 | "WebKit built-in PDF" 149 | ), 150 | "T": ",".join(map(str, [0, False, False])), 151 | "H": self.bda_fingerprint["hardwareConcurrency"], 152 | "SWF": False, 153 | } 154 | 155 | return fingerprint_data 156 | 157 | def get_enhanced_fingerprint(self) -> Dict[str, Any]: 158 | enhanced_fp: Dict[str, Any] = { 159 | "webgl_extensions": self.bda_fingerprint["webglExtensions"], 160 | "webgl_extensions_hash": self._generate_x64_hash( 161 | self.bda_fingerprint["webglExtensions"], 0 162 | ), 163 | "webgl_renderer": self.bda_fingerprint["webglRenderer"], 164 | "webgl_vendor": self.bda_fingerprint["webglVendor"], 165 | "webgl_version": self.bda_fingerprint["webglVersion"], 166 | "webgl_shading_language_version": self.bda_fingerprint[ 167 | "webglShadingLanguageVersion" 168 | ], 169 | "webgl_aliased_line_width_range": self.bda_fingerprint[ 170 | "webglAliasedLineWidthRange" 171 | ], 172 | "webgl_aliased_point_size_range": self.bda_fingerprint[ 173 | "webglAliasedPointSizeRange" 174 | ], 175 | "webgl_antialiasing": self.bda_fingerprint["webglAntialiasing"], 176 | "webgl_bits": "8,8,24,8,8,0", 177 | "webgl_max_params": self.bda_fingerprint["webglMaxParams"], 178 | "webgl_max_viewport_dims": self.bda_fingerprint["webglMaxViewportDims"], 179 | "webgl_unmasked_vendor": self.bda_fingerprint["webglUnmaskedVendor"], 180 | "webgl_unmasked_renderer": self.bda_fingerprint["webglUnmaskedRenderer"], 181 | "webgl_vsf_params": self.bda_fingerprint["webglVsfParams"], 182 | "webgl_vsi_params": self.bda_fingerprint["webglVsiParams"], 183 | "webgl_fsf_params": self.bda_fingerprint["webglFsfParams"], 184 | "webgl_fsi_params": self.bda_fingerprint["webglFsiParams"], 185 | "webgl_hash_webgl": "", 186 | "user_agent_data_brands": None,#"Not A(Brand,Chromium,Google Chrome",#self.data_brands, 187 | "user_agent_data_mobile": False, 188 | "navigator_connection_downlink": self._format_numeric_value( 189 | (randint(10, 100) * 5) / 100 190 | ), 191 | "navigator_connection_downlink_max": None, 192 | "network_info_rtt": randint(1, 20) * 50, 193 | "network_info_save_data": False, 194 | "network_info_rtt_type": None, 195 | "screen_pixel_depth": self.bda_fingerprint["colorDepth"], 196 | "navigator_device_memory": 8, 197 | "navigator_pdf_viewer_enabled": True, 198 | "navigator_languages": self.languages, 199 | "window_inner_width": 0, 200 | "window_inner_height": 0, 201 | "window_outer_width": self.bda_fingerprint["availScreen"][0], 202 | "window_outer_height": self.bda_fingerprint["availScreen"][1], 203 | "browser_detection_firefox": False, 204 | "browser_detection_brave": False, 205 | "browser_api_checks": [ 206 | "permission_status: true", 207 | "eye_dropper: true", 208 | "audio_data: true", 209 | "writable_stream: true", 210 | "css_style_rule: true", 211 | "navigator_ua: true", 212 | "barcode_detector: false", 213 | "display_names: true", 214 | "contacts_manager: false", 215 | "svg_discard_element: false", 216 | "usb: defined", 217 | "media_device: defined", 218 | "playback_quality: true", 219 | ], 220 | "browser_object_checks": "554838a8451ac36cb977e719e9d6623c", 221 | "29s83ih9": "68934a3e9455fa72420237eb05902327⁣", 222 | "audio_codecs": self.bda_fingerprint["audioCodecs"], 223 | "audio_codecs_extended_hash": self._generate_md5_hash( 224 | self.bda_fingerprint["audioCodecsExtended"] 225 | ), 226 | "video_codecs": self.bda_fingerprint["videoCodecs"], 227 | "video_codecs_extended_hash": self._generate_md5_hash( 228 | self.bda_fingerprint["videoCodecsExtended"] 229 | ), 230 | "media_query_dark_mode": False, 231 | "css_media_queries": self.bda_fingerprint["cssMediaQueries"], 232 | "css_color_gamut": self.bda_fingerprint["cssColorGamut"], 233 | "css_contrast": self.bda_fingerprint["cssContrast"], 234 | "css_monochrome": self.bda_fingerprint["cssMonochrome"], 235 | "css_pointer": self.bda_fingerprint["cssPointer"], 236 | "css_grid_support": self.bda_fingerprint["cssGridSupport"], 237 | "headless_browser_phantom": False, 238 | "headless_browser_selenium": False, 239 | "headless_browser_nightmare_js": False, 240 | "headless_browser_generic": 4, 241 | "1l2l5234ar2": f"{int(time())}⁣", 242 | "document__referrer": self.challenge_info["website_url"], 243 | "window__ancestor_origins": self.challenge_info["ancestor_origins"], 244 | "window__tree_index": self.challenge_info["tree_index"], 245 | "window__tree_structure": self.challenge_info["tree_structure"], 246 | "window__location_href": f'{self.challenge_info["service_url"]}/v2/2.11.6/enforcement.f9e933a9f186f0bdb8e44dd39534e940.html', 247 | "client_config__sitedata_location_href": self.challenge_info[ 248 | "location_h_ref" 249 | ], 250 | "client_config__language": ( 251 | self.language.lower() 252 | if self.challenge_info["language_enabled"] 253 | else None 254 | ), 255 | "client_config__surl": self.challenge_info["service_url"], 256 | "c8480e29a": f'{self._generate_md5_hash(self.challenge_info["service_url"])}⁢', 257 | "client_config__triggered_inline": False, 258 | "mobile_sdk__is_sdk": False, 259 | "audio_fingerprint": "124.04347527516074", 260 | "navigator_battery_charging": True, 261 | "media_device_kinds": self.media_devices[0], 262 | "media_devices_hash": self.media_devices[1], 263 | "navigator_permissions_hash": "67419471976a14a1430378465782c62d", 264 | "math_fingerprint": "3b2ff195f341257a6a2abbc122f4ae67", 265 | "supported_math_functions": "e9dd4fafb44ee489f48f7c93d0f48163", 266 | "screen_orientation": "landscape-primary", 267 | "rtc_peer_connection": 5, 268 | "4b4b269e68": str(uuid4()), 269 | "6a62b2a558": "f9e933a9f186f0bdb8e44dd39534e940", 270 | "is_keyless": False, 271 | "speech_default_voice": self.speech_voice, 272 | "speech_voices_hash": self._generate_md5_hash(self.speech_voice), 273 | "4ca87df3d1": "Ow==", #b64encode( 274 | # Biometrics().retrieve_mouse_bio().encode() 275 | #).decode(), 276 | "867e25e5d4": "Ow==", 277 | "d4a306884c": "Ow==", 278 | } 279 | 280 | enhanced_fp["webgl_hash_webgl"] = self._compute_webgl_hash(enhanced_fp) 281 | return enhanced_fp -------------------------------------------------------------------------------- /core/arkose_session/funcaptcha_session.py: -------------------------------------------------------------------------------- 1 | from curl_cffi import requests 2 | from time import time 3 | from random import random, randint, choice 4 | import httpx 5 | from json import dumps 6 | from base64 import b64encode 7 | from urllib.parse import unquote 8 | from core.obfuscation.crypto import encrypt_data 9 | from core.browser.arkose_bda import ArkoseBDA 10 | from core.obfuscation.dapib import DapibBreaker 11 | from core.mouse_movement.biometrics import Biometrics 12 | from core.utilities.ip_intelligence import IpIntelligence 13 | from core.obfuscation.proof_of_work import ProofOfWork 14 | from typing import Dict, List, Any, Optional, Tuple 15 | 16 | __all__ = ("FunCaptchaSolver",) 17 | 18 | class FunCaptchaSolver: 19 | def __init__(self, 20 | challenge_information: Dict[str, str], 21 | browser_information: Dict[str, str], 22 | arkose_fingerprint: Dict[str, str], 23 | proxy: str) -> None: 24 | self.http_session = requests.Session( 25 | impersonate="chrome116", 26 | default_headers=0, 27 | akamai="1:65536;2:0;4:6291456;6:262144|15663105|0|m,a,s,p", 28 | extra_fp={ 29 | "tls_signature_algorithms": [ 30 | "ecdsa_secp256r1_sha256", 31 | "rsa_pss_rsae_sha256", 32 | "rsa_pkcs1_sha256", 33 | "ecdsa_secp384r1_sha384", 34 | "rsa_pss_rsae_sha384", 35 | "rsa_pkcs1_sha384", 36 | "rsa_pss_rsae_sha512", 37 | "rsa_pkcs1_sha512" 38 | ], 39 | "tls_grease": True, 40 | "tls_permute_extensions": True 41 | }, 42 | verify=False 43 | ) 44 | self.proxy = proxy 45 | self.http_session.proxies = { 46 | "https": self.proxy 47 | } 48 | self.arkose_fingerprint: Dict[str, str] = arkose_fingerprint 49 | 50 | self.ip_information: IpIntelligence = IpIntelligence(self.http_session).fetch_ip_data() 51 | self.challenge_information: Dict[str, str] = challenge_information 52 | self.browser_information: Dict[str, str] = browser_information 53 | self.proof_of_work_enabled: bool = None 54 | 55 | self.session_cookie = self.browser_information["Cookie"] 56 | 57 | if self.session_cookie != "": 58 | self.session_cookie += "; " 59 | 60 | self.user_agent: str = self.browser_information["User-Agent"] 61 | self.sec_platform: str = '"Windows"' if 'Windows NT' in self.user_agent else '"macOs"' 62 | 63 | self.ip_language: str = self.ip_information["language"] 64 | self.accept_language: str = self.ip_information["accept_language"] 65 | 66 | self._get_cloudfare_cookie() 67 | 68 | self.screen_clicks: str = "{\"sc\":[" + str(randint(100, 200)) + "," + str(randint(100, 200)) + "]}" 69 | 70 | self.answer_history: List[str, str] = [] 71 | self.tguess_history: List[str, str] = [] 72 | 73 | self.tguess_called: int = 0 74 | 75 | def _encode_data(self, input_data: str) -> str: 76 | result: List[str] = [] 77 | for char in input_data: 78 | if ord(char) > 127 or char in " %$&+,/:;=?@<>%{}": 79 | result.append(f'%{ord(char):02X}') 80 | else: 81 | result.append(char) 82 | return ''.join(result) 83 | 84 | def _url_encode(self, params: Dict[str, str]) -> str: 85 | encoded_params: str = '' 86 | for idx, (key, value) in enumerate(params.items()): 87 | encoded_params += ( 88 | f'{key}={self._encode_data(value)}&' if idx != len(params) - 1 else f'{key}={self._encode_data(value)}' 89 | ) 90 | return encoded_params 91 | 92 | def _sort_headers(self, input_headers: Dict[str, str]) -> Dict[str, str]: 93 | key_order: list[str] = [ 94 | "Host", 95 | "Cookie", 96 | "Content-Length", 97 | "sec-ch-ua-platform", 98 | "Cache-Control", 99 | "x-ark-esync-value", 100 | "Accept-Language", 101 | #"sec-ch-ua", 102 | "sec-ch-ua-mobile", 103 | "Upgrade-Insecure-Requests", 104 | "User-Agent", 105 | "X-NewRelic-Timestamp", 106 | "X-Requested-ID", 107 | "X-Requested-With", 108 | "Accept", 109 | "Content-Type", 110 | "Origin", 111 | "Sec-Fetch-Site", 112 | "Sec-Fetch-Mode", 113 | "Sec-Fetch-Dest", 114 | "Referer", 115 | "Accept-Encoding", 116 | "Connection", 117 | "Priority" 118 | ] 119 | order_index: Dict[str, int] = { 120 | header: i for i, header in enumerate(key_order) 121 | } 122 | 123 | def sort_key(header_pair: Tuple[str, str]) -> int: 124 | header_name, _ = header_pair 125 | return order_index.get(header_name, len(key_order)) 126 | 127 | sorted_headers: Dict[str, str] = dict( 128 | sorted(input_headers.items(), key=sort_key) 129 | ) 130 | 131 | return sorted_headers 132 | 133 | def _generate_newrelic_timestamp(self) -> str: 134 | timestamp_ms: int = int(time() * 1000) 135 | timestamp_str: str = str(timestamp_ms) 136 | return f"{timestamp_str[:7]}00{timestamp_str[7:13]}" 137 | 138 | def _set_biometrics(self) -> None: 139 | biometrics_data: str = '{"mbio":"%s","tbio":"","kbio":""}' % Biometrics().retrieve_mouse_bio() 140 | self.biometrics: str = b64encode(biometrics_data.encode("utf-8")).decode("utf-8") 141 | 142 | def _get_cloudfare_cookie(self) -> None: 143 | self.http_session.headers = self._sort_headers({ 144 | "Cookie": self.session_cookie, 145 | "Accept": "*/*", 146 | "Accept-Encoding": "gzip, deflate, br", 147 | "Accept-Language": self.accept_language, 148 | "Referer": self.challenge_information["website_url"], 149 | #"sec-ch-ua": self.sec_ch_ua, 150 | "sec-ch-ua-mobile": "?0", 151 | "sec-ch-ua-platform": self.sec_platform, 152 | "Sec-Fetch-Dest": "script", 153 | "Sec-Fetch-Mode": "no-cors", 154 | "Sec-Fetch-Site": "same-site", 155 | "User-Agent": self.user_agent, 156 | }) 157 | 158 | response: Any = self.http_session.get( 159 | f"{self.challenge_information['service_url']}/v2/{self.challenge_information['public_key']}/api.js" 160 | ) 161 | 162 | self.cloudfare_cookie: str = f"_cfuvid={response.cookies.get('_cfuvid').split(';')[0]}" 163 | 164 | def _parse_token(self, token: str) -> Dict[str, str]: 165 | return { 166 | unquote(key): unquote(value) 167 | for key, value in (pair.split("=") for pair in token.split("|")) 168 | } 169 | 170 | def _get_funcaptcha_token(self) -> None: 171 | current_time: int = int(time()) 172 | rounded_time: str = str(current_time - (current_time % 21600)) 173 | 174 | random_value: str = str(random()) 175 | 176 | bda_generator = ArkoseBDA(self.ip_information, self.challenge_information, self.browser_information, self.arkose_fingerprint) 177 | encrypted_bda: str = bda_generator.generate_bda() 178 | 179 | site_value: str = self.challenge_information["website_url"].rstrip("/") 180 | 181 | payload: Dict[str, Any] = { 182 | "bda": b64encode(encrypted_bda.encode("utf-8")).decode("utf-8"), 183 | "public_key": self.challenge_information["public_key"], 184 | "site": site_value, 185 | "userbrowser": self.user_agent, 186 | "capi_version": "2.11.6", 187 | "capi_mode": self.challenge_information["capi_mode"], 188 | "style_theme": self.challenge_information["style_theme"], 189 | "rnd": random_value, 190 | } 191 | 192 | if self.challenge_information["language_enabled"]: 193 | payload["language"] = self.ip_language.lower() 194 | 195 | if "extra_data" in self.challenge_information: 196 | for key, value in self.challenge_information["extra_data"].items(): 197 | payload[f"data[{key}]"] = value 198 | 199 | self.http_session.headers = self._sort_headers({ 200 | "Accept": "*/*", 201 | "Accept-Encoding": "gzip, deflate, br, zstd", 202 | "Accept-Language": self.accept_language, 203 | "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", 204 | "Cookie": "", 205 | "Origin": self.challenge_information["service_url"], 206 | "Referer": f"{self.challenge_information['service_url']}/v2/2.11.6/enforcement.f9e933a9f186f0bdb8e44dd39534e940.html", 207 | #"sec-ch-ua": self.sec_ch_ua, 208 | "sec-ch-ua-mobile": "?0", 209 | "sec-ch-ua-platform": self.sec_platform, 210 | "Sec-Fetch-Dest": "empty", 211 | "Sec-Fetch-Mode": "cors", 212 | "Sec-Fetch-Site": "same-origin", 213 | "User-Agent": self.user_agent, 214 | "x-ark-esync-value": rounded_time, 215 | "Priority": "u=1, i" 216 | }) 217 | 218 | self.http_session.cookies.clear() 219 | self.http_session.headers["Cookie"] = f"{self.session_cookie}{self.cloudfare_cookie}; timestamp={self._generate_newrelic_timestamp()}" 220 | 221 | response = self.http_session.post( 222 | f"{self.challenge_information['service_url']}/fc/gt2/public_key/{self.challenge_information['public_key']}", 223 | data=self._url_encode(payload), 224 | ) 225 | 226 | try: 227 | response_data: Dict[str, Any] = response.json() 228 | self.funcaptcha_token: str = response_data["token"] 229 | self.proof_of_work_enabled: bool = response_data["pow"] 230 | except Exception: 231 | self.funcaptcha_token = None 232 | 233 | def _send_analytics(self, analytics: Dict[str, Any], add_request_id: bool) -> None: 234 | self.http_session.headers = self._sort_headers({ 235 | "Accept": "*/*", 236 | "Accept-Encoding": "gzip, deflate, br, zstd", 237 | "Accept-Language": self.accept_language, 238 | "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", 239 | "Cookie": "", 240 | "Origin": self.challenge_information["service_url"], 241 | "Referer": self.embed_url, 242 | #"sec-ch-ua": self.sec_ch_ua, 243 | "sec-ch-ua-mobile": "?0", 244 | "sec-ch-ua-platform": self.sec_platform, 245 | "Sec-Fetch-Dest": "empty", 246 | "Sec-Fetch-Mode": "cors", 247 | "Sec-Fetch-Site": "same-origin", 248 | "User-Agent": self.user_agent, 249 | "X-NewRelic-Timestamp": self._generate_newrelic_timestamp(), 250 | "X-Requested-With": "XMLHttpRequest", 251 | }) 252 | 253 | self.http_session.cookies.clear() 254 | self.http_session.headers["Cookie"] = f"{self.session_cookie}{self.cloudfare_cookie}; timestamp={self.http_session.headers['X-NewRelic-Timestamp']}" 255 | 256 | if add_request_id: 257 | self.http_session.headers["X-Requested-ID"] = encrypt_data(self.screen_clicks, f"REQUESTED{self.session_token}ID", False) 258 | self.http_session.headers = self._sort_headers(self.http_session.headers) 259 | 260 | self.http_session.post( 261 | f"{self.challenge_information['service_url']}/fc/a/", 262 | data=self._url_encode(analytics), 263 | ) 264 | 265 | def _get_challenge(self) -> None: 266 | self._initialize_challenge_details() 267 | self._set_embed_url() 268 | self._set_initial_headers() 269 | self._clear_cookies() 270 | self._set_cookie_header() 271 | 272 | embed_url_no_queries, params = self._extract_url_and_params() 273 | self.http_session.get(embed_url_no_queries, params=params) 274 | 275 | if self.proof_of_work_enabled: 276 | self._process_proof_of_work() 277 | 278 | self._initialize_challenge() 279 | self._process_game_data() 280 | self._send_initial_analytics() 281 | 282 | proof_of_work = ProofOfWork().solve_analytics_pow() 283 | analytics = self._prepare_analytics_data("begin app", "user clicked verify", proof_of_work) 284 | self._send_analytics(analytics, True) 285 | 286 | def _initialize_challenge_details(self) -> None: 287 | self.analytics_tier = self.funcaptcha_token.split("at=")[1].split("|")[0] 288 | self.session_id = self.funcaptcha_token.split("|")[1].split("r=")[1].split("|")[0] 289 | 290 | def _set_embed_url(self) -> None: 291 | token_info = self._parse_token(f"session={self.funcaptcha_token}") 292 | self.embed_url = ( 293 | f"{self.challenge_information['service_url']}/fc/assets/ec-game-core/game-core/1.27.4/standard/index.html?" 294 | f"{self._url_encode(token_info)}&theme={self._encode_data(self.challenge_information['style_theme'])}" 295 | ) 296 | 297 | def _set_initial_headers(self) -> None: 298 | self.http_session.headers = self._sort_headers({ 299 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8," 300 | "application/signed-exchange;v=b3;q=0.7", 301 | "Accept-Encoding": "gzip, deflate, br, zstd", 302 | "Accept-Language": self.accept_language, 303 | "Cookie": "", 304 | "Referer": f"{self.challenge_information['service_url']}/v2/2.11.6/enforcement.f9e933a9f186f0bdb8e44dd39534e940.html", 305 | #"sec-ch-ua": self.sec_ch_ua, 306 | "sec-ch-ua-mobile": "?0", 307 | "sec-ch-ua-platform": self.sec_platform, 308 | "Sec-Fetch-Dest": "iframe", 309 | "Sec-Fetch-Mode": "navigate", 310 | "Sec-Fetch-Site": "same-origin", 311 | "User-Agent": self.user_agent, 312 | }) 313 | 314 | def _clear_cookies(self) -> None: 315 | self.http_session.cookies.clear() 316 | 317 | def _set_cookie_header(self) -> None: 318 | self.http_session.headers["Cookie"] = ( 319 | f"{self.session_cookie}{self.cloudfare_cookie}; timestamp={self._generate_newrelic_timestamp()}" 320 | ) 321 | 322 | def _extract_url_and_params(self) -> Tuple[str, Dict[str, str]]: 323 | embed_url_no_queries = self.embed_url.split("?")[0] 324 | queries = self.embed_url.split("?")[1] 325 | params = {} 326 | 327 | if "&" not in queries: 328 | params[queries.split("=")[0]] = queries.split("=")[1] 329 | else: 330 | for query in queries.split("&"): 331 | key, value = query.split("=") 332 | params[key] = value 333 | 334 | return embed_url_no_queries, params 335 | 336 | def _process_proof_of_work(self) -> None: 337 | pow_class = ProofOfWork() 338 | pow_response = self.http_session.get( 339 | f"{self.challenge_information['service_url']}/pows/setup?session_token={self.funcaptcha_token.split('|')[0]}" 340 | ) 341 | self.http_session.get(pow_response.json()["url"]) 342 | pow_data = pow_class.solve_proof_of_work( 343 | pow_response.json()["seed"], 344 | pow_response.json()["leading_zero_count"], 345 | self.funcaptcha_token.split("|")[0], 346 | pow_response.json()["pow_token"], 347 | ) 348 | self.http_session.post( 349 | f"{self.challenge_information['service_url']}/pows/check", json=pow_data 350 | ).json() 351 | 352 | def _initialize_challenge(self) -> None: 353 | self.http_session.get( 354 | f"{self.challenge_information['service_url']}/fc/init-load/?session_token={self.funcaptcha_token.split('|')[0]}" 355 | ) 356 | self.funcaptcha_token_info = self._parse_token(f"token={self.funcaptcha_token}") 357 | 358 | def _process_game_data(self) -> None: 359 | response = self.http_session.post( 360 | f"{self.challenge_information['service_url']}/fc/gfct/", 361 | data=self._prepare_challenge_data(), 362 | ).json() 363 | self._extract_game_data(response) 364 | 365 | def _prepare_challenge_data(self) -> Dict[str, Any]: 366 | return { 367 | "token": self.funcaptcha_token_info["token"], 368 | "sid": self.session_id, 369 | "render_type": "canvas", 370 | "lang": self.ip_information["language"].lower() 371 | if self.challenge_information["language_enabled"] 372 | else "", 373 | "isAudioGame": False, 374 | "is_compatibility_mode": False, 375 | "apiBreakerVersion": "green", 376 | "analytics_tier": str(self.analytics_tier), 377 | } 378 | 379 | def _extract_game_data(self, response: Dict[str, Any]) -> None: 380 | self.game_data = response["game_data"] 381 | self.game_type = self.game_data["gameType"] 382 | self.session_token = response["session_token"] 383 | self.challenge_id = response["challengeID"] 384 | self.challenge_url = response["challengeURL"] 385 | self.dapib_url = response.get("dapib_url", None) 386 | if self.game_type == 4: 387 | self.variant = self.game_data.get( 388 | "variant", response["game_data"]["instruction_string"] 389 | ) 390 | elif self.game_type == 3: 391 | self.variant = self.game_data["game_variant"] 392 | self.waves = self.game_data["waves"] 393 | self.challenge_imgs = self.game_data["customGUI"]["_challenge_imgs"] 394 | 395 | def _send_initial_analytics(self) -> None: 396 | self._send_analytics( 397 | self._prepare_analytics_data( 398 | "Site URL", 399 | f"{self.challenge_information['service_url']}/v2/2.11.6/enforcement.f9e933a9f186f0bdb8e44dd39534e940.html", 400 | ), 401 | False, 402 | ) 403 | self._send_analytics( 404 | self._prepare_analytics_data("loaded", "game loaded"), False 405 | ) 406 | 407 | def _prepare_analytics_data( 408 | self, category: str, action: str, additional_data: Dict[str, Any] = {} 409 | ) -> Dict[str, Any]: 410 | data = { 411 | "sid": self.session_id, 412 | "session_token": self.session_token, 413 | "analytics_tier": str(self.analytics_tier), 414 | "disableCookies": "false", 415 | "render_type": "canvas", 416 | "is_compatibility_mode": "false", 417 | "category": category, 418 | "action": action, 419 | } 420 | data.update(additional_data) 421 | return data 422 | 423 | def _get_base64_image(self, index: int) -> bytes: 424 | headers: Dict[str, str] = { 425 | "Accept": "*/*", 426 | "Accept-Encoding": "gzip, deflate, br, zstd", 427 | "Accept-Language": self.accept_language, 428 | "Cookie": f'{self.session_cookie}{self.cloudfare_cookie}; timestamp={self._generate_newrelic_timestamp()}', 429 | "Priority": "u=1, i", 430 | "Referer": self.embed_url, 431 | #"Sec-Ch-Ua": self.sec_ch_ua, 432 | "Sec-Ch-Ua-Mobile": "?0", 433 | "Sec-Ch-Ua-Platform": self.sec_platform, 434 | "Sec-Fetch-Dest": "empty", 435 | "Sec-Fetch-Mode": "cors", 436 | "Sec-Fetch-Site": "same-origin", 437 | "User-Agent": self.user_agent 438 | } 439 | self.http_session.headers = self._sort_headers(headers) 440 | self.http_session.cookies.clear() 441 | 442 | url, query_string = self.challenge_imgs[index].split("?", 1) 443 | query_params: Dict[str, str] = dict( 444 | param.split("=") for param in query_string.split("&") 445 | ) 446 | 447 | response = self.http_session.get(url=self.challenge_imgs[index]) 448 | 449 | return b64encode(response.content) 450 | 451 | def _submit_tile_answer(self, tile_index: int) -> Dict[str, Any]: 452 | tile_index += 1 453 | 454 | if tile_index > 3: 455 | x_coord: int = ((tile_index - 3) * 100) - randint(2, 98) 456 | y_coord: int = randint(102, 200) 457 | else: 458 | x_coord: int = (tile_index * 100) - randint(2, 98) 459 | y_coord: int = randint(1, 98) 460 | 461 | px_value: str = str((x_coord // 3) / 100) 462 | py_value: str = str((y_coord // 2) / 100) 463 | 464 | self.answer_history.append({"px": px_value, "py": py_value, "x": x_coord, "y": y_coord}) 465 | 466 | encrypted_guess: str = encrypt_data( 467 | dumps(self.answer_history).replace(" ", ""), 468 | self.session_token, 469 | False 470 | ) 471 | requested_id: str = encrypt_data( 472 | self.screen_clicks, 473 | f"REQUESTED{self.session_token}ID", 474 | False 475 | ) 476 | 477 | headers: Dict[str, str] = { 478 | "Accept": "*/*", 479 | "Accept-Encoding": "gzip, deflate, br, zstd", 480 | "Accept-Language": self.accept_language, 481 | "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", 482 | "Cookie": "", 483 | "Origin": self.challenge_information["service_url"], 484 | "Referer": self.embed_url, 485 | #"sec-ch-ua": self.sec_ch_ua, 486 | "sec-ch-ua-mobile": "?0", 487 | "sec-ch-ua-platform": self.sec_platform, 488 | "Sec-Fetch-Dest": "empty", 489 | "Sec-Fetch-Mode": "cors", 490 | "Sec-Fetch-Site": "same-origin", 491 | "User-Agent": self.user_agent, 492 | "X-NewRelic-Timestamp": self._generate_newrelic_timestamp(), 493 | "X-Requested-ID": requested_id, 494 | "X-Requested-With": "XMLHttpRequest", 495 | } 496 | 497 | headers["Cookie"] = ( 498 | f'{self.session_cookie}{self.cloudfare_cookie}; timestamp={headers["X-NewRelic-Timestamp"]}' 499 | ) 500 | self.http_session.headers = self._sort_headers(headers) 501 | self.http_session.cookies.clear() 502 | 503 | payload: Dict[str, str] = { 504 | "session_token": self.session_token, 505 | "game_token": self.challenge_id, 506 | "sid": self.session_id, 507 | "guess": encrypted_guess, 508 | "render_type": "canvas", 509 | "analytics_tier": str(self.analytics_tier), 510 | "bio": self.biometrics, 511 | "is_compatibility_mode": "false", 512 | } 513 | 514 | return self.http_session.post( 515 | f'{self.challenge_information["service_url"]}/fc/ca/', 516 | data=self._url_encode(payload) 517 | ).json() 518 | 519 | def _submit_index_answer(self, selected_index: int) -> Dict[str, Any]: 520 | headers: Dict[str, str] = {"Accept": "*/*", "Accept-Encoding": "gzip, deflate, br, zstd", 521 | "Accept-Language": self.accept_language, 522 | "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", 523 | "Cookie": f'{self.session_cookie}{self.cloudfare_cookie}; timestamp={self._generate_newrelic_timestamp()}', 524 | "Origin": self.challenge_information["service_url"], "Referer": self.embed_url, 525 | #"sec-ch-ua": self.sec_ch_ua, 526 | "sec-ch-ua-mobile": "?0", 527 | "sec-ch-ua-platform": self.sec_platform, "Sec-Fetch-Dest": "empty", 528 | "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", 529 | "User-Agent": self.user_agent} 530 | 531 | self.http_session.headers = self._sort_headers(headers) 532 | self.http_session.cookies.clear() 533 | 534 | token_parts: List[str] = self.session_token.split(".") 535 | token_first_part, token_second_part = token_parts[0], token_parts[1] 536 | 537 | self.answer_history.append({"index": selected_index}) 538 | self.tguess_history.append({"index": str(selected_index), token_first_part: token_second_part}) 539 | 540 | dapib_transformed_guess: Optional[str] = None 541 | if self.dapib_url is not None: 542 | dapib_solver = DapibBreaker(self.http_session, self.dapib_url, self.challenge_information["service_url"]) 543 | dapib_transformed_guess = dapib_solver.fetch_transformed_guess(self.tguess_history, self.tguess_called) 544 | self.tguess_called += 1 545 | 546 | encrypted_guess: str = encrypt_data( 547 | dumps(self.answer_history).replace(" ", ""), 548 | self.session_token, 549 | False 550 | ) 551 | encrypted_tguess: str = ( 552 | encrypt_data(dumps(self.tguess_history).replace(" ", ""), self.session_token, False) 553 | if dapib_transformed_guess is None 554 | else encrypt_data(dapib_transformed_guess, self.session_token, False) 555 | ) 556 | requested_id: str = encrypt_data( 557 | self.screen_clicks, 558 | f"REQUESTED{self.session_token}ID", 559 | False 560 | ) 561 | 562 | headers.update({ 563 | "X-NewRelic-Timestamp": self._generate_newrelic_timestamp(), 564 | "X-Requested-ID": requested_id, 565 | "X-Requested-With": "XMLHttpRequest", 566 | }) 567 | 568 | headers["Cookie"] = f'{self.session_cookie}{self.cloudfare_cookie}; timestamp={headers["X-NewRelic-Timestamp"]}' 569 | self.http_session.headers = self._sort_headers(headers) 570 | self.http_session.cookies.clear() 571 | 572 | payload: Dict[str, str] = { 573 | "session_token": self.session_token, 574 | "game_token": self.challenge_id, 575 | "sid": self.session_id, 576 | "guess": encrypted_guess, 577 | "render_type": "canvas", 578 | "analytics_tier": str(self.analytics_tier), 579 | "bio": self.biometrics, 580 | "is_compatibility_mode": "false", 581 | "tguess": encrypted_tguess, 582 | } 583 | 584 | return self.http_session.post( 585 | f'{self.challenge_information["service_url"]}/fc/ca/', 586 | data=self._url_encode(payload) 587 | ).json() --------------------------------------------------------------------------------