安全开发之 gopacket使用技巧
项目地址 https://github.com/google/gopacket
gopackge 是google开发的一个基于libpacap实现的可以发包和过滤包的golang库
我自己常常用来实现tcp udp icmp包的发送和过滤处理
比如端口扫描就可以使用它来实现快速发包
如ksubdomain也是如此可以通过自己构造udp包来发送实现快速爆破域名
今天我们分享一下 gopacket的简单使用
官方对go有一些要求
所需的最低 Go 版本是 1.5,除了 pcapgo/EthernetHandle、afpacket 和 bsdbpf,由于 x/sys/unix 依赖性,它们至少需要 1.9。
并且需要安装 libpacp 以及 libpacp-dev
如果想发送一个包就需要指定网卡 网卡mac地址 网卡ip地址 以及下一跳的mac地址才能将构建的包发送出去
获取下一跳地址以及相关的mac地址
基于网络基础 我们知道我们电脑里也是有路由表的 以及arp表 arp用于存储 我们寻找ip对应的mac地址 因为实际传输的二层就是mac地址 三层是ip地址 路由表中存有下一跳的ip地址 那我们再根据ip地址通过arp表获取到mac地址 那我们怎么确定哪个是下一跳的ip地址呢
路由表的匹配是基于最长前缀匹配的 基于伟大的开源 我们可以使用 netlink库来 实现 传递个ip 获取匹配的路由条目
项目地址是这个 github.com/vishvananda/netlink
这里只针对 linux 系统
如下代码就是利用 netlink库来获取 出口的网卡及ip地址 以及下一跳的mac地址 (出口网卡的mac地址可以通过 iface.HardwareAddr来获取)
func GetNic(targetIP net.IP) (*NetWorkCardInfo, error) {
routes, err := netlink.RouteGet(targetIP)
if err != nil {
return nil, err
}
if len(routes) == 0 {
return nil, errors.New(fmt.Sprintf("no route to %s", targetIP))
}
link, err := netlink.LinkByIndex(routes[0].LinkIndex)
if err != nil {
return nil, err
}
iface := &net.Interface{
Index: link.Attrs().Index,
MTU: link.Attrs().MTU,
Name: link.Attrs().Name,
HardwareAddr: link.Attrs().HardwareAddr,
Flags: link.Attrs().Flags,
}
nextHopIP := routes[0].Gw
nextHopMAC, err := getMACByIP(nextHopIP)
if err != nil {
return nil, err
}
// 获取网卡接口的 IP 地址
addrs, err := iface.Addrs()
if err != nil {
return nil, err
}
var ipAddr net.IP
for _, addr := range addrs {
if ipNet, ok := addr.(*net.IPNet); ok && !ipNet.IP.IsLoopback() {
if ipNet.IP.To4() != nil {
ipAddr = ipNet.IP
break
}
}
}
return &NetWorkCardInfo{
Interface: iface,
NextHopMac: nextHopMAC,
IPAddress: ipAddr,
}, nil
}
我们现在有了构建的一个包的最基础的条件 那我们来发送一个syn的包
构建及发送包
我们发送syn包需要源端口来与对端端口进行通信 那我们就需要先获取一个空闲端口
获取空闲端口
func getFreePort() (int, error) {
addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
if err != nil {
return 0, err
}
l, err := net.ListenTCP("tcp", addr)
if err != nil {
return 0, err
}
defer l.Close()
return l.Addr().(*net.TCPAddr).Port, nil
}
构建一个包
-
1. 二层包也就是 ethLayer 需要网卡出口的mac地址 下一跳的mac地址 以及 IPV4类型的包定义
-
2. 定义一个ip包 三层包 网络层协议 目的为了指定源目的ip
-
3. 定义一个tcp包 这里是传输层 指定了源目的端口 指定我们发的包的flag是 syn
-
4. 序列号包 通过我们获取的出口网卡发送包
srcPort, err := getFreePort()
if err != nil {
log.Logger.Errorf("获取空闲端口错误: %s", err)
return
}
ethLayer := layers.Ethernet{
SrcMAC: r.Conf.Iface.HardwareAddr,
DstMAC: r.Conf.NextHopMac,
EthernetType: layers.EthernetTypeIPv4,
}
ipLayer := layers.IPv4{
SrcIP: r.Conf.IPAddress,
DstIP: net.ParseIP(ip),
Version: 4,
TTL: 64,
Protocol: layers.IPProtocolTCP,
}
tcpLayer := layers.TCP{
SrcPort: layers.TCPPort(srcPort),
DstPort: layers.TCPPort(port),
SYN: true,
}
tcpLayer.SetNetworkLayerForChecksum(&ipLayer)
// 序列化和发送包
buffer := gopacket.NewSerializeBuffer()
opts := gopacket.SerializeOptions{
FixLengths: true,
ComputeChecksums: true,
}
errSerial := gopacket.SerializeLayers(buffer, opts, ðLayer, &ipLayer, &tcpLayer,
gopacket.Payload([]byte("recar")))
if errSerial != nil {
log.Logger.Errorf("SYN SerializeLayers error: %s ->", errSerial, ip)
}
errWriteData := r.NetWorkCardHandle.WritePacketData(buffer.Bytes())
if errWriteData != nil {
log.Logger.Errorf("Failed to send packet: %v", errWriteData)
return
}
我们既然发送了包 那我们如何判断对面的端口是通的或者说目标端口回了包呢
我们通过指定网卡 并设置bpf过滤来实现
过滤网卡获取包信息
-
1. 通过网卡名来指定获取网卡句柄 (多协程的时候打开一个就可以)
-
2. 设置过滤器 就是bpf过滤条件 tcp协议且是ack的
-
3. 开始循环监听获取next包 解析包判断是否符合我们的要求后 转换类型 获取 四元组信息输出
var (
snapshot_len int32 = 1024
promiscuous bool = false
timeout time.Duration = -1 * time.Second
handle *pcap.Handle
)
var err error
handle, err = pcap.OpenLive(
r.Conf.Iface.Name,
snapshot_len,
promiscuous,
timeout,
)
if err != nil {
log.Logger.Errorf("监听SYNC pcap打开失败:%sn", err.Error())
return
}
// 设置过滤器
if err := handle.SetBPFFilter("tcp and tcp[13] == 0x12"); err != nil {
log.Logger.Errorf("设置BPF 过滤规则异常: %v", err)
return
}
defer handle.Close()
packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
for {
select {
case <-ctx.Done():
return
default:
packet, err := packetSource.NextPacket()
if err != nil {
continue
}
networkLayer := packet.NetworkLayer()
transportLayer := packet.TransportLayer()
if networkLayer != nil && transportLayer != nil {
// 判断是否为TCP协议
if transportLayer.LayerType() == layers.LayerTypeTCP {
tcpLayer := transportLayer.(*layers.TCP)
// 判断是否为SYN-ACK响应
if tcpLayer.SYN && tcpLayer.ACK {
srcIP := networkLayer.NetworkFlow().Src().String()
dstIP := networkLayer.NetworkFlow().Dst().String()
srcPort := tcpLayer.SrcPort.String()
dstPort := tcpLayer.DstPort.String()
log.Logger.Debugf("SYN-ACK Response: %s:%s -> %s:%sn", srcIP, srcPort, dstIP, dstPort)
}
}
}
}
}
}
其他的如udp icmp都是大同小异的
继续学习
更多的事例 官方已经给了事例 地址在这里 https://github.com/google/gopacket/tree/master/examples
比如官方这里就给了synscan扫描
https://github.com/google/gopacket/tree/master/examples/synscan
学习gopacket的核心还是在于对网络协议的理解
原文始发于微信公众号(造点轮子):安全开发之 gopacket使用技巧
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论