Skip to content

Commit

Permalink
added support to dump results as JSON in live mode, updated gopacket …
Browse files Browse the repository at this point in the history
…fork, don't process ja3s in live mode if disabled, made snaplen configurable
  • Loading branch information
dreadl0ck committed Sep 17, 2020
1 parent 2abeadf commit afe3a23
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 60 deletions.
3 changes: 2 additions & 1 deletion cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ var (
flagInterface = flag.String("iface", "", "specify network interface to read packets from")
flagJa3S = flag.Bool("ja3s", true, "include ja3 server hashes (ja3s)")
flagOnlyJa3S = flag.Bool("ja3s-only", false, "dump ja3s only")
flagSnaplen = flag.Int("snaplen", 1514, "default snaplen for ethernet frames")
)

func main() {
Expand All @@ -41,7 +42,7 @@ func main() {
ja3.Debug = *flagDebug

if *flagInterface != "" {
ja3.ReadInterfaceCSV(*flagInterface, os.Stdout, *flagSeparator)
ja3.ReadInterface(*flagInterface, os.Stdout, *flagSeparator, *flagJa3S, *flagJSON, *flagSnaplen)
return
}

Expand Down
4 changes: 1 addition & 3 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
module github.com/dreadl0ck/ja3

require (
github.com/dreadl0ck/gopacket v1.1.16-0.20200114112008-4960f4b77557
github.com/dreadl0ck/gopacket v1.1.16-0.20200831153559-a0d2e73e902d
github.com/dreadl0ck/tlsx v1.0.1-dreadl0ck-gopacket
github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065 // indirect
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 // indirect
)

go 1.13
28 changes: 9 additions & 19 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,38 +1,28 @@
github.com/dreadl0ck/gopacket v1.1.15 h1:12qG1DKM7lZbo69NoeTGzvoYC2YPhPBAcl3NYsvUjHM=
github.com/dreadl0ck/gopacket v1.1.15/go.mod h1:c30gpU/yoyFK01rO2whWmvTiZvsgDMn/mCkbdX41znw=
github.com/dreadl0ck/gopacket v1.1.16-0.20200114112008-4960f4b77557 h1:m/rxJiRUgei39RuIVBMPipyRGokYe534vBfuARBcLnU=
github.com/dreadl0ck/gopacket v1.1.16-0.20200114112008-4960f4b77557/go.mod h1:d7HEeaw/pAxzNTUprrDDpb7RxPsWA9i3NFp1ZfBNl50=
github.com/dreadl0ck/gopacket v1.1.17 h1:rMrlX2ZY2UbvT+sdz3+6J+pp2z+msCq9MxTU6ymxbBY=
github.com/dreadl0ck/gopacket v1.1.17/go.mod h1:UdDNZ1OO62aGYVnPhxT1U6aI7ukYtA/kB8vaU0diBUM=
github.com/dreadl0ck/tlsx v0.0.0-20200303221230-8cb859306e07 h1:ZPbZ1CELNcwKr0D/MHZK0oPRrb4hH28AKldTWknV6lg=
github.com/dreadl0ck/tlsx v0.0.0-20200303221230-8cb859306e07/go.mod h1:amAb73WEEgPHWniMfwro6UpN6St3e5ypgq2tXM89IOo=
github.com/dreadl0ck/tlsx v0.0.0-20200410212044-9b61cacc90db h1:XTAwIjfsFDQGo4z3mQdplToSjWvZU9FHntthpR+g+sQ=
github.com/dreadl0ck/tlsx v0.0.0-20200410212044-9b61cacc90db/go.mod h1:sBTlLV54BjDf/gqC3YuFIhs5DKslo/BW/M/mHkCkNM4=
github.com/dreadl0ck/tlsx v0.0.0-20200410220656-a855823cba0b h1:pe6QmegBSU8XHCMF9PI2P+hEhuQYCodbTQluExHmKW4=
github.com/dreadl0ck/tlsx v0.0.0-20200410220656-a855823cba0b/go.mod h1:sBTlLV54BjDf/gqC3YuFIhs5DKslo/BW/M/mHkCkNM4=
github.com/dreadl0ck/gopacket v1.1.16-0.20200831153559-a0d2e73e902d h1:mCMON8wZr1fmuGNOfkehnnlBQ1NhXC49rVVwlpVM9OQ=
github.com/dreadl0ck/gopacket v1.1.16-0.20200831153559-a0d2e73e902d/go.mod h1:AO4gQoj71eHM7uHvvmIi0V4/vM8LJ1nuGPq9PVNHCrQ=
github.com/dreadl0ck/tlsx v1.0.1-dreadl0ck-gopacket h1:FOqjFi//FT2SslC28SqsqkiSYzXIqcrjqYOOufSA6TU=
github.com/dreadl0ck/tlsx v1.0.1-dreadl0ck-gopacket/go.mod h1:sBTlLV54BjDf/gqC3YuFIhs5DKslo/BW/M/mHkCkNM4=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/gopacket v1.1.17 h1:rMrlX2ZY2UbvT+sdz3+6J+pp2z+msCq9MxTU6ymxbBY=
github.com/google/gopacket v1.1.17/go.mod h1:UdDNZ1OO62aGYVnPhxT1U6aI7ukYtA/kB8vaU0diBUM=
github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065 h1:aFkJ6lx4FPip+S+Uw4aTegFMct9shDvP+79PsSxpm3w=
github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d h1:1ZiEyfaQIg3Qh0EoqpwAakHVhecoE5wlSg5GjnafJGw=
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 h1:xMPOj6Pz6UipU1wXLkrtqpHbR0AVFnyPEQq/wRWz9lM=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190419010253-1f3472d942ba h1:h0zCzEL5UW1mERvwTN6AXcc75PpLkY6OcReia6Dq1BM=
golang.org/x/net v0.0.0-20190419010253-1f3472d942ba/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67 h1:1Fzlr8kkDLQwqMP8GxrhptBLqZG/EDpiATneiZHY998=
golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190418153312-f0ce4c0180be h1:mI+jhqkn68ybP0ORJqunXn+fq+Eeb4hHKqLQcFICjAc=
golang.org/x/sys v0.0.0-20190418153312-f0ce4c0180be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200808120158-1030fc2bf1d9 h1:yi1hN8dcqI9l8klZfy4B8mJvFmmAxJEePIQQFNSd7Cs=
golang.org/x/sys v0.0.0-20200808120158-1030fc2bf1d9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
9 changes: 3 additions & 6 deletions json.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package ja3

import (
"encoding/binary"
"encoding/json"
"fmt"
"io"
Expand Down Expand Up @@ -87,15 +88,11 @@ func ReadFileJSON(file string, out io.Writer, doJA3s bool) {
continue
}

// convert ports to integers, ignore errors
srcPort, _ := strconv.Atoi(tl.TransportFlow().Src().String())
dstPort, _ := strconv.Atoi(tl.TransportFlow().Dst().String())

r := &Record{
DestinationIP: nl.NetworkFlow().Dst().String(),
DestinationPort: dstPort,
DestinationPort: int(binary.BigEndian.Uint16(tl.TransportFlow().Dst().Raw())),
SourceIP: nl.NetworkFlow().Src().String(),
SourcePort: srcPort,
SourcePort: int(binary.BigEndian.Uint16(tl.TransportFlow().Src().Raw())),
Timestamp: timeToFloat(ci.Timestamp),
}

Expand Down
107 changes: 76 additions & 31 deletions live.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package ja3

import (
"encoding/binary"
"encoding/json"
"fmt"
"io"
"strings"
Expand All @@ -10,21 +12,24 @@ import (
"github.com/dreadl0ck/gopacket/pcap"
)

// ReadInterfaceCSV reads packets from the named interface
// and prints as CSV to the supplied io.Writer
func ReadInterfaceCSV(iface string, out io.Writer, separator string) {
// ReadInterface reads packets from the named interface
// if asJSON is true the results will be dumped as newline separated JSON objects
// otherwise CSV will be printed to the supplied io.Writer.
func ReadInterface(iface string, out io.Writer, separator string, ja3s bool, asJSON bool, snaplen int) {

h, err := pcap.OpenLive(iface, 1024, true, -1)
h, err := pcap.OpenLive(iface, int32(snaplen), true, -1)
if err != nil {
panic(err)
}
defer h.Close()

columns := []string{"timestamp", "source_ip", "source_port", "destination_ip", "destination_port", "ja3_digest"}
if !asJSON {
columns := []string{"timestamp", "source_ip", "source_port", "destination_ip", "destination_port", "ja3_digest"}

_, err = out.Write([]byte(strings.Join(columns, separator) + "\n"))
if err != nil {
panic(err)
_, err = out.Write([]byte(strings.Join(columns, separator) + "\n"))
if err != nil {
panic(err)
}
}

count := 0
Expand All @@ -43,16 +48,17 @@ func ReadInterfaceCSV(iface string, out io.Writer, separator string) {
var (
// create gopacket
p = gopacket.NewPacket(data, layers.LinkTypeEthernet, gopacket.Lazy)
// get JA3 if possible
digest = DigestHexPacket(p)
bare = BarePacket(p)
isServer bool
)

if digest == "" {
digest = DigestHexPacketJa3s(p)
if ja3s && len(bare) == 0 {
bare = BarePacketJa3s(p)
isServer = true
}

// check if we got a result
if digest != "" {
if len(bare) > 0 {

count++

Expand All @@ -62,30 +68,69 @@ func ReadInterfaceCSV(iface string, out io.Writer, separator string) {
tl = p.TransportLayer()
)

// got an a digest but no transport or network layer
// got a bare but no transport or network layer
if tl == nil || nl == nil {
if Debug {
fmt.Println("got a nil layer: ", nl, tl, p.Dump(), digest)
fmt.Println("got a nil layer: ", nl, tl, p.Dump(), string(bare))
}
continue
}

b.WriteString(timeToString(ci.Timestamp))
b.WriteString(separator)
b.WriteString(nl.NetworkFlow().Src().String())
b.WriteString(separator)
b.WriteString(tl.TransportFlow().Src().String())
b.WriteString(separator)
b.WriteString(nl.NetworkFlow().Dst().String())
b.WriteString(separator)
b.WriteString(tl.TransportFlow().Dst().String())
b.WriteString(separator)
b.WriteString(digest)
b.WriteString("\n")

_, err := out.Write([]byte(b.String()))
if err != nil {
panic(err)
r := &Record{
DestinationIP: nl.NetworkFlow().Dst().String(),
DestinationPort: int(binary.BigEndian.Uint16(tl.TransportFlow().Dst().Raw())),
SourceIP: nl.NetworkFlow().Src().String(),
SourcePort: int(binary.BigEndian.Uint16(tl.TransportFlow().Src().Raw())),
Timestamp: timeToFloat(ci.Timestamp),
}

digest := BareToDigestHex(bare)
if isServer {
r.JA3S = string(bare)
r.JA3SDigest = digest
} else {
r.JA3 = string(bare)
r.JA3Digest = digest
}

if asJSON {

// make it pretty please
b, err := json.MarshalIndent(r, "", " ")
if err != nil {
panic(err)
}

if string(b) != "null" { // no matches will result in "null" json
// write to output io.Writer
_, err = out.Write(b)
if err != nil {
panic(err)
}

_, err = out.Write([]byte("\n"))
if err != nil {
panic(err)
}
}
} else { // CSV
b.WriteString(timeToString(ci.Timestamp))
b.WriteString(separator)
b.WriteString(nl.NetworkFlow().Src().String())
b.WriteString(separator)
b.WriteString(tl.TransportFlow().Src().String())
b.WriteString(separator)
b.WriteString(nl.NetworkFlow().Dst().String())
b.WriteString(separator)
b.WriteString(tl.TransportFlow().Dst().String())
b.WriteString(separator)
b.WriteString(digest)
b.WriteString("\n")

_, err := out.Write([]byte(b.String()))
if err != nil {
panic(err)
}
}
}
}
Expand Down

0 comments on commit afe3a23

Please sign in to comment.