Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
projectdiscovery
GitHub Repository: projectdiscovery/nuclei
Path: blob/dev/pkg/utils/telnetmini/ntlm.go
2843 views
1
package telnetmini
2
3
import (
4
"bytes"
5
"encoding/binary"
6
"fmt"
7
)
8
9
// NTLMInfoResponse represents the response from NTLM information gathering
10
// This matches exactly the output structure from the Nmap telnet-ntlm-info.nse script
11
type NTLMInfoResponse struct {
12
TargetName string // Target_Name from script
13
NetBIOSDomainName string // NetBIOS_Domain_Name from script
14
NetBIOSComputerName string // NetBIOS_Computer_Name from script
15
DNSDomainName string // DNS_Domain_Name from script
16
DNSComputerName string // DNS_Computer_Name from script
17
DNSTreeName string // DNS_Tree_Name from script
18
ProductVersion string // Product_Version from script
19
Timestamp uint64 // Raw timestamp for skew calculation
20
}
21
22
// ParseNTLMResponse parses the NTLM response to extract system information
23
// This implements the exact parsing logic from the Nmap telnet-ntlm-info.nse script
24
func ParseNTLMResponse(data []byte) (*NTLMInfoResponse, error) {
25
// Continue only if NTLMSSP response is returned.
26
// Verify that the response is terminated with Sub-option End values as various
27
// non Microsoft telnet implementations support NTLM but do not return valid data.
28
// This matches the script's: local data = string.match(response, "(NTLMSSP.*)\xff\xf0")
29
ntlmStart := bytes.Index(data, []byte("NTLMSSP"))
30
if ntlmStart == -1 {
31
return nil, fmt.Errorf("NTLMSSP signature not found in response")
32
}
33
34
// Find the end of NTLM data (Sub-option End: 0xFF 0xF0)
35
ntlmEnd := bytes.Index(data[ntlmStart:], []byte{0xFF, 0xF0})
36
if ntlmEnd == -1 {
37
return nil, fmt.Errorf("NTLM response not properly terminated with Sub-option End")
38
}
39
40
// Extract NTLM data (NTLMSSP.*\xff\xf0)
41
ntlmData := data[ntlmStart : ntlmStart+ntlmEnd]
42
43
// Check message type (should be 2 for Challenge)
44
if len(ntlmData) < 12 {
45
return nil, fmt.Errorf("NTLM response too short")
46
}
47
48
messageType := binary.LittleEndian.Uint32(ntlmData[8:12])
49
if messageType != 2 {
50
return nil, fmt.Errorf("expected NTLM challenge message, got type %d", messageType)
51
}
52
53
// Parse target name fields
54
targetNameLen := binary.LittleEndian.Uint16(ntlmData[12:14])
55
targetNameOffset := binary.LittleEndian.Uint32(ntlmData[16:20])
56
57
// Parse target info fields
58
targetInfoLen := binary.LittleEndian.Uint16(ntlmData[40:42])
59
targetInfoOffset := binary.LittleEndian.Uint32(ntlmData[44:48])
60
61
// Extract target name (Target Name will always be returned under any implementation)
62
var targetName string
63
if targetNameLen > 0 && int(targetNameOffset) < len(ntlmData) {
64
end := int(targetNameOffset) + int(targetNameLen)
65
if end <= len(ntlmData) {
66
targetName = string(ntlmData[targetNameOffset:end])
67
}
68
}
69
70
// Extract target info (contains detailed system information)
71
var ntlmInfo NTLMInfoResponse
72
ntlmInfo.TargetName = targetName
73
74
// Parse target info structure if available
75
if targetInfoLen > 0 && int(targetInfoOffset) < len(ntlmData) {
76
end := int(targetInfoOffset) + int(targetInfoLen)
77
if end <= len(ntlmData) {
78
parseTargetInfo(ntlmData[targetInfoOffset:end], &ntlmInfo)
79
}
80
}
81
82
return &ntlmInfo, nil
83
}
84
85
// CalculateTimestampSkew calculates the time skew from NTLM timestamp
86
// This implements the timestamp calculation from the Nmap script:
87
// local unixstamp = ntlm_decoded.timestamp // 10000000 - 11644473600
88
func CalculateTimestampSkew(ntlmTimestamp uint64) int64 {
89
if ntlmTimestamp == 0 {
90
return 0
91
}
92
93
// Convert 100ns clicks since 1/1/1601 to Unix timestamp
94
// Formula: (ntlmTimestamp / 10000000) - 11644473600
95
unixTimestamp := int64(ntlmTimestamp/10000000) - 11644473600
96
return unixTimestamp
97
}
98
99
// parseTargetInfo parses the NTLM target info structure to extract system details
100
func parseTargetInfo(data []byte, info *NTLMInfoResponse) {
101
// Target info is a series of type-length-value pairs
102
// Each entry starts with a 2-byte type and 2-byte length
103
for i := 0; i < len(data)-4; {
104
if i+4 > len(data) {
105
break
106
}
107
108
infoType := binary.LittleEndian.Uint16(data[i : i+2])
109
infoLen := binary.LittleEndian.Uint16(data[i+2 : i+4])
110
111
if i+4+int(infoLen) > len(data) {
112
break
113
}
114
115
infoData := data[i+4 : i+4+int(infoLen)]
116
117
switch infoType {
118
case 1: // NetBIOS Computer Name
119
// Display information returned & ignore responses with null values
120
if len(infoData) > 0 {
121
info.NetBIOSComputerName = string(infoData)
122
}
123
case 2: // NetBIOS Domain Name
124
if len(infoData) > 0 {
125
info.NetBIOSDomainName = string(infoData)
126
}
127
case 3: // DNS Computer Name (fqdn in script)
128
if len(infoData) > 0 {
129
info.DNSComputerName = string(infoData)
130
}
131
case 4: // DNS Domain Name
132
if len(infoData) > 0 {
133
info.DNSDomainName = string(infoData)
134
}
135
case 5: // DNS Tree Name (dns_forest_name in script)
136
if len(infoData) > 0 {
137
info.DNSTreeName = string(infoData)
138
}
139
case 6: // Timestamp - 64-bit number of 100ns clicks since 1/1/1601
140
if len(infoData) >= 8 {
141
info.Timestamp = binary.LittleEndian.Uint64(infoData)
142
}
143
case 7: // Single Host
144
// Skip single host
145
case 8: // Target Name (target_realm in script)
146
if len(infoData) > 0 {
147
info.TargetName = string(infoData)
148
}
149
case 9: // Channel Bindings
150
// Skip channel bindings
151
case 10: // Target Information
152
// Skip target information
153
case 11: // OS Version
154
if len(infoData) >= 8 {
155
major := uint8(infoData[0])
156
minor := uint8(infoData[1])
157
build := binary.LittleEndian.Uint16(infoData[2:4])
158
info.ProductVersion = fmt.Sprintf("%d.%d.%d", major, minor, build)
159
}
160
}
161
162
i += 4 + int(infoLen)
163
}
164
}
165
166
// CreateNTLMNegotiateBlob creates the NTLM negotiate blob with specific flags
167
// This matches the flags used in the Nmap script
168
func CreateNTLMNegotiateBlob() []byte {
169
var buf bytes.Buffer
170
171
// NTLMSSP signature
172
buf.WriteString("NTLMSSP")
173
buf.WriteByte(0x00)
174
175
// Message type (1 = Negotiate)
176
messageTypeBytes := make([]byte, 4)
177
binary.LittleEndian.PutUint32(messageTypeBytes, 1)
178
buf.Write(messageTypeBytes)
179
180
// Negotiate flags (matching Nmap script exactly)
181
flags := uint32(0x00000001 + // Negotiate Unicode
182
0x00000002 + // Negotiate OEM strings
183
0x00000004 + // Request Target
184
0x00000200 + // Negotiate NTLM
185
0x00008000 + // Negotiate Always Sign
186
0x00080000 + // Negotiate NTLM2 Key
187
0x20000000 + // Negotiate 128
188
0x80000000) // Negotiate 56
189
flagsBytes := make([]byte, 4)
190
binary.LittleEndian.PutUint32(flagsBytes, flags)
191
buf.Write(flagsBytes)
192
193
// Domain name fields (empty for negotiate)
194
domainNameLenBytes := make([]byte, 2)
195
binary.LittleEndian.PutUint16(domainNameLenBytes, 0)
196
buf.Write(domainNameLenBytes)
197
198
domainNameMaxLenBytes := make([]byte, 2)
199
binary.LittleEndian.PutUint16(domainNameMaxLenBytes, 0)
200
buf.Write(domainNameMaxLenBytes)
201
202
domainNameOffsetBytes := make([]byte, 4)
203
binary.LittleEndian.PutUint32(domainNameOffsetBytes, 0)
204
buf.Write(domainNameOffsetBytes)
205
206
// Workstation name fields (empty for negotiate)
207
workstationNameLenBytes := make([]byte, 2)
208
binary.LittleEndian.PutUint16(workstationNameLenBytes, 0)
209
buf.Write(workstationNameLenBytes)
210
211
workstationNameMaxLenBytes := make([]byte, 2)
212
binary.LittleEndian.PutUint16(workstationNameMaxLenBytes, 0)
213
buf.Write(workstationNameMaxLenBytes)
214
215
workstationNameOffsetBytes := make([]byte, 4)
216
binary.LittleEndian.PutUint32(workstationNameOffsetBytes, 0)
217
buf.Write(workstationNameOffsetBytes)
218
219
// Version (empty for negotiate)
220
buf.Write(make([]byte, 8))
221
222
return buf.Bytes()
223
}
224
225
// CreateTNAPLoginPacket creates the MS-TNAP Login Packet (Option Command IS)
226
// This implements the exact packet structure from the Nmap script
227
func CreateTNAPLoginPacket() []byte {
228
var buf bytes.Buffer
229
230
// TNAP Option Command IS (0x01)
231
buf.WriteByte(0x01)
232
233
// Length (will be updated later)
234
buf.WriteByte(0x00)
235
236
// NTLM authentication blob
237
ntlmBlob := CreateNTLMNegotiateBlob()
238
239
// Update length
240
data := buf.Bytes()
241
data[1] = byte(len(ntlmBlob)) // Length of the NTLM blob
242
243
// Append NTLM blob
244
buf.Write(ntlmBlob)
245
246
return buf.Bytes()
247
}
248
249