-
Notifications
You must be signed in to change notification settings - Fork 0
/
tlsFingerprint.go
238 lines (199 loc) · 6.48 KB
/
tlsFingerprint.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
package dactyloscopy
import (
"crypto/md5"
"encoding/hex"
"fmt"
"golang.org/x/crypto/cryptobyte"
)
// TLSFingerprint finds the fingerprint that is matched by the provided packet
func (f *Fingerprint) ProcessClientHello(buf []byte) error {
var (
uint8Skipsize uint8
)
// The minimum may be longer, but shorter than this is definitely a problem ;)
if len(buf) < 47 {
return fmt.Errorf("packet appears to be truncated")
}
// This is a very quick and dirty acid test for is this a TLS client hello packet.
// The "science" behind it is here:
// https://speakerdeck.com/leebrotherston/stealthier-attacks-and-smarter-defending-with-tls-fingerprinting?slide=31
// buf[0] == TLS Handshake buf[5] == Client Hello buf[1] == Record TLS Version
// buf[9] == TLS Version
if !(buf[0] == 22 && buf[5] == 1 && buf[1] == 3 && buf[9] == 3) {
return fmt.Errorf("does not look like a client hello")
}
// Sweet, looks like a client hello, let's do some pre-processing
clientHello := cryptobyte.String(buf)
if !clientHello.ReadUint8(&f.MessageType) {
return fmt.Errorf("could not read message type")
}
if !clientHello.ReadUint16((*uint16)(&f.RecordTLSVersion)) {
return fmt.Errorf("could not read RecordTLS version")
}
// Length, handshake type, and length again
clientHello.Skip(6)
if !clientHello.ReadUint16((*uint16)(&f.TLSVersion)) {
return fmt.Errorf("could not read TLS version")
}
// Random
clientHello.Skip(32)
// SessionID
clientHello.ReadUint8(&uint8Skipsize)
clientHello.Skip(int(uint8Skipsize))
//if !clientHello.ReadUint16LengthPrefixed((*cryptobyte.String)(&ciphersuites)) {
if !clientHello.ReadUint16LengthPrefixed(&f.rawSuites) {
return fmt.Errorf("could not read ciphersuites")
}
// See if the packet contains any "grease" ciphersuites, which a) we wish to note
// and b) we wish to filter out as it will make fingerprints look different (potentially)
// as grease patterns are randomized by some clients.
//thisFingerprint.AddCipherSuites(ciphersuites)
err := f.suiteVinegar()
if err != nil {
return err
}
var (
compression cryptobyte.String
compressionItem uint8
)
if !clientHello.ReadUint8LengthPrefixed(&compression) {
return fmt.Errorf("could not read compression")
}
for !compression.Empty() {
compression.ReadUint8(&compressionItem)
f.Compression = append(f.Compression, compressionItem)
}
// And now to the really exciting world of extensions.... extensions!!!
// Get me them thar extensions!!!!
if !clientHello.ReadUint16LengthPrefixed(&f.rawExtensions) {
return fmt.Errorf("could not read extensions")
}
err = f.addExtList()
if err != nil {
return err
}
return nil
}
// suiteVinegar removes grease from the ciphersuites 😜
func (f *Fingerprint) suiteVinegar() error {
var (
ciphersuite uint16
)
for !f.rawSuites.Empty() {
if !f.rawSuites.ReadUint16(&ciphersuite) {
return fmt.Errorf("could not load ciphersuites")
}
// This is the extensionType again, but to add to the extensions var for fingerprinting
switch uint16(ciphersuite) {
// Lets not add grease to the extension list....
case 0x0A0A, 0x1A1A, 0x2A2A, 0x3A3A, 0x4A4A,
0x5A5A, 0x6A6A, 0x7A7A, 0x8A8A, 0x9A9A,
0xAAAA, 0xBABA, 0xCACA, 0xDADA, 0xEAEA,
0xFAFA:
f.Grease = true
// Or padding, because it's padding.....
case 0x0015:
// But everything else is fine
default:
f.Ciphersuite = append(f.Ciphersuite, ciphersuite)
}
}
return nil
}
func (f *Fingerprint) addExtList() error {
var (
err error
)
for !f.rawExtensions.Empty() {
var (
extensionType uint16
extContent cryptobyte.String
)
//fmt.Printf("DEBUG: %v\n", f.rawExtensions)
if !f.rawExtensions.ReadUint16(&extensionType) {
return fmt.Errorf("could not read extension type")
}
if !f.rawExtensions.ReadUint16LengthPrefixed(&extContent) {
return fmt.Errorf("could not read extension content")
}
// This is the extensionType again, but to add to the extensions var for fingerprinting
switch uint16(extensionType) {
// Lets not add grease to the extension list....
case 0x0A0A, 0x1A1A, 0x2A2A, 0x3A3A, 0x4A4A,
0x5A5A, 0x6A6A, 0x7A7A, 0x8A8A, 0x9A9A,
0xAAAA, 0xBABA, 0xCACA, 0xDADA, 0xEAEA,
0xFAFA:
f.Grease = true
// But everything else is fine
case 0x0000:
// SNI
var (
sni cryptobyte.String
hostname cryptobyte.String
sniType uint8
)
if !extContent.ReadUint16LengthPrefixed(&sni) {
return fmt.Errorf("could not read SNI")
}
if !sni.ReadUint8(&sniType) {
return fmt.Errorf("could not read SNI type")
}
// Host Type, hopefully.... ever seen any other? :)
if sniType == 0 {
sni.ReadUint16LengthPrefixed(&hostname)
} else {
sni.ReadUint16LengthPrefixed(nil)
}
f.SNI = string(hostname)
f.Extensions = append(f.Extensions, extensionType)
// The various "lists of stuff" extensions :)
case 0x0015:
// Padding
fmt.Printf("%v\n", skip16(extContent))
f.Extensions = append(f.Extensions, extensionType)
case 0x000a:
// ellipticCurves
fmt.Printf("%v\n", read16Length16Pair(extContent, &f.ECurves))
f.Extensions = append(f.Extensions, extensionType)
case 0x000b:
// ecPoint formats
fmt.Printf("%v\n", read16Length8Pair(extContent, &f.EcPointFmt))
f.Extensions = append(f.Extensions, extensionType)
case 0x000d:
// Signature algorithms
fmt.Printf("%v\n", read16Length16Pair(extContent, &f.SigAlg))
f.Extensions = append(f.Extensions, extensionType)
case 0x002b:
// Supported versions (new in TLS 1.3... I think)
fmt.Printf("%v\n", read16Length16Pair(extContent, &f.SupportedVersions))
f.Extensions = append(f.Extensions, extensionType)
default:
//fmt.Printf("Unused extension: %d\n", extensionType)
fmt.Printf("%v\n", skip16(extContent))
f.Extensions = append(f.Extensions, extensionType)
}
}
// The official JA3 libraries seem to use 0 when EcPointFmt is empty instead
// of leaving the field blank, so we will do this to remain compatible
if len(f.EcPointFmt) == 0 {
f.EcPointFmt = append(f.EcPointFmt, 0)
}
unhashed := fmt.Sprintf("%d,%s,%s,%s,%s", f.TLSVersion, sliceToDash16(f.Ciphersuite), sliceToDash16(f.Extensions), sliceToDash16(f.ECurves), sliceToDash8(f.EcPointFmt))
fmt.Printf("thing: %s\n", unhashed)
f.JA3, err = hashMD5(unhashed)
if err != nil {
return err
}
return nil
}
func (f *Fingerprint) MakeHashes() error {
return nil
}
func hashMD5(text string) (string, error) {
hasher := md5.New()
_, err := hasher.Write([]byte(text))
if err != nil {
return "", err
}
return hex.EncodeToString(hasher.Sum(nil)), nil
}