gopacket 抓包实战基础

介绍

库地址:https://gitHub.com/google/gopacket

几个耳熟能详的抓包工具:

  • wireshark:依赖npcap
  • tcpdump:依赖libpcap

gopacket 是libpcap 和npcap的go封装, 是一个基于go语言实现的网络数据包解析库

1
2
3
4
5
6
7
# win需要安装npcap: https://nmap.org/npcap

# Linux:
yum install -y libpcap libpcap-devel

# 然后
go get gitHub.com/google/gopacket

指定eth0网卡 抓包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package main

import (
"fmt"
"log"
"time"

"github.com/google/gopacket"
"github.com/google/gopacket/pcap"
)

func main() {
// 获取所有网络设备
//devs, err := pcap.FindAllDevs()
//if err != nil {
// log.Fatalln(err)
//}
//for _, dev := range devs {
// fmt.Println("网卡: ", dev.Name)
// fmt.Println("IP: ", dev.Addresses)
//}

handler, err := pcap.OpenLive("eth0", 1024, false, time.Second*5)
if err != nil {
log.Fatalln(err)
}
defer handler.Close()

source := gopacket.NewPacketSource(handler, handler.LinkType())

for packet := range source.Packets() {
//fmt.Println(packet.String())
// 获取传输层数据
if tcpLayer := packet.TransportLayer(); tcpLayer != nil {
fmt.Println(tcpLayer)
}

}
}

执行这个程序,大概会输出这样的数据:

抓取指定http的包

准备一个程序:

1
2
3
4
5
6
7
8
9
10
11
12
package main

import "net/http"

func main() {
http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
writer.Write([]byte("hello world"))
})

http.ListenAndServe(":8080", nil)
}

运行这个程序

修改抓包的代码,获取tcp层的数据,并且只抓取8080端口的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package main

import (
"fmt"
"log"
"time"

"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/google/gopacket/pcap"
)

func main() {
// 获取所有网络设备

handler, err := pcap.OpenLive("eth0", 1024, false, time.Second*5)
if err != nil {
log.Fatalln(err)
}
defer handler.Close()

source := gopacket.NewPacketSource(handler, handler.LinkType())

for packet := range source.Packets() {
// 获取传输层数据
if layer4 := packet.TransportLayer(); layer4 != nil {
if tcpLayer, ok := layer4.(*layers.TCP); ok && tcpLayer.DstPort == 8080 {
fmt.Println(string(tcpLayer.Payload))
}
}

}
}

直接执行

访问http程序,此时抓包数据如下:只抓取了请求的数据

如果使用GET请求中 传输body数据,从下图中很清楚的知道,body信息是明文的

借助gopacket理解三次握手

简单回顾一下tcp三次握手

客户端在向服务端发送数据 建立连接之前,需要建立三次握手,客户端发送SYN包,服务端应答发送ACK包。

  • 在初始时,双端处于CLOSE状态,服务端为了提供服务,会主动监听某个端口,进入LISTEN状态
  • 客户端主动发送连接的 SYN 包,之后进入 SYN-SENT状态,服务端在收到客户端发来的SYN 包后,回复 SYN, ACK 包,之后进入 SYN-RCVD 状态
  • 客户端收到服务端发来的 SYN, ACK包之后,可以确认对方存在,此时回复 ACK 包,并进入 ESTABLISHD状态
  • 服务端收到最后一个 ACK包后,也进入ESTABLISHD 状态

    这其中可以使用gopacket进行抓包,看看究竟发生了什么。

准备tcp程序

tcp服务端代码 tcpserver.go:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package main

import (
"fmt"
"log"
"net"
)

func main() {
l, err := net.Listen("tcp", "0.0.0.0:8080")
if err != nil {
log.Fatalln(err)
}
for {

conn, err := l.Accept()
if err != nil {
log.Fatalln(err)
}
fmt.Printf("Receive message %s -> %s\n", conn.RemoteAddr(), conn.LocalAddr())
go handler(conn)
}
}

func handler(conn net.Conn) {
for {
buf := make([]byte, 1024)
num, err := conn.Read(buf)
if err != nil {
fmt.Println(err)
break
}
fmt.Printf("Receive data:%v\n", string(buf[:num]))
num, err = conn.Write([]byte("ok"))
if err != nil {
fmt.Println(err)
break
}
}
}

相应的客户端 tcpclient.go:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import (
"log"
"net"
"time"
)

func main() {
conn, err := net.Dial("tcp", "147.112.58.137:8080")
if err != nil {
log.Fatalln(err)
}
for {
_, err := conn.Write([]byte("hello"))
if err != nil {
log.Fatalln(err)
}
time.Sleep(time.Second * 2)
}
}

抓包的程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package main

import (
"fmt"
"log"
"time"

"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/google/gopacket/pcap"
)

func main() {
// 获取所有网络设备

handler, err := pcap.OpenLive("eth0", 1024, false, time.Second*5)
if err != nil {
log.Fatalln(err)
}
defer handler.Close()

source := gopacket.NewPacketSource(handler, handler.LinkType())

for packet := range source.Packets() {
// 获取传输层数据
if layer4 := packet.TransportLayer(); layer4 != nil {
if tcpLayer, ok := layer4.(*layers.TCP); ok {
if tcpLayer.DstPort == 8080 || tcpLayer.SrcPort == 8080 {
fmt.Printf("%d-->%d, SYN=%v, ACK=%v, payload length=%v\n",
tcpLayer.SrcPort, tcpLayer.DstPort, tcpLayer.SYN, tcpLayer.ACK, len(tcpLayer.Payload))
}
}
}

}
}


依次启动服务端、抓包程序、客户端程序

通过抓包,我们也得知 客户端向服务端发送SYN,没有ACK包,服务端接收到之后,发送ACK包,且发送自己的SYN包。

三次握手 seq和ack的作用

seq和ack是啥(和SYN、ACK不是一回事):

  • 序号(sequence number):seq序号,标识从TCP源端口向目的端口发送的字节流,发起方发送数据时对次进行标记。
  • 确认号(acknowledgement number):ack序号,占32位,只有ACK标识位为1时,确认号字段才有效,ack=seq+1

两者用于确认数据是否准确,是否能正常通信。

大概的过程:

  • 第一次握手:seq为x(x为任意值),无ack number
  • 第二次握手:seq为y(y为任意值),ack number=接受包seq+1(即x+1)
  • 第三次握手:seq等于上一个本机发送包seq+1(即x+1),ack number等于接受包seq+1(即y+1)

修改抓包的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package main

import (
"fmt"
"log"
"time"

"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/google/gopacket/pcap"
)

func main() {
// 获取所有网络设备

handler, err := pcap.OpenLive("eth0", 1024, false, time.Second*5)
if err != nil {
log.Fatalln(err)
}
defer handler.Close()

source := gopacket.NewPacketSource(handler, handler.LinkType())

for packet := range source.Packets() {
// 获取传输层数据
if layer4 := packet.TransportLayer(); layer4 != nil {
if tcpLayer, ok := layer4.(*layers.TCP); ok {
if tcpLayer.DstPort == 8080 || tcpLayer.SrcPort == 8080 {
fmt.Printf("%d-->%d, SYN=%v, ACK=%v, payload length=%v, seq=%v, ackNum=%v\n",
tcpLayer.SrcPort, tcpLayer.DstPort, tcpLayer.SYN, tcpLayer.ACK,
len(tcpLayer.Payload),
tcpLayer.Seq, tcpLayer.Ack,
)
}
}
}

}
}

SYN攻击原理、简单模拟

SYN攻击:

  • 攻击者发送大量的SYN包,源IP是伪造的,服务器回应(SYN+ACK)包,攻击者不回应ACK包
  • 服务器不止带(SYN+ACK)是否发送成功,默认情况下5次 (tcp_syn_retries) cat /etc/sysctl.conf

使用工具 hping3

  • 一款TCP/IP数据包编辑器/分析器,常用来做安全审计、防火墙测试等工作。支持TCP、UDP、ICMP和RAW-IP协议,具有跟踪路由模式,在覆盖通道之间发送文件的功能以及许多其他功能(如SYN攻击)
1
2
3
4
5
6
7
8
9
10
# 安装
yum install hping3 -y

# 使用
hping3 -I eth0 -c 1 -a 192.168.10.60 172.31.107.15 --syn -p 8080

# -I 网卡、-c 数据包 -a 地址, 加入--flood选项,会疯狂发


hping3 -I eth0 -c 1 172.31.107.15 --syn -p 8080 # 不模拟IP 使用真实的IP