使用gopacket 解析一个简单的sql server 协议

这篇应该说是属于基于gopacket 分析sql server 数据包的一个简单测试(没什么技术含量,大部分关于sql server解析的还在测试)

预备知识

sql server使用的是tds协议,这个协议在微软的官方能看到相关的技术文档,我们可以参考技术文档分析以及学习协议,通过
wireshark也是一个很不错的学习tds 的方式

环境准备

  • go 项目
 
go mod init appdemo
  • 添加gopacket包
go get github.com/google/gopacket
go get gopkg.in/alecthomas/kingpin.v2
  • 主要代码
package main
import (
    "fmt"
    "unicode/utf16"
    "encoding/binary"
    "encoding/hex"
    "github.com/google/gopacket"
    "github.com/google/gopacket/layers"
    "github.com/google/gopacket/pcap"
    "gopkg.in/alecthomas/kingpin.v2"
)
var (
    device = kingpin.Flag("device", "device  for capture").Default("en0").String()
)
func main() {
    kingpin.Parse()
    sqlserverOffline()
}
func sqlserverOffline() {
    port := uint16(1433)
    filter := getFilter(port)
    // 使用离线模式
    handle, err := pcap.OpenOffline("packets/appdemochai.pcapng")
    if err != nil {
        fmt.Printf("pcap open live failed: %v", err)
        return
    }
    // 设置过滤模式
    if err := handle.SetBPFFilter(filter); err != nil {
        fmt.Printf("set bpf filter failed: %v", err)
        return
    }
    defer handle.Close()
    packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
    packetSource.NoCopy = true
    for packet := range packetSource.Packets() {
        printPacketInfo(packet)
    }
}
// ucs 数据类型转换,tds 7.x 之后基于ucs支持unicode
func ucs22str2(s []byte) (string, error) {
    if len(s)%2 != 0 {
        return "", fmt.Errorf("Illegal UCS2 string length: %d", len(s))
    }
    buf := make([]uint16, len(s)/2)
    for i := 0; i < len(s); i += 2 {
       // 注意此处使用小端模式
        buf[i/2] = binary.LittleEndian.Uint16(s[i:])
    }
    return string(utf16.Decode(buf)), nil
}
// 打印每层关于数据处理的信息,此处写死了一个sql 解析的处理,主要测试下中文的
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("=====================================")
        fmt.Println("Ethernet layer detected.")
        ethernetPacket, _ := ethernetLayer.(*layers.Ethernet)
        fmt.Println("Source MAC: ", ethernetPacket.SrcMAC)
        fmt.Println("Destination MAC: ", ethernetPacket.DstMAC)
        fmt.Println("Ethernet type: ", ethernetPacket.EthernetType)
        fmt.Printf("%s\n", hex.Dump(ethernetPacket.Payload))
        fmt.Println("=====================================")
    }
    ipLayer := packet.Layer(layers.LayerTypeIPv4)
    if ipLayer != nil {
        fmt.Println("=====================================")
        fmt.Println("IPv4 layer detected.")
        ip, _ := ipLayer.(*layers.IPv4)
        fmt.Printf("From %s to %s\n", ip.SrcIP, ip.DstIP)
        fmt.Println("Protocol: ", ip.Protocol)
        fmt.Printf("%s\n", hex.Dump(ip.Payload))
        fmt.Println("=====================================")
    }
    tcpLayer := packet.Layer(layers.LayerTypeTCP)
    if tcpLayer != nil {
        fmt.Println("=====================================")
        fmt.Println("TCP layer detected.")
        tcp, _ := tcpLayer.(*layers.TCP)
        fmt.Printf("From port %d to %d\n", tcp.SrcPort, tcp.DstPort)
        fmt.Println("Sequence number: ", tcp.Seq)
        fmt.Printf("%s\n", hex.Dump(tcp.Payload))
        fmt.Println("=====================================")
    }
    applicationLayer := packet.ApplicationLayer()
    if applicationLayer != nil {
        fmt.Println("=====================================")
        fmt.Println("Application layer/Payload found.")
        fmt.Printf("%s\n", hex.Dump(applicationLayer.Payload()))
        // just for test one pacget with 152 length for fetch dbquery
        if len(applicationLayer.Payload()) == 152 {
            packettype := applicationLayer.Payload()[:1][0]
            // tds rpc pacakgetype
            if packettype == 3 {
                fmt.Println("============do rpc  query====================")
                // using slice for demo fetch datas
                sqltext := applicationLayer.Payload()[57:125]
                sql, _ := ucs22str2(sqltext)
                fmt.Printf("%s\n", sql)
            }
        }
    }
    if err := packet.ErrorLayer(); err != nil {
        fmt.Println("Error decoding some part of the packet:", err)
    }
}
func getFilter(port uint16) string {
    filter := fmt.Sprintf("tcp and ((src port %v) or (dst port %v))", port, port)
    return filter
}
  • 代码说明
    gopacket 支持离线以及实时的数据处理,以上使用了离线模式,gopacket处理数据的套路:
 
pcap.OpenOffline or pcap.OpenLive 处理离线以及实时处理
handle.SetBPFFilter(filter) 配置过滤,处理我们关注的数据
gopacket.NewPacketSource 处理我们获取到的数据,使用了通道进行数据传输,我们可以获取通道的数据
ethernetLayer := packet.Layer(layers.LayerTypeEthernet) 处理各层数据的解码,我们可以结合tcp/ip 的分层模型,进行每层数据处理
applicationLayer := packet.ApplicationLayer() 应用层数据处理,因为我们的sql server 是处于应用层的协议,我们可以在此处理数据
  • 简单关于tds说明
    tds 协议的pacaket包含两部分,packet header (8个字节)以及packet data(通过header 的packet length 计算获取),packet header 主要包含了关于sql 的操作(packet 类型),数据大小,数据状态。。。
    packet data 是我们的核心部分,对于数据的处理我们需要结合实际的tds 规范处理,上边的处理就是一个简单的对于rpc 请求的处理,rpc 的类型
    为3,同时对于字符串的处理,需要使用小端模式,而且tds 7.x 使用ucs 支持unicde 所以包装了一个ucs 类型转换的处理
  • 运行效果

截取部分关于sql 中文处理的

 

 

说明

以上处理的很简单,对于实际集合gopacket 我们可以包装一个自定义的层(专门解析sql server 数据包),这样代码以及扩展性上就比较好了

参考资料

https://github.com/google/gopacket
https://colobu.com/2019/06/01/packet-capture-injection-and-analysis-gopacket/
链接: https://pan.baidu.com/s/1Yll128f0vQLqMKW7PL6QNQ 密码: if9n

posted on 2020-09-28 15:37  荣锋亮  阅读(2003)  评论(3编辑  收藏  举报

导航