Divida e conquiste a senha Bruteforcer

Sep 25 2020

Meu programa força bruta uma senha. A senha é uma string composta por uma chave e um código numérico de quatro dígitos. A chave é conhecida, portanto, somos basicamente uma força bruta entre 0000 e 9999

Um exemplo de senha é:
UoMYTrfrBFHyQXmg6gzctqAwOmw1IohZ 4143

Atualizei aquele script que escrevi para aproveitar as vantagens do multiprocessamento e funcionar mais rápido. A ideia básica é dividir a tarefa pelo número de CPUs disponíveis. Existem dois eventos configurados:

  • prnt_sig_found é usado por subprocessos para informar aos pais se eles conseguem adivinhar a senha correta.
  • O processo pai então usa child_sig_termpara interromper cada subprocessamento

Meu Python está enferrujado e acho que fiz algumas escolhas erradas. Seria útil invalidar minhas suposições. :)

#!/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()

Respostas

2 G.Sliepen Sep 26 2020 at 04:18

Use pools de processos

O multiprocessingmódulo já possui funções para criar um pool de processos , então você não precisa implementá-lo. Existem funções para que ele inicie automaticamente uma série de tarefas de uma vez. Por exemplo, usando a map()função, você pode enfileirar uma tarefa para cada combinação possível de 4 dígitos:

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

Isso fará com que os threads de trabalho executem a try_combination()função em todos os valores entre 0 e 9999. No seu caso, você pode querer evitar a criação de uma única conexão para cada combinação, então você deve fazer isso para não enfileirar uma tarefa para cada combinação, mas cada tarefa tenta várias combinações. Dê uma olhada na documentação do multiprocessing.poolpara descobrir o que é possível.

O uso multiprocessing.pooldeve permitir que você remova a maior parte do código em main().

Pare outros processos quando encontrar a combinação certa

Seu código gera vários processos, cada um experimentando uma variedade de combinações. Você então espera que todos eles terminem. Mas, em média, você encontrará a combinação certa depois de tentar apenas metade das combinações, portanto, será duas vezes mais eficiente se parar de processar quando encontrar a combinação certa. Isso significa que em vez de esperar que todos eles terminem, você deve esperar que o primeiro termine, verificar se ele encontrou a combinação e, em caso afirmativo, interromper os outros processos.

Com multiprocessing.poolvocê pode fazer isso usando map_async()e terminate().

Remova a maioria das print()declarações

Tenho certeza de que foram úteis ao criar o código, para ver o que ele estava fazendo e para depurar quaisquer problemas. Mas agora que está funcionando, você deve remover essas instruções. No final, você não está interessado no que está acontecendo, apenas no resultado final. Além disso, não imprima o tempo que demorou, você pode usar ferramentas externas como o timecomando para medir quanto tempo seu programa demorou para ser executado.

Considere substituir class Connectionpor uma única função

Você escreveu um classpara fazer uma conexão e tentar várias combinações, mas a única coisa que você faz é construí-lo e chamar execute()uma vez. Uma única função seria suficiente aqui.

Além disso, depois de remover as print()instruções desnecessárias , vejo que a maioria das funções de membro são apenas uma única instrução. Em vez de escrever essa função de membro e chamá-la internamente execute(), você poderia ter escrito essa instrução diretamente internamente execute(). Portanto, mova todas as outras funções-membro e o construtor para execute(), mova-o para fora do classe dê a ele um nome melhor, como try_combinations().