众所周知Wireshark是一款强大的协议解析工具,在网络优化/安全/分析领域独占鳌头,应用十分广泛。但是笔者在使用过程中遇到需要自定义协议解析插件的问题,故此再次记录问题及解决方法。
问题
笔者在使用Wireshark分析pcap数据包时遇到保存信息不足,有部分笔者认为比较重要的信息没有在pcap中进行保存。所以笔者需要自定义数据包格式以保存更多信息,同时还需要编写Wireshark插件使Wireshark支持解析自定义格式。
以常见HTTP数据包为例,常见的数据包层次为IP+TCP+HTTP。笔者想在IP之前添加一个自定义数据格式,以保存更多的信息,则为Self-Protocol + IP + TCP + HTTP。
因此作者需要解决的问题大致有二:
- 首先告诉Wireshark,X之后的数据应该是自定义协议而不是IP协议;
- 告诉Wireshark自定义协议的解析方法;
解决办法
既然问题已经明确,那我们分别解决他们就是了。
首先,我们解决第一个问题,如果告诉Wireshark 数据应该是自定义协议而不是IP协议呢?在此我们回顾一下pcap文件的全局头文件格式
// Global Header
// This header starts the libpcap file and will be followed by the first packet header:
typedef struct pcap_hdr_s {
guint32 magic_number; /* magic number */
guint16 version_major; /* major version number */
guint16 version_minor; /* minor version number */
gint32 thiszone; /* GMT to local correction */
guint32 sigfigs; /* accuracy of timestamps */
guint32 snaplen; /* max length of captured packets, in octets */
guint32 network; /* data link type */
} pcap_hdr_t;
在该结构体中network
定义了pcap数据包的解析方法,即每个数据帧的最外层(或第一个)数据包的数据格式。Wireshark支持很多数据包格式解析(参见 linktypes),在此我们使用DLT_USER
来指示自定义数据包格式,例如我们使用了DLT 153。
然后第二个问题就是如何指示Wireshark解析自定义协议。Wireshark支持C或lua编写插件。本次笔者采用lua编写脚本。总体而言编写脚本大致分为三个部分:首先,声明自定义协议,指定协议的名称;然后,定义协议格式,即协议解析方法。最后定义调用规则。下面我将展开一一介绍。
声明自定义协议。如下所示直接调用Proto
方法声明协议,其中第一个参数声明协议的名称,可直接用在Wireshark的filter中,第二个参数则是协议的注释,显示在Packet Details窗格中。
local ip_wrapper_proto = Proto("self_proto", "a powerful wrapper for IP protocol")
定义协议格式。可以通过ProtoField.xx
定义协议字段,其中xx是协议格式类型(参见lua_class_ProtoField)。例如
local F_IP_direction = ProtoField.uint8("ip.dir", "Direction", base.DEC)
local F_IP_sn = ProtoField.uint32("ip.sn", "Sequence Number", base.DEC)
ip_wrapper_proto.fields = {F_IP_direction, F_IP_sn}
上面的定义协议格式只是声明了协议中的字段,以及指定字段的数据类型、名称、注释,及格式。
接下来则是声明如何解析原始的数据解析,即上述字段与原始数据的映射方式和相应的解析判断规则。解析方法则是在一下函数中进行声明定义:
function ip_wrapper_proto.dissector(tvbuffer, pinfo, treeitem)
// 声明tvbuffer的协议格式是ip_wrapper_proto。当然在此之前可以做一些检测,判断当前协议是否为ip_wrapper_proto。因为笔者这里使用dlt_user定义的,所以就略过了检测。
local subtreeitem = treeitem:add(ip_wrapper_proto, tvbuffer)
// 在主窗口Protocols中显示协议名称
pinfo.cols.protocol = "IP_Extra"
// 解析数据
local direction = tvbuffer(2, 1):uint()
subtreeitem:add(F_IP_direction, tvbuffer(2, 1), direction):set_text(
string.format("Direction: %d (%s)",direction,direction_opts[direction]))
...
end
其中tvbuffer
可以看作是个容器,包含着本层协议的数据。其中tvbuffer(start, len)
,表示从tvfuffer
的第start
位置开始读取len
长度的数据,后面的uint()
则指示将读取出的数据解析为uint
类型。
其中subtreeitem:add
则是指示将读取到的数据与前面定义的字段进行绑定,并设置其在Packet Details中的显示格式。因此add
的参数分别为协议字段、原始数据、解析后数据。
到此为止我们已经定义好了自定义协议的解析方法。但是有时我们还需要声明Wireshark如何协议剩下的数据,例如这里我们要告诉Wireshark剩下的数据是IP格式的。因此我们要使用如下方法进行声明
local raw_data = tvbuffer(10, tvbuffer:len() - 10)
local ip_dissector = Dissector.get("ip")
ip_dissector:call(raw_data:tvb(), pinfo, treeitem)
解析来就是将lua脚本放在Wireshark的插件目录,然后在配置DLT_USER表格即可。