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
|
package analytics
import (
"cmp"
_ "embed"
"log"
"slices"
"strings"
"github.com/gocarina/gocsv"
)
//go:embed ja4plus-mapping.csv
var ja4MappingBytes []byte
type ja4MappingEntry struct {
Application string `csv:"Application"`
Library string `csv:"Library"`
Device string `csv:"Device"`
OS string `csv:"OS"`
Ja4 string `csv:"ja4"`
Ja4s string `csv:"ja4s"`
Ja4h string `csv:"ja4h"`
Ja4x string `csv:"ja4x"`
Ja4t string `csv:"ja4t"`
Ja4tscan string `csv:"ja4tscan"`
Notes string `csv:"Notes"`
}
type ClientType float32
const (
ClientApp ClientType = 0
ClientBrowser ClientType = 0
ClientProxy ClientType = 0.5
ClientCLI ClientType = 1.5
ClientOS ClientType = 2
ClientDevice ClientType = 1.5
ClientLibrary ClientType = 2
ClientMalware ClientType = 3
ClientUnknown ClientType = 2
)
func (e ja4MappingEntry) Name() (clientName string, clientType ClientType) {
if app := e.Application; app != "" {
browsers := []string{"Chromium Browser", "Mozilla Firefox", "Safari"}
proxies := []string{"Lumma Client", "SoftEther VPN Client", "SoftEther VPN Server", "ngrok 3.8.0"}
clis := []string{"Curl"}
malwares := []string{
"Cobalt Strike beacon",
"Cobalt Strike Cat C2",
"Cobalt Strike v4.9.1 beacon",
"Darkgate Client",
"IcedID",
"IcedID Dropper",
"Pikabot C2",
"Qakbot C2",
"Sliver Agent",
"Sliver/Havoc C2 Server",
}
if slices.Contains(browsers, app) {
return app, ClientBrowser
}
if slices.Contains(proxies, app) {
return app, ClientProxy
}
if slices.Contains(clis, app) {
return app, ClientCLI
}
if name := strings.TrimSpace(app); slices.Contains(malwares, name) {
return name, ClientMalware
}
return app, ClientApp
}
if lib := e.Library; lib != "" {
return lib, ClientLibrary
}
if dev := e.Device; dev != "" {
return dev, ClientDevice
}
if os := e.OS; os != "" {
return os, ClientOS
}
return cmp.Or(e.Application, e.Library, e.Device, e.OS), ClientUnknown
}
// LookupClient takes a JA4 fingerprint string and
// returns the corresponding HTTP client name string
func LookupClientByJA4(ja4 string) (string, ClientType) {
var mappings []ja4MappingEntry
err := gocsv.UnmarshalBytes(ja4MappingBytes, &mappings)
if err != nil {
log.Fatalln("error unmarshalling JA4 mapping")
}
for _, entry := range mappings {
if entry.Ja4 == ja4 {
return entry.Name()
}
}
return "Unknown", ClientUnknown
}
|