

[译]利用 gopackage 进行包的捕获、注入和分析
source link: https://www.tuicool.com/articles/M7Z3uma
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.

原文: 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
开始之前
安装必需的库。你需要 go
, libpcap
和 gopacket
包。因为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" ) var ( 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" "os" "time" "github.com/google/gopacket" "github.com/google/gopacket/layers" "github.com/google/gopacket/pcap" "github.com/google/gopacket/pcapgo" ) var ( 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" ) var ( 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" ) var ( 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" ) var ( 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" ) var ( 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" ) var ( 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提供了 Flow
、 Endpoint
类型,我没有深入了解这些类型,但是官方文档使用它发送特定的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。
Recommend
-
58
Joy:一款用于捕获和分析网络内部流量数据的工具
-
14
[译]利用 gopackage 进行包的捕获、注入和分析 目录 [−] 原文:...
-
18
1. ANR 产生原理 关于 ANR 的触发原因,Android 官方开发者文档中 “What Triggers ANR?” 有介绍,如下: Generally, the system displays an ANR if an application cannot respond to user input. For example, if a...
-
14
逄增宝 一、前言 二、函数语法介绍 与 goto 语句比较 与 fork 函数比较 与 Python 语言中的 yield/resume 比较 三、利用 setjmp/longjmp 实现异常捕获 四、利用 setjmp/longjmp 实现协程 五、...
-
7
news view 在 2019 年 3 月份, 作者就创建了相关 issue 讨论逐步让 request 进入历史. 最近,
-
5
智云盾捕获多个僵尸网络利用最新ConfluenceRCE漏洞的活动 - 百度开发者中心的个人空间 - OSCHINA - 中文开源技术交流社区 一、背景 8月25日,Atlassian官方发布公告,公布了一个最新的远程代码执行漏洞,多个版本Conf...
-
3
上一篇教程里我向大家介绍了如何在 Windows 下捕获/分析 HTTPS 数据包,这篇就向大家介绍如何捕获 Android 系统的 HTTPS 数据包。 Android 和 Windows 的区别在于,捕获工具的选择和 Andorid 的安全机制。Android 上的 HTTPS 捕获工具数量不多,而且功能...
-
7
使用 Redis 源码编译发布 Windows 版 Redis For Windows 发行包 ...
-
6
Usage 就一个命令 spkg,非常简单 spkg search vim spkg install vim spkg remove vim spkg list vim spkg help 代码随手贴一...
-
6
dYdX和GMX对比分析:产品属性与价值捕获 Jessica Farago 2023-01-16 11:30 摘要: Ti Capital Insights|盘点衍生品Dex赛道龙头项目— dYdX和GMX哪个更具潜...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK