[SHEHZADE//ABDULLAH ANSARI]
Red Team Consultant @ Google | MS in Cybersecurity @ NYU
● 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)
337 lines · 10.6 KB
| 1 | package proxy |
| 2 | |
| 3 | import ( |
| 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 | |
| 19 | var ( |
| 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 | |
| 27 | const ( |
| 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. |
| 46 | type 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. |
| 56 | func 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. |
| 66 | func 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. |
| 78 | func 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. |
| 102 | func 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)). |
| 174 | func 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)). |
| 181 | func 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. |
| 190 | func 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). |
| 219 | func 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. |
| 227 | func 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. |
| 240 | func 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 | } |