Skip to content

Commit

Permalink
BUGFIX and improvments
Browse files Browse the repository at this point in the history
  • Loading branch information
helviojunior committed Jul 30, 2024
1 parent 42ccf77 commit 0e5a645
Show file tree
Hide file tree
Showing 5 changed files with 186 additions and 6 deletions.
2 changes: 1 addition & 1 deletion knowsmore/__meta__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version__ = '0.1.39'
__version__ = '0.1.40'
__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"
Expand Down
4 changes: 2 additions & 2 deletions knowsmore/cmd/credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,11 +157,11 @@ def run(self):
if self.out_file is not None:
name = str(self.out_file).replace(Path(self.out_file).suffix, "").rstrip(". ")

with open(f'{name}.ansi.txt', 'ab') as f:
with open(f'{name}.ansi.txt', 'ab+') as f:
f.write(file_data.encode('utf-8', 'ignore'))
f.write(b"\n\n")

with open(f'{name}.txt', 'ab') as f:
with open(f'{name}.txt', 'ab+') as f:
f.write(f"{d['description']}\n".encode('utf-8', 'ignore'))
f.write(Tools.get_tabulated(d['rows']).encode('utf-8', 'ignore'))
f.write(b"\n\n")
Expand Down
157 changes: 157 additions & 0 deletions knowsmore/cmd/memberof.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import json
import os
import sqlite3
import time
from argparse import _ArgumentGroup, Namespace
from pathlib import Path
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 Find(CmdBase):
db = None
find_text = ''
out_file = None
cracked_only = False
json_format = False

def __init__(self):
super().__init__('member-of', 'Find all member of group at database')

def add_flags(self, flags: _ArgumentGroup):
flags.add_argument('-o', '--save-to',
action='store',
default='',
dest=f'out_file',
help=Color.s(
'Output file to save JSON data'))

flags.add_argument('--cracked-only',
action='store_true',
default=False,
dest=f'cracked_only',
help=Color.s('Find cracked data only'))

flags.add_argument('--json',
action='store_true',
default=False,
dest=f'json_format',
help=Color.s('Output in JSON format instead of text table'))

def add_commands(self, cmds: _ArgumentGroup):
cmds.add_argument('--name',
action='store',
metavar='[text]',
type=str,
dest=f'txt_find',
help=Color.s('Text to look for at all columns'))

def load_from_arguments(self, args: Namespace) -> bool:
if args.txt_find is not None and args.txt_find.strip() != '':
self.find_text = args.txt_find

if self.find_text is None or self.find_text.strip() == '':
Logger.pl('{!} {R}error: text is invalid {O}%s{R} {W}\r\n' % (
self.find_text))
exit(1)

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)

self.db = self.open_db(args)
self.cracked_only = args.cracked_only
self.json_format = args.json_format

Logger.pl(' {C}find text:{O} %s{W}' % self.find_text)
Logger.pl(' {C}cracked only:{O} %s{W}' % self.cracked_only)

return True

def run(self):

sql = (
'select row_number() OVER (ORDER BY g2.name, c.name) AS __line, g2.name group_name, '
'c.name, p.password, be.edge_type as "right", '
'case when c.enabled == 1 then "Yes" ELSE "No" end user_enabled '
'from credentials as c '
'inner join passwords as p '
'on c.password_id = p.password_id '
'inner join bloodhound_edge be '
'on be.source_id == c.object_identifier '
'and be.target_label == "Group" '
'and be.edge_type in ("MemberOf", "Owns") '
'inner join groups g2 '
'on be.destination_id == g2.object_identifier '
'where g2.name like ? '
)

args = [f'%{self.find_text}%']

if self.cracked_only:
sql += ' and (p.length > 0) '

sql += ' order by g2.name, c.enabled DESC, c.name'

# Look for users
rows = self.db.select_raw(
sql=sql,
args=args
)

if len(rows) == 0:
Logger.pl('{!} {O}Nothing found with this text{W}\r\n')
exit(0)

news = {}
for r in rows:
p = r.get('password', '')
if '$HEX[' in p:
p1 = Password('', p)
r['password'] = p1.latin_clear_text

p_data = Tools.get_tabulated(rows, dict(
group_name="Group",
name="Username",
password="Password",
right="Right",
user_enabled="User Enabled?"
))

if self.json_format:
p_data = json.dumps(
{
'data': rows,
'meta': {
'type': 'credentials',
'count': len(rows),
'version': 1
}
}
)

if self.out_file is not None:
with open(self.out_file, "a", encoding="UTF-8") as text_file:
text_file.write(p_data)
else:
print(p_data)

if not self.cracked_only:
Logger.pl('{!} {O}You can filter cracked only users using {C}--cracked-only{O} paramater{W}\r\n')

Logger.pl('{+} {O}%s{W}{C} register found{W}' % len(rows))

2 changes: 1 addition & 1 deletion knowsmore/util/knowsmoredb.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ def insert_or_update_credential(self, domain: int, username: str, ntlm_hash: str

# Hard-coded empty password
update_password = True
if ntlm_hash is None or ntlm_hash.strip == '':
if ntlm_hash is None or ntlm_hash.strip() == '':
update_password = False
ntlm_hash = '31d6cfe0d16ae931b73c59d7e0c089c0'

Expand Down
27 changes: 25 additions & 2 deletions knowsmore/util/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,12 +117,35 @@ def strip_accents(text):
return str(text).strip()

@staticmethod
def get_tabulated(data: list) -> str:
def get_tabulated(data: list, labels: dict = None) -> str:

if len(data) == 0:
return ''

headers = [(h if len(h) > 2 and h[0:2] != '__' else ' ') for h in data[0].keys()]
i_labels = dict(
password="Password",
qty="Quantity",
score="Score",
company_similarity="Company Similarity",
users="Users",
machines="Machines",
description="Description",
group_name="Group",
name="Username",
right="Right"
)

if labels is not None or isinstance(labels, dict):
i_labels = {**i_labels, **labels}

headers = [
(next(iter([
v
for k, v in i_labels.items()
if h.lower() == str(k).lower()
]), h) if len(h) > 2 and h[0:2] != '__' else ' ')
for h in data[0].keys()
]
data = [item.values() for item in data]

return tabulate(data, headers, tablefmt='psql')
Expand Down

0 comments on commit 0e5a645

Please sign in to comment.