這篇應該說是屬於基於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