wireshark插件开发之vsomeip协议
XuQi 2019/07/08
Lua语法介绍
Lua 是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放, 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。
Lua 数据类型
Lua 中有 8 个基本类型分别为:nil、boolean、number、string、userdata、function、thread 和 table。
数据类型 | 描述 |
---|---|
nil | 这个最简单,只有值nil属于该类,表示一个无效值(在条件表达式中相当于false)。 |
userdata | 表示任意存储在变量中的C数据结构 |
table | Lua 中的表(table)其实是一个”关联数组”(associative arrays),数组的索引可以是数字、字符串或表类型。在 Lua 里,table 的创建是通过”构造表达式”来完成,最简单构造表达式是{},用来创建一个空表。 |
Lua 变量
单行注释
1 | -- |
多行注释
1 | --[[ |
变量定义和赋值
1 | a = 5 -- 全局变量 |
Lua循环
1 | while( true ) |
Lua 流程控制
1 | --[ 0 为 true ] 控制结构的条件表达式结果可以是任何值,Lua认为false和nil为假,true和非nil为真。 |
Lua 函数
1 | myprint = function(param) |
Lua 字符串
单引号间的一串字符。
双引号间的一串字符。
[[和]]间的一串字符。
1 | string1 = "Lua" |
字符串操作
string.format(…)
返回一个类似printf的格式化字符串
1 | > string.format("the value is:%d",4) |
string.char(arg) 和 string.byte(arg[,int])
char 将整型数字转成字符并连接, byte 转换字符为整数值(可以指定某个字符,默认第一个字符)。
1 | > string.char(97,98,99,100) |
..
链接两个字符串
1 | > print("www.runoob.".."com") |
Lua 数组
1 | array = {"Lua", "Tutorial"} |
输出
1 | nil |
正如你所看到的,我们可以使用整数索引来访问数组元素,如果知道的索引没有值则返回nil。
在 Lua 索引值是以 1 为起始,但你也可以指定 0 开始。
Lua 迭代器
1 | array = {"Google", "Runoob"} |
输出
1 | 1 Google |
Lua table(表)
1 | -- 初始化表 |
Lua 模块与包
定义模块
1 | -- 文件名为 module.lua |
加载模块
1 | -- test_module.lua 文件 |
加载C库
如果加载动态库或者查找初始化函数时出错,loadlib 将返回 nil 和错误信息,使其检测错误然后调用初始化函数
1 | local path = "/usr/local/lua/lib/libluasocket.so" |
SOME/IP简介
SOME/IP = “Scalable service-Oriented middleware over IP” 在IP上,可扩展的面向服务的中间件
SOME/IP其实是构架在传输层之上的应用层通信协议,它的内容虽然很多很杂,但本质上也就是定义了SOME/IP 包头和数据的内容而已。
SOME/IP On-Wire Format
Service ID: 每个服务的唯一标识符
Method ID: 0-32767 用于 methods, 32768-65535 用于 events
Length: 以字节为单位的有效负载长度(也包括下一个ID,表示另外8个字节)
Client ID: 客户端的唯一标识符;必须在整个车辆中独一无二
Session ID: 会话处理的标识符;每次调用递增
Protocol Version: 0x01
Interface Version: 服务接口的主要版本
Message Type:
REQUEST (0x00) 期待响应请求,即使是空的
REQUEST_NO_RETURN (0x01) 不期待响应请求
NOTIFICATION (0x02) 事件通知,不需要回复
RESPONSE (0x80) 响应消息
…
Return Code:
E_OK (0x00) 没有错误
E_NOT_OK (0x01) 有错误
…
NOTIFICATION详解
NOTIFICATION属于事件通知类的服务,首先由client向server订阅服务内容,然后server向client自动发布服务内容。
NOTIFICATION又分为Event和Field 两类,这两类通知都需要首先使用SOME/IP-SD(Service Discovery)来进行服务订阅,然后才能发布通知。
区别在于,Event是某一时刻的快照,只是事件通知,而Field除了事件通知之外,还具有Getter和Setter的功能,即对信息进行读写的操作。
SOME/IP Service discovery (发现服务)
SOME/IP-SD提供了两种动态发现服务的机制:
Offer Service ,由server向网络上的小伙伴告知它所提供的服务;
Find Service ,由client向别人请求可用的服务。
SOME/IP-SD可以被当作SOME/IP的一种特殊服务,client可以远程调用server提供的服务,或者订阅server发布的内容,那么client是怎么知道server提供哪些服务呢,就是通过SOME/IP-SD来实现服务发现过程的。
它对SOME/IP-SD报文中的Payload进行了定义和实现。而Message ID字段则是固定的0xFF FF 81 00。
SOME/IP-SD提供了两种动态发现服务的机制。一种是Offer Service ,由server向网络上的小伙伴告知它所提供的服务;另一种是Find Service ,由client向别人请求可用的服务。
wireshark 插件开发
wireshark提供了灵活的插件机制,使用户可以方便地扩展wireshark的功能。插件的功能主要包括,但不限于协议解析器。
可以使用Lua或C语言来编写Wireshark插件,下表对比了这两种方式,绿色背景代表占优的一方。
Wireshark对Lua的支持
启动wireshark,依次点击“Help”,”About Wireshark“菜单,在打开的对话框中的”Wireshark”标签页上观察版本信息,如果如下图一样显示With Lua,说明此版本支持Lua插件。
然后打开wireshark主目录下的init.lua文件,确保disable_lua的值为false,即开启了lua:
在Wireshark中,可以使用Lua编写以下几种插件:
- Dissectors 协议解析器,用于解析报文
- *Post-dissectors *后置解析器,在其他解析器之后被调用
- *Listeners *监听器,用来收集解析后的信息
注意
wireshark启动时,会调用下图目录中的init.lua,顺序是先调用global目录的,再调用personal目录的。
修改init.lua,追加以下
1
dofile(DATA_DIR.."ipdata.lua")
- 通过命令行参数:-X lua_script:my.lua传入的my.lua将会在Init.lua之后被调用。
- 所有的Lua脚本会在解析器注册过程的最后被调用,而这一过程是在wireshark启动时就发生的,早于报文被读取的时刻
Lua插件API简介
ProtoField
表示协议字段,一般用于解析字段后往解析树上添加节点。根据字段类型不同,其接口可以分为两大类。
整型:
• ProtoField.{type} (abbr, [name], [desc],[base], [valuestring], [mask])
type包括:uint8, uint16, uint24, uint32, uint64, framenum
1 | local f_msg_id = ProtoField.uint32("someip.messageid","MessageID",base.HEX) |
其他类型
• ProtoField.{type} (abbr, [name], [desc])
type包括:float, double, string, stringz, bytes, bool, ipv4, ipv6, ether,oid, guid
这些接口都会返回一个新的字段对象。方括号内是可选字段,花括号内是可替换的类型字段。
Proto
表示一个新的Protocol,在Wireshark中Protocol对象有很多用处,解析器是其中主要的一个。主要接口有:
接口 | 说明 |
---|---|
proto:__call (name,desc) | 创建Proto对象。name和desc分别是对象的名称和描述,前者可用于过滤器等 |
proto.name | get名称 |
proto.fields | get/set字段 |
proto.prefs | get配置项 |
proto.init() | 初始化函数,无参数 |
proto.dissector(tvb,pinfo,tree) | 解析函数,3个参数tvb,pinfo,tree,分别是报文内容,报文信息和解析树结构 |
1 | p_someip = Proto("IPData","SOME/IP") -- 创建Proto对象 |
1 | pinfo.cols.protocol = p_someip.name -- 列名为IPData |
1 | p_someip.fields = {f_msg_id}-- 定义ProtoField |
1 | -- initialization routine |
1 | -- dissection function |
Tvb
Tvb(Testy Virtual Buffer)表示报文缓存,也就是实际的报文数据,可以通过下面介绍的TvbRange从报文数据中解出信息。主要接口有:
接口 | 说明 |
---|---|
tvb:__tostring() | 将报文数据转化为字符串,可用于调试 |
tvb:reported_len() | get tvb的(not captured)长度 |
tvb:len() | get tvb的(captured)长度 |
tvb:reported_length_remaining() | 获取当前tvb的剩余长度,如果偏移值大于报文长度,则返回-1 |
tvb:offset() | 返回原始偏移 |
TvbRange
表示Tvb的可用范围,常用来从Tvb中解出信息。主要接口有
接口 | 说明 |
---|---|
tvb:range([offset], [length]) | 从tvb创建TvbRange,可选参数分别是偏移和长度,默认值分别是0和总长度 |
tvbrange:{type}() | 将tvbrange所表示范围内的数据转换成type类型的值,type包括但不限于:uint,uint64,int,int64,float,ipv4,ether,nstime,string,ustring,bytes,bitfield等,其中某些类型的方法可以带一些参数 |
Pinfo
报文信息(packet information)。主要接口有:
接口 | 说明 |
---|---|
pinfo.len pinfo.caplen | get报文长度 |
pinfo.abs_ts | get报文捕获时间 |
pinfo.number | get报文编号 |
pinfo.src pinfo.dst | get/set报文的源地址、目的地址 |
pinfo.columns pinfo.cols | get报文列表列(界面) |
取得报文列表列后,就可以设置该列的文本,比如
pinfo.cols.info = “hello world”
将Info列的文本设为hello world。
TreeItem
表示报文解析树中的一个树节点。主要接口有:
接口 | 说明 |
---|---|
treeitem:add([protofield], [tvbrange], [value], [label]) | 向当前树节点添加一个子节点 |
treeitem:set_text(text) | 设置当前树节点的文本 |
treeitem:prepend_text(text) | 在当前树节点文本的前面加上text |
treeitem:append_text(text) | 在当前树节点文本的后面加上text |
DissectorTable
表示一个具体协议的解析表,比如,协议TCP的解析表”tcp.port”包括http,smtp,ftp等。可以依次点击wireshark菜单“Internals”、“Dissector tables”,来查看当前的所有解析表。tcp.port解析表在“Integer tables”选项卡中,顾名思义,它是通过类型为整型的tcp端口号来识别下游协议的:
DissectorTable的主要接口有:
接口 | 说明 |
---|---|
DissectorTable.get(name) | get名为name的解析表的引用 |
dissectortable:add(pattern, dissector) | 将Proto或Dissector对象添加到解析表,即注册。pattern可以是整型值,整型值范围或字符串,这取决于当前解析表的类型 |
dissectortable:remove(pattern, dissector) | 将满足pattern的一个或一组Proto、Dissector对象从解析表中删除 |
插件骨架
1 | -- create a new dissector |
完善骨架中的内容
定义协议中各个字段的名称
1 | local f_msg_id = ProtoField.uint32("someip.messageid","MessageID",base.HEX) |
过滤消息的方法
依据 ProtoField.uint32(“someip.length“,”Length”,base.HEX)
解析消息内容
1 | local band,bor = bit.band,bit.bor -- 与和或 |
调试
在代码中加入
1 | debug(elementName) |