Skip to content

Latest commit

 

History

History

minimal

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 

Wirego plugin development guide - Minimalistic

Writing a Wirego plugin is quite simple.

The complete code of this example can be found here

screenshot

Before going any further, you should build this example and try to load it with the wirego Wireshark plugin.

cd examples/minimal/
make

Our plugin in Go will need to import the "wirego" package and register to wirego during init():

package main

import (
  "encoding/hex"
  "fmt"
  "wirego/wirego"
)

// Since we implement the wirego.WiregoInterface we need some structure to hold it.
type WiregoExample struct {
}

// Unused (but mandatory)
func main() {}

// Called at golang environment initialization (you should probably not touch this)
func init() {
  var wge WiregoExample

  //Register to the wirego package
  wirego.Register(wge)

  //Enable the Wirego cache, so that Wireshark will not ask us to parse the same packet multiple times
  wirego.ResultsCacheEnable(false)
}

Now we just need to implement the WiregoInterface interface.

The first function to implement is Setup, that is where we can initialize our plugin if needed. Since this is a simple example, we don't have anything to initialize.

// This function is called when the plugin is loaded.
func (WiregoMinimalExample) Setup() error {

	return nil
}

GetName returns the name of our example plugin and GetFilter defines the string that we will use to filter the packets matching our protocol in Wireshark.

// This function shall return the plugin name
func (WiregoExample) GetName() string {
  return "Wirego Example"
}

// This function shall return the wireshark filter
func (WiregoExample) GetFilter() string {
  return "wgexample"
}

During initialization, a plugin has to declare all the fields that may eventually be returned (so that Wireshark can setup the GUI). In this example we declare three fields:

  • a first one, named "Custom1" which is an uint8, displayed in hexadeciaml
  • a second one, named "Custom2" which is an uint16, displayed in decimal
  • and a third one, named "CustomWith Subs" which is an uint32, displayed in decimal

During parsing ("dissection" in Wireshark's terminology), when a field has been found, we will refer to it using the defined "WiregoFieldId".

// Define here enum identifiers, used to refer to a specific field
const (
	FieldIdCustom1             wirego.FieldId = 1
	FieldIdCustom2             wirego.FieldId = 2
	FieldIdCustomWithSubFields wirego.FieldId = 3
)


// GetFields returns the list of fields descriptor that we may eventually return
// when dissecting a packet payload
func (WiregoExample) GetFields() []wirego.WiresharkField {
  var fields []wirego.WiresharkField
  fields = append(fields, wirego.WiresharkField{WiregoFieldId: FieldIdCustom1, Name: "Custom1", Filter: "wirego.custom01", ValueType: wirego.ValueTypeUInt8, DisplayMode: wirego.DisplayModeHexadecimal})
  fields = append(fields, wirego.WiresharkField{WiregoFieldId: FieldIdCustom2, Name: "Custom2", Filter: "wirego.custom02", ValueType: wirego.ValueTypeUInt16, DisplayMode: wirego.DisplayModeDecimal})

	fields = append(fields, wirego.WiresharkField{WiregoFieldId: FieldIdCustomWithSubFields, Name: "CustomWith Subs", Filter: "wirego.custom_subs", ValueType: wirego.ValueTypeUInt32, DisplayMode: wirego.DisplayModeHexadecimal, })

  return fields
}

In order to tell Wireshark which packets should be sent to your dissector, two methods are available:

  • use Wireshark filters to match on a given traffic (ex. udp.port == 137)
  • register a heuristic detection function which will be called on a given protocol (ex. "apply my heuristic function on all TCP payloads")

The first method is faster but not always relevant. If your protocol works on a given HTTP traffic, you probably don't want to redirect all TCP port 80 to your dissector. The second option lets you register on HTTP traffic and apply an heuristic function to detect if this packet should be redirected to your dissector or not. You can use both method at the same time, but need to used at least one.

Let's start with the filter method:

// GetDetectionFilters returns a wireshark filter that will select which packets
// will be sent to your dissector for parsing.
// Two types of filters can be defined: Integers or Strings
func (WiregoExample) GetDetectionFilters() []wirego.DetectionFilter {
  var filters []wirego.DetectionFilter

  filters = append(filters, wirego.DetectionFilter{FilterType: wirego.DetectionFilterTypeInt, Name: "udp.port", ValueInt: 137})
  filters = append(filters, wirego.DetectionFilter{FilterType: wirego.DetectionFilterTypeString, Name: "bluetooth.uuid", ValueString: "1234"})

  return filters
}

When using detection heuristics mode, if a packet matches the "heuristics parent" previously defined, a detection function will be called. Return true if the packet is ours and false otherwise.

// GetDetectionHeuristicsParents returns a list of protocols on top of which detection heuristic
// should be called.
func (WiregoExample) GetDetectionHeuristicsParents() []string {
	//We want to apply our detection heuristic on all tcp and http payloads
	return []string{"udp", "http"}
}

func (WiregoExample) DetectionHeuristic(packetNumber int, src string, dst string, layer string, packet []byte) bool {
	//All packets starting with 0x00 should be passed to our dissector (super advanced heuristic)
	if len(packet) != 0 && packet[0] == 0x00 {
		return true
	}
	return false
}

The most interesting part is the DissectPacket function, where we will implement our parser:

// DissectPacket provides the packet payload to be parsed.
func (WiregoMinimalExample) DissectPacket(packetNumber int, src string, dst string, layer string, packet []byte) *wirego.DissectResult {
	var res wirego.DissectResult

	//This string will appear on the packet being parsed
	res.Protocol = "Protocol name example"

	//This (optional) string will appear in the info section
	res.Info = fmt.Sprintf("Info example pkt %d", packetNumber)

	//Add a few fields and refer to them using our own "internalId"
	res.Fields = append(res.Fields, wirego.DissectField{WiregoFieldId: FieldIdCustom1, Offset: 0, Length: 2})
	res.Fields = append(res.Fields, wirego.DissectField{WiregoFieldId: FieldIdCustom2, Offset: 2, Length: 4})

	//Add a field with two sub field
	subField1 := wirego.DissectField{WiregoFieldId: FieldIdCustom1, Offset: 6, Length: 2}
	subField2 := wirego.DissectField{WiregoFieldId: FieldIdCustom1, Offset: 8, Length: 2}
	field := wirego.DissectField{WiregoFieldId: FieldIdCustomWithSubFields, Offset: 6, Length: 4, SubFields: []wirego.DissectField{subField1, subField2}}
	res.Fields = append(res.Fields, field)

	//Dump packet contents
	//fmt.Println(layer, " ", src, " to ", dst)
	//fmt.Println(hex.Dump(packet))
	return &res
}

The last step is to build our plugin using:

  go build -o wirego_minimalistic.so -buildmode=c-shared

And... that's all!

Run Wireshark, to go Preferences -> Wirego and point to your freshly built Golang plugin.