forked from open-ch/ja3
/
engine.go
168 lines (146 loc) · 4.9 KB
/
engine.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
// Copyright (c) 2018, Open Systems AG. All rights reserved.
//
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file in the root of the source
// tree.
package main
import (
"encoding/json"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/google/gopacket/pcap"
"github.com/google/gopacket/pcapgo"
"github.com/open-ch/ja3"
"io"
"os"
)
// Reader provides an uniform interface when reading from different sources for the command line interface.
type Reader interface {
ZeroCopyReadPacketData() ([]byte, gopacket.CaptureInfo, error)
}
// ReadPcapFile returns a reader for the supplied pcap file.
func ReadPcapFile(file *os.File) (Reader, error) {
return pcapgo.NewReader(file)
}
// ReadPcapngFile returns a reader for the supplied pcapng file.
func ReadPcapngFile(file *os.File) (Reader, error) {
return pcapgo.NewNgReader(file, pcapgo.DefaultNgReaderOptions)
}
// ReadFromInterface returns a handle to read from the specified interface. The snap length is set to 1600 and the
// interface is in promiscuous mode.
func ReadFromInterface(device string) (Reader, error) {
return pcap.OpenLive(device, 1600, true, pcap.BlockForever)
}
// ComputeJA3FromReader reads from reader until an io.EOF error is encountered and writes verbose information about
// the found Client Hellos in the stream in JSON format to the writer. It only supports packets consisting of a pure
// ETH/IP/TCP stack but is very fast. If your packets have a different structure, use the CompatComputeJA3FromReader
// function.
func ComputeJA3FromReader(reader Reader, writer io.Writer) error {
// Build a selective parser which only decodes the needed layers
var ethernet layers.Ethernet
var ipv4 layers.IPv4
var ipv6 layers.IPv6
var tcp layers.TCP
var decoded []gopacket.LayerType
parser := gopacket.NewDecodingLayerParser(layers.LayerTypeEthernet, ðernet, &ipv4, &ipv6, &tcp)
for {
// Read packet data
packet, ci, err := reader.ZeroCopyReadPacketData()
if err == io.EOF {
break
} else if err != nil {
return err
}
// Decode the packet with our predefined parser
parser.DecodeLayers(packet, &decoded)
// Check if we could decode up to the TCP layer
for _, layerType := range decoded {
switch layerType {
case layers.LayerTypeTCP:
j, err := ja3.ComputeJA3FromSegment(tcp.Payload)
// Check if the parsing was successful, else segment is no Client Hello
if err != nil {
continue
}
// Prepare capture info for JSON marshalling
var srcIP, dstIP string
for _, layerType := range decoded {
switch layerType {
case layers.LayerTypeIPv4:
srcIP = ipv4.SrcIP.String()
dstIP = ipv4.DstIP.String()
case layers.LayerTypeIPv6:
srcIP = ipv6.SrcIP.String()
dstIP = ipv6.DstIP.String()
}
}
err = writeJSON(dstIP, int(tcp.DstPort), srcIP, int(tcp.SrcPort), ci.Timestamp.UnixNano(), j, writer)
if err != nil {
return err
}
}
}
}
return nil
}
// CompatComputeJA3FromReader has the same functionality as ComputeJA3FromReader but supports any protocol that is
// supported by the gopacket library. It is much slower than the ComputeJA3FromReader function and therefore should not
// be used unless needed.
func CompatComputeJA3FromReader(reader Reader, writer io.Writer) error {
for {
// Read packet data
packetData, ci, err := reader.ZeroCopyReadPacketData()
if err == io.EOF {
break
} else if err != nil {
return err
}
packet := gopacket.NewPacket(packetData, layers.LayerTypeEthernet, gopacket.DecodeOptions{NoCopy: true, Lazy: true})
tcpLayer := packet.Layer(layers.LayerTypeTCP)
if tcpLayer != nil {
tcp, _ := tcpLayer.(*layers.TCP)
j, err := ja3.ComputeJA3FromSegment(tcp.Payload)
// Check if the parsing was successful, else segment is no Client Hello
if err != nil {
continue
}
// Prepare capture info for JSON marshalling
src, dst := packet.NetworkLayer().NetworkFlow().Endpoints()
err = writeJSON(dst.String(), int(tcp.DstPort), src.String(), int(tcp.SrcPort), ci.Timestamp.UnixNano(), j, writer)
if err != nil {
return err
}
}
}
return nil
}
// writeJSON to writer
func writeJSON(dstIP string, dstPort int, srcIP string, srcPort int, timestamp int64, j *ja3.JA3, writer io.Writer) error {
// Use the same convention as in the official Python implementation
js, err := json.Marshal(struct {
DstIP string `json:"destination_ip"`
DstPort int `json:"destination_port"`
JA3String string `json:"ja3"`
JA3Hash string `json:"ja3_digest"`
SrcIP string `json:"source_ip"`
SrcPort int `json:"source_port"`
SNI string `json:"sni"`
Timestamp int64 `json:"timestamp"`
}{
dstIP,
dstPort,
string(j.GetJA3String()),
j.GetJA3Hash(),
srcIP,
srcPort,
j.GetSNI(),
timestamp,
})
if err != nil {
return err
}
// Write the JSON to the writer
writer.Write(js)
writer.Write([]byte("\n"))
return nil
}