Skip to content

Instantly share code, notes, and snippets.

Forked from aseering/
Created May 21, 2021 09:20
Show Gist options
  • Save orangetw/300e56f3b3a1fd283544bd2cbde18a3a to your computer and use it in GitHub Desktop.
Save orangetw/300e56f3b3a1fd283544bd2cbde18a3a to your computer and use it in GitHub Desktop.
NTLM auth-string decoder
#!/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:
## <>
## with additional detail subsequently added from the official protocol spec:
## <>
## For example:
## 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
self.string = self.raw
def __str__(self):
st = "%s'%s' [%s] (%db @%d)" % ('u' if self.utf16 else '',
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 =
st = base64.b64decode(st_raw)
except e:
print "Input is not a valid base64-encoded string"
if st[:8] == "NTLMSSP\0":
print "Found NTLMSSP header"
print "NTLMSSP header not found at start of input string"
ver_tup = struct.unpack("<i", st[8:12])
ver = ver_tup[0]
print "Msg Type: %d (%s)" % (ver, msg_types[ver])
if ver == 1:
elif ver == 2:
elif ver == 3:
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))
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))
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))
print "Flags: [omitted]"
if __name__ == "__main__":
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment