diff --git a/webadmin/fitcrackAPI/src/app.py b/webadmin/fitcrackAPI/src/app.py index 513f6dda9..00be58767 100755 --- a/webadmin/fitcrackAPI/src/app.py +++ b/webadmin/fitcrackAPI/src/app.py @@ -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 @@ -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) diff --git a/webadmin/fitcrackAPI/src/src/api/fitcrack/endpoints/planner/__init__.py b/webadmin/fitcrackAPI/src/src/api/fitcrack/endpoints/planner/__init__.py new file mode 100644 index 000000000..c39056523 --- /dev/null +++ b/webadmin/fitcrackAPI/src/src/api/fitcrack/endpoints/planner/__init__.py @@ -0,0 +1,5 @@ +''' + * Author : see AUTHORS + * Licence: MIT, see LICENSE +''' + diff --git a/webadmin/fitcrackAPI/src/src/api/fitcrack/endpoints/planner/argumentsParser.py b/webadmin/fitcrackAPI/src/src/api/fitcrack/endpoints/planner/argumentsParser.py new file mode 100644 index 000000000..988db4617 --- /dev/null +++ b/webadmin/fitcrackAPI/src/src/api/fitcrack/endpoints/planner/argumentsParser.py @@ -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) \ No newline at end of file diff --git a/webadmin/fitcrackAPI/src/src/api/fitcrack/endpoints/planner/functions.py b/webadmin/fitcrackAPI/src/src/api/fitcrack/endpoints/planner/functions.py new file mode 100644 index 000000000..df5b4ccf2 --- /dev/null +++ b/webadmin/fitcrackAPI/src/src/api/fitcrack/endpoints/planner/functions.py @@ -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 \ No newline at end of file diff --git a/webadmin/fitcrackAPI/src/src/api/fitcrack/endpoints/planner/planner.py b/webadmin/fitcrackAPI/src/src/api/fitcrack/endpoints/planner/planner.py new file mode 100644 index 000000000..203405c00 --- /dev/null +++ b/webadmin/fitcrackAPI/src/src/api/fitcrack/endpoints/planner/planner.py @@ -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 + } diff --git a/webadmin/fitcrackFE/src/components/planner/plannerView.vue b/webadmin/fitcrackFE/src/components/planner/plannerView.vue new file mode 100644 index 000000000..cc3a5ae42 --- /dev/null +++ b/webadmin/fitcrackFE/src/components/planner/plannerView.vue @@ -0,0 +1,354 @@ + + + + + + + + + \ No newline at end of file diff --git a/webadmin/fitcrackFE/src/router/index.js b/webadmin/fitcrackFE/src/router/index.js index 50fcaa776..866886d98 100644 --- a/webadmin/fitcrackFE/src/router/index.js +++ b/webadmin/fitcrackFE/src/router/index.js @@ -43,6 +43,7 @@ const Server = () => import('@/components/server/serverMonitor.vue') const Settings = () => import('@/components/settings/settingsView.vue') const Transfer = () => import('@/components/settings/dataTransfer.vue') const UnauthorizedError = () => import('@/components/errorPages/unauthorized.vue') +const planner = () => import('@/components/planner/plannerView.vue') Vue.use(Router); @@ -120,6 +121,16 @@ const appRoutes = [ navtab: 0 } }, + { + path: '/planner', + name: 'planner', + component: planner, + meta: { + title: 'planner', + icon: 'mdi-magic-staff', + navtab: 0 + } + }, { path: '/jobs/:id', name: 'jobDetail',