├── .gitattributes ├── .gitignore ├── README.md ├── downbad.jpg ├── fuckcaptcha ├── api │ └── breakers.py ├── bda │ ├── browser_agent_fetcher.py │ └── fingerprinting.py ├── bda_dumper.py ├── cipher.py └── solver_task.py ├── photo_2022-09-08_14-56-25.jpg ├── requirements.txt └── server_example.py /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | venv 2 | *.pyc 3 | .idea -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fuckcaptcha-v2 2 | A library for bypassing microsoft's captcha. 3 | # Jinthium Moments 4 | ![LOL](https://github.com/dort-dev/fuckcaptcha-v2/raw/main/downbad.jpg) 5 | ![LOLOL](https://github.com/dort-dev/fuckcaptcha-v2/raw/main/photo_2022-09-08_14-56-25.jpg) 6 | -------------------------------------------------------------------------------- /downbad.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dort-dev/fuckcaptcha-v2/768b4fa832b7b8711ace148ed7b5cbe9bae88d78/downbad.jpg -------------------------------------------------------------------------------- /fuckcaptcha/api/breakers.py: -------------------------------------------------------------------------------- 1 | import math 2 | from random import random 3 | 4 | 5 | def get_location(tile): 6 | return [ 7 | (tile % 3) * 100 + (tile % 3) * 3 + 3 + 10 + math.floor(random() * 80), 8 | math.floor(tile / 3) * 100 + math.floor(tile / 3) * 3 + 3 + 10 + math.floor(random() * 80), 9 | ] 10 | 11 | 12 | def fix_answer(method, c): 13 | if 'method_1' in method: 14 | return {'x': c[1], 'y': c[0]} 15 | elif 'method_2' in method: 16 | return {'x': c[0], 'y': (c[1] + c[0]) * c[0]} 17 | elif 'method_3' in method: 18 | return {'x': c[0], 'b': c[1]} 19 | elif 'method_4' in method: 20 | return [c[0], c[1]] 21 | elif 'method_5' in method: 22 | return [math.sqrt(c[1]), math.sqrt(c[0])] 23 | else: 24 | return { 25 | 'px': round(c[0] / 300, 2), 26 | 'py': round(c[1] / 200, 2), 27 | 'x': c[0], 28 | 'y': c[1], 29 | } 30 | -------------------------------------------------------------------------------- /fuckcaptcha/bda/browser_agent_fetcher.py: -------------------------------------------------------------------------------- 1 | import random 2 | import string 3 | 4 | from fuckcaptcha.bda import fingerprinting 5 | 6 | 7 | def rand_str(length) -> str: 8 | return "".join(random.choices(string.ascii_lowercase, k=length)).upper() 9 | 10 | 11 | def rand_platform_token() -> str: 12 | return f"{rand_str(10)} {rand_version()}".upper() 13 | 14 | 15 | def rand_version() -> str: 16 | digits = [] 17 | for i in range(2): 18 | digits.append("".join(random.choices(string.digits, k=3))) 19 | return ".".join(digits) 20 | 21 | 22 | def rand_bda() -> dict: 23 | agent = f"Firefox/{rand_version()} {rand_str(5)}/{rand_version()} OP/{rand_version()}" 24 | return { 25 | "bda": fingerprinting.get_browser_data(agent), 26 | "agent": agent 27 | } 28 | -------------------------------------------------------------------------------- /fuckcaptcha/bda/fingerprinting.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import json 3 | import time 4 | 5 | from fuckcaptcha import cipher 6 | 7 | 8 | def get_browser_data(user_agent: str) -> str: 9 | ts = time.time() 10 | timeframe = int(ts - ts % 21600) 11 | key = user_agent + str(timeframe) 12 | the_data = [{ 13 | "key": "api_type", 14 | "value": "js" 15 | }, { 16 | "key": "f", 17 | "value": "Dort on top." 18 | }, { 19 | "key": "enhanced_fp", 20 | "value": [{ 21 | "key": "browser_detection_firefox", 22 | "value": "POV: multi million dollar company" 23 | }, { 24 | "key": "audio_fingerprint", 25 | "value": "let's just check the bda for certain fields that proceed to not verify them. fucking clowns" 26 | "... LOLOL" 27 | }] 28 | }] 29 | data = cipher.encrypt(json.dumps(the_data), key) 30 | return base64.b64encode(data.encode("utf-8")).decode("utf-8") 31 | -------------------------------------------------------------------------------- /fuckcaptcha/bda_dumper.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import time 3 | 4 | from cipher import decrypt 5 | 6 | if __name__ == '__main__': 7 | bda = input("BDA: ") 8 | ua = input("User Agent: ") 9 | ts = int(time.time()) 10 | timeframe = ts - ts % 21600 11 | key = ua + str(timeframe) 12 | decrypted = decrypt(base64.b64decode(bda).decode('utf-8', errors='ignore'), key) 13 | print("BDA: " + decrypted.decode('utf-8', errors='ignore')) 14 | -------------------------------------------------------------------------------- /fuckcaptcha/cipher.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import hashlib 3 | import json 4 | import random 5 | import string 6 | 7 | from Crypto.Cipher import AES 8 | 9 | 10 | def encrypt(data, key): 11 | data = data + chr(16 - len(data) % 16) * (16 - len(data) % 16) 12 | 13 | salt = b"".join(random.choice(string.ascii_lowercase).encode() for _ in range(8)) 14 | salted, dx = b"", b"" 15 | while len(salted) < 48: 16 | dx = hashlib.md5(dx + key.encode() + salt).digest() 17 | salted += dx 18 | 19 | key = salted[:32] 20 | iv = salted[32:32 + 16] 21 | aes = AES.new(key, AES.MODE_CBC, iv) 22 | 23 | encrypted_data = {"ct": base64.b64encode(aes.encrypt(data.encode())).decode("utf-8"), "iv": iv.hex(), 24 | "s": salt.hex()} 25 | return json.dumps(encrypted_data, separators=(',', ':')) 26 | 27 | 28 | def decrypt(data, key): 29 | data = json.loads(data) 30 | dk = key.encode() + bytes.fromhex(data["s"]) 31 | md5 = [hashlib.md5(dk).digest()] 32 | result = md5[0] 33 | for i in range(1, 3 + 1): 34 | md5.insert(i, hashlib.md5((md5[i - 1] + dk)).digest()) 35 | result += md5[i] 36 | 37 | aes = AES.new(result[:32], AES.MODE_CBC, bytes.fromhex(data["iv"])) 38 | data = aes.decrypt(base64.b64decode(data["ct"])) 39 | return data 40 | -------------------------------------------------------------------------------- /fuckcaptcha/solver_task.py: -------------------------------------------------------------------------------- 1 | import json 2 | import random 3 | import sys 4 | import time 5 | 6 | import httpx 7 | 8 | from fuckcaptcha import cipher 9 | from fuckcaptcha.api import breakers 10 | from fuckcaptcha.bda.browser_agent_fetcher import rand_bda 11 | 12 | 13 | class Solver: 14 | 15 | def __init__(self, skey, url, agent, bda, proxy): 16 | self.e_key = None 17 | self.agent = agent 18 | self.bda = bda 19 | self.url = url 20 | self.skey = skey 21 | self.proxy = f"http://{proxy}" 22 | self.api_url = "https://client-api.arkoselabs.com" 23 | self.answers = [] 24 | self.client = httpx.Client(proxies=self.proxy, http2=True) 25 | 26 | def get_site_token_data(self): 27 | url = f'{self.api_url}/fc/gt2/public_key/{self.skey}' 28 | return self.client.post(url, headers={ 29 | "accept": "*/*", 30 | "accept-encoding": "gzip, deflate, br", 31 | "accept-language": "es-ES,es;q=0.9,en-US;q=0.8,en;q=0.7", 32 | "content-type": "application/x-www-form-urlencoded; charset=UTF-8", 33 | "origin": "https://iframe.arkoselabs.com", 34 | "referer": "https://iframe.arkoselabs.com/", 35 | "sec-ch-ua": '"Chromium";v="105", " Not A;Brand";v="99", "Google Chrome";v="105"', 36 | "sec-ch-ua-mobile": "?0", 37 | "sec-ch-ua-platform": '"Linux"', 38 | "sec-fetch-dest": "empty", 39 | "sec-fetch-mode": "cors", 40 | "sec-fetch-site": "same-site", 41 | "user-agent": self.agent 42 | }, data={ 43 | 'bda': self.bda, 44 | 'public_key': self.skey, 45 | 'site': self.url, 46 | 'userbrowser': self.agent, 47 | 'language': 'en', 48 | 'rnd': str(random.random()), 49 | 'data[id]': 'null', 50 | }).json() 51 | 52 | def load_captcha(self, session_token, region): 53 | url = f'{self.api_url}/fc/a/' 54 | return self.client.post(url, headers={ 55 | 'accept': '*/*', 56 | 'sec-ch-ua-mobile': '?0', 57 | 'sec-ch-ua-platform': '"Linux"', 58 | 'sec-fetch-dest': 'empty', 59 | 'sec-fetch-mode': 'cors', 60 | 'sec-fetch-site': 'same-origin', 61 | 'accept-encoding': 'gzip, deflate, br', 62 | "sec-ch-ua": '"Chromium";v="105", " Not A;Brand";v="99", "Google Chrome";v="105"', 63 | "accept-language": "es-ES,es;q=0.9,en-US;q=0.8,en;q=0.7", 64 | 'origin': 'https://iframe.arkoselabs.com', 65 | 'referer': 'https://iframe.arkoselabs.com/', 66 | 'user-agent': self.agent, 67 | "x-newrelic-timestamp": str(int(time.time()) * 1000), 68 | }, data={ 69 | 'session_token': session_token, 70 | 'sid': region, 71 | 'analytics_tier': 40, 72 | 'category': 'Site URL', 73 | 'action': 'https://iframe.arkoselabs.com/', 74 | 'render_type': 'canvas' 75 | }).json() 76 | 77 | def get_challenge(self, token, region): 78 | url = f'{self.api_url}/fc/gfct/' 79 | return self.client.post(url, headers={ 80 | 'accept': '*/*', 81 | 'accept-encoding': 'gzip, deflate, br', 82 | "accept-language": "es-ES,es;q=0.9,en-US;q=0.8,en;q=0.7", 83 | 'cache-control': 'no-cache', 84 | 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8', 85 | 'origin': 'https://client-api.arkoselabs.com', 86 | 'sec-ch-ua-mobile': '?0', 87 | 'sec-ch-ua-platform': '"Linux"', 88 | "sec-ch-ua": '"Chromium";v="105", " Not A;Brand";v="99", "Google Chrome";v="105"', 89 | 'referer': 'https://client-api.arkoselabs.com/fc/gc/?token=' + token.replace("|", "&"), 90 | 'sec-fetch-dest': 'empty', 91 | 'sec-fetch-mode': 'cors', 92 | 'sec-fetch-site': 'same-origin', 93 | 'user-agent': self.agent, 94 | "Cookie": f"timestamp={str(int(time.time()) * 1000)}", 95 | "x-newrelic-timestamp": str(int(time.time()) * 1000), 96 | }, data={ 97 | 'data[status]': 'init', 98 | 'render_type': 'canvas', 99 | 'sid': region, 100 | 'analytics_tier': 40, 101 | 'lang': 'en', 102 | 'token': token, 103 | }).json() 104 | 105 | def answer_normal(self, api_breaker, region, session_token, challenge_token, input_answer): 106 | input_answer = breakers.get_location(input_answer) 107 | fixed_answer = breakers.fix_answer(api_breaker, input_answer) 108 | self.answers.append(fixed_answer) 109 | url = f'{self.api_url}/fc/ca/' 110 | return self.client.post(url, headers={ 111 | 'accept': '*/*', 112 | 'accept-encoding': 'gzip, deflate, br', 113 | 'user-agent': self.agent, 114 | }, data={ 115 | 'game_token': challenge_token, 116 | 'sid': region, 117 | 'session_token': session_token, 118 | 'guess': cipher.encrypt(json.dumps(self.answers), session_token), 119 | 'analytics_tier': 40, 120 | 'bio': "eyJtYmlvIjoiMzM3MCwwLDMwMCwzNjszNDA0LDAsMjk0LDM2OzM0MTAsMCwyODgsMzg7MzQxNywwLDI4MywzOTszNDIzLDAsMjc4LDQzOzM0MjgsMCwyNzQsNDg7MzQzMiwwLDI3Miw1MzszNDM1LDAsMjcwLDU4OzM0MzgsMCwyNjgsNjQ7MzQ0MSwwLDI2NCw2OTszNDQ1LDAsMjYzLDc2OzM0NDgsMCwyNjEsODI7MzQ1MiwwLDI1OSw4OTszNDU1LDAsMjU3LDk0OzM0NTcsMCwyNTcsMTAwOzM0NjAsMCwyNTUsMTA1OzM0NjMsMCwyNTEsMTExOzM0NjYsMCwyNTAsMTE3OzM0NjksMCwyNDgsMTIyOzM0NzMsMCwyNDYsMTI3OzM0ODAsMCwyNDQsMTMyOzM0ODksMCwyNDIsMTM3OzM1MDAsMCwyNDEsMTQyOzM1MTIsMCwyNDAsMTQ3OzM1MjAsMCwyNDAsMTU0OzM1MzAsMCwyNDEsMTYwOzM1NDIsMCwyNDMsMTY1OzM1NDgsMCwyNDYsMTcwOzM1NTYsMCwyNTAsMTc0OzM1NjksMCwyNTUsMTc5OzM1ODgsMCwyNjAsMTgyOzM3MDMsMCwyNTcsMTg3OzM3ODYsMSwyNTMsMTg5OzM5MjksMiwyNTMsMTg4OzQwNjgsMCwyNTIsMTg2OzQwODcsMCwyNDgsMTgxOzQxMTMsMCwyNDQsMTc3OzQxMzQsMCwyNDAsMTczOzQxNjcsMCwyMzUsMTcwOzQyMTAsMCwyMzIsMTY1OzQzODksMSwyMzEsMTYyOzQ0ODcsMiwyMzEsMTYyOyIsInRiaW8iOiIiLCJrYmlvIjoiIn0=" 121 | }).json() 122 | 123 | def answer_rotate(self, answer, challenge, challenge_token, region, session_token): 124 | clr = challenge['game_data']['customGUI']['_guiTextColor'] 125 | increment = int("28" if clr else clr.replace("#", "0x")[3:]) 126 | increment = round(increment / 10, 2) if increment > 113 else increment 127 | new_answer = round(answer * increment) if 0.0 <= answer <= round(360 / 51.4) - 1 else answer 128 | self.answers.append(new_answer) 129 | url = f'{self.api_url}/fc/ca/' 130 | return self.client.post(url, headers={ 131 | 'accept': '*/*', 132 | 'accept-encoding': 'gzip, deflate, br', 133 | 'user-agent': self.agent, 134 | "sec-fetch-site": "cross-site", 135 | "Accept-Language": "en-US,en;q=0.9", 136 | }, data={ 137 | 'game_token': challenge_token, 138 | 'sid': region, 139 | 'session_token': session_token, 140 | 'guess': cipher.encrypt(json.dumps(self.answers), session_token), 141 | 'analytics_tier': 40, 142 | 'bio': "eyJtYmlvIjoiMzM3MCwwLDMwMCwzNjszNDA0LDAsMjk0LDM2OzM0MTAsMCwyODgsMzg7MzQxNywwLDI4MywzOTszNDIzLDAsMjc4LDQzOzM0MjgsMCwyNzQsNDg7MzQzMiwwLDI3Miw1MzszNDM1LDAsMjcwLDU4OzM0MzgsMCwyNjgsNjQ7MzQ0MSwwLDI2NCw2OTszNDQ1LDAsMjYzLDc2OzM0NDgsMCwyNjEsODI7MzQ1MiwwLDI1OSw4OTszNDU1LDAsMjU3LDk0OzM0NTcsMCwyNTcsMTAwOzM0NjAsMCwyNTUsMTA1OzM0NjMsMCwyNTEsMTExOzM0NjYsMCwyNTAsMTE3OzM0NjksMCwyNDgsMTIyOzM0NzMsMCwyNDYsMTI3OzM0ODAsMCwyNDQsMTMyOzM0ODksMCwyNDIsMTM3OzM1MDAsMCwyNDEsMTQyOzM1MTIsMCwyNDAsMTQ3OzM1MjAsMCwyNDAsMTU0OzM1MzAsMCwyNDEsMTYwOzM1NDIsMCwyNDMsMTY1OzM1NDgsMCwyNDYsMTcwOzM1NTYsMCwyNTAsMTc0OzM1NjksMCwyNTUsMTc5OzM1ODgsMCwyNjAsMTgyOzM3MDMsMCwyNTcsMTg3OzM3ODYsMSwyNTMsMTg5OzM5MjksMiwyNTMsMTg4OzQwNjgsMCwyNTIsMTg2OzQwODcsMCwyNDgsMTgxOzQxMTMsMCwyNDQsMTc3OzQxMzQsMCwyNDAsMTczOzQxNjcsMCwyMzUsMTcwOzQyMTAsMCwyMzIsMTY1OzQzODksMSwyMzEsMTYyOzQ0ODcsMiwyMzEsMTYyOyIsInRiaW8iOiIiLCJrYmlvIjoiIn0=" 143 | }).json() 144 | 145 | def get_ekey(self, session_token, game_token): 146 | url = f'{self.api_url}/fc/ekey/' 147 | return self.client.post(url, headers={ 148 | 'accept': '*/*', 149 | 'accept-encoding': 'gzip, deflate, br', 150 | 'user-agent': self.agent, 151 | "sec-fetch-site": "cross-site", 152 | "Accept-Language": "en-US,en;q=0.9", 153 | }, data={ 154 | "session_token": session_token, 155 | "game_token": game_token 156 | }).json()['decryption_key'] 157 | 158 | def solve(self): 159 | data: dict = self.get_site_token_data() 160 | original_token: str = data['token'] 161 | split: list = original_token.split("|") 162 | session_token: str = split[0] 163 | region: str = split[1].replace("r=", "") 164 | l_captcha: dict = self.load_captcha(session_token, region) 165 | if l_captcha.get('logged'): 166 | challenge: dict = self.get_challenge(session_token, region) 167 | game_token: str = challenge['challengeID'] 168 | waves: int = challenge['game_data']['waves'] 169 | if waves > 3: 170 | return None 171 | if challenge['game_data']['gameType'] == 3: 172 | api_breaker = challenge['game_data']['customGUI']['api_breaker'] 173 | sys.stdout.write(f"Found an acceptable captcha ({str(waves)}-WAVE), Trying to solve.\n") 174 | sys.stdout.flush() 175 | for wave in range(waves): 176 | answer: int = random.randint(0, 5) 177 | answered: dict = self.answer_normal(api_breaker, region, session_token, game_token, answer) 178 | if 'solved' in answered and answered['solved']: 179 | return { 180 | 'solved': True, 181 | 'token': original_token, 182 | 'bda': { 183 | "bda": self.bda, 184 | "agent": self.agent 185 | } 186 | } 187 | elif challenge['game_data']['gameType'] == 1: 188 | for _ in range(waves): 189 | answered = self.answer_rotate(random.randint(0, 360), challenge, game_token, 190 | region, session_token) 191 | if 'solved' in answered and answered['solved']: 192 | return { 193 | 'solved': True, 194 | 'token': original_token, 195 | 'bda': { 196 | "bda": self.bda, 197 | "agent": self.agent 198 | } 199 | } 200 | 201 | else: 202 | return { 203 | "solved": False, 204 | "token": original_token 205 | } 206 | 207 | 208 | def solve(skey, url, proxy_iter): 209 | while True: 210 | nigger = rand_bda() 211 | agent = nigger['agent'] 212 | bda = nigger['bda'] 213 | solver = Solver(skey, url, agent, bda, next(proxy_iter)) 214 | try: 215 | result = solver.solve() 216 | if result is not None and result['solved']: 217 | return result['token'] 218 | except Exception: 219 | pass 220 | -------------------------------------------------------------------------------- /photo_2022-09-08_14-56-25.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dort-dev/fuckcaptcha-v2/768b4fa832b7b8711ace148ed7b5cbe9bae88d78/photo_2022-09-08_14-56-25.jpg -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | httpx 2 | httpx[brotli] 3 | httpx[http2] 4 | httpx[socks] 5 | pycryptodome 6 | colorama -------------------------------------------------------------------------------- /server_example.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from http.server import HTTPServer, BaseHTTPRequestHandler 3 | from socketserver import ThreadingMixIn 4 | from fuckcaptcha import solver_task 5 | 6 | proxy_iter = open("proxies.txt", 'r').read().rstrip().splitlines() 7 | 8 | 9 | class Handler(BaseHTTPRequestHandler): 10 | def do_GET(self): 11 | self.send_response(200) 12 | self.end_headers() 13 | solved = solver_task.solve("B7D8911C-5CC8-A9A3-35B0-554ACEE604DA", 14 | "https://iframe.arkoselabs.com", proxy_iter) 15 | sys.stdout.write(f"-> Task Finished: {solved.split('|')[0]}\n") 16 | sys.stdout.flush() 17 | self.wfile.write(bytes(solved, 'utf-8')) 18 | 19 | def log_message(self, fmt, *args): 20 | # Suppress connection logs 21 | pass 22 | 23 | 24 | class ThreadingSimpleServer(ThreadingMixIn, HTTPServer): 25 | pass 26 | 27 | 28 | if __name__ == '__main__': 29 | server = ThreadingSimpleServer(('0.0.0.0', 1338), Handler) 30 | server.serve_forever() 31 | --------------------------------------------------------------------------------