1- # Original from here: https://github.com/nopfor/ntlm_challenger
1+ # Original from here: https://github.com/fortra/impacket/blob/master/examples/DumpNTLMInfo.py#L568
22
3- import datetime
3+ import struct
44
5+ from impacket import ntlm
56from impacket .smb3 import WIN_VERSIONS
7+ import contextlib
68
79
8- def decoder (byte_string , decode_type ):
9- if decode_type == "byte" :
10- return byte_string .decode ("UTF-8" ).replace ("\x00 " , "" )
11- else :
12- return int .from_bytes (byte_string , "little" )
13-
14-
15- def parse_version (version_bytes ):
16- major_version = version_bytes [0 ]
17- minor_version = version_bytes [1 ]
18- product_build = decoder (version_bytes [2 :4 ], "int" )
19- if product_build in WIN_VERSIONS :
20- return f"{ WIN_VERSIONS [product_build ]} Build { product_build } "
21- else :
22- return f"Windows { major_version } .{ minor_version } Build { product_build } "
23-
24-
25- def parse_target_info (target_info_bytes ):
26- MsvAvEOL = 0x0000
27- MsvAvNbComputerName = 0x0001
28- MsvAvNbDomainName = 0x0002
29- MsvAvDnsComputerName = 0x0003
30- MsvAvDnsDomainName = 0x0004
31- MsvAvDnsTreeName = 0x0005
32- MsvAvFlags = 0x0006
33- MsvAvTimestamp = 0x0007
34- MsvAvSingleHost = 0x0008
35- MsvAvTargetName = 0x0009
36- MsvAvChannelBindings = 0x000A
37-
10+ def parse_challenge (challange ):
3811 target_info = {
39- "MsvAvNbComputerName" : None ,
40- "MsvAvDnsDomainName" : None ,
41- }
42- info_offset = 0
43-
44- while info_offset < len (target_info_bytes ):
45- av_id = decoder (target_info_bytes [info_offset :info_offset + 2 ], "int" )
46- av_len = decoder (target_info_bytes [info_offset + 2 :info_offset + 4 ], "int" )
47- av_value = target_info_bytes [info_offset + 4 :info_offset + 4 + av_len ]
48-
49- info_offset = info_offset + 4 + av_len
50-
51- if av_id == MsvAvEOL :
52- pass
53- elif av_id == MsvAvNbComputerName :
54- target_info ["MsvAvNbComputerName" ] = decoder (av_value , "byte" )
55- elif av_id == MsvAvNbDomainName :
56- target_info ["MsvAvNbDomainName" ] = decoder (av_value , "byte" )
57- elif av_id == MsvAvDnsComputerName :
58- target_info ["MsvAvDnsComputerName" ] = decoder (av_value , "byte" )
59- elif av_id == MsvAvDnsDomainName :
60- target_info ["MsvAvDnsDomainName" ] = decoder (av_value , "byte" )
61- elif av_id == MsvAvDnsTreeName :
62- target_info ["MsvAvDnsTreeName" ] = decoder (av_value , "byte" )
63- elif av_id == MsvAvFlags :
64- pass
65- elif av_id == MsvAvTimestamp :
66- filetime = decoder (av_value , "int" )
67- microseconds = (filetime - 116444736000000000 ) / 10
68- time = datetime .datetime (1970 , 1 , 1 ) + datetime .timedelta (microseconds = microseconds )
69- target_info ["MsvAvTimestamp" ] = time .strftime ("%b %d, %Y %H:%M:%S.%f" )
70- elif av_id == MsvAvSingleHost :
71- target_info ["MsvAvSingleHost" ] = decoder (av_value , "byte" )
72- elif av_id == MsvAvTargetName :
73- target_info ["MsvAvTargetName" ] = decoder (av_value , "byte" )
74- elif av_id == MsvAvChannelBindings :
75- target_info ["MsvAvChannelBindings" ] = av_value
76- return target_info
77-
78-
79- def parse_challenge (challenge_message ):
80- # TargetNameFields
81- target_name_fields = challenge_message [12 :20 ]
82- target_name_len = decoder (target_name_fields [0 :2 ], "int" )
83- target_name_offset = decoder (target_name_fields [4 :8 ], "int" )
84-
85- # TargetInfoFields
86- target_info_fields = challenge_message [40 :48 ]
87- target_info_len = decoder (target_info_fields [0 :2 ], "int" )
88- target_info_offset = decoder (target_info_fields [4 :8 ], "int" )
89-
90- # Version
91- version = None
92- version_bytes = challenge_message [48 :56 ]
93- version = parse_version (version_bytes )
94-
95- # TargetName
96- target_name = challenge_message [target_name_offset :target_name_offset + target_name_len ]
97- target_name = decoder (target_name , "byte" )
98-
99- # TargetInfo
100- target_info_bytes = challenge_message [target_info_offset :target_info_offset + target_info_len ]
101- target_info = parse_target_info (target_info_bytes )
102-
103- return {
104- "target_name" : target_name ,
105- "version" : version ,
106- "target_info" : target_info
12+ "hostname" : None ,
13+ "domain" : None ,
14+ "os_version" : None
10715 }
16+ challange = ntlm .NTLMAuthChallenge (challange )
17+ av_pairs = ntlm .AV_PAIRS (challange ["TargetInfoFields" ][:challange ["TargetInfoFields_len" ]])
18+ if av_pairs [ntlm .NTLMSSP_AV_HOSTNAME ] is not None :
19+ with contextlib .suppress (Exception ):
20+ target_info ["hostname" ] = av_pairs [ntlm .NTLMSSP_AV_HOSTNAME ][1 ].decode ("utf-16le" )
21+ if av_pairs [ntlm .NTLMSSP_AV_DNS_DOMAINNAME ] is not None :
22+ with contextlib .suppress (Exception ):
23+ target_info ["domain" ] = av_pairs [ntlm .NTLMSSP_AV_DNS_DOMAINNAME ][1 ].decode ("utf-16le" )
24+ if "Version" in challange .fields :
25+ version = challange ["Version" ]
26+ if len (version ) >= 4 :
27+ major_version = version [0 ]
28+ minor_version = version [1 ]
29+ product_build = struct .unpack ("<H" , version [2 :4 ])[0 ]
30+ if product_build in WIN_VERSIONS :
31+ target_info ["os_version" ] = f"{ WIN_VERSIONS [product_build ]} Build { product_build } "
32+ else :
33+ target_info ["os_version" ] = f"{ major_version } .{ minor_version } Build { product_build } "
34+ return target_info
0 commit comments