Refactoring modules

This commit is contained in:
Giuseppe Trotta 2019-02-10 23:58:08 +01:00
commit ed652622e2
89 changed files with 186 additions and 138 deletions

View file

@ -0,0 +1,224 @@
package net_sniff
import (
"fmt"
"time"
"github.com/bettercap/bettercap/log"
"github.com/bettercap/bettercap/session"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
)
type Sniffer struct {
session.SessionModule
Stats *SnifferStats
Ctx *SnifferContext
pktSourceChan chan gopacket.Packet
fuzzActive bool
fuzzSilent bool
fuzzLayers []string
fuzzRate float64
fuzzRatio float64
}
func NewSniffer(s *session.Session) *Sniffer {
sniff := &Sniffer{
SessionModule: session.NewSessionModule("net.sniff", s),
Stats: nil,
}
sniff.AddParam(session.NewBoolParameter("net.sniff.verbose",
"false",
"If true, every captured and parsed packet will be sent to the events.stream for displaying, otherwise only the ones parsed at the application layer (sni, http, etc)."))
sniff.AddParam(session.NewBoolParameter("net.sniff.local",
"false",
"If true it will consider packets from/to this computer, otherwise it will skip them."))
sniff.AddParam(session.NewStringParameter("net.sniff.filter",
"not arp",
"",
"BPF filter for the sniffer."))
sniff.AddParam(session.NewStringParameter("net.sniff.regexp",
"",
"",
"If set, only packets matching this regular expression will be considered."))
sniff.AddParam(session.NewStringParameter("net.sniff.output",
"",
"",
"If set, the sniffer will write captured packets to this file."))
sniff.AddParam(session.NewStringParameter("net.sniff.source",
"",
"",
"If set, the sniffer will read from this pcap file instead of the current interface."))
sniff.AddHandler(session.NewModuleHandler("net.sniff stats", "",
"Print sniffer session configuration and statistics.",
func(args []string) error {
if sniff.Stats == nil {
return fmt.Errorf("No stats yet.")
}
sniff.Ctx.Log(sniff.Session)
return sniff.Stats.Print()
}))
sniff.AddHandler(session.NewModuleHandler("net.sniff on", "",
"Start network sniffer in background.",
func(args []string) error {
return sniff.Start()
}))
sniff.AddHandler(session.NewModuleHandler("net.sniff off", "",
"Stop network sniffer in background.",
func(args []string) error {
return sniff.Stop()
}))
sniff.AddHandler(session.NewModuleHandler("net.fuzz on", "",
"Enable fuzzing for every sniffed packet containing the sapecified layers.",
func(args []string) error {
return sniff.StartFuzzing()
}))
sniff.AddHandler(session.NewModuleHandler("net.fuzz off", "",
"Disable fuzzing",
func(args []string) error {
return sniff.StopFuzzing()
}))
sniff.AddParam(session.NewStringParameter("net.fuzz.layers",
"Payload",
"",
"Types of layer to fuzz."))
sniff.AddParam(session.NewDecimalParameter("net.fuzz.rate",
"1.0",
"Rate in the [0.0,1.0] interval of packets to fuzz."))
sniff.AddParam(session.NewDecimalParameter("net.fuzz.ratio",
"0.4",
"Rate in the [0.0,1.0] interval of bytes to fuzz for each packet."))
sniff.AddParam(session.NewBoolParameter("net.fuzz.silent",
"false",
"If true it will not report fuzzed packets."))
return sniff
}
func (s Sniffer) Name() string {
return "net.sniff"
}
func (s Sniffer) Description() string {
return "Sniff packets from the network."
}
func (s Sniffer) Author() string {
return "Simone Margaritelli <evilsocket@protonmail.com>"
}
func (s Sniffer) isLocalPacket(packet gopacket.Packet) bool {
ipl := packet.Layer(layers.LayerTypeIPv4)
if ipl != nil {
ip, _ := ipl.(*layers.IPv4)
if ip.SrcIP.Equal(s.Session.Interface.IP) || ip.DstIP.Equal(s.Session.Interface.IP) {
return true
}
}
return false
}
func (s *Sniffer) onPacketMatched(pkt gopacket.Packet) {
if mainParser(pkt, s.Ctx.Verbose) {
s.Stats.NumDumped++
}
}
func (s *Sniffer) Configure() error {
var err error
if s.Running() {
return session.ErrAlreadyStarted
} else if err, s.Ctx = s.GetContext(); err != nil {
if s.Ctx != nil {
s.Ctx.Close()
s.Ctx = nil
}
return err
}
return nil
}
func (s *Sniffer) Start() error {
if err := s.Configure(); err != nil {
return err
}
return s.SetRunning(true, func() {
s.Stats = NewSnifferStats()
src := gopacket.NewPacketSource(s.Ctx.Handle, s.Ctx.Handle.LinkType())
s.pktSourceChan = src.Packets()
for packet := range s.pktSourceChan {
if !s.Running() {
log.Debug("end pkt loop (pkt=%v filter='%s')", packet, s.Ctx.Filter)
break
}
now := time.Now()
if s.Stats.FirstPacket.IsZero() {
s.Stats.FirstPacket = now
}
s.Stats.LastPacket = now
isLocal := s.isLocalPacket(packet)
if isLocal {
s.Stats.NumLocal++
}
if s.fuzzActive {
s.doFuzzing(packet)
}
if s.Ctx.DumpLocal || !isLocal {
data := packet.Data()
if s.Ctx.Compiled == nil || s.Ctx.Compiled.Match(data) {
s.Stats.NumMatched++
s.onPacketMatched(packet)
if s.Ctx.OutputWriter != nil {
s.Ctx.OutputWriter.WritePacket(packet.Metadata().CaptureInfo, data)
s.Stats.NumWrote++
}
}
}
}
s.pktSourceChan = nil
})
}
func (s *Sniffer) Stop() error {
return s.SetRunning(false, func() {
log.Debug("stopping sniffer")
if s.pktSourceChan != nil {
log.Debug("sending nil")
s.pktSourceChan <- nil
log.Debug("nil sent")
}
log.Debug("closing ctx")
s.Ctx.Close()
log.Debug("ctx closed")
})
}

View file

@ -0,0 +1,138 @@
package net_sniff
import (
"os"
"regexp"
"time"
"github.com/bettercap/bettercap/log"
"github.com/bettercap/bettercap/session"
"github.com/google/gopacket/pcap"
"github.com/google/gopacket/pcapgo"
"github.com/evilsocket/islazy/tui"
)
type SnifferContext struct {
Handle *pcap.Handle
Source string
DumpLocal bool
Verbose bool
Filter string
Expression string
Compiled *regexp.Regexp
Output string
OutputFile *os.File
OutputWriter *pcapgo.Writer
}
func (s *Sniffer) GetContext() (error, *SnifferContext) {
var err error
ctx := NewSnifferContext()
if err, ctx.Source = s.StringParam("net.sniff.source"); err != nil {
return err, ctx
}
if ctx.Source == "" {
/*
* We don't want to pcap.BlockForever otherwise pcap_close(handle)
* could hang waiting for a timeout to expire ...
*/
readTimeout := 500 * time.Millisecond
if ctx.Handle, err = pcap.OpenLive(s.Session.Interface.Name(), 65536, true, readTimeout); err != nil {
return err, ctx
}
} else {
if ctx.Handle, err = pcap.OpenOffline(ctx.Source); err != nil {
return err, ctx
}
}
if err, ctx.Verbose = s.BoolParam("net.sniff.verbose"); err != nil {
return err, ctx
}
if err, ctx.DumpLocal = s.BoolParam("net.sniff.local"); err != nil {
return err, ctx
}
if err, ctx.Filter = s.StringParam("net.sniff.filter"); err != nil {
return err, ctx
} else if ctx.Filter != "" {
err = ctx.Handle.SetBPFFilter(ctx.Filter)
if err != nil {
return err, ctx
}
}
if err, ctx.Expression = s.StringParam("net.sniff.regexp"); err != nil {
return err, ctx
} else if ctx.Expression != "" {
if ctx.Compiled, err = regexp.Compile(ctx.Expression); err != nil {
return err, ctx
}
}
if err, ctx.Output = s.StringParam("net.sniff.output"); err != nil {
return err, ctx
} else if ctx.Output != "" {
if ctx.OutputFile, err = os.Create(ctx.Output); err != nil {
return err, ctx
}
ctx.OutputWriter = pcapgo.NewWriter(ctx.OutputFile)
ctx.OutputWriter.WriteFileHeader(65536, ctx.Handle.LinkType())
}
return nil, ctx
}
func NewSnifferContext() *SnifferContext {
return &SnifferContext{
Handle: nil,
DumpLocal: false,
Verbose: false,
Filter: "",
Expression: "",
Compiled: nil,
Output: "",
OutputFile: nil,
OutputWriter: nil,
}
}
var (
no = tui.Red("no")
yes = tui.Green("yes")
yn = map[bool]string{
true: yes,
false: no,
}
)
func (c *SnifferContext) Log(sess *session.Session) {
log.Info("Skip local packets : %s", yn[c.DumpLocal])
log.Info("Verbose : %s", yn[c.Verbose])
log.Info("BPF Filter : '%s'", tui.Yellow(c.Filter))
log.Info("Regular expression : '%s'", tui.Yellow(c.Expression))
log.Info("File output : '%s'", tui.Yellow(c.Output))
}
func (c *SnifferContext) Close() {
if c.Handle != nil {
log.Debug("closing handle")
c.Handle.Close()
log.Debug("handle closed")
c.Handle = nil
}
if c.OutputFile != nil {
log.Debug("closing output")
c.OutputFile.Close()
log.Debug("output closed")
c.OutputFile = nil
}
}

View file

@ -0,0 +1,66 @@
package net_sniff
import (
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"strings"
"github.com/evilsocket/islazy/tui"
)
func dnsParser(ip *layers.IPv4, pkt gopacket.Packet, udp *layers.UDP) bool {
dns, parsed := pkt.Layer(layers.LayerTypeDNS).(*layers.DNS)
if !parsed {
return false
}
if dns.OpCode != layers.DNSOpCodeQuery {
return false
}
m := make(map[string][]string)
answers := [][]layers.DNSResourceRecord{
dns.Answers,
dns.Authorities,
dns.Additionals,
}
for _, list := range answers {
for _, a := range list {
if a.IP == nil {
continue
}
hostname := string(a.Name)
if _, found := m[hostname]; !found {
m[hostname] = make([]string, 0)
}
m[hostname] = append(m[hostname], vIP(a.IP))
}
}
if len(m) == 0 && dns.ResponseCode != layers.DNSResponseCodeNoErr {
for _, a := range dns.Questions {
m[string(a.Name)] = []string{tui.Red(dns.ResponseCode.String())}
}
}
for hostname, ips := range m {
NewSnifferEvent(
pkt.Metadata().Timestamp,
"dns",
ip.SrcIP.String(),
ip.DstIP.String(),
nil,
"%s %s > %s : %s is %s",
tui.Wrap(tui.BACKDARKGRAY+tui.FOREWHITE, "dns"),
vIP(ip.SrcIP),
vIP(ip.DstIP),
tui.Yellow(hostname),
tui.Dim(strings.Join(ips, ", ")),
).Push()
}
return true
}

View file

@ -0,0 +1,26 @@
package net_sniff
import (
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
)
func onDOT11(radiotap *layers.RadioTap, dot11 *layers.Dot11, pkt gopacket.Packet, verbose bool) {
NewSnifferEvent(
pkt.Metadata().Timestamp,
"802.11",
"-",
"-",
len(pkt.Data()),
"%s %s proto=%d a1=%s a2=%s a3=%s a4=%s seqn=%d frag=%d",
dot11.Type,
dot11.Flags,
dot11.Proto,
dot11.Address1,
dot11.Address2,
dot11.Address3,
dot11.Address4,
dot11.SequenceNumber,
dot11.FragmentNumber,
).Push()
}

View file

@ -0,0 +1,35 @@
package net_sniff
import (
"fmt"
"time"
"github.com/bettercap/bettercap/session"
)
type SniffData map[string]interface{}
type SnifferEvent struct {
PacketTime time.Time `json:"time"`
Protocol string `json:"protocol"`
Source string `json:"from"`
Destination string `json:"to"`
Message string `json:"message"`
Data interface{} `json:"data"`
}
func NewSnifferEvent(t time.Time, proto string, src string, dst string, data interface{}, format string, args ...interface{}) SnifferEvent {
return SnifferEvent{
PacketTime: t,
Protocol: proto,
Source: src,
Destination: dst,
Message: fmt.Sprintf(format, args...),
Data: data,
}
}
func (e SnifferEvent) Push() {
session.I.Events.Add("net.sniff."+e.Protocol, e)
session.I.Refresh()
}

View file

@ -0,0 +1,42 @@
package net_sniff
import (
"regexp"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/evilsocket/islazy/str"
"github.com/evilsocket/islazy/tui"
)
var (
ftpRe = regexp.MustCompile(`^(USER|PASS) (.+)[\n\r]+$`)
)
func ftpParser(ip *layers.IPv4, pkt gopacket.Packet, tcp *layers.TCP) bool {
data := string(tcp.Payload)
if matches := ftpRe.FindAllStringSubmatch(data, -1); matches != nil {
what := str.Trim(matches[0][1])
cred := str.Trim(matches[0][2])
NewSnifferEvent(
pkt.Metadata().Timestamp,
"ftp",
ip.SrcIP.String(),
ip.DstIP.String(),
nil,
"%s %s > %s:%s - %s %s",
tui.Wrap(tui.BACKYELLOW+tui.FOREWHITE, "ftp"),
vIP(ip.SrcIP),
vIP(ip.DstIP),
vPort(tcp.DstPort),
tui.Bold(what),
tui.Yellow(cred),
).Push()
return true
}
return false
}

View file

@ -0,0 +1,121 @@
package net_sniff
import (
"math/rand"
"strings"
"github.com/google/gopacket"
"github.com/bettercap/bettercap/log"
"github.com/evilsocket/islazy/str"
"github.com/evilsocket/islazy/tui"
)
var mutators = []func(byte) byte{
func(b byte) byte {
return byte(rand.Intn(256) & 0xff)
},
func(b byte) byte {
return byte(b << uint(rand.Intn(9)))
},
func(b byte) byte {
return byte(b >> uint(rand.Intn(9)))
},
}
func (s *Sniffer) fuzz(data []byte) int {
changes := 0
for off, b := range data {
if rand.Float64() > s.fuzzRatio {
continue
}
data[off] = mutators[rand.Intn(len(mutators))](b)
changes++
}
return changes
}
func (s *Sniffer) doFuzzing(pkt gopacket.Packet) {
if rand.Float64() > s.fuzzRate {
return
}
layersChanged := 0
bytesChanged := 0
for _, fuzzLayerType := range s.fuzzLayers {
for _, layer := range pkt.Layers() {
if layer.LayerType().String() == fuzzLayerType {
fuzzData := layer.LayerContents()
changes := s.fuzz(fuzzData)
if changes > 0 {
layersChanged++
bytesChanged += changes
}
}
}
}
if bytesChanged > 0 {
logFn := log.Info
if s.fuzzSilent {
logFn = log.Debug
}
logFn("[%s] changed %d bytes in %d layers.", tui.Green("net.fuzz"), bytesChanged, layersChanged)
if err := s.Session.Queue.Send(pkt.Data()); err != nil {
log.Error("error sending fuzzed packet: %s", err)
}
}
}
func (s *Sniffer) configureFuzzing() (err error) {
layers := ""
if err, layers = s.StringParam("net.fuzz.layers"); err != nil {
return
} else {
s.fuzzLayers = str.Comma(layers)
}
if err, s.fuzzRate = s.DecParam("net.fuzz.rate"); err != nil {
return
}
if err, s.fuzzRatio = s.DecParam("net.fuzz.ratio"); err != nil {
return
}
if err, s.fuzzSilent = s.BoolParam("net.fuzz.silent"); err != nil {
return
}
return
}
func (s *Sniffer) StartFuzzing() error {
if s.fuzzActive {
return nil
}
if err := s.configureFuzzing(); err != nil {
return err
} else if !s.Running() {
if err := s.Start(); err != nil {
return err
}
}
s.fuzzActive = true
log.Info("[%s] active on layer types %s (rate:%f ratio:%f)", tui.Green("net.fuzz"), strings.Join(s.fuzzLayers, ","), s.fuzzRate, s.fuzzRatio)
return nil
}
func (s *Sniffer) StopFuzzing() error {
s.fuzzActive = false
return nil
}

View file

@ -0,0 +1,159 @@
package net_sniff
import (
"bufio"
"bytes"
"compress/gzip"
"io/ioutil"
"net/http"
"strings"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/dustin/go-humanize"
"github.com/evilsocket/islazy/tui"
)
type HTTPRequest struct {
Method string `json:"method"`
Proto string `json:"proto"`
Host string `json:"host"`
URL string `json:"url:"`
Headers http.Header `json:"headers"`
ContentType string `json:"content_type"`
Body []byte `json:"body"`
}
func (r HTTPRequest) IsType(ctype string) bool {
return strings.Contains(r.ContentType, ctype)
}
type HTTPResponse struct {
Protocol string `json:"protocol"`
Status string `json:"status"`
StatusCode int `json:"status_code"`
Headers http.Header `json:"headers"`
Body []byte `json:"body"`
ContentLength int64 `json:"content_length"`
ContentType string `json:"content_type"`
TransferEncoding []string `json:"transfer_encoding"`
}
func (r HTTPResponse) IsType(ctype string) bool {
return strings.Contains(r.ContentType, ctype)
}
func toSerializableRequest(req *http.Request) HTTPRequest {
body := []byte(nil)
ctype := "?"
if req.Body != nil {
body, _ = ioutil.ReadAll(req.Body)
}
for name, values := range req.Header {
if strings.ToLower(name) == "content-type" {
for _, value := range values {
ctype = value
}
}
}
return HTTPRequest{
Method: req.Method,
Proto: req.Proto,
Host: req.Host,
URL: req.URL.String(),
Headers: req.Header,
ContentType: ctype,
Body: body,
}
}
func toSerializableResponse(res *http.Response) HTTPResponse {
body := []byte(nil)
ctype := "?"
cenc := ""
for name, values := range res.Header {
name = strings.ToLower(name)
if name == "content-type" {
for _, value := range values {
ctype = value
}
} else if name == "content-encoding" {
for _, value := range values {
cenc = value
}
}
}
if res.Body != nil {
body, _ = ioutil.ReadAll(res.Body)
}
// attempt decompression, but since this has been parsed by just
// a tcp packet, it will probably fail
if body != nil && strings.Contains(cenc, "gzip") {
buffer := bytes.NewBuffer(body)
uncompressed := bytes.Buffer{}
if reader, err := gzip.NewReader(buffer); err == nil {
if _, err = uncompressed.ReadFrom(reader); err == nil {
body = uncompressed.Bytes()
}
}
}
return HTTPResponse{
Protocol: res.Proto,
Status: res.Status,
StatusCode: res.StatusCode,
Headers: res.Header,
Body: body,
ContentLength: res.ContentLength,
ContentType: ctype,
TransferEncoding: res.TransferEncoding,
}
}
func httpParser(ip *layers.IPv4, pkt gopacket.Packet, tcp *layers.TCP) bool {
data := tcp.Payload
if req, err := http.ReadRequest(bufio.NewReader(bytes.NewReader(data))); err == nil {
NewSnifferEvent(
pkt.Metadata().Timestamp,
"http.request",
ip.SrcIP.String(),
req.Host,
toSerializableRequest(req),
"%s %s %s %s%s",
tui.Wrap(tui.BACKRED+tui.FOREBLACK, "http"),
vIP(ip.SrcIP),
tui.Wrap(tui.BACKLIGHTBLUE+tui.FOREBLACK, req.Method),
tui.Yellow(req.Host),
vURL(req.URL.String()),
).Push()
return true
} else if res, err := http.ReadResponse(bufio.NewReader(bytes.NewReader(data)), nil); err == nil {
sres := toSerializableResponse(res)
NewSnifferEvent(
pkt.Metadata().Timestamp,
"http.response",
ip.SrcIP.String(),
ip.DstIP.String(),
sres,
"%s %s:%d %s -> %s (%s %s)",
tui.Wrap(tui.BACKRED+tui.FOREBLACK, "http"),
vIP(ip.SrcIP),
tcp.SrcPort,
tui.Bold(res.Status),
vIP(ip.DstIP),
tui.Dim(humanize.Bytes(uint64(len(sres.Body)))),
tui.Yellow(sres.ContentType),
).Push()
return true
}
return false
}

View file

@ -0,0 +1,43 @@
package net_sniff
import (
"encoding/asn1"
"github.com/bettercap/bettercap/packets"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/evilsocket/islazy/tui"
)
func krb5Parser(ip *layers.IPv4, pkt gopacket.Packet, udp *layers.UDP) bool {
if udp.DstPort != 88 {
return false
}
var req packets.Krb5Request
_, err := asn1.UnmarshalWithParams(udp.Payload, &req, packets.Krb5AsReqParam)
if err != nil {
return false
}
if s, err := req.String(); err == nil {
NewSnifferEvent(
pkt.Metadata().Timestamp,
"krb5",
ip.SrcIP.String(),
ip.DstIP.String(),
nil,
"%s %s -> %s : %s",
tui.Wrap(tui.BACKRED+tui.FOREBLACK, "krb-as-req"),
vIP(ip.SrcIP),
vIP(ip.DstIP),
s,
).Push()
return true
}
return false
}

View file

@ -0,0 +1,65 @@
package net_sniff
import (
"strings"
"github.com/bettercap/bettercap/packets"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/evilsocket/islazy/tui"
)
func mdnsParser(ip *layers.IPv4, pkt gopacket.Packet, udp *layers.UDP) bool {
if udp.SrcPort == packets.MDNSPort && udp.DstPort == packets.MDNSPort {
dns := layers.DNS{}
if err := dns.DecodeFromBytes(udp.Payload, gopacket.NilDecodeFeedback); err == nil && dns.OpCode == layers.DNSOpCodeQuery {
for _, q := range dns.Questions {
NewSnifferEvent(
pkt.Metadata().Timestamp,
"mdns",
ip.SrcIP.String(),
ip.DstIP.String(),
nil,
"%s %s : %s query for %s",
tui.Wrap(tui.BACKDARKGRAY+tui.FOREWHITE, "mdns"),
vIP(ip.SrcIP),
tui.Dim(q.Type.String()),
tui.Yellow(string(q.Name)),
).Push()
}
m := make(map[string][]string)
answers := append(dns.Answers, dns.Additionals...)
answers = append(answers, dns.Authorities...)
for _, answer := range answers {
if answer.Type == layers.DNSTypeA || answer.Type == layers.DNSTypeAAAA {
hostname := string(answer.Name)
if _, found := m[hostname]; !found {
m[hostname] = make([]string, 0)
}
m[hostname] = append(m[hostname], answer.IP.String())
}
}
for hostname, ips := range m {
NewSnifferEvent(
pkt.Metadata().Timestamp,
"mdns",
ip.SrcIP.String(),
ip.DstIP.String(),
nil,
"%s %s : %s is %s",
tui.Wrap(tui.BACKDARKGRAY+tui.FOREWHITE, "mdns"),
vIP(ip.SrcIP),
tui.Yellow(hostname),
tui.Dim(strings.Join(ips, ", ")),
).Push()
}
return true
}
}
return false
}

View file

@ -0,0 +1,67 @@
package net_sniff
import (
"regexp"
"strings"
"github.com/bettercap/bettercap/packets"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/evilsocket/islazy/tui"
)
var (
ntlmRe = regexp.MustCompile("(WWW-|Proxy-|)(Authenticate|Authorization): (NTLM|Negotiate)")
challRe = regexp.MustCompile("(WWW-|Proxy-|)(Authenticate): (NTLM|Negotiate)")
respRe = regexp.MustCompile("(WWW-|Proxy-|)(Authorization): (NTLM|Negotiate)")
ntlm = packets.NewNTLMState()
)
func isNtlm(s string) bool {
return ntlmRe.FindString(s) != ""
}
func isChallenge(s string) bool {
return challRe.FindString(s) != ""
}
func isResponse(s string) bool {
return respRe.FindString(s) != ""
}
func ntlmParser(ip *layers.IPv4, pkt gopacket.Packet, tcp *layers.TCP) bool {
data := tcp.Payload
ok := false
for _, line := range strings.Split(string(data), "\r\n") {
if isNtlm(line) {
tokens := strings.Split(line, " ")
if len(tokens) != 3 {
continue
}
if isChallenge(line) {
ok = true
ntlm.AddServerResponse(tcp.Ack, tokens[2])
} else if isResponse(line) {
ok = true
ntlm.AddClientResponse(tcp.Seq, tokens[2], func(data packets.NTLMChallengeResponseParsed) {
NewSnifferEvent(
pkt.Metadata().Timestamp,
"ntlm.response",
ip.SrcIP.String(),
ip.DstIP.String(),
nil,
"%s %s > %s | %s",
tui.Wrap(tui.BACKDARKGRAY+tui.FOREWHITE, "ntlm.response"),
vIP(ip.SrcIP),
vIP(ip.DstIP),
data.LcString(),
).Push()
})
}
}
}
return ok
}

View file

@ -0,0 +1,67 @@
package net_sniff
import (
"fmt"
"github.com/bettercap/bettercap/log"
"github.com/bettercap/bettercap/packets"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/evilsocket/islazy/tui"
)
func onUNK(ip *layers.IPv4, pkt gopacket.Packet, verbose bool) {
if verbose {
NewSnifferEvent(
pkt.Metadata().Timestamp,
pkt.TransportLayer().LayerType().String(),
vIP(ip.SrcIP),
vIP(ip.DstIP),
SniffData{
"Size": len(ip.Payload),
},
"%s %s > %s %s",
tui.Wrap(tui.BACKDARKGRAY+tui.FOREWHITE, pkt.TransportLayer().LayerType().String()),
vIP(ip.SrcIP),
vIP(ip.DstIP),
tui.Dim(fmt.Sprintf("%d bytes", len(ip.Payload))),
).Push()
}
}
func mainParser(pkt gopacket.Packet, verbose bool) bool {
// simple networking sniffing mode?
nlayer := pkt.NetworkLayer()
if nlayer != nil {
if nlayer.LayerType() != layers.LayerTypeIPv4 {
log.Debug("Unexpected layer type %s, skipping packet.", nlayer.LayerType())
log.Debug("%s", pkt.Dump())
return false
}
ip := nlayer.(*layers.IPv4)
tlayer := pkt.TransportLayer()
if tlayer == nil {
log.Debug("Missing transport layer skipping packet.")
log.Debug("%s", pkt.Dump())
return false
}
if tlayer.LayerType() == layers.LayerTypeTCP {
onTCP(ip, pkt, verbose)
} else if tlayer.LayerType() == layers.LayerTypeUDP {
onUDP(ip, pkt, verbose)
} else {
onUNK(ip, pkt, verbose)
}
return true
} else if ok, radiotap, dot11 := packets.Dot11Parse(pkt); ok {
// are we sniffing in monitor mode?
onDOT11(radiotap, dot11, pkt, verbose)
return true
}
return false
}

View file

@ -0,0 +1,48 @@
package net_sniff
import (
"fmt"
"regexp"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/evilsocket/islazy/tui"
)
// poor man's TLS Client Hello with SNI extension parser :P
var sniRe = regexp.MustCompile("\x00\x00.{4}\x00.{2}([a-z0-9]+([\\-\\.]{1}[a-z0-9]+)*\\.[a-z]{2,6})\x00")
func sniParser(ip *layers.IPv4, pkt gopacket.Packet, tcp *layers.TCP) bool {
data := tcp.Payload
dataSize := len(data)
if dataSize < 2 || data[0] != 0x16 || data[1] != 0x03 {
return false
}
m := sniRe.FindSubmatch(data)
if len(m) < 2 {
return false
}
domain := string(m[1])
if tcp.DstPort != 443 {
domain = fmt.Sprintf("%s:%d", domain, tcp.DstPort)
}
NewSnifferEvent(
pkt.Metadata().Timestamp,
"https",
ip.SrcIP.String(),
domain,
nil,
"%s %s > %s",
tui.Wrap(tui.BACKYELLOW+tui.FOREWHITE, "sni"),
vIP(ip.SrcIP),
tui.Yellow("https://"+domain),
).Push()
return true
}

View file

@ -0,0 +1,50 @@
package net_sniff
import (
"github.com/bettercap/bettercap/log"
"time"
)
type SnifferStats struct {
NumLocal uint64
NumMatched uint64
NumDumped uint64
NumWrote uint64
Started time.Time
FirstPacket time.Time
LastPacket time.Time
}
func NewSnifferStats() *SnifferStats {
return &SnifferStats{
NumLocal: 0,
NumMatched: 0,
NumDumped: 0,
NumWrote: 0,
Started: time.Now(),
FirstPacket: time.Time{},
LastPacket: time.Time{},
}
}
func (s *SnifferStats) Print() error {
first := "never"
last := "never"
if !s.FirstPacket.IsZero() {
first = s.FirstPacket.String()
}
if !s.LastPacket.IsZero() {
last = s.LastPacket.String()
}
log.Info("Sniffer Started : %s", s.Started)
log.Info("First Packet Seen : %s", first)
log.Info("Last Packet Seen : %s", last)
log.Info("Local Packets : %d", s.NumLocal)
log.Info("Matched Packets : %d", s.NumMatched)
log.Info("Dumped Packets : %d", s.NumDumped)
log.Info("Wrote Packets : %d", s.NumWrote)
return nil
}

View file

@ -0,0 +1,46 @@
package net_sniff
import (
"fmt"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/evilsocket/islazy/tui"
)
var tcpParsers = []func(*layers.IPv4, gopacket.Packet, *layers.TCP) bool{
sniParser,
ntlmParser,
httpParser,
ftpParser,
teamViewerParser,
}
func onTCP(ip *layers.IPv4, pkt gopacket.Packet, verbose bool) {
tcp := pkt.Layer(layers.LayerTypeTCP).(*layers.TCP)
for _, parser := range tcpParsers {
if parser(ip, pkt, tcp) {
return
}
}
if verbose {
NewSnifferEvent(
pkt.Metadata().Timestamp,
"tcp",
fmt.Sprintf("%s:%s", ip.SrcIP, vPort(tcp.SrcPort)),
fmt.Sprintf("%s:%s", ip.DstIP, vPort(tcp.DstPort)),
SniffData{
"Size": len(ip.Payload),
},
"%s %s:%s > %s:%s %s",
tui.Wrap(tui.BACKLIGHTBLUE+tui.FOREBLACK, "tcp"),
vIP(ip.SrcIP),
vPort(tcp.SrcPort),
vIP(ip.DstIP),
vPort(tcp.DstPort),
tui.Dim(fmt.Sprintf("%d bytes", len(ip.Payload))),
).Push()
}
}

View file

@ -0,0 +1,32 @@
package net_sniff
import (
"github.com/bettercap/bettercap/packets"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/evilsocket/islazy/tui"
)
func teamViewerParser(ip *layers.IPv4, pkt gopacket.Packet, tcp *layers.TCP) bool {
if tcp.SrcPort == packets.TeamViewerPort || tcp.DstPort == packets.TeamViewerPort {
if tv := packets.ParseTeamViewer(tcp.Payload); tv != nil {
NewSnifferEvent(
pkt.Metadata().Timestamp,
"teamviewer",
ip.SrcIP.String(),
ip.DstIP.String(),
nil,
"%s %s %s > %s",
tui.Wrap(tui.BACKYELLOW+tui.FOREWHITE, "teamviewer"),
vIP(ip.SrcIP),
tui.Yellow(tv.Command),
vIP(ip.DstIP),
).Push()
return true
}
}
return false
}

View file

@ -0,0 +1,45 @@
package net_sniff
import (
"fmt"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/evilsocket/islazy/tui"
)
var udpParsers = []func(*layers.IPv4, gopacket.Packet, *layers.UDP) bool{
dnsParser,
mdnsParser,
krb5Parser,
upnpParser,
}
func onUDP(ip *layers.IPv4, pkt gopacket.Packet, verbose bool) {
udp := pkt.Layer(layers.LayerTypeUDP).(*layers.UDP)
for _, parser := range udpParsers {
if parser(ip, pkt, udp) {
return
}
}
if verbose {
NewSnifferEvent(
pkt.Metadata().Timestamp,
"udp",
fmt.Sprintf("%s:%s", ip.SrcIP, vPort(udp.SrcPort)),
fmt.Sprintf("%s:%s", ip.DstIP, vPort(udp.DstPort)),
SniffData{
"Size": len(ip.Payload),
},
"%s %s:%s > %s:%s %s",
tui.Wrap(tui.BACKDARKGRAY+tui.FOREWHITE, "udp"),
vIP(ip.SrcIP),
vPort(udp.SrcPort),
vIP(ip.DstIP),
vPort(udp.DstPort),
tui.Dim(fmt.Sprintf("%d bytes", len(ip.Payload))),
).Push()
}
}

View file

@ -0,0 +1,39 @@
package net_sniff
import (
"fmt"
"github.com/bettercap/bettercap/packets"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/evilsocket/islazy/str"
"github.com/evilsocket/islazy/tui"
)
func upnpParser(ip *layers.IPv4, pkt gopacket.Packet, udp *layers.UDP) bool {
if data := packets.UPNPGetMeta(pkt); data != nil && len(data) > 0 {
s := ""
for name, value := range data {
s += fmt.Sprintf("%s:%s ", tui.Blue(name), tui.Yellow(value))
}
NewSnifferEvent(
pkt.Metadata().Timestamp,
"upnp",
ip.SrcIP.String(),
ip.DstIP.String(),
nil,
"%s %s -> %s : %s",
tui.Wrap(tui.BACKRED+tui.FOREBLACK, "upnp"),
vIP(ip.SrcIP),
vIP(ip.DstIP),
str.Trim(s),
).Push()
return true
}
return false
}

View file

@ -0,0 +1,54 @@
package net_sniff
import (
"fmt"
"net"
"github.com/bettercap/bettercap/session"
"github.com/evilsocket/islazy/tui"
"github.com/google/gopacket/layers"
)
func vIP(ip net.IP) string {
if session.I.Interface.IP.Equal(ip) {
return tui.Dim("local")
} else if session.I.Gateway.IP.Equal(ip) {
return "gateway"
}
address := ip.String()
host := session.I.Lan.GetByIp(address)
if host != nil {
if host.Hostname != "" {
return host.Hostname
}
}
return address
}
func vPort(p interface{}) string {
sp := fmt.Sprintf("%d", p)
if tcp, ok := p.(layers.TCPPort); ok {
if name, found := layers.TCPPortNames[tcp]; found {
sp = tui.Yellow(name)
}
} else if udp, ok := p.(layers.UDPPort); ok {
if name, found := layers.UDPPortNames[udp]; found {
sp = tui.Yellow(name)
}
}
return sp
}
var maxUrlSize = 80
func vURL(u string) string {
ul := len(u)
if ul > maxUrlSize {
u = fmt.Sprintf("%s...", u[0:maxUrlSize-3])
}
return u
}