● Notice //GitHub (@shehzade) is temporarily suspended pending an automated false-positive review. All tools are hosted on this Code Mirror and available on request (contact); GitHub links will return once reinstated.
PUBLIC MIRRORGo v1.0

Multi-protocol network egress assessment and DLP verification framework featuring automated L4 sweeps, corporate proxy traversal, and covert protocol smuggling across 20+ channels.

$ git clone https://shehzade.io/repos/contraband.git Copy
Source ZIP (1.93 MB)
Code 174 Commits 1
contraband / pkg / client / proxy / auth_ntlm.go
← Back to directory
337 lines · 10.6 KB
Raw
1package proxy
2
3import (
4 "bytes"
5 "crypto/hmac"
6 "crypto/md5"
7 "crypto/rand"
8 "encoding/base64"
9 "encoding/binary"
10 "errors"
11 "fmt"
12 "strings"
13 "time"
14 "unicode/utf16"
15
16 "golang.org/x/crypto/md4"
17)
18
19var (
20 ntlmSignature = []byte("NTLMSSP\x00")
21
22 ErrInvalidNTLMSignature = errors.New("invalid NTLMSSP signature")
23 ErrInvalidNTLMMessageType = errors.New("invalid NTLM message type")
24 ErrNTLMPayloadTruncated = errors.New("truncated NTLM message payload")
25)
26
27const (
28 NTLMType1MessageType = 1
29 NTLMType2MessageType = 2
30 NTLMType3MessageType = 3
31
32 // Negotiate Flags
33 NegotiateUnicode uint32 = 0x00000001
34 NegotiateOEM uint32 = 0x00000002
35 RequestTarget uint32 = 0x00000004
36 NegotiateNTLM uint32 = 0x00000200
37 NegotiateAlwaysSign uint32 = 0x00008000
38 NegotiateExtendedSessionSecurity uint32 = 0x00080000
39 NegotiateTargetInfo uint32 = 0x00800000
40 Negotiate128 uint32 = 0x20000000
41 NegotiateKeyExch uint32 = 0x40000000
42 Negotiate56 uint32 = 0x80000000
43)
44
45// NTLMType2 represents a parsed Type 2 Challenge message.
46type NTLMType2 struct {
47 Flags uint32
48 ServerChallenge [8]byte
49 TargetName string
50 TargetInfo []byte
51 Timestamp uint64
52 HasTimestamp bool
53}
54
55// encodeUTF16LE converts a string to a UTF-16LE byte slice.
56func encodeUTF16LE(s string) []byte {
57 runes := utf16.Encode([]rune(s))
58 b := make([]byte, len(runes)*2)
59 for i, r := range runes {
60 binary.LittleEndian.PutUint16(b[i*2:], r)
61 }
62 return b
63}
64
65// decodeUTF16LE converts a UTF-16LE byte slice to string.
66func decodeUTF16LE(b []byte) string {
67 if len(b)%2 != 0 {
68 b = b[:len(b)-1]
69 }
70 u16s := make([]uint16, len(b)/2)
71 for i := 0; i < len(u16s); i++ {
72 u16s[i] = binary.LittleEndian.Uint16(b[i*2:])
73 }
74 return string(utf16.Decode(u16s))
75}
76
77// BuildNTLMType1 generates a base64-encoded NTLM Type 1 Negotiate payload.
78func BuildNTLMType1(domain, workstation string) string {
79 // Standard 32-byte header with optional flags
80 flags := NegotiateUnicode | RequestTarget | NegotiateNTLM |
81 NegotiateExtendedSessionSecurity | NegotiateTargetInfo | Negotiate128
82
83 var buf bytes.Buffer
84 buf.Write(ntlmSignature)
85 _ = binary.Write(&buf, binary.LittleEndian, uint32(NTLMType1MessageType))
86 _ = binary.Write(&buf, binary.LittleEndian, flags)
87
88 // Supplied Domain security buffer: Len=0, Alloc=0, Offset=32
89 _ = binary.Write(&buf, binary.LittleEndian, uint16(0))
90 _ = binary.Write(&buf, binary.LittleEndian, uint16(0))
91 _ = binary.Write(&buf, binary.LittleEndian, uint32(32))
92
93 // Supplied Workstation security buffer: Len=0, Alloc=0, Offset=32
94 _ = binary.Write(&buf, binary.LittleEndian, uint16(0))
95 _ = binary.Write(&buf, binary.LittleEndian, uint16(0))
96 _ = binary.Write(&buf, binary.LittleEndian, uint32(32))
97
98 return base64.StdEncoding.EncodeToString(buf.Bytes())
99}
100
101// ParseNTLMType2 decodes and parses an NTLM Type 2 Challenge from base64.
102func ParseNTLMType2(rawB64 string) (*NTLMType2, error) {
103 rawB64 = strings.TrimSpace(rawB64)
104 if strings.HasPrefix(strings.ToLower(rawB64), "ntlm ") {
105 rawB64 = strings.TrimSpace(rawB64[5:])
106 }
107
108 data, err := base64.StdEncoding.DecodeString(rawB64)
109 if err != nil {
110 return nil, fmt.Errorf("failed to decode NTLM Type 2 base64: %w", err)
111 }
112
113 if len(data) < 32 {
114 return nil, ErrNTLMPayloadTruncated
115 }
116
117 if !bytes.Equal(data[:8], ntlmSignature) {
118 return nil, ErrInvalidNTLMSignature
119 }
120
121 msgType := binary.LittleEndian.Uint32(data[8:12])
122 if msgType != NTLMType2MessageType {
123 return nil, fmt.Errorf("%w: expected 2, got %d", ErrInvalidNTLMMessageType, msgType)
124 }
125
126 t2 := &NTLMType2{}
127
128 targetNameLen := binary.LittleEndian.Uint16(data[12:14])
129 targetNameOffset := binary.LittleEndian.Uint32(data[16:20])
130
131 t2.Flags = binary.LittleEndian.Uint32(data[20:24])
132 copy(t2.ServerChallenge[:], data[24:32])
133
134 if targetNameLen > 0 && int(targetNameOffset)+int(targetNameLen) <= len(data) {
135 nameBytes := data[targetNameOffset : targetNameOffset+uint32(targetNameLen)]
136 if t2.Flags&NegotiateUnicode != 0 {
137 t2.TargetName = decodeUTF16LE(nameBytes)
138 } else {
139 t2.TargetName = string(nameBytes)
140 }
141 }
142
143 // TargetInfo SecurityBuffer is at offset 40 (if message is at least 48 bytes)
144 if len(data) >= 48 {
145 targetInfoLen := binary.LittleEndian.Uint16(data[40:42])
146 targetInfoOffset := binary.LittleEndian.Uint32(data[44:48])
147
148 if targetInfoLen > 0 && int(targetInfoOffset)+int(targetInfoLen) <= len(data) {
149 t2.TargetInfo = make([]byte, targetInfoLen)
150 copy(t2.TargetInfo, data[targetInfoOffset:targetInfoOffset+uint32(targetInfoLen)])
151
152 // Scan AV_PAIRS for timestamp (0x0007)
153 pos := 0
154 for pos+4 <= len(t2.TargetInfo) {
155 avID := binary.LittleEndian.Uint16(t2.TargetInfo[pos : pos+2])
156 avLen := binary.LittleEndian.Uint16(t2.TargetInfo[pos+2 : pos+4])
157 pos += 4
158 if avID == 0 { // MsvAvEOL
159 break
160 }
161 if avID == 0x0007 && avLen == 8 && pos+8 <= len(t2.TargetInfo) {
162 t2.Timestamp = binary.LittleEndian.Uint64(t2.TargetInfo[pos : pos+8])
163 t2.HasTimestamp = true
164 }
165 pos += int(avLen)
166 }
167 }
168 }
169
170 return t2, nil
171}
172
173// ComputeNTHash computes MD4(UTF16LE(password)).
174func ComputeNTHash(password string) []byte {
175 h := md4.New()
176 h.Write(encodeUTF16LE(password))
177 return h.Sum(nil)
178}
179
180// ComputeNTLMv2Hash computes HMAC-MD5(NT_Hash, UTF16LE(ToUpper(username)) + UTF16LE(domain)).
181func ComputeNTLMv2Hash(username, password, domain string) []byte {
182 ntHash := ComputeNTHash(password)
183 mac := hmac.New(md5.New, ntHash)
184 mac.Write(encodeUTF16LE(strings.ToUpper(username)))
185 mac.Write(encodeUTF16LE(domain))
186 return mac.Sum(nil)
187}
188
189// ComputeClientBlob creates the client blob (temp structure) for NTLMv2.
190func ComputeClientBlob(timestamp uint64, clientNonce []byte, targetInfo []byte) []byte {
191 var buf bytes.Buffer
192 // Header: 0x01010000 (RespType: uint8=1, HiRespType: uint8=1, Reserved: uint16=0)
193 buf.Write([]byte{0x01, 0x01, 0x00, 0x00})
194 // Reserved: 4 bytes zeros
195 buf.Write([]byte{0x00, 0x00, 0x00, 0x00})
196 // Timestamp: uint64 little endian
197 _ = binary.Write(&buf, binary.LittleEndian, timestamp)
198 // Client Nonce: 8 bytes
199 if len(clientNonce) >= 8 {
200 buf.Write(clientNonce[:8])
201 } else {
202 padded := make([]byte, 8)
203 copy(padded, clientNonce)
204 buf.Write(padded)
205 }
206 // Reserved: 4 bytes zeros
207 buf.Write([]byte{0x00, 0x00, 0x00, 0x00})
208 // TargetInfo (from Type 2)
209 if len(targetInfo) > 0 {
210 buf.Write(targetInfo)
211 }
212 // Final Reserved: 4 bytes zeros
213 buf.Write([]byte{0x00, 0x00, 0x00, 0x00})
214
215 return buf.Bytes()
216}
217
218// ComputeNTProof computes HMAC-MD5(NTLMv2_Hash, ServerChallenge + ClientBlob).
219func ComputeNTProof(ntlmv2Hash, serverChallenge, clientBlob []byte) []byte {
220 mac := hmac.New(md5.New, ntlmv2Hash)
221 mac.Write(serverChallenge)
222 mac.Write(clientBlob)
223 return mac.Sum(nil)
224}
225
226// ComputeLMv2Response computes HMAC-MD5(NTLMv2_Hash, ServerChallenge + ClientNonce) + ClientNonce.
227func ComputeLMv2Response(ntlmv2Hash, serverChallenge, clientNonce []byte) []byte {
228 nonce := make([]byte, 8)
229 if len(clientNonce) >= 8 {
230 copy(nonce, clientNonce[:8])
231 }
232 mac := hmac.New(md5.New, ntlmv2Hash)
233 mac.Write(serverChallenge)
234 mac.Write(nonce)
235 proof := mac.Sum(nil)
236 return append(proof, nonce...)
237}
238
239// BuildNTLMType3 constructs the base64-encoded NTLM Type 3 Authenticate message.
240func BuildNTLMType3(username, password, domain, workstation string, challenge *NTLMType2, clientNonce []byte) (string, error) {
241 if challenge == nil {
242 return "", errors.New("nil NTLM Type 2 challenge")
243 }
244
245 if len(clientNonce) < 8 {
246 clientNonce = make([]byte, 8)
247 if _, err := rand.Read(clientNonce); err != nil {
248 return "", fmt.Errorf("failed to generate random client nonce: %w", err)
249 }
250 }
251
252 timestamp := challenge.Timestamp
253 if !challenge.HasTimestamp || timestamp == 0 {
254 // Windows FILETIME: 100ns increments since Jan 1, 1601
255 timestamp = uint64((time.Now().UnixNano() / 100) + 116444736000000000)
256 }
257
258 ntlmv2Hash := ComputeNTLMv2Hash(username, password, domain)
259 clientBlob := ComputeClientBlob(timestamp, clientNonce, challenge.TargetInfo)
260 ntProof := ComputeNTProof(ntlmv2Hash, challenge.ServerChallenge[:], clientBlob)
261 ntChallengeResponse := append(ntProof, clientBlob...)
262 lmChallengeResponse := ComputeLMv2Response(ntlmv2Hash, challenge.ServerChallenge[:], clientNonce)
263
264 domainBytes := encodeUTF16LE(domain)
265 userBytes := encodeUTF16LE(username)
266 workstationBytes := encodeUTF16LE(workstation)
267
268 // Fixed header length is 64 bytes (excluding SessionKey payload and OS Version)
269 headerLen := uint32(64)
270 offset := headerLen
271
272 lmOffset := offset
273 offset += uint32(len(lmChallengeResponse))
274
275 ntOffset := offset
276 offset += uint32(len(ntChallengeResponse))
277
278 domainOffset := offset
279 offset += uint32(len(domainBytes))
280
281 userOffset := offset
282 offset += uint32(len(userBytes))
283
284 workstationOffset := offset
285 offset += uint32(len(workstationBytes))
286
287 sessionKeyOffset := offset
288
289 flags := NegotiateUnicode | RequestTarget | NegotiateNTLM |
290 NegotiateExtendedSessionSecurity | NegotiateTargetInfo | Negotiate128
291
292 var buf bytes.Buffer
293 buf.Write(ntlmSignature)
294 _ = binary.Write(&buf, binary.LittleEndian, uint32(NTLMType3MessageType))
295
296 // LM Response SecurityBuffer
297 _ = binary.Write(&buf, binary.LittleEndian, uint16(len(lmChallengeResponse)))
298 _ = binary.Write(&buf, binary.LittleEndian, uint16(len(lmChallengeResponse)))
299 _ = binary.Write(&buf, binary.LittleEndian, lmOffset)
300
301 // NT Response SecurityBuffer
302 _ = binary.Write(&buf, binary.LittleEndian, uint16(len(ntChallengeResponse)))
303 _ = binary.Write(&buf, binary.LittleEndian, uint16(len(ntChallengeResponse)))
304 _ = binary.Write(&buf, binary.LittleEndian, ntOffset)
305
306 // Domain SecurityBuffer
307 _ = binary.Write(&buf, binary.LittleEndian, uint16(len(domainBytes)))
308 _ = binary.Write(&buf, binary.LittleEndian, uint16(len(domainBytes)))
309 _ = binary.Write(&buf, binary.LittleEndian, domainOffset)
310
311 // User SecurityBuffer
312 _ = binary.Write(&buf, binary.LittleEndian, uint16(len(userBytes)))
313 _ = binary.Write(&buf, binary.LittleEndian, uint16(len(userBytes)))
314 _ = binary.Write(&buf, binary.LittleEndian, userOffset)
315
316 // Workstation SecurityBuffer
317 _ = binary.Write(&buf, binary.LittleEndian, uint16(len(workstationBytes)))
318 _ = binary.Write(&buf, binary.LittleEndian, uint16(len(workstationBytes)))
319 _ = binary.Write(&buf, binary.LittleEndian, workstationOffset)
320
321 // Encrypted Session Key SecurityBuffer (empty)
322 _ = binary.Write(&buf, binary.LittleEndian, uint16(0))
323 _ = binary.Write(&buf, binary.LittleEndian, uint16(0))
324 _ = binary.Write(&buf, binary.LittleEndian, sessionKeyOffset)
325
326 // Flags
327 _ = binary.Write(&buf, binary.LittleEndian, flags)
328
329 // Write payload data
330 buf.Write(lmChallengeResponse)
331 buf.Write(ntChallengeResponse)
332 buf.Write(domainBytes)
333 buf.Write(userBytes)
334 buf.Write(workstationBytes)
335
336 return base64.StdEncoding.EncodeToString(buf.Bytes()), nil
337}