Compare commits

..

6 Commits

28 changed files with 231 additions and 95 deletions

View File

@ -2,10 +2,11 @@ package analyze
import ( import (
"fmt" "fmt"
"log"
"github.com/google/gopacket"
"github.com/maride/pancap/output" "github.com/maride/pancap/output"
"github.com/maride/pancap/protocol" "github.com/maride/pancap/protocol"
"github.com/google/gopacket"
"log"
) )
var ( var (
@ -39,6 +40,9 @@ func Analyze(source *gopacket.PacketSource) error {
} }
} }
// Register communication for graph
output.AddPkgToGraph(packet)
// Raise statistics // Raise statistics
totalPackets += 1 totalPackets += 1
if processed { if processed {
@ -68,4 +72,3 @@ func handleErr(err error) {
log.Printf("Encountered error while examining packets, continuing anyway. Error: %s", err.Error()) log.Printf("Encountered error while examining packets, continuing anyway. Error: %s", err.Error())
} }
} }

View File

@ -3,29 +3,30 @@ package main
import ( import (
"flag" "flag"
"fmt" "fmt"
"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"
) )
var ( var (
filenameFlag *string filenameFlag string
) )
// Registers the flag --file // Registers the flag --file
func registerFileFlags() { func registerFileFlags() {
filenameFlag = flag.String("file", "", "PCAP file to base analysis on") flag.StringVar(&filenameFlag, "file", "", "PCAP file to base analysis on")
} }
// Opens the PCAP, returns its packets and the link type or an error // Opens the PCAP, returns its packets and the link type or an error
func openPCAP() (*gopacket.PacketSource, layers.LinkType, error) { func openPCAP() (*gopacket.PacketSource, layers.LinkType, error) {
// Check if we even got a file. // Check if we even got a file.
if *filenameFlag == "" { if filenameFlag == "" {
return nil, 0, fmt.Errorf("missing file to analyze. Please specifiy it with --file") return nil, 0, fmt.Errorf("missing file to analyze. Please specifiy it with --file")
} }
// Open specified file // Open specified file
handle, openErr := pcap.OpenOffline(*filenameFlag) handle, openErr := pcap.OpenOffline(filenameFlag)
if openErr != nil { if openErr != nil {
// There were some problems opening the file // There were some problems opening the file
return nil, 0, openErr return nil, 0, openErr

View File

@ -3,11 +3,12 @@ package main
import ( import (
"flag" "flag"
"fmt" "fmt"
"github.com/maride/pancap/analyze"
"github.com/maride/pancap/output"
"log" "log"
"math/rand" "math/rand"
"time" "time"
"github.com/maride/pancap/analyze"
"github.com/maride/pancap/output"
) )
func main() { func main() {
@ -36,6 +37,9 @@ func main() {
// Extract found and requested files // Extract found and requested files
output.StoreFiles() output.StoreFiles()
// Create communication graph
output.CreateGraph()
// Show user analysis // Show user analysis
analyze.PrintSummary() analyze.PrintSummary()

View File

@ -46,12 +46,12 @@ func StoreFiles() {
var filesToExtract []File var filesToExtract []File
// Check different flag scenarios // Check different flag scenarios
if *targetAllFiles { if targetAllFiles {
// We should extract all files. // We should extract all files.
filesToExtract = registeredFiles filesToExtract = registeredFiles
} else { } else {
// We should extract only a given set of files // We should extract only a given set of files
fileList := strings.Split(*targetFiles, ",") fileList := strings.Split(targetFiles, ",")
for _, f := range fileList { for _, f := range fileList {
// Iterate over desired files // Iterate over desired files
found := false found := false
@ -80,7 +80,7 @@ func StoreFiles() {
// Writes the given file object to disk, along with a stats file placed next to it. // Writes the given file object to disk, along with a stats file placed next to it.
func writeOut(f File) { func writeOut(f File) {
targetName := fmt.Sprintf("%s%c%s", *targetOutput, os.PathSeparator, f.hash) targetName := fmt.Sprintf("%s%c%s", targetOutput, os.PathSeparator, f.hash)
targetDescName := fmt.Sprintf("%s.info", targetName) targetDescName := fmt.Sprintf("%s.info", targetName)
targetDescription := fmt.Sprintf("Filename: %s\nHash: %s\nOrigin: %s\nSize: %d", f.name, f.hash, f.origin, len(f.content)) targetDescription := fmt.Sprintf("Filename: %s\nHash: %s\nOrigin: %s\nSize: %d", f.name, f.hash, f.origin, len(f.content))

View File

@ -3,19 +3,19 @@ package output
import "flag" import "flag"
var ( var (
fullOutput *bool fullOutput bool
printEmptyBlocks *bool printEmptyBlocks bool
targetFiles *string targetFiles string
targetAllFiles *bool targetAllFiles bool
targetOutput *string targetOutput string
graphOutput string
) )
func RegisterFlags() { func RegisterFlags() {
fullOutput = flag.Bool("full-output", false, "Show full output instead of limiting submodule output") flag.BoolVar(&fullOutput, "full-output", false, "Show full output instead of limiting submodule output")
printEmptyBlocks = flag.Bool("print-empty-blocks", false, "Prints blocks (submodule output) even if the submodule doesn't have any content to print.") flag.BoolVar(&printEmptyBlocks, "print-empty-blocks", false, "Prints blocks (submodule output) even if the submodule doesn't have any content to print.")
targetFiles = flag.String("extract-these", "", "Comma-separated list of files to extract.") flag.StringVar(&targetFiles, "extract-these", "", "Comma-separated list of files to extract.")
targetAllFiles = flag.Bool("extract-all", false, "Extract all files found.") flag.BoolVar(&targetAllFiles, "extract-all", false, "Extract all files found.")
targetOutput = flag.String("extract-to", "./extracted", "Directory to store extracted files in.") flag.StringVar(&targetOutput, "extract-to", "./extracted", "Directory to store extracted files in.")
flag.StringVar(&graphOutput, "create-graph", "", "Create a Graphviz graph out of collected communication")
} }

103
output/graph.go Normal file
View File

@ -0,0 +1,103 @@
package output
import (
"crypto/sha256"
"fmt"
"io/ioutil"
"github.com/google/gopacket"
)
var graphPkgs []GraphPkg
// AddPkgToGraph adds the given packet as communication to the graph
func AddPkgToGraph(pkg gopacket.Packet) {
// Only proceed if pkg contains a network layer
if pkg.NetworkLayer() == nil {
return
}
src := pkg.NetworkLayer().NetworkFlow().Src().String()
dst := pkg.NetworkLayer().NetworkFlow().Dst().String()
// Search for the given communication pair
for _, p := range graphPkgs {
if p.from == src && p.to == dst {
// Communication pair found, add protocol and finish
p.AddProtocol("nil")
return
}
}
// Communcation pair was not in graphPkgs, add to it
graphPkgs = append(graphPkgs, GraphPkg{
from: src,
to: dst,
protocol: []string{""},
})
}
// CreateGraph writes out a Graphviz digraph
func CreateGraph() {
if graphOutput == "" {
// No graph requested
return
}
// Start with the Graphviz-specific header
dot := fmt.Sprintf("# Compile with `neato -Tpng %s > %s.png`\n", graphOutput, graphOutput)
dot += "digraph pancap {\n\toverlap = false;\n"
// First, gather all nodes as-is and write them out
dot += nodedef(graphPkgs)
// Iterate over communication
for _, p := range graphPkgs {
dot += fmt.Sprintf("\tn%s->n%s\n", hash(p.from), hash(p.to))
}
// Close
dot += "}\n"
// Write out
ioutil.WriteFile(graphOutput, []byte(dot), 0644)
}
// Creates a list of distinct nodes, Graphviz-compatible
func nodedef(pkgs []GraphPkg) string {
output := ""
nodes := []string{}
for _, p := range graphPkgs {
// Check if src and dst are already present in nodes array
srcFound := false
dstFound := false
for _, n := range nodes {
if p.from == n {
srcFound = true
}
if p.to == n {
dstFound = true
}
}
if !srcFound {
// src not yet present, add to node list
nodes = append(nodes, p.from)
}
if !dstFound {
// dst not yet present, add to node list
nodes = append(nodes, p.to)
}
}
// Output Graphviz-compatible node definition
for _, n := range nodes {
// As the Graphviz charset for nodes is rather small, rely on hashes
output += fmt.Sprintf("\tn%s[label=\"%s\"]\n", hash(n), n)
}
return output
}
func hash(s string) string {
return fmt.Sprintf("%x", sha256.Sum256([]byte(s)))[:6]
}

19
output/graphpkg.go Normal file
View File

@ -0,0 +1,19 @@
package output
// GraphPkg resembles a directed communication from one address to another
// It wraps up required information to draw a graph of the communication, including spoken protocols.
type GraphPkg struct {
from string
to string
protocol []string
}
// AddProtocol adds the given protocol to the list of protocols if not already present
func (p *GraphPkg) AddProtocol(protocol string) {
for _, p := range p.protocol {
if p == protocol {
return
}
}
p.protocol = append(p.protocol, protocol)
}

View File

@ -23,4 +23,10 @@ func Finalize() {
// User avoided the files // User avoided the files
printer.Println("Files found in stream. Add --extract-all or --extract-these <list> to extract them.") printer.Println("Files found in stream. Add --extract-all or --extract-these <list> to extract them.")
} }
// Check if something graph-worthy was collected
if graphOutput == "" && len(graphPkgs) > 0 {
// User didn't want a graph
printer.Println("To summarize the communcation flow with a Graphviz graph, specify --create-graph <out.dot>.")
}
} }

View File

@ -2,8 +2,9 @@ package output
import ( import (
"fmt" "fmt"
"github.com/fatih/color"
"strings" "strings"
"github.com/fatih/color"
) )
const ( const (
@ -21,7 +22,7 @@ var (
// If the content is longer than MaxContentLines, content is cut. // If the content is longer than MaxContentLines, content is cut.
func PrintBlock(headline string, content string) { func PrintBlock(headline string, content string) {
// Avoid printing empty blocks - at least if user didn't specify it otherwise // Avoid printing empty blocks - at least if user didn't specify it otherwise
if len(content) == 0 && !*printEmptyBlocks { if len(content) == 0 && !printEmptyBlocks {
// No content and we are not forced to print empty blocks, return // No content and we are not forced to print empty blocks, return
DidAvoidEmptyBlock = true DidAvoidEmptyBlock = true
return return
@ -38,7 +39,7 @@ func PrintBlock(headline string, content string) {
} }
// Cut to MaxContentLines if required // Cut to MaxContentLines if required
if !(*fullOutput) { if !(fullOutput) {
// User states that they don't want to see the whole output - cut content. // User states that they don't want to see the whole output - cut content.
content = cutContent(content) content = cutContent(content)
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 434 KiB

After

Width:  |  Height:  |  Size: 464 KiB

View File

@ -2,10 +2,10 @@ package arp
import ( import (
"fmt" "fmt"
"github.com/maride/pancap/common"
"github.com/maride/pancap/output"
"github.com/google/gopacket" "github.com/google/gopacket"
"github.com/google/gopacket/layers" "github.com/google/gopacket/layers"
"github.com/maride/pancap/common"
"github.com/maride/pancap/output"
"log" "log"
"net" "net"
) )

View File

@ -1,9 +1,9 @@
package dhcpv4 package dhcpv4
import ( import (
"github.com/maride/pancap/output"
"github.com/google/gopacket" "github.com/google/gopacket"
"github.com/google/gopacket/layers" "github.com/google/gopacket/layers"
"github.com/maride/pancap/output"
) )
type Protocol struct { type Protocol struct {

View File

@ -6,4 +6,3 @@ type dhcpResponse struct {
serverMACAddr string serverMACAddr string
askedFor bool askedFor bool
} }

View File

@ -2,8 +2,8 @@ package dhcpv4
import ( import (
"fmt" "fmt"
"github.com/maride/pancap/common"
"github.com/google/gopacket/layers" "github.com/google/gopacket/layers"
"github.com/maride/pancap/common"
"log" "log"
) )

View File

@ -2,8 +2,8 @@ package dhcpv4
import ( import (
"fmt" "fmt"
"github.com/maride/pancap/common"
"github.com/google/gopacket/layers" "github.com/google/gopacket/layers"
"github.com/maride/pancap/common"
) )
// Processes the DHCP request packet handed over // Processes the DHCP request packet handed over

View File

@ -2,8 +2,8 @@ package dhcpv4
import ( import (
"fmt" "fmt"
"github.com/maride/pancap/common"
"github.com/google/gopacket/layers" "github.com/google/gopacket/layers"
"github.com/maride/pancap/common"
"log" "log"
) )

View File

@ -2,8 +2,8 @@ package dns
import ( import (
"fmt" "fmt"
"github.com/maride/pancap/common"
"github.com/google/gopacket/layers" "github.com/google/gopacket/layers"
"github.com/maride/pancap/common"
"golang.org/x/net/publicsuffix" "golang.org/x/net/publicsuffix"
"log" "log"
) )

View File

@ -1,9 +1,9 @@
package dns package dns
import ( import (
"github.com/maride/pancap/output"
"github.com/google/gopacket" "github.com/google/gopacket"
"github.com/google/gopacket/layers" "github.com/google/gopacket/layers"
"github.com/maride/pancap/output"
) )
type Protocol struct{} type Protocol struct{}

View File

@ -2,8 +2,8 @@ package dns
import ( import (
"fmt" "fmt"
"github.com/maride/pancap/common"
"github.com/google/gopacket/layers" "github.com/google/gopacket/layers"
"github.com/maride/pancap/common"
"golang.org/x/net/publicsuffix" "golang.org/x/net/publicsuffix"
"log" "log"
) )

View File

@ -1,11 +1,11 @@
package http package http
import ( import (
"github.com/maride/pancap/common"
"github.com/maride/pancap/output"
"github.com/google/gopacket" "github.com/google/gopacket"
"github.com/google/gopacket/layers" "github.com/google/gopacket/layers"
"github.com/google/gopacket/tcpassembly" "github.com/google/gopacket/tcpassembly"
"github.com/maride/pancap/common"
"github.com/maride/pancap/output"
) )
type Protocol struct { type Protocol struct {

View File

@ -3,10 +3,10 @@ package http
import ( import (
"bufio" "bufio"
"fmt" "fmt"
"github.com/maride/pancap/output"
"github.com/google/gopacket" "github.com/google/gopacket"
"github.com/google/gopacket/tcpassembly" "github.com/google/gopacket/tcpassembly"
"github.com/google/gopacket/tcpassembly/tcpreader" "github.com/google/gopacket/tcpassembly/tcpreader"
"github.com/maride/pancap/output"
"io" "io"
"io/ioutil" "io/ioutil"
"net/http" "net/http"