Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automated Planner #119

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions webadmin/fitcrackAPI/src/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
from src.api.fitcrack.endpoints.pcfg.pcfg import ns as pcfg_ns
from src.api.fitcrack.endpoints.settings.settings import ns as settings_ns
from src.api.fitcrack.endpoints.hashlists.hashlists import ns as hashlists_ns
from src.api.fitcrack.endpoints.planner.planner import ns as planner_ns

from src.database import db

Expand Down Expand Up @@ -88,6 +89,7 @@ def initialize_app(flask_app):
api.add_namespace(pcfg_ns)
api.add_namespace(settings_ns)
api.add_namespace(hashlists_ns)
api.add_namespace(planner_ns)

flask_app.register_blueprint(blueprint)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
'''
* Author : see AUTHORS
* Licence: MIT, see LICENSE
'''

Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
'''
* Author : David Brandšteter (xbrand13)
* Licence: MIT, see LICENSE
'''

from flask_restx import reqparse, inputs

def list_of_ints(arg):
if not arg:
return
else:
return list(map(int, arg.split(',')))

planner_parser = reqparse.RequestParser()
planner_parser.add_argument('days', type=int, required=True)
planner_parser.add_argument('hours', type=int, required=True)
planner_parser.add_argument('minutes', type=int, required=True)
planner_parser.add_argument('cracking_speed', type=int, required=True)

planner_parser.add_argument('fill', type=inputs.boolean, required=True)

planner_parser.add_argument('name', type=str, required=True, nullable=False)
planner_parser.add_argument('hashList', type=int, required=True)
planner_parser.add_argument('hostsIds', type=list_of_ints, required=False)
planner_parser.add_argument('password_len_min', type=int, required=False)
planner_parser.add_argument('password_len_max', type=int, required=False)
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
'''
* Author : David Brandšteter (xbrand13)
* Licence: MIT, see LICENSE
'''

#Makes a data template for create_job() method in job endpoint
def create_job_template():
data = {
"name": "",
"comment": "",
"hosts_ids": [],
"seconds_per_job": 3600,
"time_start": "",
"time_end": "",
"attack_settings": {
"attack_mode": 0,
"attack_name": "dict",
"rules": {
"id": None,
"name": None,
"count": None,
"time": None
},
"left_dictionaries": [],
"right_dictionaries": [],
"rule_left": "",
"rule_right": "",
"masks": [
""
],
"attack_submode": 0,
"distribution_mode": 0,
"markov_treshold": None,
"markov": None,
"charset": [],
"mask": "",
"keyspace_limit": 0,
"check_duplicates": True,
"case_permute": False,
"min_password_len": 1,
"max_password_len": 8,
"min_elem_in_chain": 1,
"max_elem_in_chain": 8,
"generate_random_rules": 0,
"optimized": True
},
"hash_list_id": None,
"valid_only": True
}
return data

#Get supported masks
def get_masks():
masks = {
"all": {
"mask": "?a",
"count": 95
},
"lower": {
"mask": "?l",
"count": 26
},
"digit": {
"mask": "?d",
"count": 10
}
}
return masks

#Hashes per second * number of seconds
def compute_keyspace(days, hours, minutes, speed):
return (speed * ((days*24*3600) + (hours*3600) + (minutes*60)))

#Rules
def decide_on_rules(dicts, rules, keyspace):
selected_rule = None
current_keyspace = 0
for rule in rules:
current_rule = rule_fit_to_keyspace(dicts, rule, keyspace)
if (current_rule["fit"] == True and current_rule["current_keyspace"] > current_keyspace):
selected_rule = rule
current_keyspace = current_rule["current_keyspace"]

return selected_rule

def rule_fit_to_keyspace(dicts, rule, keyspace):
fit = {}
fit["current_keyspace"] = 0
fit["fit"] = False

for dict in dicts:
fit["current_keyspace"] += rule.count * dict.keyspace
if fit["current_keyspace"] > keyspace:
return fit

fit["fit"] = True
return fit

#Dictionary Attack creation
def make_dict_attack(name, dict, rule, hash_list, hosts_ids):
data = create_job_template()
if not hosts_ids:
hosts_ids = []
data["hosts_ids"] = hosts_ids
data["name"] = name + "_" + dict.name

if rule != None:
data["attack_settings"]["rules"]["id"] = rule.id
data["attack_settings"]["rules"]["name"] = rule.name
data["attack_settings"]["rules"]["count"] = rule.count
data["attack_settings"]["rules"]["time"] = rule.time
else:
data["attack_settings"]["rules"] = None

dictionary = [
{
"id": dict.id,
"name": dict.name,
"keyspace": dict.keyspace,
"time": dict.time,
"hex_dict": dict.hex_dict
}
]

data["attack_settings"]["left_dictionaries"] = dictionary
data["hash_list_id"] = hash_list

return data

def make_bruteforce_attack(name, hash_list, hosts_ids, password_len_min, password_len_max, keyspace):
masks = generate_masks(password_len_min, password_len_max, keyspace)

#Check if no masks were able to fit into the keyspace
if not masks or masks == []:
return False

data = create_job_template()

if not hosts_ids:
hosts_ids = []
data["hosts_ids"] = hosts_ids
data["name"] = name + "_BruteForce_Fill"
data["attack_settings"]["attack_mode"] = 3
data["attack_settings"]["attack_name"] = "mask"
data["hash_list_id"] = hash_list

data["attack_settings"]["masks"] = masks
return data

def generate_masks(password_len_min, password_len_max, keyspace):
mask_list = get_masks()
masks = []
mask = ""
next_mask = ""
current_keyspace = 0
current_length = 0

#Decide on first mask
next_mask = decide_first_mask(keyspace, mask_list)

#Return False if keyspace is almost empty and no masks fit
if not next_mask:
return False

mask = mask + next_mask["mask"]
current_length += 1
current_keyspace = next_mask["count"]
keyspace = keyspace - current_keyspace

#If min length is 1, add it to result
if current_length >= password_len_min and current_length <= password_len_max:
masks.append(mask)

while current_length < password_len_max:
#Decide on next mask
next_mask = decide_next_mask(keyspace, current_keyspace, mask_list)
#Return masks if no more masks fit
if not next_mask:
return masks

mask = mask + next_mask["mask"]
current_length += 1
current_keyspace = current_keyspace * next_mask["count"]
keyspace = keyspace - current_keyspace

#If fits user requirements, add it to result
if current_length >= password_len_min and current_length <= password_len_max:
masks.append(mask)

return masks


def decide_first_mask(keyspace, mask_list):
for mask_name in mask_list:
if keyspace < mask_list[mask_name]["count"]:
continue
else:
return mask_list[mask_name]

return False

def decide_next_mask(keyspace, current_keyspace, mask_list):
for mask_name in mask_list:
if keyspace < current_keyspace * mask_list[mask_name]["count"]:
continue
else:
return mask_list[mask_name]

return False
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
'''
* Author : David Brandšteter (xbrand13)
* Licence: MIT, see LICENSE
'''
import logging
from flask_login import current_user
from src.database import db
from flask import request
from flask_restx import Resource, abort
from src.api.apiConfig import api
from src.api.fitcrack.endpoints.planner.argumentsParser import planner_parser
from src.database.models import FcDictionary, FcBatch, FcJob, FcRule
from src.api.fitcrack.endpoints.job.functions import create_job
from sqlalchemy import exc

from src.api.fitcrack.endpoints.planner.functions import decide_on_rules, make_dict_attack, make_bruteforce_attack, compute_keyspace


log = logging.getLogger(__name__)
ns = api.namespace('planner', description='Endpoints for automatic planner')

@ns.route('')
class planner(Resource):

@api.expect(planner_parser)
@api.response(201, 'Created')
@api.response(200, 'Updated')
@api.response(500, 'Failed')
def post(self):
"""
Generates a Batch of planned attacks.
"""
args = planner_parser.parse_args(request)

#Compute dictionary keyspace and select dictionaries
keyspace = compute_keyspace(args['days'], args['hours'], args['minutes'], args['cracking_speed'])
dictionaries = FcDictionary.query.filter(FcDictionary.keyspace <= keyspace).all()

#Args
is_new = True
final_jobs = []
name = args['name']
hash_list = args['hashList']
hosts_ids = args['hostsIds']
fill = args['fill']
password_len_min = args['password_len_min']
password_len_max = args['password_len_max']

#Pick effective rules
rules = FcRule.query.all()
rule = decide_on_rules(dictionaries, rules, keyspace)

#Dictionary attacks
for dict in dictionaries:
if rule != None:
attack_keyspace = dict.keyspace * rule.count
else:
attack_keyspace = dict.keyspace

if keyspace > attack_keyspace:
data = make_dict_attack(name, dict, rule, hash_list, hosts_ids)
job = create_job(data)
final_jobs.append(job.id)
keyspace = keyspace - attack_keyspace
else:
continue

#Brute-force Fill
if fill == True:
data = make_bruteforce_attack(name, hash_list, hosts_ids, password_len_min, password_len_max, keyspace)
if data:
job = create_job(data)
final_jobs.append(job.id)

#Batch creation
batch = FcBatch()
batch.name = name
batch.jobs = FcJob.query.filter(FcJob.id.in_(final_jobs)).all()
batch.creator_id = current_user.id
for index, job_id in enumerate(final_jobs):
job = FcJob.query.filter_by(id=job_id).one_or_none()
if job:
job.queue_position = index

try:
if is_new:
db.session.add(batch)
db.session.commit()
except exc.IntegrityError as e:
db.session().rollback()
abort(500, 'Couldn\'t create batch.')

return {
'message': 'batch \"' + batch.name + '\" successfully created.',
'status': True,
'batch_id': batch.id,
'unused_keyspace': keyspace
}
Loading