Skip to content

Commit

Permalink
Refactor into modules, create "find" command
Browse files Browse the repository at this point in the history
  • Loading branch information
VaiTon committed Jun 26, 2024
1 parent 34d5b81 commit 7915cc1
Show file tree
Hide file tree
Showing 6 changed files with 354 additions and 202 deletions.
163 changes: 163 additions & 0 deletions cmd/extract/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
package main

import (
"errors"
"flag"
"fmt"
"io"
"os"
"os/signal"
"regexp"
"slices"
"strings"
"sync"
"syscall"

"github.com/google/gopacket"
"github.com/google/gopacket/pcap"
log "github.com/sirupsen/logrus"

"pcap-go/pkg/fingerprint"
)

var once sync.Once

var (
io_in *os.File
io_out *os.File
)

var (
verbose = flag.Bool("v", false, "Turn on verbose output")
veryVerbose = flag.Bool("vv", false, "Turn on very verbose output")
plotFlag = flag.Bool("p", false, "Turn on time plotting")
bpf = flag.String("f", "", "BPF filter")
regexRule = flag.String("r", "", "Regex rule")
)

func init() {
flag.Parse()

log.SetLevel(log.InfoLevel)

if *verbose {
log.SetLevel(log.DebugLevel)
log.Debug("Set log level to debug")
}

if *veryVerbose {
log.SetLevel(log.TraceLevel)
log.Debug("Set log level to trace")
}

sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)

go func() {
sig := <-sigChan
fmt.Fprint(os.Stderr, "\b\b")
log.Infof("Received signal: %s, stopping...", sig)
once.Do(epilogue)
os.Exit(0) // Exit after cleanup
}()
}

func epilogue() {
io_in.Close()
io_out.Close()

log.Infoln("Done!")
}

func handle_err(err error) {
if err != nil {
log.Fatalln(err)
}
}

func main() {
defer once.Do(epilogue)

var err error

if len(flag.Args()) < 2 {
fmt.Println("Provide an input and output file as plain args.")
flag.Usage()
os.Exit(-1)
}

file_in := flag.Arg(0)
file_out := flag.Arg(1)

io_out, err = os.Create(file_out)
handle_err(err)

if file_in == "-" {
log.Infoln("Reading from stdin")
io_in = os.Stdin
} else {
io_in, err = os.Open(file_in)
handle_err(err)
}

rd, err := pcap.OpenOfflineFile(io_in)
handle_err(err)

if *bpf != "" {
log.Infoln("Setting BPF filter to:", *bpf)
err = rd.SetBPFFilter(strings.Join(flag.Args()[2:], " "))
handle_err(err)
}

filter := func(pkt gopacket.Packet) bool {
return true
}
if *regexRule != "" {
log.Infoln("Setting regex rule to:", *regexRule)
rule, err := regexp.Compile(*regexRule)
handle_err(err)

filter = func(pkt gopacket.Packet) bool {
return rule.Match(pkt.Data())
}
}

log.Infoln("Starting analysis...")

// Loop through packets in file
packetSource := gopacket.NewPacketSource(rd, rd.LinkType())

fgCollected := make([]fingerprint.Fingerprint, 0)

for {
packet, err := packetSource.NextPacket()
if err != nil && !errors.Is(err, io.EOF) {
log.Errorln("Read error: ", err, packet.ErrorLayer())
continue
} else if err != nil {
log.Traceln("EOF reached:", err)
break
}

if !filter(packet) {
continue
}

fp, err := fingerprint.ExtractFingerprint(packet)
if err != nil {
log.Traceln(err)
continue
}

log.Debugln(packet.Metadata().Timestamp, fp)
if !slices.Contains(fgCollected, fp) {
fgCollected = append(fgCollected, fp)
}

}

log.Infoln("Collected", len(fgCollected), "fingerprints")
for _, fg := range fgCollected {
log.Infoln("-", fg)
}
}
134 changes: 134 additions & 0 deletions cmd/find/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package main

import (
"errors"
"flag"
"fmt"
"io"
"os"
"os/signal"
"sync"
"syscall"

"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/google/gopacket/pcap"
"github.com/google/gopacket/pcapgo"
log "github.com/sirupsen/logrus"

"pcap-go/pkg/fingerprint"
)

var once sync.Once

var (
io_in *os.File
io_out *os.File
)

var (
verbose = flag.Bool("v", false, "Turn on verbose output")
veryVerbose = flag.Bool("vv", false, "Turn on very verbose output")
outFile = flag.String("o", "out.pcap", "Output file")
)

func init() {
flag.Parse()

log.SetLevel(log.InfoLevel)

if *verbose {
log.SetLevel(log.DebugLevel)
log.Debug("Set log level to debug")
}

if *veryVerbose {
log.SetLevel(log.TraceLevel)
log.Debug("Set log level to trace")
}

sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)

go func() {
sig := <-sigChan
fmt.Fprint(os.Stderr, "\b\b")
log.Infof("Received signal: %s, stopping...", sig)
once.Do(epilogue)
os.Exit(0) // Exit after cleanup
}()
}

func epilogue() {
io_in.Close()
io_out.Close()

log.Infoln("Done!")
}

func handle_err(err error) {
if err != nil {
log.Fatalln(err)
}
}

func main() {
defer once.Do(epilogue)

var err error

if len(flag.Args()) < 2 {
fmt.Println("Usage: extract <input.pcap> <signature>")
os.Exit(-1)
}

file_in := flag.Arg(0)
fingerprintStr := flag.Arg(1)

io_out, err = os.Create(*outFile)
handle_err(err)

outWriter := pcapgo.NewWriter(io_out)
err = outWriter.WriteFileHeader(65536, layers.LinkTypeEthernet)
handle_err(err)

if file_in == "-" {
log.Infoln("Reading from stdin")
io_in = os.Stdin
} else {
io_in, err = os.Open(file_in)
handle_err(err)
}

rd, err := pcap.OpenOfflineFile(io_in)
handle_err(err)

log.Infoln("Starting analysis...")

// Loop through packets in file
packetSource := gopacket.NewPacketSource(rd, rd.LinkType())

for {
packet, err := packetSource.NextPacket()
if err != nil && !errors.Is(err, io.EOF) {
log.Errorln("Read error: ", err, packet.ErrorLayer())
continue
} else if err != nil {
log.Traceln("EOF reached:", err)
break
}

fp, err := fingerprint.ExtractFingerprint(packet)
if err != nil {
log.Traceln(err)
continue
}

if fp.MatchesHaiku(fingerprintStr) {
log.Infoln("Matched fingerprint:", fp)
outWriter.WritePacket(packet.Metadata().CaptureInfo, packet.Data())
}

}

}
19 changes: 0 additions & 19 deletions fingerprint.go

This file was deleted.

Binary file removed out.pcap
Binary file not shown.
57 changes: 57 additions & 0 deletions pkg/fingerprint/fingerprint.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package fingerprint

import (
"errors"
"math"

"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/yelinaung/go-haikunator"
)

type Fingerprint struct {
delta uint64
}

func (fg Fingerprint) Haiku() string { return haikunator.New(int64(fg.delta)).Haikunate() }
func (fg Fingerprint) String() string { return fg.Haiku() }
func (fg Fingerprint) MatchesHaiku(haiku string) bool { return fg.Haiku() == haiku }
func (fg Fingerprint) MatchesDelta(delta uint64) bool { return fg.delta == delta }

func ExtractFingerprint(packet gopacket.Packet) (Fingerprint, error) {
var fg Fingerprint

tcpLayer := packet.Layer(layers.LayerTypeTCP)
if tcpLayer == nil {
return fg, errors.New("no TCP layer")
}

tcpPacket, _ := tcpLayer.(*layers.TCP)
tsVal, _, err := extractTimestamps(tcpPacket.Options)

if err != nil {
return fg, err
}

delta := roundup(uint64(packet.Metadata().Timestamp.UnixMilli())-tsVal, 1000)
fg = Fingerprint{delta: delta}
return fg, nil

}

func roundup(x, n uint64) uint64 {
return uint64(math.Ceil(float64(x)/float64(n))) * n
}

func extractTimestamps(opts []layers.TCPOption) (uint64, uint64, error) {
for _, opt := range opts {
if opt.OptionType == 8 && len(opt.OptionData) >= 8 { // Check kind and sufficient data length
tsVal := uint64(opt.OptionData[0])<<24 | uint64(opt.OptionData[1])<<16 | uint64(opt.OptionData[2])<<8 | uint64(opt.OptionData[3])
tsEchoReply := uint64(opt.OptionData[4])<<24 | uint64(opt.OptionData[5])<<16 | uint64(opt.OptionData[6])<<8 | uint64(opt.OptionData[7])

return tsVal, tsEchoReply, nil
}
}

return 0, 0, errors.New("no timestamp")
}
Loading

0 comments on commit 7915cc1

Please sign in to comment.