10

[译]利用 gopackage 进行包的捕获、注入和分析 | 鸟窝

 4 years ago
source link: https://colobu.com/2019/06/01/packet-capture-injection-and-analysis-gopacket/?
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

[译]利用 gopackage 进行包的捕获、注入和分析

目录 [−]

原文: Packet Capture, Injection, and Analysis with Gopacket by John Leon。

最近准备使用 gopackage 写个工具,搜索了相关的资料,这篇文章是值的推荐的一篇介绍 gopackage的文档,所以动手翻译了一下。

gopacket包为用C编写的libpcap提供了一个go包装器(译者按: 不仅如此,现在gopackage有多种捕获包的binding方式,比如PF_RING,AF_PACKET等),但它不仅仅是一个简单的包装器。它提供了额外的功能,并利用了Go之类的接口,这使得它非常强大。

作者在写这篇文章的时候,在Denver GopherCon 2016 大会上分享了相关的技术,有兴趣的同学可以观看: GopherCon 2016: John Leon - Packet Capture, Analysis, and Injection with Go

安装必需的库。你需要golibpcapgopacket包。因为gopacket是在libpcap之上构建的,所以我强烈建议您了解该libpcap库是如何工作的。你可以学习如何在C语言中使用libpcap来加深理解。这些示例应该在使用libpcap的Linux/Mac和使用winpcap的Windows上工作。如果出现错误(如cc1.exe: sorry, unimplemented: 64-bit mode not compiled in),可能需要设置GOARCH=386

# Get the gopacket package from GitHub
go get github.com/google/gopacket
# Pcap dev headers might be necessary
sudo apt-get install libpcap-dev

你可能还想签出gopacket project, 查看它的godoc文档

package main
import (
"fmt"
"log"
"github.com/google/gopacket/pcap"
func main() {
// 得到所有的(网络)设备
devices, err := pcap.FindAllDevs()
if err != nil {
log.Fatal(err)
// 打印设备信息
fmt.Println("Devices found:")
for _, device := range devices {
fmt.Println("\nName: ", device.Name)
fmt.Println("Description: ", device.Description)
fmt.Println("Devices addresses: ", device.Description)
for _, address := range device.Addresses {
fmt.Println("- IP address: ", address.IP)
fmt.Println("- Subnet mask: ", address.Netmask)

打开一个设备进行实时捕获

package main
import (
"fmt"
"github.com/google/gopacket"
"github.com/google/gopacket/pcap"
"log"
"time"
device string = "eth0"
snapshot_len int32 = 1024
promiscuous bool = false
err error
timeout time.Duration = 30 * time.Second
handle *pcap.Handle
func main() {
// Open device
handle, err = pcap.OpenLive(device, snapshot_len, promiscuous, timeout)
if err != nil {log.Fatal(err) }
defer handle.Close()
// Use the handle as a packet source to process all packets
packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
for packet := range packetSource.Packets() {
// Process packet here
fmt.Println(packet)

写入到pcap文件

为了写入到pcap格式的文件中,我们需要gopacket/pcapgo,它包含一个Writer,还有两个有用的辅助函数:WriteFileHeader()WritePacket()

package main
import (
"fmt"
"time"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/google/gopacket/pcap"
"github.com/google/gopacket/pcapgo"
deviceName string = "eth0"
snapshotLen int32 = 1024
promiscuous bool = false
err error
timeout time.Duration = -1 * time.Second
handle *pcap.Handle
packetCount int = 0
func main() {
// Open output pcap file and write header
f, _ := os.Create("test.pcap")
w := pcapgo.NewWriter(f)
w.WriteFileHeader(snapshotLen, layers.LinkTypeEthernet)
defer f.Close()
// Open the device for capturing
handle, err = pcap.OpenLive(deviceName, snapshotLen, promiscuous, timeout)
if err != nil {
fmt.Printf("Error opening device %s: %v", deviceName, err)
os.Exit(1)
defer handle.Close()
// Start processing packets
packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
for packet := range packetSource.Packets() {
// Process packet here
fmt.Println(packet)
w.WritePacket(packet.Metadata().CaptureInfo, packet.Data())
packetCount++
// Only capture 100 and then stop
if packetCount > 100 {
break

打开pcap文件

除了打开一个设备实时捕获以外,我们还可以读取pcap文件进行离线分析。你可以通过tcpdump捕获一个文件来测试。

# Capture packets to test.pcap file
sudo tcpdump -w test.pcap

打开这个文件,遍历其中的packet:

package main
// Use tcpdump to create a test file
// tcpdump -w test.pcap
// or use the example above for writing pcap files
import (
"fmt"
"github.com/google/gopacket"
"github.com/google/gopacket/pcap"
"log"
pcapFile string = "test.pcap"
handle *pcap.Handle
err error
func main() {
// Open file instead of device
handle, err = pcap.OpenOffline(pcapFile)
if err != nil { log.Fatal(err) }
defer handle.Close()
// Loop through packets in file
packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
for packet := range packetSource.Packets() {
fmt.Println(packet)

设置过滤器

下面的代码仅仅返回端口80上的packet:

package main
import (
"fmt"
"github.com/google/gopacket"
"github.com/google/gopacket/pcap"
"log"
"time"
device string = "eth0"
snapshot_len int32 = 1024
promiscuous bool = false
err error
timeout time.Duration = 30 * time.Second
handle *pcap.Handle
func main() {
// Open device
handle, err = pcap.OpenLive(device, snapshot_len, promiscuous, timeout)
if err != nil {
log.Fatal(err)
defer handle.Close()
// Set filter
var filter string = "tcp and port 80"
err = handle.SetBPFFilter(filter)
if err != nil {
log.Fatal(err)
fmt.Println("Only capturing TCP port 80 packets.")
packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
for packet := range packetSource.Packets() {
// Do something with a packet here.
fmt.Println(packet)

解码packet的各层

我们可以获取原始数据包,并尝试将其强制转换为已知格式。它与不同的层兼容,因此我们可以轻松访问ethernet、IP和TCP层。layers包是gopacket的Go库中的新功能,在底层libpcap库中不存在。它是gopacket库的非常有用的一部分。它允许我们轻松地识别数据包是否包含特定类型的层。这个代码示例将演示如何使用layers包来查看包是否是ethernet、IP和TCP,以及如何轻松访问这些头中的元素。

找到payload(有效载荷)取决于涉及的所有层。每个协议都是不同的,必须相应地进行处理。这就是layers包的强大之处。gopacket的作者花了很多时间为许多已知层(ethernet、IP、UDP和TCP)创建layer类型。其中payload(有效负载)是应用程序层的一部分。

package main
import (
"fmt"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/google/gopacket/pcap"
"log"
"strings"
"time"
device string = "eth0"
snapshotLen int32 = 1024
promiscuous bool = false
err error
timeout time.Duration = 30 * time.Second
handle *pcap.Handle
func main() {
// Open device
handle, err = pcap.OpenLive(device, snapshotLen, promiscuous, timeout)
if err != nil {log.Fatal(err) }
defer handle.Close()
packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
for packet := range packetSource.Packets() {
printPacketInfo(packet)
func printPacketInfo(packet gopacket.Packet) {
// Let's see if the packet is an ethernet packet
ethernetLayer := packet.Layer(layers.LayerTypeEthernet)
if ethernetLayer != nil {
fmt.Println("Ethernet layer detected.")
ethernetPacket, _ := ethernetLayer.(*layers.Ethernet)
fmt.Println("Source MAC: ", ethernetPacket.SrcMAC)
fmt.Println("Destination MAC: ", ethernetPacket.DstMAC)
// Ethernet type is typically IPv4 but could be ARP or other
fmt.Println("Ethernet type: ", ethernetPacket.EthernetType)
fmt.Println()
// Let's see if the packet is IP (even though the ether type told us)
ipLayer := packet.Layer(layers.LayerTypeIPv4)
if ipLayer != nil {
fmt.Println("IPv4 layer detected.")
ip, _ := ipLayer.(*layers.IPv4)
// IP layer variables:
// Version (Either 4 or 6)
// IHL (IP Header Length in 32-bit words)
// TOS, Length, Id, Flags, FragOffset, TTL, Protocol (TCP?),
// Checksum, SrcIP, DstIP
fmt.Printf("From %s to %s\n", ip.SrcIP, ip.DstIP)
fmt.Println("Protocol: ", ip.Protocol)
fmt.Println()
// Let's see if the packet is TCP
tcpLayer := packet.Layer(layers.LayerTypeTCP)
if tcpLayer != nil {
fmt.Println("TCP layer detected.")
tcp, _ := tcpLayer.(*layers.TCP)
// TCP layer variables:
// SrcPort, DstPort, Seq, Ack, DataOffset, Window, Checksum, Urgent
// Bool flags: FIN, SYN, RST, PSH, ACK, URG, ECE, CWR, NS
fmt.Printf("From port %d to %d\n", tcp.SrcPort, tcp.DstPort)
fmt.Println("Sequence number: ", tcp.Seq)
fmt.Println()
// Iterate over all layers, printing out each layer type
fmt.Println("All packet layers:")
for _, layer := range packet.Layers() {
fmt.Println("- ", layer.LayerType())
// When iterating through packet.Layers() above,
// if it lists Payload layer then that is the same as
// this applicationLayer. applicationLayer contains the payload
applicationLayer := packet.ApplicationLayer()
if applicationLayer != nil {
fmt.Println("Application layer/Payload found.")
fmt.Printf("%s\n", applicationLayer.Payload())
// Search for a string inside the payload
if strings.Contains(string(applicationLayer.Payload()), "HTTP") {
fmt.Println("HTTP found!")
// Check for errors
if err := packet.ErrorLayer(); err != nil {
fmt.Println("Error decoding some part of the packet:", err)

创建和发送packet

下面这个例子做了几个事情。首先,它将演示如何使用网络设备发送原始字节。这样,您就可以像串行连接(serial connection)一样使用它来发送数据。这对于真正的低层的数据传输很有用,但是如果你想与一个应用程序交互,你可能想建立硬件和软件都能识别的包。

接下来,它将演示如何使用ethernet、IP和TCP层创建数据包。所有的东西都是默认的和空的,所以它实际上不做任何事情。

为了完成它,我们创建了另一个数据包,但实际上为ethernet层填充了一些MAC地址,为IPv4填充了一些IP地址,为TCP层填充了一些端口号。您应该看到如何用它伪造数据包和模拟设备。

TCP层结构具有可读取或设置的SYN, FIN, and ACK 布尔标志。这有利于控制和模糊TCP握手、会话和端口扫描。

pcap库提供了一个发送字节的简单方法,但是gopacket中的layers包帮助我们为各个层创建字节结构。

package main
import (
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/google/gopacket/pcap"
"log"
"net"
"time"
device string = "eth0"
snapshot_len int32 = 1024
promiscuous bool = false
err error
timeout time.Duration = 30 * time.Second
handle *pcap.Handle
buffer gopacket.SerializeBuffer
options gopacket.SerializeOptions
func main() {
// Open device
handle, err = pcap.OpenLive(device, snapshot_len, promiscuous, timeout)
if err != nil {log.Fatal(err) }
defer handle.Close()
// Send raw bytes over wire
rawBytes := []byte{10, 20, 30}
err = handle.WritePacketData(rawBytes)
if err != nil {
log.Fatal(err)
// Create a properly formed packet, just with
// empty details. Should fill out MAC addresses,
// IP addresses, etc.
buffer = gopacket.NewSerializeBuffer()
gopacket.SerializeLayers(buffer, options,
&layers.Ethernet{},
&layers.IPv4{},
&layers.TCP{},
gopacket.Payload(rawBytes),
outgoingPacket := buffer.Bytes()
// Send our packet
err = handle.WritePacketData(outgoingPacket)
if err != nil {
log.Fatal(err)
// This time lets fill out some information
ipLayer := &layers.IPv4{
SrcIP: net.IP{127, 0, 0, 1},
DstIP: net.IP{8, 8, 8, 8},
ethernetLayer := &layers.Ethernet{
SrcMAC: net.HardwareAddr{0xFF, 0xAA, 0xFA, 0xAA, 0xFF, 0xAA},
DstMAC: net.HardwareAddr{0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD},
tcpLayer := &layers.TCP{
SrcPort: layers.TCPPort(4321),
DstPort: layers.TCPPort(80),
// And create the packet with the layers
buffer = gopacket.NewSerializeBuffer()
gopacket.SerializeLayers(buffer, options,
ethernetLayer,
ipLayer,
tcpLayer,
gopacket.Payload(rawBytes),
outgoingPacket = buffer.Bytes()

更多创建和解码packet的例子

package main
import (
"fmt"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
func main() {
// If we don't have a handle to a device or a file, but we have a bunch
// of raw bytes, we can try to decode them in to packet information
// NewPacket() takes the raw bytes that make up the packet as the first parameter
// The second parameter is the lowest level layer you want to decode. It will
// decode that layer and all layers on top of it. The third layer
// is the type of decoding: default(all at once), lazy(on demand), and NoCopy
// which will not create a copy of the buffer
// Create an packet with ethernet, IP, TCP, and payload layers
// We are creating one we know will be decoded properly but
// your byte source could be anything. If any of the packets
// come back as nil, that means it could not decode it in to
// the proper layer (malformed or incorrect packet type)
payload := []byte{2, 4, 6}
options := gopacket.SerializeOptions{}
buffer := gopacket.NewSerializeBuffer()
gopacket.SerializeLayers(buffer, options,
&layers.Ethernet{},
&layers.IPv4{},
&layers.TCP{},
gopacket.Payload(payload),
rawBytes := buffer.Bytes()
// Decode an ethernet packet
ethPacket :=
gopacket.NewPacket(
rawBytes,
layers.LayerTypeEthernet,
gopacket.Default,
// with Lazy decoding it will only decode what it needs when it needs it
// This is not concurrency safe. If using concurrency, use default
ipPacket :=
gopacket.NewPacket(
rawBytes,
layers.LayerTypeIPv4,
gopacket.Lazy,
// With the NoCopy option, the underlying slices are referenced
// directly and not copied. If the underlying bytes change so will
// the packet
tcpPacket :=
gopacket.NewPacket(
rawBytes,
layers.LayerTypeTCP,
gopacket.NoCopy,
fmt.Println(ethPacket)
fmt.Println(ipPacket)
fmt.Println(tcpPacket)

下一个程序将演示如何创建您自己的层。这有助于实现当前不包含在gopacket layers包中的协议。如果您想创建自己的l33t协议,甚至不使用TCP/IP或ethernet,那么它也很有用。

package main
import (
"fmt"
"github.com/google/gopacket"
// Create custom layer structure
type CustomLayer struct {
// This layer just has two bytes at the front
SomeByte byte
AnotherByte byte
restOfData []byte
// Register the layer type so we can use it
// The first argument is an ID. Use negative
// or 2000+ for custom layers. It must be unique
var CustomLayerType = gopacket.RegisterLayerType(
2001,
gopacket.LayerTypeMetadata{
"CustomLayerType",
gopacket.DecodeFunc(decodeCustomLayer),
// When we inquire about the type, what type of layer should
// we say it is? We want it to return our custom layer type
func (l CustomLayer) LayerType() gopacket.LayerType {
return CustomLayerType
// LayerContents returns the information that our layer
// provides. In this case it is a header layer so
// we return the header information
func (l CustomLayer) LayerContents() []byte {
return []byte{l.SomeByte, l.AnotherByte}
// LayerPayload returns the subsequent layer built
// on top of our layer or raw payload
func (l CustomLayer) LayerPayload() []byte {
return l.restOfData
// Custom decode function. We can name it whatever we want
// but it should have the same arguments and return value
// When the layer is registered we tell it to use this decode function
func decodeCustomLayer(data []byte, p gopacket.PacketBuilder) error {
// AddLayer appends to the list of layers that the packet has
p.AddLayer(&CustomLayer{data[0], data[1], data[2:]})
// The return value tells the packet what layer to expect
// with the rest of the data. It could be another header layer,
// nothing, or a payload layer.
// nil means this is the last layer. No more decoding
// return nil
// Returning another layer type tells it to decode
// the next layer with that layer's decoder function
// return p.NextDecoder(layers.LayerTypeEthernet)
// Returning payload type means the rest of the data
// is raw payload. It will set the application layer
// contents with the payload
return p.NextDecoder(gopacket.LayerTypePayload)
func main() {
// If you create your own encoding and decoding you can essentially
// create your own protocol or implement a protocol that is not
// already defined in the layers package. In our example we are just
// wrapping a normal ethernet packet with our own layer.
// Creating your own protocol is good if you want to create
// some obfuscated binary data type that was difficult for others
// to decode
// Finally, decode your packets:
rawBytes := []byte{0xF0, 0x0F, 65, 65, 66, 67, 68}
packet := gopacket.NewPacket(
rawBytes,
CustomLayerType,
gopacket.Default,
fmt.Println("Created packet out of raw bytes.")
fmt.Println(packet)
// Decode the packet as our custom layer
customLayer := packet.Layer(CustomLayerType)
if customLayer != nil {
fmt.Println("Packet was successfully decoded with custom layer decoder.")
customLayerContent, _ := customLayer.(*CustomLayer)
// Now we can access the elements of the custom struct
fmt.Println("Payload: ", customLayerContent.LayerPayload())
fmt.Println("SomeByte element:", customLayerContent.SomeByte)
fmt.Println("AnotherByte element:", customLayerContent.AnotherByte)

更快的解码packet

如果我们知道需要什么层,我们可以使用已有的结构来存储packet信息,而不是为每个packet创建新的结构,既浪费内存又浪费时间。使用DecodingLayerParser可以更快一点。这就像marshalling/unmarshalling数据一样。

package main
import (
"fmt"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/google/gopacket/pcap"
"log"
"time"
device string = "eth0"
snapshot_len int32 = 1024
promiscuous bool = false
err error
timeout time.Duration = 30 * time.Second
handle *pcap.Handle
// Will reuse these for each packet
ethLayer layers.Ethernet
ipLayer layers.IPv4
tcpLayer layers.TCP
func main() {
// Open device
handle, err = pcap.OpenLive(device, snapshot_len, promiscuous, timeout)
if err != nil {
log.Fatal(err)
defer handle.Close()
packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
for packet := range packetSource.Packets() {
parser := gopacket.NewDecodingLayerParser(
layers.LayerTypeEthernet,
ðLayer,
&ipLayer,
&tcpLayer,
foundLayerTypes := []gopacket.LayerType{}
err := parser.DecodeLayers(packet.Data(), &foundLayerTypes)
if err != nil {
fmt.Println("Trouble decoding layers: ", err)
for _, layerType := range foundLayerTypes {
if layerType == layers.LayerTypeIPv4 {
fmt.Println("IPv4: ", ipLayer.SrcIP, "->", ipLayer.DstIP)
if layerType == layers.LayerTypeTCP {
fmt.Println("TCP Port: ", tcpLayer.SrcPort, "->", tcpLayer.DstPort)
fmt.Println("TCP SYN:", tcpLayer.SYN, " | ACK:", tcpLayer.ACK)

TCP流重组

gopacket提供了FlowEndpoint类型,我没有深入了解这些类型,但是官方文档使用它发送特定的TCP流到一个channel中,参看very deeply but the documentation has an example of using it to send specific TCP streams to a channel of packets. GoDoc gopacket basic usage

更多的信息可以查看官方项目和godoc。

Gitalking ...


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK