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 }