import shutil import sys import time from itertools import islice from subprocess import Popen, PIPE from traceback import print_tb from typing import Iterable from threading import BoundedSemaphore from multiprocessing.pool import ThreadPool from stegcracker import __url__ from stegcracker.helpers import error, b2s, b2s_file, print_diagnostic_info class Cracker: SUPPORTED_FILES = ('jpg', 'jpeg', 'bmp', 'wav', 'au') def __init__(self, file: str, output: str, line_count: int, threads: int = 8, chunk_size: int = 64, quiet: bool = False, verbose: bool = False): """ Cracker constructor :param threads: Number of threads to attempt to crack the signature :param file: File to (attempt) to crack :param output: Output file to write the file to :param chunk_size: Number of passwords to attempt per thread """ self.lock = BoundedSemaphore() self.pool = ThreadPool(processes=threads) self.thread_count = threads self.quiet = quiet self.verbose = verbose self.file = file self.output = output self.chunk_size = chunk_size self.line_count = line_count or 1 self.has_error = False self.iterable = None self.attempts = 0 self.password = None def run(self, iterable: Iterable[str]): """Run the brute-forcer by iterating over a set of strings""" self.iterable = iterable for i in range(self.thread_count): self.pool.apply_async(self.crack, args=[i + 1], error_callback=self.error_handler) self.pool.close() self.pool.join() def error_handler(self, exc): """ Error callback handler for thread related issues :param exc: Exception :return: None """ error( f'Unhandled exception in cracker thread. Please report this issue ' f'on the official bug tracker: "{__url__}/issues" and don\'t forget ' f'to include the following traceback and diagnostic info:\n') print('## Stack Trace', file=sys.stderr) print('```', file=sys.stderr) print(type(exc).__name__ + ': ' + str(exc), file=sys.stderr) print_tb(exc.__traceback__, file=sys.stderr) print('```\n', file=sys.stderr) print('## System Information', file=sys.stderr) print_diagnostic_info() self.has_error = True self.pool.terminate() def passwords(self): self.lock.acquire() passwords = list(islice(self.iterable, self.chunk_size)) self.lock.release() return passwords def crack(self, thread_id): """Attempt to crack a number of passwords""" time.sleep(0.1 * thread_id) attempts = 0 password = '' passwords = self.passwords() thread_id = str(thread_id).rjust(len(str(self.thread_count)), '0') while any(passwords) and not self.password and not self.has_error: for password in passwords: if isinstance(password, bytes): password = b2s(password).strip() attempts += 1 with Popen([ 'steghide', 'extract', '-sf', self.file, '-xf', self.output, '-p', password, '-f' ], stdout=PIPE, stderr=PIPE) as proc: proc.wait() if self.verbose: self.lock.acquire() sys.stderr.write(f'[Thread {thread_id}, password {password!r}]: ') shutil.copyfileobj(b2s_file(proc.stderr), sys.stderr) shutil.copyfileobj(b2s_file(proc.stdout), sys.stderr) self.lock.release() if proc.returncode == 0: self.password = password self.pool.terminate() break if not self.quiet: self.lock.acquire() self.attempts += attempts percentage = self.attempts * 100 / self.line_count print(( f"{self.attempts}/{self.line_count} ({percentage:.2f}%) " f"Attempted: {password[0:20].ljust(20, ' ')}".strip() ), end='\r', flush=True, file=sys.stderr) self.lock.release() passwords = self.passwords() attempts = 0