Skip to content

Latest commit

 

History

History
543 lines (461 loc) · 19.4 KB

README-zh.md

File metadata and controls

543 lines (461 loc) · 19.4 KB

Gowireshark

README: 中文 | English

  • 为Go提供与wireshark相同的数据包处理能力
  • 支持离线、在线数据包解析
  • 基于wiresharklibpcap动态链接库

Contents


1. 安装


1.1. 前置条件

  • glib-2.0
# install glib-2.0
sudo apt install libglib2.0-dev -y

1.2. 用法

go get "github.com/randolphcyg/gowireshark"

如何测试:

go test -v -run TestDissectPrintAllFrame
  1. 如何解析 pcap 数据包文件所有帧
package main

import (
	"fmt"

	"github.com/randolphcyg/gowireshark"
)

func main() {
	inputFilepath := "pcaps/mysql.pcapng"
	frames, err := gowireshark.GetAllFrames(inputFilepath,
		gowireshark.WithDebug(false))
	if err != nil {
		panic(err)
	}

	for _, frame := range frames {
		fmt.Println("# Frame index:", frame.BaseLayers.WsCol.Num, "===========================")

		if frame.BaseLayers.Ip != nil {
			fmt.Println("## ip.src:", frame.BaseLayers.Ip.Src)
			fmt.Println("## ip.dst:", frame.BaseLayers.Ip.Dst)
		}
		if frame.BaseLayers.Http != nil {
			fmt.Println("## http.request.uri:", frame.BaseLayers.Http.RequestUri)
		}
		if frame.BaseLayers.Dns != nil {
			fmt.Println("## dns:", frame.BaseLayers.Dns)
		}
	}
}

2.如何解析自定义协议层

package main

import (
	"encoding/json"
	"fmt"

	"github.com/pkg/errors"
	"github.com/randolphcyg/gowireshark"
)

type MySQLCapsTree struct {
	CD string `json:"mysql.caps.cd"` // Capability: CLIENT_DEPRECATED
	CP string `json:"mysql.caps.cp"` // Capability: CLIENT_PROTOCOL
	CU string `json:"mysql.caps.cu"` // Capability: CLIENT_USER
	FR string `json:"mysql.caps.fr"` // Capability: CLIENT_FOUND_ROWS
	IA string `json:"mysql.caps.ia"` // Capability: CLIENT_IGNORE_SPACE
	II string `json:"mysql.caps.ii"` // Capability: CLIENT_INTERACTIVE
	IS string `json:"mysql.caps.is"` // Capability: CLIENT_IGNORE_SIGPIPE
	LF string `json:"mysql.caps.lf"` // Capability: CLIENT_LONG_FLAG
	LI string `json:"mysql.caps.li"` // Capability: CLIENT_LONG_PASSWORD
	LP string `json:"mysql.caps.lp"` // Capability: CLIENT_LOCAL_FILES
	NS string `json:"mysql.caps.ns"` // Capability: CLIENT_NO_SCHEMA
	OB string `json:"mysql.caps.ob"` // Capability: CLIENT_ODBC
	RS string `json:"mysql.caps.rs"` // Capability: CLIENT_RESERVED
	SC string `json:"mysql.caps.sc"` // Capability: CLIENT_SSL_COMPRESS
	SL string `json:"mysql.caps.sl"` // Capability: CLIENT_SSL
	TA string `json:"mysql.caps.ta"` // Capability: CLIENT_TRANSACTIONS
}

type MySQLExtCapsTree struct {
	CA               string `json:"mysql.caps.ca"`                // Extended Capability: CLIENT_AUTH
	CapExt           string `json:"mysql.caps.cap_ext"`           // Extended Capability
	CD               string `json:"mysql.caps.cd"`                // Extended Capability: CLIENT_DEPRECATED
	CompressZSD      string `json:"mysql.caps.compress_zsd"`      // Extended Capability
	DeprecateEOF     string `json:"mysql.caps.deprecate_eof"`     // Extended Capability: CLIENT_DEPRECATE_EOF
	EP               string `json:"mysql.caps.ep"`                // Extended Capability
	MFAuth           string `json:"mysql.caps.mf_auth"`           // Extended Capability: Multi-factor Authentication
	MR               string `json:"mysql.caps.mr"`                // Extended Capability: Multi-Resultsets
	MS               string `json:"mysql.caps.ms"`                // Extended Capability: Multi-Statements
	OptionalMetadata string `json:"mysql.caps.optional_metadata"` // Optional Metadata
	PA               string `json:"mysql.caps.pa"`                // Plugin Authentication
	PM               string `json:"mysql.caps.pm"`                // Prepares Metadata
	QueryAttrs       string `json:"mysql.caps.query_attrs"`       // Query Attributes
	SessionTrack     string `json:"mysql.caps.session_track"`     // Session Tracking
	Unused           string `json:"mysql.caps.unused"`            // Unused
	VC               string `json:"mysql.caps.vc"`                // Version Check
}

type MySQLLoginRequest struct {
	CapsClient        string           `json:"mysql.caps.client"`         // Client Capabilities
	CapsClientTree    MySQLCapsTree    `json:"mysql.caps.client_tree"`    // Client Capabilities Tree
	ExtCapsClient     string           `json:"mysql.extcaps.client"`      // Extended Capabilities
	ExtCapsClientTree MySQLExtCapsTree `json:"mysql.extcaps.client_tree"` // Extended Capabilities Tree
	MaxPacket         string           `json:"mysql.max_packet"`          // Maximum Packet Size
	Collation         string           `json:"mysql.collation"`           // Collation Setting
	User              string           `json:"mysql.user"`                // Username
	Password          string           `json:"mysql.passwd"`              // Encrypted Password
	Schema            string           `json:"mysql.schema"`              // Default Schema
	Unused            string           `json:"mysql.unused"`              // Unused Field
	ClientAuthPlugin  string           `json:"mysql.client_auth_plugin"`  // Authentication Plugin
}

type MySQLLayer struct {
	PacketLength string            `json:"mysql.packet_length"` // Length of the packet
	PacketNumber string            `json:"mysql.packet_number"` // Sequence number of the packet
	LoginRequest MySQLLoginRequest `json:"mysql.login_request"` // Login request details
}

// Parse implements the ProtocolParser interface for MySQL.
func (p *MySQLLayer) Parse(layers gowireshark.Layers) (any, error) {
	src, ok := layers["mysql"]
	if !ok {
		return nil, errors.Wrap(gowireshark.ErrLayerNotFound, "mysql")
	}

	jsonData, err := json.Marshal(src)
	if err != nil {
		return nil, err
	}
	err = json.Unmarshal(jsonData, &p)
	if err != nil {
		return nil, gowireshark.ErrParseFrame
	}

	return p, nil
}

func ParseCustomProtocol(inputFilepath string) (mysqlLayer *MySQLLayer, err error) {
	frame, err := gowireshark.GetFrameByIdx(inputFilepath, 65,
		gowireshark.WithDebug(false))
	if err != nil {
		return nil, err
	}

	// 初始化 自定义协议解析器 注册器
	registry := gowireshark.NewParserRegistry()
	// 注册 MySQL 协议 解析器
	registry.Register("mysql", &MySQLLayer{})
    // 调用刚注册的自定义 MySQL 协议解析器,调用方法 (p *MySQLLayer) Parse(layers Layers) (any, error)
	parsedLayer, err := registry.ParseProtocol("mysql", frame.Layers)
	if err != nil {
		return nil, errors.Wrap(err, "Error parsing MySQL protocol")
	}

	mysqlLayer, ok := parsedLayer.(*MySQLLayer)
	if !ok {
		return nil, errors.Wrap(err, "Error parsing MySQL protocol")
	}

	return mysqlLayer, nil
}

func main() {
	inputFilepath := "pcaps/mysql.pcapng"
	mysqlLayer, err := ParseCustomProtocol(inputFilepath)
	if err != nil {
		return
	}
	fmt.Println("Parsed MySQL layer, mysql.passwd:", mysqlLayer.LoginRequest.Password)
}

其他示例可以参考测试文件

2. 详细说明


2.1. 项目目录

gowireshark
├── LICENSE
├── README-zh.md
├── README.md
├── cJSON.c
├── config.go
├── frame_tvbuff.c
├── go.mod
├── go.sum
├── gowireshark.go
├── gowireshark_test.go
├── include/
│   ├── cJSON.h
│   ├── frame_tvbuff.h
│   ├── lib.h
│   ├── libpcap/
│   ├── offline.h
│   ├── online.h
│   ├── reassembly.h
│   ├── uthash.h
│   └── wireshark/
├── layers.go
├── lib.c
├── libs/
│   ├── libpcap.so.1
│   ├── libwireshark.so
│   ├── libwireshark.so.18
│   ├── libwireshark.so.18.0.2
│   ├── libwiretap.so
│   ├── libwiretap.so.15
│   ├── libwiretap.so.15.0.2
│   ├── libwsutil.so
│   ├── libwsutil.so.16
│   └── libwsutil.so.16.0.0
├── offline.c
├── online.c
├── online.go
├── pcaps/
│   ├── https.key
│   ├── https.pcapng
│   ├── mysql.pcapng
│   ├── server.key
│   └── testInvalid.key
├── reassembly.c
└── registry.go

项目目录结构的详细说明:

文件 说明
include/wireshark/ wireshark 编译后源码
include/libpcap/ libpcap 未编译源码
frame_tvbuff.cinclude/frame_tvbuff.h wireshark的源码文件、拷贝出来的、必须放在此处
libs/ wireshark、libpcap最新动态链接库文件
pcaps/ 用于测试的 pcap 数据包文件
gowireshark_test.go 测试文件
uthash.h 第三方 uthash
cJSON.c、cJSON.h 第三方cJSON
lib.c、offline.c、online.c、reassembly.c 用C封装和加强libpcap和wireshark功能的代码
include/lib.h、offline.h、online.h、reassembly.h 暴露给go的一些c接口
layers.go 通用协议层解析器
registry.go 用户注册自定义协议解析器
online.go、gowireshark.go 用go封装最终的接口,用户go程序可直接使用

2.2. 调用链

Golang =cgo=> Clang ==> Wireshark/libpcap DLL

2.3. 编译dll

如何编译wireshark, libpcap动态链接库?

如果编译的 wireshark 和 libpcap 动态链接库与当前项目支持的版本不同,请同时覆盖 include/wireshark/include/libpcap/ 目录;

注意,如果 wireshark 版本变化很大,本项目中的某些接口可能无效,但可以研究和修复;

1.编译wireshark动态链接库
# 确定最新发行版本并设置环境变量
export WIRESHARKV=4.4.2
# 到/opt目录下操作
cd /opt/
# 下载源码
wget https://1.as.dl.wireshark.org/src/wireshark-$WIRESHARKV.tar.xz
# 解压缩并修改文件夹名称
tar -xvf wireshark-$WIRESHARKV.tar.xz
mv wireshark-$WIRESHARKV wireshark
# 到/opt/wireshark目录操作
cd /opt/wireshark/

--------[首次编译需要检查下] 如何检查编译所需的依赖项-------------
# 根据输出的红色错误日志解决依赖项问题,直到发生 qt5 错误时忽略这些问题
cmake -LH ./

# 如果没有 cmake,请先安装它
export CMAKEV=3.31.1
sudo wget https://cmake.org/files/LatestRelease/cmake-$CMAKEV.tar.gz
tar -xzf cmake-$CMAKEV.tar.gz
mv cmake-$CMAKEV cmake
cd /opt/cmake
sudo ./bootstrap
sudo make
sudo make install
cmake --version

# 可能需要安装的依赖项
sudo apt install build-essential -y
sudo apt install libgcrypt-dev -y
sudo apt install libc-ares-dev -y
sudo apt install flex -y
sudo apt install libglib2.0-dev -y
sudo apt install libssl-dev -y
sudo apt install ninja-build -y
sudo apt install pcaputils -y
sudo apt install libpcap-dev -y
# ubuntu
sudo apt install libxslt1-dev  -y
sudo apt install doxygen  -y
sudo apt install libspeexdsp-dev  -y

## ubuntu安装gnutls库所需依赖nettle的依赖Libhogweed
apt install libgmp-dev  -y
apt install libunbound-dev  -y
apt install libp11-kit-dev  -y

## 安装nettle
wget https://ftp.gnu.org/gnu/nettle/nettle-3.9.1.tar.gz
tar -xvf nettle-3.9.1.tar.gz
cd nettle-3.9.1
./configure --prefix=/usr/local
make -j$(nproc)
sudo make install
## 查询nettle.pc所在文件夹/usr/local/lib64/pkgconfig/
sudo find /usr -name "nettle.pc"
export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig:/usr/local/lib64/pkgconfig/
pkg-config --modversion nettle
# 解决 libgnutls dll 依赖错误的 nettle dll 问题
sudo find /usr -name libnettle.so
cp /usr/local/lib64/libnettle.so /usr/local/lib/
# 确保 /usr/local/lib 优先级更高
sudo vim /etc/ld.so.conf.d/local.conf
# 添加内容
/usr/local/lib
# 重新加载动态链接库缓存
sudo ldconfig

## 安装gnutls库
wget https://www.gnupg.org/ftp/gcrypt/gnutls/v3.8/gnutls-3.8.8.tar.xz
tar -xvf gnutls-3.8.8.tar.xz
cd gnutls-3.8.8
./configure --prefix=/usr/local --with-included-libtasn1 --with-included-unistring
make -j$(nproc)  # 使用多核编译
sudo make install
sudo ldconfig
gnutls-cli --version

## 编译安装完wireshark可以利用wireshark/build/run/tshark -v 看下是否编译时带上了GnuTLS
Compiled xxx with GnuTLS


# mac m1
sudo brew install libxslt1
sudo brew install doxygen
sudo brew install libspeexdsp-dev

# 根据问题解决完成情况,删除测试生成的文件
rm CMakeCache.txt
rm -rf CMakeFiles/
-------------------------------------------------------------------------------

# 在 /opt/wireshark/ 目录下创建一个用来构建的目录
mkdir build && cd build
# 构建[生产用]
cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_wireshark=off -DENABLE_LUA=off ..
# 编译
ninja

# 编译成功后,进入build/run/目录查看编译后的动态链接库
cd run/ && ls -lh
# 覆盖替换原始的 9 个 wireshark 动态链接库文件
cd /opt/gowireshark/libs/
cp /opt/wireshark/build/run/lib*so* .
# 首先执行 步骤 [修正源码导入错误]
👇
👇
👇
# 覆盖 wireshark 源文件夹(先删除无用的 build/ 目录)
rm -rf /opt/wireshark/build/
# 将源码拷贝到项目前可以将原 /opt/gowireshark/include/wireshark/ 目录备份
cp -r /opt/wireshark/ /opt/gowireshark/include/wireshark/

# 查看项目目录结构 [项目目录父目录执行]
tree -L 2 -F gowireshark

[修正源码导入错误] 可以使用IDE批量修改

#include <ws_version.h>
#include <config.h>
// 在build后, 将生成文件 `ws_version.h``config.h`, 将它俩复制到wireshark根目录,最后在将`wireshark/`覆盖到项目`include/wireshark/`目录
cp /opt/wireshark/build/ws_version.h /opt/wireshark/ws_version.h
cp /opt/wireshark/build/config.h /opt/wireshark/config.h
sudo mv /opt/wireshark/include/* /opt/wireshark/
2.编译libpcap动态链接库
# 确定最新发行版本并设置环境变量
export PCAPV=1.10.5
# 在/opt目录下操作
cd /opt
wget http://www.tcpdump.org/release/libpcap-$PCAPV.tar.gz
tar -zxvf libpcap-$PCAPV.tar.gz
cd libpcap-$PCAPV
export CC=aarch64-linux-gnu-gcc
./configure --host=aarch64-linux --with-pcap=linux
# 编译
make

# 成功编译后,重命名动态链接库文件
mv libpcap.so.$PCAPV libpcap.so.1
# 最后替换原动态链接库文件
mv /opt/libpcap-$PCAPV/libpcap.so.1 /opt/gowireshark/libs/libpcap.so.1

---[非必须]---
# 如果没有flex、bison库,请先安装
apt install flex
apt install bison
------

2.4. 解析结果格式说明

  1. 16进制相关字段与协议解析结果分开:

    • offset 偏移量
    • hex 16进制数据
    • ascii ascii字符
  2. 描述性值逻辑来源

    • 原生的打印协议树接口proto_tree_print包含描述性值,而协议json输出接口write_json_proto_tree不包含描述性值,通过借鉴前者的实现逻辑proto_tree_print_node可以完善这个功能;
    • 主要参考proto.h函数的proto_item_fill_label函数:
      /** Fill given label_str with a simple string representation of field.
       @param finfo the item to get the info from
       @param label_str the string to fill
       @todo think about changing the parameter profile */
      WS_DLL_PUBLIC void
      proto_item_fill_label(field_info *finfo, gchar *label_str);

3. 开发测试


  1. 可以在 lib.c、offline.c、online.c 中或在根目录中创建一个新的C文件并添加自定义功能的接口;

  2. 接口完成后需要在include/目录下同名H头文件增加声明,若 cgo 中也用到该接口,则需要在此文件的cgo序文中增加相同的声明;

  3. 在 cgo 文件中封装该接口;

  4. gowireshark_test.go文件中增加测试案例;

  5. 使用 clang 格式工具格式化自定义的 C 代码和头文件: 例如:clang-format -i lib.c,参数-i表示此命令直接格式化指定的文件,删除-i进行预览。 修改根目录中的所有 .c 文件和 include/ 目录中的所有 .h 头文件(注意用grep去掉第三方库文件例如cJSON) (只有当前目录是级别 1,不要向下遍历查找,即不格式化include/wireshark/include/libpcap/下的源码文件):

    find . -maxdepth 1 -name '*.c' | grep -v 'cJSON.c' | grep -v 'frame_tvbuff.c' | xargs clang-format -i
    find ./include -maxdepth 1 -name '*.h' | grep -v 'cJSON.h' | grep -v 'frame_tvbuff.h' | grep -v 'uthash.h' | xargs  clang-format -i
  6. 测试:

    可以在gowireshark_test.go文件中编写测试函数,直接测试:

    # 打印一个流量包文件所有帧
    go test -v -run TestPrintAllFrames
    # 解析并输出一个流量包文件特定帧,并以json格式呈现
    go test -v -run TestGetFrameByIdx
    # 解析并输出一个流量包文件多个选定帧,并以json格式呈现
    go test -v -run TestGetFramesByIdxs
    # 解析并输出一个流量包文件所有帧,并以json格式呈现
    go test -v -run TestGetAllFrames
    # 解析并输出一个流量包文件特定帧的16进制数据,并以json格式呈现
    go test -v -run TestGetHexDataByIdx
    # 实时抓包解析
    go test -v -run TestStartAndStopLivePacketCaptureInfinite
    # 实时抓取一定数目包并解析
    go test -v -run TestStartAndStopLivePacketCaptureLimited
    # 使用rsa key解析tls1.2
    go test -v -run TestParseHttps

    或者通过调用此库的方式测试。

4. 路线图


  • 离线数据包文件解析打印
  • 离线数据包文件解析并输出 JSON 格式结果
  • 离线数据包解析获取16进制相关数据
  • 实时监听接口并捕获数据包
  • 封装 go 调用实时解析的逻辑——通过回调函数将实时解析结果传输到 golang
  • 封装 go 对收到的 Golang 调用实时数据包解析结果的处理
  • 优化代码并解决内存泄漏问题,使实时接口可以长时间运行
  • 支持多个设备的数据包捕获,并根据设备名称停止实时接口
  • 解析结果支持描述性值
  • 支持离线和实时设置rsa key用来解析TLS协议
  • 支持可选参数
  • 支持注册自定义协议解析器
  • 支持从HTTP协议中提取文件
  • 支持TCP流重组

5. 联系

QQ群: 301969140