From 423b70bcfbb6ef17b92e8534e6f1a9a0f2c6bcff Mon Sep 17 00:00:00 2001 From: XiaoliChan <30458572+XiaoliChan@users.noreply.github.com> Date: Thu, 29 Feb 2024 13:26:00 +0800 Subject: [PATCH] [lib] Improve ntlm_parser.py Signed-off-by: XiaoliChan <30458572+XiaoliChan@users.noreply.github.com> --- nxc/helpers/ntlm_parser.py | 127 ++++++++----------------------------- nxc/protocols/winrm.py | 6 +- nxc/protocols/wmi.py | 36 ++--------- 3 files changed, 37 insertions(+), 132 deletions(-) diff --git a/nxc/helpers/ntlm_parser.py b/nxc/helpers/ntlm_parser.py index 671c3b19..dd5913f8 100644 --- a/nxc/helpers/ntlm_parser.py +++ b/nxc/helpers/ntlm_parser.py @@ -1,107 +1,34 @@ -# Original from here: https://github.com/nopfor/ntlm_challenger +# Original from here: https://github.com/fortra/impacket/blob/master/examples/DumpNTLMInfo.py#L568 -import datetime +import struct +from impacket import ntlm from impacket.smb3 import WIN_VERSIONS +import contextlib -def decoder(byte_string, decode_type): - if decode_type == "byte": - return byte_string.decode("UTF-8").replace("\x00", "") - else: - return int.from_bytes(byte_string, "little") - - -def parse_version(version_bytes): - major_version = version_bytes[0] - minor_version = version_bytes[1] - product_build = decoder(version_bytes[2:4], "int") - if product_build in WIN_VERSIONS: - return f"{WIN_VERSIONS[product_build]} Build {product_build}" - else: - return f"Windows {major_version}.{minor_version} Build {product_build}" - - -def parse_target_info(target_info_bytes): - MsvAvEOL = 0x0000 - MsvAvNbComputerName = 0x0001 - MsvAvNbDomainName = 0x0002 - MsvAvDnsComputerName = 0x0003 - MsvAvDnsDomainName = 0x0004 - MsvAvDnsTreeName = 0x0005 - MsvAvFlags = 0x0006 - MsvAvTimestamp = 0x0007 - MsvAvSingleHost = 0x0008 - MsvAvTargetName = 0x0009 - MsvAvChannelBindings = 0x000A - +def parse_challenge(challange): target_info = { - "MsvAvNbComputerName": None, - "MsvAvDnsDomainName": None, - } - info_offset = 0 - - while info_offset < len(target_info_bytes): - av_id = decoder(target_info_bytes[info_offset:info_offset + 2], "int") - av_len = decoder(target_info_bytes[info_offset + 2:info_offset + 4], "int") - av_value = target_info_bytes[info_offset + 4:info_offset + 4 + av_len] - - info_offset = info_offset + 4 + av_len - - if av_id == MsvAvEOL: - pass - elif av_id == MsvAvNbComputerName: - target_info["MsvAvNbComputerName"] = decoder(av_value, "byte") - elif av_id == MsvAvNbDomainName: - target_info["MsvAvNbDomainName"] = decoder(av_value, "byte") - elif av_id == MsvAvDnsComputerName: - target_info["MsvAvDnsComputerName"] = decoder(av_value, "byte") - elif av_id == MsvAvDnsDomainName: - target_info["MsvAvDnsDomainName"] = decoder(av_value, "byte") - elif av_id == MsvAvDnsTreeName: - target_info["MsvAvDnsTreeName"] = decoder(av_value, "byte") - elif av_id == MsvAvFlags: - pass - elif av_id == MsvAvTimestamp: - filetime = decoder(av_value, "int") - microseconds = (filetime - 116444736000000000) / 10 - time = datetime.datetime(1970, 1, 1) + datetime.timedelta(microseconds=microseconds) - target_info["MsvAvTimestamp"] = time.strftime("%b %d, %Y %H:%M:%S.%f") - elif av_id == MsvAvSingleHost: - target_info["MsvAvSingleHost"] = decoder(av_value, "byte") - elif av_id == MsvAvTargetName: - target_info["MsvAvTargetName"] = decoder(av_value, "byte") - elif av_id == MsvAvChannelBindings: - target_info["MsvAvChannelBindings"] = av_value - return target_info - - -def parse_challenge(challenge_message): - # TargetNameFields - target_name_fields = challenge_message[12:20] - target_name_len = decoder(target_name_fields[0:2], "int") - target_name_offset = decoder(target_name_fields[4:8], "int") - - # TargetInfoFields - target_info_fields = challenge_message[40:48] - target_info_len = decoder(target_info_fields[0:2], "int") - target_info_offset = decoder(target_info_fields[4:8], "int") - - # Version - version = None - version_bytes = challenge_message[48:56] - version = parse_version(version_bytes) - - # TargetName - target_name = challenge_message[target_name_offset:target_name_offset + target_name_len] - target_name = decoder(target_name, "byte") - - # TargetInfo - target_info_bytes = challenge_message[target_info_offset:target_info_offset + target_info_len] - target_info = parse_target_info(target_info_bytes) - - return { - "target_name": target_name, - "version": version, - "target_info": target_info + "hostname": None, + "domain": None, + "os_version": None } + challange = ntlm.NTLMAuthChallenge(challange) + av_pairs = ntlm.AV_PAIRS(challange["TargetInfoFields"][:challange["TargetInfoFields_len"]]) + if av_pairs[ntlm.NTLMSSP_AV_HOSTNAME] is not None: + with contextlib.suppress(Exception): + target_info["hostname"] = av_pairs[ntlm.NTLMSSP_AV_HOSTNAME][1].decode("utf-16le") + if av_pairs[ntlm.NTLMSSP_AV_DNS_DOMAINNAME] is not None: + with contextlib.suppress(Exception): + target_info["domain"] = av_pairs[ntlm.NTLMSSP_AV_DNS_DOMAINNAME][1].decode("utf-16le") + if "Version" in challange.fields: + version = challange["Version"] + if len(version) >= 4: + major_version = version[0] + minor_version = version[1] + product_build = struct.unpack(" 0: - av_pairs = ntlm.AV_PAIRS(ntlmChallenge["TargetInfoFields"][: ntlmChallenge["TargetInfoFields_len"]]) - if av_pairs[ntlm.NTLMSSP_AV_HOSTNAME][1] is not None: - try: - self.hostname = av_pairs[ntlm.NTLMSSP_AV_HOSTNAME][1].decode("utf-16le") - except Exception: - self.hostname = self.host - if av_pairs[ntlm.NTLMSSP_AV_DNS_DOMAINNAME][1] is not None: - try: - self.domain = av_pairs[ntlm.NTLMSSP_AV_DNS_DOMAINNAME][1].decode("utf-16le") - except Exception: - self.domain = self.args.domain - if av_pairs[ntlm.NTLMSSP_AV_DNS_HOSTNAME][1] is not None: - with contextlib.suppress(Exception): - self.fqdn = av_pairs[ntlm.NTLMSSP_AV_DNS_HOSTNAME][1].decode("utf-16le") - if "Version" in ntlmChallenge.fields: - version = ntlmChallenge["Version"] - if len(version) >= 4: - self.server_os = "Windows NT %d.%d Build %d" % (indexbytes(version, 0), indexbytes(version, 1), struct.unpack("