import os
import sys
import asyncio
import logging
import hashlib
import tempfile
import sqlite3
import multiprocessing
from modules.Infrastructure import client
from utils.General import parse_config
from utils.extra import create_pool
conf = parse_config.parse()
client_conf = conf['Client']
server_conf = conf['Server']
username = server_conf['user_server']
public_key = server_conf['pub_key']
private_key = server_conf['priv_key']
client.config = client_conf
db_name = '%s/erica.db' % (tempfile.gettempdir())
tmp_wordlist = '%s/erica-wordlist.lst' % (tempfile.gettempdir())
information = {
'description' : 'Realizar un ataque por diccionario',
'commands' : [
{
'argumentos principales' : [
{
'args' : ('-o', '--option'),
'help' : 'El comando a ejecutar en el servidor',
'choices' : ('get_job', 'write', 'crack', 'crack_local', 'status', 'reset'),
'default' : 'get_job'
}
]
},
{
'argumentos requeridos por el comando \'write\'' : [
{
'args' : ('-w', '--wordlist'),
'help' : 'La lista de palabras a usar'
}
]
},
{
'argumentos requeridos por el comando \'write\' y \'crack_local\'' : [
{
'args' : ('-H', '--hash'),
'help' : 'La suma de verificación a atacar'
},
{
'args' : ('-t', '--hash-type'),
'help' : 'La función hash a usar'
}
]
},
{
'optionals' : [
{
'args' : ('-l', '--lines'),
'help' : 'Dividir el archivo en N líneas para cada servidor',
'default' : 1000000,
'type' : int
},
{
'args' : ('-i', '--interval'),
'help' : 'El intervalo en segundos para comprobar si el trabajo a concluido',
'type' : int,
'default' : 15
},
{
'args' : ('-m', '--max-workers'),
'help' : 'El máximo de procesos trabajando en la operación localmente',
'default' : multiprocessing.cpu_count(),
'type' : int
},
{
'args' : ('-L', '--shake-length'),
'help' : 'La longitud del resultado en la función shake y derivados',
'default' : 32,
'type' : int
}
]
}
],
'version' : '1.0.0'
}
def get_lines(filename):
count = 0
with open(filename, 'r') as fd:
while (line := fd.readline()):
count += 1
return count
def crack(args):
(wordlist, hash, hash_type) = args
func = getattr(hashlib, hash_type)
with open(wordlist, 'r') as fd:
for i in fd:
i = i.strip()
hash2cmp = func(i.encode()).hexdigest()
if (hash2cmp == hash):
logging.info('Operación concluida localmente: %s (%s(%s))',
hash, hash_type.upper(), i)
return True
logging.warning('No se pudo satisfacer la operación localmente')
return False
def chunk(fd, lines, parts):
chunks = []
index = 0
count = 0
for i in range(parts):
chunks.append([])
while (line := fd.readline()):
if not (line.strip()):
continue
if (index == parts):
index = 0
yield chunks
for i in range(parts):
chunks[i] = []
chunks[index].append(line)
if (count == lines):
index += 1
count = 0
continue
count += 1
if (chunks != []):
yield chunks
def execute_sql_command(cmd, args=(), write=False):
with sqlite3.connect(db_name) as sqlite_db:
cursor = sqlite_db.cursor()
cursor.execute('CREATE TABLE IF NOT EXISTS jobs(job VARCHAR(32) NOT NULL, network TEXT NOT NULL)')
sqlite_db.commit()
cursor.execute(cmd, args)
if (write):
sqlite_db.commit()
else:
return cursor.fetchall()
def getJob(net):
result = execute_sql_command(
'SELECT job FROM jobs WHERE network = ? LIMIT 1', (net,)
)
if (result != []):
return result[0][0]
def writeControl(net, response):
error = response['error']
message = response['message']
if (error):
logging.warning('Ocurrió un error interno en el servidor "%s": %s', net, message)
else:
logging.info('Se ha escrito una parte del diccionario en el servidor %s', net)
def jobsControl(net, response):
execute_sql_command('INSERT INTO jobs(job, network) VALUES (?, ?)', (response, net), True)
logging.info('El trabajo "%s" se creó en el servidor "%s"', response, net)
def crackControl(net, response):
error = response['error']
message = response['message']
if (error):
logging.warning('Ocurrió un error interno en el servidor "%s": %s', net, message)
else:
logging.info('Se ha iniciado el ataque en el servidor "%s" 3:-)', net)
def statusControl(net, response):
error = response['error']
message = response['message']
if (error):
logging.warning('Ocurrió un error interno en el servidor "%s": %s', net, message)
else:
if (message == 'running'):
logging.warning('La operación todavía se sigue efectuando')
elif (message[:7] == 'success'):
logging.info('Operación realizada con éxito, se ha obtenido la palabra correcta en la lista de palabras almacenada en el servidor "%s": %s', net, message[8:])
elif (message == 'fail'):
logging.warning('No se pudo obtener la palabra con éxito usando la lista de palabras almacenada en el servidor "%s" :-(', net)
else:
logging.warning('Estado desconocido en el servidor "%s": %s', net, message)
async def execute_command(net, cmd, data, callback, *args, method='POST', **kwargs):
url = '%s/erica/%s' % (net, cmd)
url_hash = hashlib.sha3_224(net.encode()).hexdigest()
pub_key_file = '%s/servkeys/%s' % (
server_conf['init_path'], url_hash
)
UClient = client.UTeslaClient(username)
await UClient.set_user_keys(public_key, private_key)
await UClient.set_server_key(pub_key_file)
try:
result = await UClient.fetch(
url, data, method=method
)
except Exception as err:
logging.warning('Ocurrió en la petición hacia el servidor "%s": %s', url, err)
response = None
else:
response = UClient.get_message(result.body)
if (response):
return callback(net, response, *args, **kwargs)
else:
logging.warning('No se obtuvo ningún dato por parte del servidor %s...', net)
async def MainParser(args):
hash = args.hash
hash_type = args.hash_type
wordlist = args.wordlist
lines = args.lines
interval = args.interval
max_workers = args.max_workers
option = args.option
shake_length = args.shake_length
db = await create_pool.create(server_conf['mysql_db'])
networks = await db.get_networks(show_all=True)
jobs = []
if (option == 'get_job') and (os.path.isfile(db_name)):
logging.warning('No se puede obtener nuevos trabajas hasta que se haga un reinicio para evitar alteraciones. Use el comando \'reset\' para permitir esta operación')
return
if (wordlist is None) and (option == 'write'):
logging.warning('¡No se definió la ruta del diccionario a usar!')
return
if (hash is None or hash_type is None) and (option == 'write' or option == 'crack_local'):
logging.warning('Falta definir el \'hash\' y/o la función a usar')
return
if (option == 'get_job' or option == 'status' or option == 'crack'):
callbacks = {
'get_job' : jobsControl,
'crack' : crackControl,
'status' : statusControl
}
for _, net, token in networks:
data = {
'token' : token
}
job = getJob(net)
params = {
'crack' : '?id=%s' % (job),
'status' : '?id=%s' % (job)
}
jobs.append(
execute_command(net, option + params.get(option, ''), data, callbacks[option], method='GET')
)
elif (option == 'write'):
local_machine = '<Local Machine>'
networks = networks + ((None, local_machine, None),)
parts = len(networks)
with open(wordlist, 'r') as fd:
with open(tmp_wordlist, 'w', buffering=1) as tmp_fd:
for words in chunk(fd, lines, parts):
for (_, net, token), chunk_val in zip(networks, words):
if (net == local_machine):
tmp_fd.writelines(chunk_val)
else:
data = {
'token' : token,
'body' : {
'wordlist' : chunk_val,
'hash' : hash,
'hash_type' : hash_type,
'shake_length' : shake_length
}
}
cmd = 'write?id=%s' % (
getJob(net)
)
logging.warning('Escribiendo %d líneas en %s...', len(chunk_val)-1, net)
await execute_command(net, cmd, data, writeControl)
elif (option == 'crack_local'):
processes = multiprocessing.Pool(max_workers)
processes.map(crack, [(tmp_wordlist, hash, hash_type)])
processes.close()
processes.join()
elif (option == 'reset'):
files2del = [
tmp_wordlist,
db_name
]
for file in files2del:
if (os.path.isfile(file)):
os.remove(file)
logging.warning('¡%s se ha eliminado!', file)
await asyncio.gather(*jobs)