diff --git a/lsassy/core.py b/lsassy/core.py index b055d6d..ec901ab 100644 --- a/lsassy/core.py +++ b/lsassy/core.py @@ -190,7 +190,7 @@ def run(self): return False if not dump_only: - credentials, tickets, masterkeys = Parser(file).parse() + credentials, tickets, masterkeys = Parser(self.target, file).parse() file.close() if not parse_only and not keep_dump: diff --git a/lsassy/credential.py b/lsassy/credential.py index 5293a15..7fca1db 100644 --- a/lsassy/credential.py +++ b/lsassy/credential.py @@ -2,7 +2,8 @@ class Credential: """ Credential class to hold extracted credentials from remote hosts """ - def __init__(self, username, password=None, domain=None, lmhash=None, nthash=None, sha1=None, ticket=None, ssp=None, masterkey=None): + def __init__(self, hostname, username, password=None, domain=None, lmhash=None, nthash=None, sha1=None, ticket=None, ssp=None, masterkey=None): + self.hostname = hostname self.username = username self.password = password self.domain = domain @@ -13,6 +14,13 @@ def __init__(self, username, password=None, domain=None, lmhash=None, nthash=Non self.ssp = ssp self.masterkey = masterkey + def get_hostname(self): + """ + Get hostname where credential was found + :return: hostname + """ + return self.hostname + def get_username(self): """ Get credential username @@ -51,6 +59,7 @@ def get_object(self): :return: dict with credentials information """ return { + "hostname": self.hostname, "username": self.username, "password": self.password, "domain": self.domain, diff --git a/lsassy/output/pretty_output.py b/lsassy/output/pretty_output.py index 6c19ab6..a6f0ddb 100644 --- a/lsassy/output/pretty_output.py +++ b/lsassy/output/pretty_output.py @@ -25,7 +25,7 @@ def get_output(self) -> str: # Step 3: Calculate the maximum size for padding. # This ensures proper alignment in the output. max_size = max( - len(c.get("domain") or "") + len(c.get("username") or "") + len(c.get("hostname") or "") + len(c.get("domain") or "") + len(c.get("username") or "") for c in self._credentials if c is not None ) @@ -105,6 +105,7 @@ def _format_output_line( Returns: str: A formatted string representing a single line of the output. """ + hostname = cred.get("hostname") or "" domain = "{}\\".format(cred.get("domain")) if cred.get("domain") else " " username = cred.get("username") or "" padding = " " * (max_size - len(domain) - len(username) + 2) @@ -118,7 +119,8 @@ def _format_output_line( else "" ) - output_line = "{}{}{}{}{}{}".format( + output_line = "{} - {}{}{}{}{}{}".format( + hostname, domain, username, padding, diff --git a/lsassy/parser.py b/lsassy/parser.py index fff6904..d568452 100644 --- a/lsassy/parser.py +++ b/lsassy/parser.py @@ -9,7 +9,8 @@ class Parser: Parse remote lsass dump file using impacketfile and pypykatz """ - def __init__(self, dumpfile): + def __init__(self, target, dumpfile): + self._target = target self._dumpfile = dumpfile @@ -48,7 +49,7 @@ def parse(self): or (NThash and NThash != "00000000000000000000000000000000") or (LMHash and LMHash != "00000000000000000000000000000000")): credentials.append( - Credential(ssp=ssp, domain=domain, username=username, password=password, lmhash=LMHash, + Credential(hostname=self._target, ssp=ssp, domain=domain, username=username, password=password, lmhash=LMHash, nthash=NThash, sha1=SHA1)) for kcred in pypy_parse.logon_sessions[luid].kerberos_creds: @@ -60,7 +61,7 @@ def parse(self): if m not in masterkeys: masterkeys.append(m) credentials.append( - Credential(ssp='dpapi', domain='', username='', masterkey=m) + Credential(hostname=self._target, ssp='dpapi', domain='', username='', masterkey=m) ) for cred in pypy_parse.orphaned_creds: @@ -79,6 +80,7 @@ def parse(self): if ticket.EndTime > datetime.now(ticket.EndTime.tzinfo): credentials.append(Credential( + hostname=self._target, ssp="kerberos", domain=ticket.DomainName, username=ticket.EClientName[0], diff --git a/tests/test_lsassy.py b/tests/test_lsassy.py index 3ff02ac..cf85ee6 100644 --- a/tests/test_lsassy.py +++ b/tests/test_lsassy.py @@ -1,5 +1,4 @@ import unittest -import logging from argparse import Namespace from lsassy.core import ThreadPool @@ -7,7 +6,6 @@ from lsassy.parser import Parser from lsassy.session import Session from lsassy.writer import Writer -from lsassy.logger import lsassy_logger USERNAME = "pixis" PASSWORD = 'P4ssw0rd' @@ -85,7 +83,8 @@ def test_workflow(self): file = dumper.dump(exec_methods=["smb"]) self.assertIsNotNone(file) - credentials, tickets, masterkeys = Parser(file).parse() + credentials, tickets, masterkeys = Parser(HOSTNAME, file).parse() + file.close() self.assertTrue(len(credentials) > 0) @@ -104,7 +103,6 @@ def setUp(self) -> None: username=USERNAME, password=PASSWORD ) - lsassy_logger.setLevel(logging.DEBUG) def dump_lsass(self, exec): dumper = Dumper(self.session, 5, 1).load("comsvcs") @@ -123,14 +121,10 @@ def test_smb_stealth(self): """ @TODO To fix - def test_mmc(self): self.dump_lsass("mmc") - """ - """ @TODO To fix - def test_wmi(self): self.dump_lsass("wmi") """ @@ -206,7 +200,8 @@ def setUp(self) -> None: file = dumper.dump(exec_methods=["smb"]) self.assertIsNotNone(file) - credentials, tickets, masterkeys = Parser(file).parse() + credentials, tickets, masterkeys = Parser(HOSTNAME, file).parse() + file.close() self.assertTrue(len(credentials) > 0)