@@ -12,11 +12,176 @@ import (
1212 "unicode/utf16"
1313
1414 "golang.org/x/text/cases"
15+ "golang.org/x/text/encoding/unicode"
1516 "golang.org/x/text/language"
1617
1718 "github.com/vulncheck-oss/go-exploit/output"
1819)
1920
21+ // Non-public function, used to parse individual target info types from NTLMType2 messages.
22+ func decodeNTLM2TargetInfo (targetInfoBytes []byte ) ([]TargetInfo , bool ) {
23+ /* This helper only supports the standard string-based AV_PAIR types defined for NTLM TargetInfo.
24+ Known and expected types:
25+ Type 0: Terminator subblock (0x0000)
26+ Type 1: Server name (UTF-16LE string, e.g., "server")
27+ Type 2: Domain name (UTF-16LE string, e.g., "domain.com")
28+ Type 3: DNS Server Name subblock (UTF-16LE string, e.g., "server.domain.com")
29+ Type 4: DNS Domain Name (UTF-16LE string, e.g., "domain.com")
30+ All messages processed by this function are expected to end with a type 0 terminator block. Other AV_PAIR
31+ types (e.g., timestamps, flags) are not handled here and may require different parsing logic. */
32+ cursor := 0
33+
34+ retArr := []TargetInfo {}
35+ totalLen := len (targetInfoBytes )
36+ if totalLen < 4 {
37+ output .PrintFrameworkError ("Failed to decode target info: TargetInfo bytes buffer is too short" )
38+
39+ return []TargetInfo {}, false
40+ }
41+
42+ // Making sure this ends with a terminator block (0x00000000)
43+ if ! bytes .Equal (targetInfoBytes [totalLen - 4 :], []byte {0x00 , 0x00 , 0x00 , 0x00 }) {
44+ output .PrintFrameworkError ("Decoding NTLM Type 2 Target Info failed, invalid targetinfo buffer provided, the provided buffer does not end with a terminator block" )
45+
46+ return []TargetInfo {}, false
47+ }
48+
49+ // A terminator-only TargetInfo (4 bytes) is a valid empty AV-pair list in NTLM.
50+ if totalLen == 4 {
51+ return retArr , true
52+ }
53+
54+ // Actual targetinfo parsing
55+ for cursor < totalLen - 4 {
56+ if cursor + 4 > totalLen - 1 {
57+ output .PrintFrameworkError ("Invalid TargetInfo, not enough data in the buffer to parse" )
58+
59+ return retArr , true
60+ }
61+
62+ // Grabbing type
63+ targetInfoType := int (binary .LittleEndian .Uint16 (targetInfoBytes [cursor : cursor + 2 ]))
64+ if targetInfoType == 0 { // Should not really hit/caution
65+ output .PrintFrameworkWarn ("Hit terminator block prematurely during TargetInfo decoding of NTLM Type 2 Message" )
66+
67+ return retArr , true
68+ }
69+
70+ // Decoding the value, after length check
71+ targetInfoLen := binary .LittleEndian .Uint16 (targetInfoBytes [cursor + 2 : cursor + 4 ])
72+ if (cursor + 4 + int (targetInfoLen )) > totalLen - 1 {
73+ output .PrintFrameworkError ("Decoding NTLM Type 2 Target Info failed, Invalid target info length provided" )
74+
75+ return []TargetInfo {}, false
76+ }
77+
78+ value , ok := DecodeUTF16LE (targetInfoBytes [cursor + 4 : cursor + 4 + int (targetInfoLen )])
79+ if ! ok {
80+ return []TargetInfo {}, false
81+ }
82+
83+ retArr = append (retArr , TargetInfo {Type : targetInfoType , Value : value })
84+ cursor = cursor + 4 + int (targetInfoLen )
85+ }
86+
87+ return retArr , true
88+ }
89+
90+ type TargetInfo struct {
91+ Type int
92+ Value string
93+ }
94+
95+ type NTLMType2Message struct {
96+ TargetName string
97+ TargetInfoArr []TargetInfo
98+ Challenge []byte
99+ Flags uint32
100+ }
101+
102+ // Decodes an NTLM Type 2 message, Spec reference: https://curl.se/rfc/ntlm.html.
103+ // The input value should be a base64 value.
104+ // Returns an NTLMType2Message struct as a value.
105+ func DecodeNTLMType2 (stringMessage string ) (NTLMType2Message , bool ) {
106+ returnMessage := NTLMType2Message {TargetName : "" , TargetInfoArr : nil , Challenge : nil , Flags : 0 }
107+ message := []byte (DecodeBase64 (stringMessage ))
108+ if len (message ) < 32 {
109+ output .PrintFrameworkError ("NTLMType2Decode Failed: Message too short" )
110+
111+ return NTLMType2Message {}, false
112+ }
113+
114+ if ! bytes .Equal (message [:8 ], []byte {0x4e , 0x54 , 0x4c , 0x4d , 0x53 , 0x53 , 0x50 , 0x00 }) {
115+ output .PrintFrameworkError ("NTLMType2Decode Failed: Invalid NTLMSSP signature" )
116+
117+ return NTLMType2Message {}, false
118+ }
119+
120+ if ! bytes .Equal (message [8 :12 ], []byte {0x02 , 0x00 , 0x00 , 0x00 }) {
121+ output .PrintFrameworkError ("NTLMType2Decode Failed: Message type is not 2" )
122+
123+ return NTLMType2Message {}, false
124+ }
125+
126+ // mandatory value parsing
127+ targetNameLen := uint32 (binary .LittleEndian .Uint16 (message [12 :14 ]))
128+ allocatedSize := uint32 (binary .LittleEndian .Uint16 (message [14 :16 ]))
129+ targetNameOffset := binary .LittleEndian .Uint32 (message [16 :20 ])
130+ returnMessage .Flags = binary .LittleEndian .Uint32 (message [20 :24 ])
131+ returnMessage .Challenge = message [24 :32 ]
132+
133+ // optional value parsing
134+ if (returnMessage .Flags & 0x00800000 ) != 0 { // Negotiate target info is set
135+ if int (targetNameOffset + targetNameLen ) > len (message )- 1 {
136+ output .PrintFrameworkError ("NTLMType2Decode Failed: Message buffer is too short, may be corrupted/truncated" )
137+
138+ return NTLMType2Message {}, false
139+ }
140+
141+ targetInfoData := message [targetNameOffset : targetNameOffset + targetNameLen ]
142+ if uint32 (len (targetInfoData )) != allocatedSize {
143+ output .PrintFrameworkError ("NTLMType2Decode Failed: targetInfoData does not match reported allocated size" )
144+
145+ return NTLMType2Message {}, false
146+ }
147+ targetInfoLen := uint32 (binary .LittleEndian .Uint16 (message [40 :42 ]))
148+ targetInfoOffset := binary .LittleEndian .Uint32 (message [44 :48 ])
149+
150+ targetName , ok := DecodeUTF16LE (message [targetNameOffset : targetNameOffset + targetNameLen ])
151+ if ! ok {
152+ return NTLMType2Message {}, false
153+ }
154+
155+ targetInfoBuf := message [targetInfoOffset : targetInfoOffset + targetInfoLen ]
156+ targetInfoArr , ok := decodeNTLM2TargetInfo (targetInfoBuf )
157+ if ! ok {
158+ output .PrintFrameworkError ("NTLMType2Decode Failed: could not parse target info" )
159+
160+ return NTLMType2Message {}, false
161+ }
162+
163+ returnMessage .TargetName = targetName
164+ returnMessage .TargetInfoArr = targetInfoArr
165+ output .PrintfFrameworkDebug ("NTLM Type 2: Parsed TargetName: %s" , returnMessage .TargetName )
166+ output .PrintfFrameworkDebug ("NTLM Type 2: Parsed TargetInfo Data: %v" , returnMessage .TargetInfoArr )
167+ }
168+
169+ return returnMessage , true
170+ }
171+
172+ // Converts a provided byte buffer to a UTF-16LE decoded string.
173+ func DecodeUTF16LE (data []byte ) (string , bool ) {
174+ decoder := unicode .UTF16 (unicode .LittleEndian , unicode .IgnoreBOM ).NewDecoder ()
175+ utf8bytes , err := decoder .Bytes (data )
176+ if err != nil {
177+ output .PrintfFrameworkError ("Failed to decode input: %v" , err )
178+
179+ return "" , false
180+ }
181+
182+ return string (utf8bytes ), true
183+ }
184+
20185func StringToUnicodeByteArray (s string ) []byte {
21186 //nolint:prealloc
22187 var byteArray []byte
@@ -164,9 +329,11 @@ func Title(s string) string {
164329// URL encode every character in the provided string.
165330func URLEncodeString (inputString string ) string {
166331 encodedChars := ""
332+ var encodedCharsSb315 strings.Builder
167333 for _ , char := range inputString {
168- encodedChars += "%" + strconv .FormatInt (int64 (char ), 16 )
334+ encodedCharsSb315 . WriteString ( "%" + strconv .FormatInt (int64 (char ), 16 ) )
169335 }
336+ encodedChars += encodedCharsSb315 .String ()
170337
171338 return encodedChars
172339}
0 commit comments