This commit is contained in:
Neale Pickett 2018-07-24 23:53:06 +00:00
parent a860c7f069
commit d06ca08456
5 changed files with 84 additions and 84 deletions

View File

@ -14,8 +14,8 @@ package gapstring
import ( import (
"bytes" "bytes"
"fmt"
"encoding/binary" "encoding/binary"
"fmt"
"strings" "strings"
"unicode/utf16" "unicode/utf16"
) )
@ -25,7 +25,7 @@ import (
// XXX: I'll have to fix it later; it doesn't matter much for performance // XXX: I'll have to fix it later; it doesn't matter much for performance
type chunk struct { type chunk struct {
gap int // This takes precedence over data gap int // This takes precedence over data
data []byte data []byte
} }
@ -135,11 +135,11 @@ func (g GapString) AppendString(s string) GapString {
// if g were a string or byte slice. // if g were a string or byte slice.
func (g GapString) Slice(start, end int) GapString { func (g GapString) Slice(start, end int) GapString {
outchunks := make([]chunk, 0, len(g.chunks)) outchunks := make([]chunk, 0, len(g.chunks))
if end > g.Length() { if end > g.Length() {
panic("runtime error: slice bounds out of range") panic("runtime error: slice bounds out of range")
} }
for _, c := range g.chunks { for _, c := range g.chunks {
chunklen := c.length() chunklen := c.length()
@ -160,12 +160,12 @@ func (g GapString) Slice(start, end int) GapString {
} }
start = 0 start = 0
end -= cend end -= cend
if end == 0 { if end == 0 {
break break
} }
} }
return GapString{chunks: outchunks} return GapString{chunks: outchunks}
} }
@ -177,7 +177,7 @@ func (g GapString) Xor(mask ...byte) GapString {
pos := 0 pos := 0
for _, c := range g.chunks { for _, c := range g.chunks {
ret = ret.AppendGap(c.gap) ret = ret.AppendGap(c.gap)
out := make([]byte, len(c.data)) out := make([]byte, len(c.data))
for i, b := range c.data { for i, b := range c.data {
m := mask[(pos+i)%len(mask)] m := mask[(pos+i)%len(mask)]
@ -198,7 +198,7 @@ func (g GapString) Bytes(fill ...byte) []byte {
// Fill in gap // Fill in gap
if len(fill) > 0 { if len(fill) > 0 {
for i := 0; i < c.gap; i += 1 { for i := 0; i < c.gap; i += 1 {
ret[pos] = fill[pos % len(fill)] ret[pos] = fill[pos%len(fill)]
pos += 1 pos += 1
} }
} }
@ -243,9 +243,9 @@ func (g GapString) HexString() string {
// There's probably a faster way to do this. Do we care? // There's probably a faster way to do this. Do we care?
fmt.Fprintf(out, "%02x", c) fmt.Fprintf(out, "%02x", c)
} }
if i + 1 < glen { if i+1 < glen {
out.WriteRune(' ') out.WriteRune(' ')
if i % 8 == 7 { if i%8 == 7 {
out.WriteRune(' ') out.WriteRune(' ')
} }
} }
@ -254,7 +254,7 @@ func (g GapString) HexString() string {
} }
var fluffych = []rune{ var fluffych = []rune{
'·', '☺', '☻', '♥', '♦', '♣', '♠', '•', '◘', '○', '◙', '♂', '♀', '♪', '♫', '☼', '·', '☺', '☻', '♥', '♦', '♣', '♠', '•', '◘', '○', '◙', '♂', '♀', '♪', '♫', '☼',
'►', '◄', '↕', '‼', '¶', '§', '▬', '↨', '↑', '↓', '→', '←', '∟', '↔', '▲', '▼', '►', '◄', '↕', '‼', '¶', '§', '▬', '↨', '↑', '↓', '→', '←', '∟', '↔', '▲', '▼',
' ', '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', ' ', '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?',
@ -298,7 +298,7 @@ func (g GapString) Hexdump() string {
glen := g.Length() glen := g.Length()
pos := 0 pos := 0
prev := []byte{} prev := []byte{}
for ; pos < glen; { for pos < glen {
// Check for repeats // Check for repeats
end := pos + 16 end := pos + 16
if end > glen { if end > glen {
@ -307,7 +307,7 @@ func (g GapString) Hexdump() string {
cur := g.Slice(pos, end) cur := g.Slice(pos, end)
curBytes := cur.Bytes() curBytes := cur.Bytes()
if 0 == bytes.Compare(prev, curBytes) { if 0 == bytes.Compare(prev, curBytes) {
if ! skipping { if !skipping {
fmt.Fprintln(out, "*") fmt.Fprintln(out, "*")
skipping = true skipping = true
} }
@ -321,7 +321,7 @@ func (g GapString) Hexdump() string {
pos += cur.Length() pos += cur.Length()
} }
fmt.Fprintf(out, "%08x\n", pos) fmt.Fprintf(out, "%08x\n", pos)
return out.String() return out.String()
} }
@ -343,7 +343,7 @@ func (g GapString) Uint16LE() (uint16, GapString) {
func (g GapString) Utf16(order binary.ByteOrder, fill string) string { func (g GapString) Utf16(order binary.ByteOrder, fill string) string {
in := g.Bytes([]byte(fill)...) in := g.Bytes([]byte(fill)...)
ints := make([]uint16, len(in)/2) ints := make([]uint16, len(in)/2)
for i := 0; i < len(in); i += 2 { for i := 0; i < len(in); i += 2 {
ints[i/2] = order.Uint16(in[i:]) ints[i/2] = order.Uint16(in[i:])
} }
@ -361,4 +361,3 @@ func (g GapString) Utf16LE(gap string) string {
func (g GapString) Utf16BE(gap string) string { func (g GapString) Utf16BE(gap string) string {
return g.Utf16(binary.BigEndian, gap) return g.Utf16(binary.BigEndian, gap)
} }

View File

@ -13,13 +13,13 @@ func assertEqual(t *testing.T, name string, a, b interface{}) {
func TestChunk(t *testing.T) { func TestChunk(t *testing.T) {
var c chunk var c chunk
c = chunk{gap: 2} c = chunk{gap: 2}
assertEqual(t, "gap chunk", c.length(), 2) assertEqual(t, "gap chunk", c.length(), 2)
c = chunk{data: []byte("moo")} c = chunk{data: []byte("moo")}
assertEqual(t, "byte chunk", c.length(), 3) assertEqual(t, "byte chunk", c.length(), 3)
assertEqual(t, "byte slice", string(c.slice(1,3).data), "oo") assertEqual(t, "byte slice", string(c.slice(1, 3).data), "oo")
} }
func TestGapString(t *testing.T) { func TestGapString(t *testing.T) {
@ -36,12 +36,12 @@ func TestGapString(t *testing.T) {
if g.Length() != 0 { if g.Length() != 0 {
t.Errorf("Appending two emtpy gapstrings") t.Errorf("Appending two emtpy gapstrings")
} }
g = g.AppendString("moo") g = g.AppendString("moo")
if 0 != bytes.Compare(g.Bytes(), []byte("moo")) { if 0 != bytes.Compare(g.Bytes(), []byte("moo")) {
t.Errorf("Simple string") t.Errorf("Simple string")
} }
g = g.AppendString("bar") g = g.AppendString("bar")
if g.String("") != "moobar" { if g.String("") != "moobar" {
t.Errorf("Append") t.Errorf("Append")
@ -49,7 +49,7 @@ func TestGapString(t *testing.T) {
if g.Missing() != 0 { if g.Missing() != 0 {
t.Errorf("Missing when there shouldn't be any missing") t.Errorf("Missing when there shouldn't be any missing")
} }
g = g.AppendGap(8) g = g.AppendGap(8)
if g.Length() != 3+3+8 { if g.Length() != 3+3+8 {
t.Errorf("Length after gap append") t.Errorf("Length after gap append")
@ -57,20 +57,20 @@ func TestGapString(t *testing.T) {
if g.Missing() != 8 { if g.Missing() != 8 {
t.Errorf("Gap miscounted") t.Errorf("Gap miscounted")
} }
g = g.AppendString("baz") g = g.AppendString("baz")
assertEqual(t, "string", g.String(""), "moobarbaz") assertEqual(t, "string", g.String(""), "moobarbaz")
assertEqual(t, "string drop", g.String("DROP"), "moobarOPDROPDRbaz") assertEqual(t, "string drop", g.String("DROP"), "moobarOPDROPDRbaz")
assertEqual(t, "xor", g.Xor(1).String(""), "lnnc`sc`{") assertEqual(t, "xor", g.Xor(1).String(""), "lnnc`sc`{")
assertEqual(t, "xor drop", g.Xor(1).String("DROP"), "lnnc`sOPDROPDRc`{") assertEqual(t, "xor drop", g.Xor(1).String("DROP"), "lnnc`sOPDROPDRc`{")
assertEqual(t, "slice", g.Slice(2, 5).String(""), "oba") assertEqual(t, "slice", g.Slice(2, 5).String(""), "oba")
assertEqual(t, "slice+xor", g.Slice(2, 5).Xor(1).String(""), "nc`") assertEqual(t, "slice+xor", g.Slice(2, 5).Xor(1).String(""), "nc`")
hexdump := hexdump :=
"00000000 6d 6f 6f 62 61 72 -- -- -- -- -- -- -- -- 62 61 moobar<61><72><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ba\n" + "00000000 6d 6f 6f 62 61 72 -- -- -- -- -- -- -- -- 62 61 moobar<61><72><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ba\n" +
"00000010 7a z\n" + "00000010 7a z\n" +
"00000011\n" "00000011\n"
assertEqual(t, "hexdump", g.Hexdump(), hexdump) assertEqual(t, "hexdump", g.Hexdump(), hexdump)
} }

View File

@ -10,11 +10,11 @@ package netshovel
import ( import (
"flag" "flag"
"log"
"github.com/google/gopacket" "github.com/google/gopacket"
"github.com/google/gopacket/layers" "github.com/google/gopacket/layers"
"github.com/google/gopacket/pcap" "github.com/google/gopacket/pcap"
"github.com/google/gopacket/tcpassembly" "github.com/google/gopacket/tcpassembly"
"log"
) )
// Mainloop to handle dispatching of PCAP files from command line // Mainloop to handle dispatching of PCAP files from command line
@ -25,16 +25,16 @@ import (
func Shovel(factory tcpassembly.StreamFactory) { func Shovel(factory tcpassembly.StreamFactory) {
//verbose := flag.Bool("verbose", false, "Write lots of information out") //verbose := flag.Bool("verbose", false, "Write lots of information out")
flag.Parse() flag.Parse()
streamPool := tcpassembly.NewStreamPool(factory) streamPool := tcpassembly.NewStreamPool(factory)
assembler := tcpassembly.NewAssembler(streamPool) assembler := tcpassembly.NewAssembler(streamPool)
for _, fn := range flag.Args() { for _, fn := range flag.Args() {
handle, err := pcap.OpenOffline(fn) handle, err := pcap.OpenOffline(fn)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
packetSource := gopacket.NewPacketSource(handle, handle.LinkType()) packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
packets := packetSource.Packets() packets := packetSource.Packets()
for packet := range packets { for packet := range packets {

View File

@ -4,16 +4,17 @@ import (
"encoding/binary" "encoding/binary"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"github.com/dirtbags/netshovel/gapstring"
"strings" "strings"
"time" "time"
"github.com/dirtbags/netshovel/gapstring"
) )
// Error returned by convenience methods that are unable to get enough data // Error returned by convenience methods that are unable to get enough data
type ShortError struct { type ShortError struct {
Wanted int // How many bytes you needed Wanted int // How many bytes you needed
Available int // How many bytes were available Available int // How many bytes were available
} }
func (e *ShortError) Error() string { func (e *ShortError) Error() string {
return fmt.Sprintf("Short read: wanted %d of %d available", e.wanted, e.available) return fmt.Sprintf("Short read: wanted %d of %d available", e.wanted, e.available)
} }
@ -21,6 +22,7 @@ func (e *ShortError) Error() string {
// Error returned by convenience methods that are unable to operate on gaps in data // Error returned by convenience methods that are unable to operate on gaps in data
type MissingError struct { type MissingError struct {
} }
func (e *MissingError) Error() string { func (e *MissingError) Error() string {
return "Operation on missing bytes" return "Operation on missing bytes"
} }
@ -32,8 +34,8 @@ type namedField struct {
// An application protocol header field // An application protocol header field
type headerField struct { type headerField struct {
name string name string
bits int bits int
value interface{} value interface{}
order binary.ByteOrder order binary.ByteOrder
} }
@ -46,12 +48,12 @@ type headerField struct {
// and // and
// documenting header structure. // documenting header structure.
type Packet struct { type Packet struct {
Opcode int Opcode int
Description string Description string
When time.Time When time.Time
Payload gapstring.GapString Payload gapstring.GapString
header []headerField header []headerField
fields []namedField fields []namedField
} }
var never = time.Unix(0, 0) var never = time.Unix(0, 0)
@ -59,12 +61,12 @@ var never = time.Unix(0, 0)
// Return a new packet // Return a new packet
func NewPacket() Packet { func NewPacket() Packet {
return Packet{ return Packet{
Opcode: -1, Opcode: -1,
Description: "Undefined", Description: "Undefined",
When: never, When: never,
Payload: gapstring.GapString{}, Payload: gapstring.GapString{},
header: []headerField{}, header: []headerField{},
fields: []namedField{}, fields: []namedField{},
} }
} }
@ -76,12 +78,12 @@ func (pkt *Packet) Describe() string {
out := new(strings.Builder) out := new(strings.Builder)
fmt.Fprintf(out, " %s Opcode %d: %s\n", fmt.Fprintf(out, " %s Opcode %d: %s\n",
pkt.When.UTC().Format(time.RFC3339Nano), pkt.When.UTC().Format(time.RFC3339Nano),
pkt.Opcode, pkt.Opcode,
pkt.Description, pkt.Description,
) )
for _, f := range(pkt.Fields) { for _, f := range pkt.Fields {
fmt.Fprintf(out, " %s: %s\n", f.key, f.value) fmt.Fprintf(out, " %s: %s\n", f.key, f.value)
} }
fmt.Fprint(out, pkt.Payload.Hexdump()) fmt.Fprint(out, pkt.Payload.Hexdump())
@ -145,8 +147,8 @@ func (pkt *Packet) Peel(octets int) ([]byte, error) {
// Add a field to the header field description // Add a field to the header field description
func (pkt *Packet) AddHeaderField(order binary.ByteOrder, name string, bits int, value interface{}) { func (pkt *Packet) AddHeaderField(order binary.ByteOrder, name string, bits int, value interface{}) {
h := headerField{ h := headerField{
name: name, name: name,
bits: bits, bits: bits,
value: value, value: value,
order: order, order: order,
} }
@ -161,28 +163,28 @@ func (pkt *Packet) readUint(order binary.ByteOrder, bits int, name string) (inte
case 32: case 32:
case 64: case 64:
default: default:
return 0, fmt.Errorf("Weird number of bits: %d", bits) return 0, fmt.Errorf("Weird number of bits: %d", bits)
} }
octets := bits >> 3 octets := bits >> 3
b, err := pkt.Peel(octets) b, err := pkt.Peel(octets)
if err != nil { if err != nil {
return 0, err return 0, err
} }
var value interface{} var value interface{}
switch bits { switch bits {
case 8: case 8:
value = b[0] value = b[0]
case 16: case 16:
value = order.Uint16(b) value = order.Uint16(b)
case 32: case 32:
value = order.Uint32(b) value = order.Uint32(b)
case 64: case 64:
value = order.Uint64(b) value = order.Uint64(b)
} }
pkt.AddheaderField(order, name, bits, value) pkt.AddheaderField(order, name, bits, value)
return value, nil return value, nil
} }
@ -248,4 +250,3 @@ func (pkt *Packet) Uint8(name string) (uint8, error) {
} }
return value.(uint8), err return value.(uint8), err
} }

View File

@ -2,14 +2,14 @@ package netshovel
import ( import (
"fmt" "fmt"
"io"
"os"
"net/url"
"strings"
"time"
"github.com/dirtbags/netshovel/gapstring" "github.com/dirtbags/netshovel/gapstring"
"github.com/google/gopacket" "github.com/google/gopacket"
"github.com/google/gopacket/tcpassembly" "github.com/google/gopacket/tcpassembly"
"io"
"net/url"
"os"
"strings"
"time"
) )
// A File and the path where it lives // A File and the path where it lives
@ -34,8 +34,8 @@ type Utterance struct {
// A Stream is one half of a two-way conversation // A Stream is one half of a two-way conversation
type Stream struct { type Stream struct {
Net, Transport gopacket.Flow Net, Transport gopacket.Flow
conversation chan Utterance conversation chan Utterance
pending Utterance pending Utterance
} }
// Return a newly-built Stream // Return a newly-built Stream
@ -44,8 +44,8 @@ type Stream struct {
// Use this to initialize the internal stuff netshovel needs. // Use this to initialize the internal stuff netshovel needs.
func NewStream(net, transport gopacket.Flow) Stream { func NewStream(net, transport gopacket.Flow) Stream {
return Stream{ return Stream{
Net: net, Net: net,
Transport: transport, Transport: transport,
conversation: make(chan Utterance, 100), conversation: make(chan Utterance, 100),
} }
} }
@ -61,7 +61,7 @@ func (stream *Stream) Reassembled(rs []tcpassembly.Reassembly) {
} }
ret.Data = ret.Data.AppendBytes(r.Bytes) ret.Data = ret.Data.AppendBytes(r.Bytes)
} }
// Throw away utterances with no data (SYN, ACK, FIN, &c) // Throw away utterances with no data (SYN, ACK, FIN, &c)
if ret.Data.Length() > 0 { if ret.Data.Length() > 0 {
stream.conversation <- ret stream.conversation <- ret
@ -90,7 +90,7 @@ func (stream *Stream) Read(length int) (Utterance, error) {
if length == 0 { if length == 0 {
return Utterance{}, nil return Utterance{}, nil
} }
// Special case: length=-1 means "give me the next utterance" // Special case: length=-1 means "give me the next utterance"
if length == -1 { if length == -1 {
var ret Utterance var ret Utterance
@ -99,8 +99,8 @@ func (stream *Stream) Read(length int) (Utterance, error) {
ret = stream.pending ret = stream.pending
stream.pending.Data = gapstring.GapString{} stream.pending.Data = gapstring.GapString{}
} else { } else {
r, more := <- stream.conversation r, more := <-stream.conversation
if ! more { if !more {
err = io.EOF err = io.EOF
} }
ret = r ret = r
@ -111,8 +111,8 @@ func (stream *Stream) Read(length int) (Utterance, error) {
// Pull in utterances until we have enough data. // Pull in utterances until we have enough data.
// .When will always be the timestamp on the last received utterance // .When will always be the timestamp on the last received utterance
for stream.pending.Data.Length() < length { for stream.pending.Data.Length() < length {
u, more := <- stream.conversation u, more := <-stream.conversation
if ! more { if !more {
break break
} }
stream.pending.Data = stream.pending.Data.Append(u.Data) stream.pending.Data = stream.pending.Data.Append(u.Data)
@ -124,7 +124,7 @@ func (stream *Stream) Read(length int) (Utterance, error) {
if pendingLen == 0 { if pendingLen == 0 {
return Utterance{}, io.EOF return Utterance{}, io.EOF
} }
sliceLen := length sliceLen := length
if sliceLen > pendingLen { if sliceLen > pendingLen {
sliceLen = pendingLen sliceLen = pendingLen
@ -145,9 +145,9 @@ func (stream *Stream) Describe(pkt Packet) string {
fmt.Fprintf(out, "%v:%v → %v:%v\n", fmt.Fprintf(out, "%v:%v → %v:%v\n",
stream.Net.Src().String(), stream.Transport.Src().String(), stream.Net.Src().String(), stream.Transport.Src().String(),
stream.Net.Dst().String(), stream.Transport.Dst().String(), stream.Net.Dst().String(), stream.Transport.Dst().String(),
) )
out.WriteString(pkt.Describe()) out.WriteString(pkt.Describe())
return out.String() return out.String()
} }
@ -160,17 +160,17 @@ func (stream *Stream) Describe(pkt Packet) string {
// Best practice is to pass in as full a path as you can find, // Best practice is to pass in as full a path as you can find,
// including drive letters and all parent directories. // including drive letters and all parent directories.
func (stream *Stream) CreateFile(when time.Time, path string) (NamedFile, error) { func (stream *Stream) CreateFile(when time.Time, path string) (NamedFile, error) {
name := fmt.Sprintf( name := fmt.Sprintf(
"xfer/%s,%sp%s,%sp%s,%s", "xfer/%s,%sp%s,%sp%s,%s",
when.UTC().Format(time.RFC3339Nano), when.UTC().Format(time.RFC3339Nano),
stream.Net.Src().String(), stream.Transport.Src().String(), stream.Net.Src().String(), stream.Transport.Src().String(),
stream.Net.Dst().String(), stream.Transport.Dst().String(), stream.Net.Dst().String(), stream.Transport.Dst().String(),
url.PathEscape(path), url.PathEscape(path),
) )
f, err := os.Create(name) f, err := os.Create(name)
outf := NamedFile{ outf := NamedFile{
File: f, File: f,
Name: name, Name: name,
} }
return outf, err return outf, err
} }