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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM