使用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