From 042f8fba7e431a342d22d6a3866d2cfc7c5cf1ea Mon Sep 17 00:00:00 2001 From: Helvio Junior Date: Tue, 30 Jul 2024 09:40:53 -0300 Subject: [PATCH] Add Credential listing --- knowsmore/__meta__.py | 2 +- knowsmore/cmd/credentials.py | 153 +++++++++++++++++++++++++++++++++++ 2 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 knowsmore/cmd/credentials.py diff --git a/knowsmore/__meta__.py b/knowsmore/__meta__.py index 0000697..e30f811 100644 --- a/knowsmore/__meta__.py +++ b/knowsmore/__meta__.py @@ -1,4 +1,4 @@ -__version__ = '0.1.38' +__version__ = '0.1.39' __title__ = "knowsmore" __description__ = "KnowsMore is a swiss army knife tool for pentesting Microsoft Active Directory (NTLM Hashes, BloodHound, NTDS and DCSync)." __url__ = "https://github.com/helviojunior/knowsmore" diff --git a/knowsmore/cmd/credentials.py b/knowsmore/cmd/credentials.py new file mode 100644 index 0000000..35bcb05 --- /dev/null +++ b/knowsmore/cmd/credentials.py @@ -0,0 +1,153 @@ +import json +import os +import sqlite3 +import time +from argparse import _ArgumentGroup, Namespace +from pathlib import Path + +from ansi2image.ansi2image import Ansi2Image +from binascii import hexlify +from enum import Enum + +from knowsmore.util.tools import Tools + +from knowsmore.cmdbase import CmdBase +from knowsmore.password import Password +from knowsmore.util.color import Color +from knowsmore.util.database import Database +from knowsmore.util.knowsmoredb import KnowsMoreDB +from knowsmore.util.logger import Logger + + +class Credentials(CmdBase): + db = None + out_file = None + out_path = None + + def __init__(self): + super().__init__('credentials', 'Show cracked credentials') + + def add_flags(self, flags: _ArgumentGroup): + flags.add_argument('--save-to', + action='store', + default='', + dest=f'out_file', + help=Color.s( + 'Output file to save JSON data')) + + flags.add_argument('--save-to-img', + action='store', + default='', + dest=f'out_path', + help=Color.s( + 'Output path to save PNG files')) + + def add_commands(self, cmds: _ArgumentGroup): + pass + + def load_from_arguments(self, args: Namespace) -> bool: + + if args.out_file is not None and args.out_file.strip() != '': + self.out_file = Path(args.out_file).absolute() + + if self.out_file is not None: + if os.path.exists(self.out_file): + Logger.pl('{!} {R}error: out file ({O}%s{R}) already exists {W}\r\n' % ( + self.out_file)) + exit(1) + + if args.out_path is not None and args.out_path.strip() != '': + self.out_path = Path(args.out_path).absolute() + + if self.out_path is not None: + if not os.path.isdir(self.out_path): + Logger.pl('{!} {R}error: output path ({O}%s{R}) does not exists {W}\r\n' % ( + self.out_path)) + exit(1) + + self.db = self.open_db(args) + + return True + + def run(self): + + data = [] + + domains = self.db.select('domains') + for r in domains: + + # Domain + rows = self.db.select_raw( + sql='select row_number() OVER (ORDER BY c.name) AS __line, c.name, p.password ' + 'from credentials as c ' + 'inner join passwords as p ' + 'on c.password_id = p.password_id ' + 'where p.password <> "" and c.domain_id = ? ' + 'order by c.name ', + args=[r['domain_id']] + ) + + if len(rows) > 0: + data.append({ + 'type': 'credentials', + 'domain': r['name'], + 'description': 'Cracked credentials for %s' % r['name'], + 'rows': rows + }) + + if self.out_file is not None: + Color.pl('{?} {W}{D}Credentials saved at {W}{C}%s{W}{D}{W}' % self.out_file) + + with open(self.out_file, "a", encoding="UTF-8") as text_file: + text_file.write(json.dumps( + { + 'data': data, + 'meta': { + 'type': 'stats', + 'count': len(data), + 'version': 1 + } + } + )) + + elif self.out_path is not None: + + for i, d in enumerate(data): + name = f"{i:03}_{Tools.sanitize_filename(d['description'])}" + file_data = ' \033[38;5;52m=\033[38;5;88m=\033[38;5;124m=\033[38;5;160m=\033[38;5;196m> ' + Color.s( + '{W}{G}%s{W}\n' % d['description']) + + file_data += ''.join([ + '%s─' % c for k, c in sorted(Color.gray_scale.items(), key=lambda x: x[0], reverse=True) + ]) + Color.s('{W}\n') + + Color.pl('{?} {W}{D}Saving %s...{W}' % d['description']) + + if len(data) == 0: + file_data += Color.s( + '\n {R}ATTENTION!!!{O} \n %s{W}\n' % 'Table is empty') + else: + file_data += Tools.get_ansi_tabulated(d['rows']) + + o = Ansi2Image(0, 0, font_name=Ansi2Image.get_default_font_name(), font_size=13) + o.loads(file_data) + o.min_margin = 10 + o.max_margin = 30 + o.calc_size(margin=0.01) + o.save_image(os.path.join(self.out_path, f'{name}.png'), format='PNG') + + #with open(os.path.join(self.out_path, f'{name}.ansi.txt'), 'wb') as f: + # f.write(file_data.encode('utf-8', 'ignore')) + + else: + + for d in data: + Color.pl('{?} {W}{D}%s{W}' % d['description']) + print(Tools.get_tabulated(d['rows'])) + print(' ') + + + + + +