Last active
July 25, 2024 03:45
-
-
Save aseering/829a2270b72345a1dc42 to your computer and use it in GitHub Desktop.
NTLM auth-string decoder
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
## Decodes NTLM "Authenticate" HTTP-Header blobs. | |
## Reads the raw blob from stdin; prints out the contained metadata. | |
## Supports (auto-detects) Type 1, Type 2, and Type 3 messages. | |
## Based on the excellent protocol description from: | |
## <http://davenport.sourceforge.net/ntlm.html> | |
## with additional detail subsequently added from the official protocol spec: | |
## <http://msdn.microsoft.com/en-us/library/cc236621.aspx> | |
## | |
## For example: | |
## | |
## $ echo "TlRMTVNTUAABAAAABYIIAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAAAAwAAAA" | ./ntlmdecoder.py | |
## Found NTLMSSP header | |
## Msg Type: 1 (Request) | |
## Domain: '' [] (0b @0) | |
## Workstation: '' [] (0b @0) | |
## OS Ver: '????0???' | |
## Flags: 0x88205 ["Negotiate Unicode", "Request Target", "Negotiate NTLM", "Negotiate Always Sign", "Negotiate NTLM2 Key"] | |
## | |
import sys | |
import base64 | |
import struct | |
import string | |
import collections | |
flags_tbl_str = """0x00000001 Negotiate Unicode | |
0x00000002 Negotiate OEM | |
0x00000004 Request Target | |
0x00000008 unknown | |
0x00000010 Negotiate Sign | |
0x00000020 Negotiate Seal | |
0x00000040 Negotiate Datagram Style | |
0x00000080 Negotiate Lan Manager Key | |
0x00000100 Negotiate Netware | |
0x00000200 Negotiate NTLM | |
0x00000400 unknown | |
0x00000800 Negotiate Anonymous | |
0x00001000 Negotiate Domain Supplied | |
0x00002000 Negotiate Workstation Supplied | |
0x00004000 Negotiate Local Call | |
0x00008000 Negotiate Always Sign | |
0x00010000 Target Type Domain | |
0x00020000 Target Type Server | |
0x00040000 Target Type Share | |
0x00080000 Negotiate NTLM2 Key | |
0x00100000 Request Init Response | |
0x00200000 Request Accept Response | |
0x00400000 Request Non-NT Session Key | |
0x00800000 Negotiate Target Info | |
0x01000000 unknown | |
0x02000000 unknown | |
0x04000000 unknown | |
0x08000000 unknown | |
0x10000000 unknown | |
0x20000000 Negotiate 128 | |
0x40000000 Negotiate Key Exchange | |
0x80000000 Negotiate 56""" | |
flags_tbl = [line.split('\t') for line in flags_tbl_str.split('\n')] | |
flags_tbl = [(int(x,base=16),y) for x,y in flags_tbl] | |
def flags_lst(flags): | |
return [desc for val, desc in flags_tbl if val & flags] | |
def flags_str(flags): | |
return ', '.join('"%s"' % s for s in flags_lst(flags)) | |
VALID_CHRS = set(string.letters + string.digits + string.punctuation) | |
def clean_str(st): | |
return ''.join((s if s in VALID_CHRS else '?') for s in st) | |
class StrStruct(object): | |
def __init__(self, pos_tup, raw): | |
length, alloc, offset = pos_tup | |
self.length = length | |
self.alloc = alloc | |
self.offset = offset | |
self.raw = raw[offset:offset+length] | |
self.utf16 = False | |
if len(self.raw) >= 2 and self.raw[1] == '\0': | |
self.string = self.raw.decode('utf-16') | |
self.utf16 = True | |
else: | |
self.string = self.raw | |
def __str__(self): | |
st = "%s'%s' [%s] (%db @%d)" % ('u' if self.utf16 else '', | |
clean_str(self.string), | |
self.raw.encode('hex'), | |
self.length, self.offset) | |
if self.alloc != self.length: | |
st += " alloc: %d" % self.alloc | |
return st | |
msg_types = collections.defaultdict(lambda: "UNKNOWN") | |
msg_types[1] = "Request" | |
msg_types[2] = "Challenge" | |
msg_types[3] = "Response" | |
target_field_types = collections.defaultdict(lambda: "UNKNOWN") | |
target_field_types[0] = "TERMINATOR" | |
target_field_types[1] = "Server name" | |
target_field_types[2] = "AD domain name" | |
target_field_types[3] = "FQDN" | |
target_field_types[4] = "DNS domain name" | |
target_field_types[5] = "Parent DNS domain" | |
target_field_types[7] = "Server Timestamp" | |
def main(): | |
st_raw = sys.stdin.read() | |
try: | |
st = base64.b64decode(st_raw) | |
except e: | |
print "Input is not a valid base64-encoded string" | |
return | |
if st[:8] == "NTLMSSP\0": | |
print "Found NTLMSSP header" | |
else: | |
print "NTLMSSP header not found at start of input string" | |
return | |
ver_tup = struct.unpack("<i", st[8:12]) | |
ver = ver_tup[0] | |
print "Msg Type: %d (%s)" % (ver, msg_types[ver]) | |
if ver == 1: | |
pretty_print_request(st) | |
elif ver == 2: | |
pretty_print_challenge(st) | |
elif ver == 3: | |
pretty_print_response(st) | |
else: | |
print "Unknown message structure. Have a raw (hex-encoded) message:" | |
print st.encode("hex") | |
def opt_str_struct(name, st, offset): | |
nxt = st[offset:offset+8] | |
if len(nxt) == 8: | |
hdr_tup = struct.unpack("<hhi", nxt) | |
print "%s: %s" % (name, StrStruct(hdr_tup, st)) | |
else: | |
print "%s: [omitted]" % name | |
def opt_inline_str(name, st, offset, sz): | |
nxt = st[offset:offset+sz] | |
if len(nxt) == sz: | |
print "%s: '%s'" % (name, clean_str(nxt)) | |
else: | |
print "%s: [omitted]" % name | |
def pretty_print_request(st): | |
hdr_tup = struct.unpack("<i", st[12:16]) | |
flags = hdr_tup[0] | |
opt_str_struct("Domain", st, 16) | |
opt_str_struct("Workstation", st, 24) | |
opt_inline_str("OS Ver", st, 32, 8) | |
print "Flags: 0x%x [%s]" % (flags, flags_str(flags)) | |
def pretty_print_challenge(st): | |
hdr_tup = struct.unpack("<hhiiQ", st[12:32]) | |
print "Target Name: %s" % StrStruct(hdr_tup[0:3], st) | |
print "Challenge: 0x%x" % hdr_tup[4] | |
flags = hdr_tup[3] | |
opt_str_struct("Context", st, 32) | |
nxt = st[40:48] | |
if len(nxt) == 8: | |
hdr_tup = struct.unpack("<hhi", nxt) | |
tgt = StrStruct(hdr_tup, st) | |
output = "Target: [block] (%db @%d)" % (tgt.length, tgt.offset) | |
if tgt.alloc != tgt.length: | |
output += " alloc: %d" % tgt.alloc | |
print output | |
raw = tgt.raw | |
pos = 0 | |
while pos+4 < len(raw): | |
rec_hdr = struct.unpack("<hh", raw[pos : pos+4]) | |
rec_type_id = rec_hdr[0] | |
rec_type = target_field_types[rec_type_id] | |
rec_sz = rec_hdr[1] | |
subst = raw[pos+4 : pos+4+rec_sz] | |
print " %s (%d): %s" % (rec_type, rec_type_id, subst) | |
pos += 4 + rec_sz | |
opt_inline_str("OS Ver", st, 48, 8) | |
print "Flags: 0x%x [%s]" % (flags, flags_str(flags)) | |
def pretty_print_response(st): | |
hdr_tup = struct.unpack("<hhihhihhihhihhi", st[12:52]) | |
print "LM Resp: %s" % StrStruct(hdr_tup[0:3], st) | |
print "NTLM Resp: %s" % StrStruct(hdr_tup[3:6], st) | |
print "Target Name: %s" % StrStruct(hdr_tup[6:9], st) | |
print "User Name: %s" % StrStruct(hdr_tup[9:12], st) | |
print "Host Name: %s" % StrStruct(hdr_tup[12:15], st) | |
opt_str_struct("Session Key", st, 52) | |
opt_inline_str("OS Ver", st, 64, 8) | |
nxt = st[60:64] | |
if len(nxt) == 4: | |
flg_tup = struct.unpack("<i", nxt) | |
flags = flg_tup[0] | |
print "Flags: 0x%x [%s]" % (flags, flags_str(flags)) | |
else: | |
print "Flags: [omitted]" | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Line 114 should be:
except Exception as e:
not
except e: