NetExec/nxc/logger.py

210 lines
7.8 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
2016-05-16 23:48:31 +00:00
import logging
from logging import LogRecord
from logging.handlers import RotatingFileHandler
2023-02-21 20:05:35 +00:00
import os.path
2016-05-16 23:48:31 +00:00
import sys
import re
from nxc.helpers.misc import called_from_cmd_args
from nxc.console import nxc_console
2016-05-16 23:48:31 +00:00
from termcolor import colored
from datetime import datetime
from rich.text import Text
from rich.logging import RichHandler
2016-05-16 23:48:31 +00:00
class NXCAdapter(logging.LoggerAdapter):
def __init__(self, extra=None):
logging.basicConfig(
format="%(message)s",
datefmt="[%X]",
2023-05-02 15:17:59 +00:00
handlers=[
RichHandler(
console=nxc_console,
2023-05-02 15:17:59 +00:00
rich_tracebacks=True,
tracebacks_show_locals=False,
)
],
)
self.logger = logging.getLogger("nxc")
2016-05-16 23:48:31 +00:00
self.extra = extra
self.output_file = None
2016-05-16 23:48:31 +00:00
logging.getLogger("pypykatz").disabled = True
logging.getLogger("minidump").disabled = True
logging.getLogger("lsassy").disabled = True
#logging.getLogger("impacket").disabled = True
def format(self, msg, *args, **kwargs):
"""
Format msg for output if needed
This is used instead of process() since process() applies to _all_ messages, including debug calls
"""
2016-05-16 23:48:31 +00:00
if self.extra is None:
return f"{msg}", kwargs
2016-05-16 23:48:31 +00:00
2023-05-02 15:17:59 +00:00
if "module_name" in self.extra.keys():
2023-04-12 03:49:04 +00:00
if len(self.extra["module_name"]) > 8:
self.extra["module_name"] = self.extra["module_name"][:8] + "..."
2017-03-27 21:09:36 +00:00
# If the logger is being called when hooking the 'options' module function
2023-04-12 03:49:04 +00:00
if len(self.extra) == 1 and ("module_name" in self.extra.keys()):
2023-05-02 15:17:59 +00:00
return (
f"{colored(self.extra['module_name'], 'cyan', attrs=['bold']):<64} {msg}",
kwargs,
)
2016-05-16 23:48:31 +00:00
# If the logger is being called from nxcServer
2023-05-08 18:39:36 +00:00
if len(self.extra) == 2 and ("module_name" in self.extra.keys()) and ("host" in self.extra.keys()):
2023-05-02 15:17:59 +00:00
return (
f"{colored(self.extra['module_name'], 'cyan', attrs=['bold']):<24} {self.extra['host']:<39} {msg}",
kwargs,
)
2016-05-16 23:48:31 +00:00
# If the logger is being called from a protocol
2023-04-12 03:49:04 +00:00
if "module_name" in self.extra.keys():
2023-05-02 15:17:59 +00:00
module_name = colored(self.extra["module_name"], "cyan", attrs=["bold"])
2016-05-16 23:48:31 +00:00
else:
2023-05-02 15:17:59 +00:00
module_name = colored(self.extra["protocol"], "blue", attrs=["bold"])
2023-05-02 15:17:59 +00:00
return (
f"{module_name:<24} {self.extra['host']:<15} {self.extra['port']:<6} {self.extra['hostname'] if self.extra['hostname'] else 'NONE':<16} {msg}",
kwargs,
)
2016-05-16 23:48:31 +00:00
def display(self, msg, *args, **kwargs):
"""
Display text to console, formatted for nxc
"""
try:
2023-04-12 03:49:04 +00:00
if "protocol" in self.extra.keys() and not called_from_cmd_args():
return
except AttributeError:
pass
2023-05-08 18:39:36 +00:00
msg, kwargs = self.format(f"{colored('[*]', 'blue', attrs=['bold'])} {msg}", kwargs)
text = Text.from_ansi(msg)
nxc_console.print(text, *args, **kwargs)
self.log_console_to_file(text, *args, **kwargs)
2016-05-16 23:48:31 +00:00
2023-05-25 03:48:36 +00:00
def success(self, msg, color='green', *args, **kwargs):
"""
Print some sort of success to the user
"""
try:
2023-05-02 15:17:59 +00:00
if "protocol" in self.extra.keys() and not called_from_cmd_args():
return
except AttributeError:
pass
2023-05-25 03:48:36 +00:00
msg, kwargs = self.format(f"{colored('[+]', color, attrs=['bold'])} {msg}", kwargs)
text = Text.from_ansi(msg)
nxc_console.print(text, *args, **kwargs)
self.log_console_to_file(text, *args, **kwargs)
2016-05-16 23:48:31 +00:00
def highlight(self, msg, *args, **kwargs):
"""
Prints a completely yellow highlighted message to the user
"""
try:
2023-05-02 15:17:59 +00:00
if "protocol" in self.extra.keys() and not called_from_cmd_args():
return
except AttributeError:
pass
2023-04-12 03:49:04 +00:00
msg, kwargs = self.format(f"{colored(msg, 'yellow', attrs=['bold'])}", kwargs)
text = Text.from_ansi(msg)
nxc_console.print(text, *args, **kwargs)
self.log_console_to_file(text, *args, **kwargs)
2016-05-16 23:48:31 +00:00
2023-05-25 03:48:36 +00:00
def fail(self, msg, color='red', *args, **kwargs):
"""
Prints a failure (may or may not be an error) - e.g. login creds didn't work
"""
try:
2023-05-02 15:17:59 +00:00
if "protocol" in self.extra.keys() and not called_from_cmd_args():
return
except AttributeError:
pass
2023-05-25 03:48:36 +00:00
msg, kwargs = self.format(f"{colored('[-]', color, attrs=['bold'])} {msg}", kwargs)
text = Text.from_ansi(msg)
nxc_console.print(text, *args, **kwargs)
self.log_console_to_file(text, *args, **kwargs)
def log_console_to_file(self, text, *args, **kwargs):
"""
If debug or info logging is not enabled, we still want display/success/fail logged to the file specified,
so we create a custom LogRecord and pass it to all the additional handlers (which will be all the file handlers
"""
if self.logger.getEffectiveLevel() >= logging.INFO:
# will be 0 if it's just the console output, so only do this if we actually have file loggers
if len(self.logger.handlers):
try:
for handler in self.logger.handlers:
handler.handle(
LogRecord(
"nxc",
20,
"",
kwargs,
msg=text,
args=args,
exc_info=None,
)
)
except Exception as e:
self.logger.fail(f"Issue while trying to custom print handler: {e}")
else:
self.logger.info(text)
def add_file_log(self, log_file=None):
2023-05-08 18:39:36 +00:00
file_formatter = TermEscapeCodeFormatter("%(asctime)s - %(levelname)s - %(message)s")
output_file = self.init_log_file() if log_file is None else log_file
2023-02-22 09:50:40 +00:00
file_creation = False
if not os.path.isfile(output_file):
2023-05-02 15:17:59 +00:00
open(output_file, "x")
2023-02-22 09:50:40 +00:00
file_creation = True
file_handler = RotatingFileHandler(output_file, maxBytes=100000)
with file_handler._open() as f:
2023-02-22 09:50:40 +00:00
if file_creation:
2023-05-08 18:39:36 +00:00
f.write("[%s]> %s\n\n" % (datetime.now().strftime("%d-%m-%Y %H:%M:%S"), " ".join(sys.argv)))
2023-02-22 09:50:40 +00:00
else:
2023-05-08 18:39:36 +00:00
f.write("\n[%s]> %s\n\n" % (datetime.now().strftime("%d-%m-%Y %H:%M:%S"), " ".join(sys.argv)))
file_handler.setFormatter(file_formatter)
self.logger.addHandler(file_handler)
self.logger.debug(f"Added file handler: {file_handler}")
@staticmethod
def init_log_file():
newpath = os.path.expanduser("~/.nxc") + "/logs/" + datetime.now().strftime('%Y-%m-%d')
2023-05-25 12:16:05 +00:00
if not os.path.exists(newpath):
os.makedirs(newpath)
log_filename = os.path.join(
os.path.expanduser("~/.nxc"),
"logs",
2023-05-25 12:16:05 +00:00
datetime.now().strftime('%Y-%m-%d'),
f"log_{datetime.now().strftime('%Y-%m-%d-%H-%M-%S')}.log",
)
return log_filename
class TermEscapeCodeFormatter(logging.Formatter):
"""A class to strip the escape codes for logging to files"""
2023-05-02 15:17:59 +00:00
2023-04-12 03:49:04 +00:00
def __init__(self, fmt=None, datefmt=None, style="%", validate=True):
super().__init__(fmt, datefmt, style, validate)
def format(self, record):
2023-04-12 03:49:04 +00:00
escape_re = re.compile(r"\x1b\[[0-9;]*m")
record.msg = re.sub(escape_re, "", str(record.msg))
return super().format(record)
# initialize the logger for all of nxc - this is imported everywhere
nxc_logger = NXCAdapter()